/* -*- 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 "dtlsidentity.h" #include "cert.h" #include "cryptohi.h" #include "keyhi.h" #include "nsError.h" #include "pk11pub.h" #include "sechash.h" #include "mozpkix/nss_scoped_ptrs.h" #include "secerr.h" #include "sslerr.h" #include "mozilla/Sprintf.h" namespace mozilla { SECItem* WrapPrivateKeyInfoWithEmptyPassword( SECKEYPrivateKey* pk) /* encrypt this private key */ { if (!pk) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return nullptr; } UniquePK11SlotInfo slot(PK11_GetInternalSlot()); if (!slot) { return nullptr; } // For private keys, NSS cannot export anything other than RSA, but we need EC // also. So, we use the private key encryption function to serialize instead, // using a hard-coded dummy password; this is not intended to provide any // additional security, it just works around a limitation in NSS. SECItem dummyPassword = {siBuffer, nullptr, 0}; UniqueSECKEYEncryptedPrivateKeyInfo epki(PK11_ExportEncryptedPrivKeyInfo( slot.get(), SEC_OID_AES_128_CBC, &dummyPassword, pk, 1, nullptr)); if (!epki) { return nullptr; } return SEC_ASN1EncodeItem( nullptr, nullptr, epki.get(), NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false)); } SECStatus UnwrapPrivateKeyInfoWithEmptyPassword( SECItem* derPKI, const UniqueCERTCertificate& aCert, SECKEYPrivateKey** privk) { if (!derPKI || !aCert || !privk) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return SECFailure; } UniqueSECKEYPublicKey publicKey(CERT_ExtractPublicKey(aCert.get())); // This is a pointer to data inside publicKey SECItem* publicValue = nullptr; switch (publicKey->keyType) { case dsaKey: publicValue = &publicKey->u.dsa.publicValue; break; case dhKey: publicValue = &publicKey->u.dh.publicValue; break; case rsaKey: publicValue = &publicKey->u.rsa.modulus; break; case ecKey: publicValue = &publicKey->u.ec.publicValue; break; default: MOZ_ASSERT(false); PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0); return SECFailure; } UniquePK11SlotInfo slot(PK11_GetInternalSlot()); if (!slot) { return SECFailure; } UniquePLArenaPool temparena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (!temparena) { return SECFailure; } SECKEYEncryptedPrivateKeyInfo* epki = PORT_ArenaZNew(temparena.get(), SECKEYEncryptedPrivateKeyInfo); if (!epki) { return SECFailure; } SECStatus rv = SEC_ASN1DecodeItem( temparena.get(), epki, NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false), derPKI); if (rv != SECSuccess) { // If SEC_ASN1DecodeItem fails, we cannot assume anything about the // validity of the data in epki. The best we can do is free the arena // and return. return rv; } // See comment in WrapPrivateKeyInfoWithEmptyPassword about this // dummy password stuff. SECItem dummyPassword = {siBuffer, nullptr, 0}; return PK11_ImportEncryptedPrivateKeyInfoAndReturnKey( slot.get(), epki, &dummyPassword, nullptr, publicValue, false, false, publicKey->keyType, KU_ALL, privk, nullptr); } nsresult DtlsIdentity::Serialize(nsTArray* aKeyDer, nsTArray* aCertDer) { ScopedSECItem derPki(WrapPrivateKeyInfoWithEmptyPassword(private_key_.get())); if (!derPki) { return NS_ERROR_FAILURE; } aKeyDer->AppendElements(derPki->data, derPki->len); aCertDer->AppendElements(cert_->derCert.data, cert_->derCert.len); return NS_OK; } /* static */ RefPtr DtlsIdentity::Deserialize( const nsTArray& aKeyDer, const nsTArray& aCertDer, SSLKEAType authType) { SECItem certDer = {siBuffer, const_cast(aCertDer.Elements()), static_cast(aCertDer.Length())}; UniqueCERTCertificate cert(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &certDer, nullptr, true, true)); SECItem derPKI = {siBuffer, const_cast(aKeyDer.Elements()), static_cast(aKeyDer.Length())}; SECKEYPrivateKey* privateKey; if (UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) != SECSuccess) { MOZ_ASSERT(false); return nullptr; } return new DtlsIdentity(UniqueSECKEYPrivateKey(privateKey), std::move(cert), authType); } RefPtr DtlsIdentity::Generate() { UniquePK11SlotInfo slot(PK11_GetInternalSlot()); if (!slot) { return nullptr; } uint8_t random_name[16]; SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), random_name, sizeof(random_name)); if (rv != SECSuccess) return nullptr; std::string name; char chunk[3]; for (unsigned char r_name : random_name) { SprintfLiteral(chunk, "%.2x", r_name); name += chunk; } std::string subject_name_string = "CN=" + name; UniqueCERTName subject_name(CERT_AsciiToName(subject_name_string.c_str())); if (!subject_name) { return nullptr; } unsigned char paramBuf[12]; // OIDs are small SECItem ecdsaParams = {siBuffer, paramBuf, sizeof(paramBuf)}; SECOidData* oidData = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1); if (!oidData || (oidData->oid.len > (sizeof(paramBuf) - 2))) { return nullptr; } ecdsaParams.data[0] = SEC_ASN1_OBJECT_ID; ecdsaParams.data[1] = oidData->oid.len; memcpy(ecdsaParams.data + 2, oidData->oid.data, oidData->oid.len); ecdsaParams.len = oidData->oid.len + 2; SECKEYPublicKey* pubkey; UniqueSECKEYPrivateKey private_key( PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &ecdsaParams, &pubkey, PR_FALSE, PR_TRUE, nullptr)); if (private_key == nullptr) return nullptr; UniqueSECKEYPublicKey public_key(pubkey); pubkey = nullptr; UniqueCERTSubjectPublicKeyInfo spki( SECKEY_CreateSubjectPublicKeyInfo(public_key.get())); if (!spki) { return nullptr; } UniqueCERTCertificateRequest certreq( CERT_CreateCertificateRequest(subject_name.get(), spki.get(), 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); UniqueCERTValidity 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.get(), reinterpret_cast(&serial), sizeof(serial)); if (rv != SECSuccess) { return nullptr; } // NB: CERTCertificates created with CERT_CreateCertificate are not safe to // use with other NSS functions like CERT_DupCertificate. // The strategy here is to create a tbsCertificate ("to-be-signed // certificate"), encode it, and sign it, resulting in a signed DER // certificate that can be decoded into a CERTCertificate. UniqueCERTCertificate tbsCertificate(CERT_CreateCertificate( serial, subject_name.get(), validity.get(), certreq.get())); if (!tbsCertificate) { return nullptr; } PLArenaPool* arena = tbsCertificate->arena; rv = SECOID_SetAlgorithmID(arena, &tbsCertificate->signature, SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, nullptr); if (rv != SECSuccess) return nullptr; // Set version to X509v3. *(tbsCertificate->version.data) = SEC_CERTIFICATE_VERSION_3; tbsCertificate->version.len = 1; SECItem innerDER; innerDER.len = 0; innerDER.data = nullptr; if (!SEC_ASN1EncodeItem(arena, &innerDER, tbsCertificate.get(), SEC_ASN1_GET(CERT_CertificateTemplate))) { return nullptr; } SECItem* certDer = PORT_ArenaZNew(arena, SECItem); if (!certDer) { return nullptr; } rv = SEC_DerSignData(arena, certDer, innerDER.data, innerDER.len, private_key.get(), SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE); if (rv != SECSuccess) { return nullptr; } UniqueCERTCertificate certificate(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), certDer, nullptr, false, true)); return new DtlsIdentity(std::move(private_key), std::move(certificate), ssl_kea_ecdh); } const std::string DtlsIdentity::DEFAULT_HASH_ALGORITHM = "sha-256"; nsresult DtlsIdentity::ComputeFingerprint(DtlsDigest* digest) const { const UniqueCERTCertificate& c = cert(); MOZ_ASSERT(c); return ComputeFingerprint(c, digest); } nsresult DtlsIdentity::ComputeFingerprint(const UniqueCERTCertificate& cert, DtlsDigest* digest) { MOZ_ASSERT(cert); HASH_HashType ht; if (digest->algorithm_ == "sha-1") { ht = HASH_AlgSHA1; } else if (digest->algorithm_ == "sha-224") { ht = HASH_AlgSHA224; } else if (digest->algorithm_ == "sha-256") { ht = HASH_AlgSHA256; } else if (digest->algorithm_ == "sha-384") { ht = HASH_AlgSHA384; } else if (digest->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 digest->value_.resize(ho->length); SECStatus rv = HASH_HashBuf(ho->type, digest->value_.data(), cert->derCert.data, cert->derCert.len); if (rv != SECSuccess) { return NS_ERROR_FAILURE; } return NS_OK; } } // namespace mozilla