diff -r 000000000000 -r 6474c204b198 media/mtransport/dtlsidentity.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/mtransport/dtlsidentity.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "logging.h" +#include "nspr.h" +#include "cryptohi.h" +#include "ssl.h" +#include "keyhi.h" +#include "pk11pub.h" +#include "sechash.h" +#include "nsError.h" +#include "dtlsidentity.h" + +namespace mozilla { + +MOZ_MTLOG_MODULE("mtransport") + +DtlsIdentity::~DtlsIdentity() { + // XXX: make cert_ a smart pointer to avoid this, after we figure + // out the linking problem. + if (cert_) + CERT_DestroyCertificate(cert_); +} + +const std::string DtlsIdentity::DEFAULT_HASH_ALGORITHM = "sha-256"; +const size_t DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH = 64; + +TemporaryRef DtlsIdentity::Generate() { + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + return nullptr; + } + + uint8_t random_name[16]; + + SECStatus rv = PK11_GenerateRandomOnSlot(slot, random_name, + sizeof(random_name)); + if (rv != SECSuccess) + return nullptr; + + std::string name; + char chunk[3]; + for (size_t i = 0; i < sizeof(random_name); ++i) { + PR_snprintf(chunk, sizeof(chunk), "%.2x", random_name[i]); + name += chunk; + } + + std::string subject_name_string = "CN=" + name; + ScopedCERTName subject_name(CERT_AsciiToName(subject_name_string.c_str())); + if (!subject_name) { + return nullptr; + } + + PK11RSAGenParams rsaparams; + rsaparams.keySizeInBits = 1024; // TODO: make this stronger when we + // pre-generate. + rsaparams.pe = 65537; // We are too paranoid to use 3 as the exponent. + + ScopedSECKEYPrivateKey private_key; + ScopedSECKEYPublicKey public_key; + SECKEYPublicKey *pubkey; + + private_key = + PK11_GenerateKeyPair(slot, + CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaparams, &pubkey, + PR_FALSE, PR_TRUE, nullptr); + if (private_key == nullptr) + return nullptr; + public_key = pubkey; + + ScopedCERTSubjectPublicKeyInfo spki( + SECKEY_CreateSubjectPublicKeyInfo(pubkey)); + if (!spki) { + return nullptr; + } + + ScopedCERTCertificateRequest certreq( + CERT_CreateCertificateRequest(subject_name, spki, nullptr)); + if (!certreq) { + return nullptr; + } + + // From 1 day before todayto 30 days after. + // This is a sort of arbitrary range designed to be valid + // now with some slack in case the other side expects + // some before expiry. + // + // Note: explicit casts necessary to avoid + // warning C4307: '*' : integral constant overflow + static const PRTime oneDay = PRTime(PR_USEC_PER_SEC) + * PRTime(60) // sec + * PRTime(60) // min + * PRTime(24); // hours + PRTime now = PR_Now(); + PRTime notBefore = now - oneDay; + PRTime notAfter = now + (PRTime(30) * oneDay); + + ScopedCERTValidity validity(CERT_CreateValidity(notBefore, notAfter)); + if (!validity) { + return nullptr; + } + + unsigned long serial; + // Note: This serial in principle could collide, but it's unlikely + rv = PK11_GenerateRandomOnSlot(slot, + reinterpret_cast(&serial), + sizeof(serial)); + if (rv != SECSuccess) { + return nullptr; + } + + ScopedCERTCertificate certificate( + CERT_CreateCertificate(serial, subject_name, validity, certreq)); + if (!certificate) { + return nullptr; + } + + PLArenaPool *arena = certificate->arena; + + rv = SECOID_SetAlgorithmID(arena, &certificate->signature, + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, 0); + if (rv != SECSuccess) + return nullptr; + + // Set version to X509v3. + *(certificate->version.data) = SEC_CERTIFICATE_VERSION_3; + certificate->version.len = 1; + + SECItem innerDER; + innerDER.len = 0; + innerDER.data = nullptr; + + if (!SEC_ASN1EncodeItem(arena, &innerDER, certificate, + SEC_ASN1_GET(CERT_CertificateTemplate))) { + return nullptr; + } + + SECItem *signedCert = PORT_ArenaZNew(arena, SECItem); + if (!signedCert) { + return nullptr; + } + + rv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len, + private_key, + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION); + if (rv != SECSuccess) { + return nullptr; + } + certificate->derCert = *signedCert; + + return new DtlsIdentity(private_key.forget(), certificate.forget()); +} + + +nsresult DtlsIdentity::ComputeFingerprint(const std::string algorithm, + unsigned char *digest, + std::size_t size, + std::size_t *digest_length) { + MOZ_ASSERT(cert_); + + return ComputeFingerprint(cert_, algorithm, digest, size, digest_length); +} + +nsresult DtlsIdentity::ComputeFingerprint(const CERTCertificate *cert, + const std::string algorithm, + unsigned char *digest, + std::size_t size, + std::size_t *digest_length) { + MOZ_ASSERT(cert); + + HASH_HashType ht; + + if (algorithm == "sha-1") { + ht = HASH_AlgSHA1; + } else if (algorithm == "sha-224") { + ht = HASH_AlgSHA224; + } else if (algorithm == "sha-256") { + ht = HASH_AlgSHA256; + } else if (algorithm == "sha-384") { + ht = HASH_AlgSHA384; + } else if (algorithm == "sha-512") { + ht = HASH_AlgSHA512; + } else { + return NS_ERROR_INVALID_ARG; + } + + const SECHashObject *ho = HASH_GetHashObject(ht); + MOZ_ASSERT(ho); + if (!ho) + return NS_ERROR_INVALID_ARG; + + MOZ_ASSERT(ho->length >= 20); // Double check + + if (size < ho->length) + return NS_ERROR_INVALID_ARG; + + SECStatus rv = HASH_HashBuf(ho->type, digest, + cert->derCert.data, + cert->derCert.len); + if (rv != SECSuccess) + return NS_ERROR_FAILURE; + + *digest_length = ho->length; + + return NS_OK; +} + +// Format the fingerprint in RFC 4572 Section 5 attribute format, including both +// the hash name and the fingerprint, colons and all. +// returns an empty string if there is a problem +std::string DtlsIdentity::GetFormattedFingerprint(const std::string &algorithm) { + unsigned char digest[HASH_ALGORITHM_MAX_LENGTH]; + size_t digest_length; + + nsresult res = this->ComputeFingerprint(algorithm, + digest, + sizeof(digest), + &digest_length); + if (NS_FAILED(res)) { + MOZ_MTLOG(ML_ERROR, "Unable to compute " << algorithm + << " hash for identity: nsresult = 0x" + << std::hex << std::uppercase + << static_cast(res) + << std::nouppercase << std::dec); + return ""; + } + + return algorithm + " " + this->FormatFingerprint(digest, digest_length); +} + +std::string DtlsIdentity::FormatFingerprint(const unsigned char *digest, + std::size_t size) { + std::string str(""); + char group[3]; + + for (std::size_t i=0; i < size; i++) { + PR_snprintf(group, sizeof(group), "%.2X", digest[i]); + if (i != 0) { + str += ":"; + } + str += group; + } + + MOZ_ASSERT(str.size() == (size * 3 - 1)); // Check result length + return str; +} + +// Parse a fingerprint in RFC 4572 format. +// Note that this tolerates some badly formatted data, in particular: +// (a) arbitrary runs of colons +// (b) colons at the beginning or end. +nsresult DtlsIdentity::ParseFingerprint(const std::string fp, + unsigned char *digest, + size_t size, + size_t *length) { + size_t offset = 0; + bool top_half = true; + uint8_t val = 0; + + for (size_t i=0; i= size) { + // Note: no known way for offset to get > size + MOZ_MTLOG(ML_ERROR, "Fingerprint too long for buffer"); + return NS_ERROR_INVALID_ARG; + } + + if (top_half && (fp[i] == ':')) { + continue; + } else if ((fp[i] >= '0') && (fp[i] <= '9')) { + val |= fp[i] - '0'; + } else if ((fp[i] >= 'A') && (fp[i] <= 'F')) { + val |= fp[i] - 'A' + 10; + } else { + MOZ_MTLOG(ML_ERROR, "Invalid fingerprint value " << fp[i]); + return NS_ERROR_ILLEGAL_VALUE; + } + + if (top_half) { + val <<= 4; + top_half = false; + } else { + digest[offset++] = val; + top_half = true; + val = 0; + } + } + + *length = offset; + + return NS_OK; +} + +} // close namespace