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