diff options
Diffstat (limited to 'security/nss/lib/ssl/tls13subcerts.c')
-rw-r--r-- | security/nss/lib/ssl/tls13subcerts.c | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/security/nss/lib/ssl/tls13subcerts.c b/security/nss/lib/ssl/tls13subcerts.c new file mode 100644 index 0000000000..4ecc0a5816 --- /dev/null +++ b/security/nss/lib/ssl/tls13subcerts.c @@ -0,0 +1,801 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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 "nss.h" +#include "pk11func.h" +#include "secder.h" +#include "sechash.h" +#include "ssl.h" +#include "sslproto.h" +#include "sslimpl.h" +#include "ssl3exthandle.h" +#include "tls13exthandle.h" +#include "tls13hkdf.h" +#include "tls13subcerts.h" + +/* Parses the delegated credential (DC) from the raw extension |b| of length + * |length|. Memory for the DC is allocated and set to |*dcp|. + * + * It's the caller's responsibility to invoke |tls13_DestroyDelegatedCredential| + * when this data is no longer needed. + */ +SECStatus +tls13_ReadDelegatedCredential(PRUint8 *b, PRUint32 length, + sslDelegatedCredential **dcp) +{ + sslDelegatedCredential *dc = NULL; + SECStatus rv; + PRUint64 n; + sslReadBuffer tmp; + sslReader rdr = SSL_READER(b, length); + + PORT_Assert(!*dcp); + + dc = PORT_ZNew(sslDelegatedCredential); + if (!dc) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* Read the valid_time field of DelegatedCredential.cred. */ + rv = sslRead_ReadNumber(&rdr, 4, &n); + if (rv != SECSuccess) { + goto loser; + } + dc->validTime = n; + + /* Read the expected_cert_verify_algorithm field of + * DelegatedCredential.cred. */ + rv = sslRead_ReadNumber(&rdr, 2, &n); + if (rv != SECSuccess) { + goto loser; + } + dc->expectedCertVerifyAlg = n; + + /* Read the ASN1_subjectPublicKeyInfo field of DelegatedCredential.cred. */ + rv = sslRead_ReadVariable(&rdr, 3, &tmp); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_MakeItem(NULL, &dc->derSpki, tmp.buf, tmp.len); + if (rv != SECSuccess) { + goto loser; + } + + /* Parse the DER-encoded SubjectPublicKeyInfo. */ + dc->spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&dc->derSpki); + if (!dc->spki) { + goto loser; + } + + /* Read the algorithm field of the DelegatedCredential. */ + rv = sslRead_ReadNumber(&rdr, 2, &n); + if (rv != SECSuccess) { + goto loser; + } + dc->alg = n; + + /* Read the signature field of the DelegatedCredential. */ + rv = sslRead_ReadVariable(&rdr, 2, &tmp); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_MakeItem(NULL, &dc->signature, tmp.buf, tmp.len); + if (rv != SECSuccess) { + goto loser; + } + + /* There should be nothing left to read. */ + if (SSL_READER_REMAINING(&rdr) > 0) { + goto loser; + } + + *dcp = dc; + return SECSuccess; + +loser: + tls13_DestroyDelegatedCredential(dc); + *dcp = NULL; + return SECFailure; +} + +/* Frees |dc| from the heap. */ +void +tls13_DestroyDelegatedCredential(sslDelegatedCredential *dc) +{ + if (!dc) { + return; + } + + SECKEY_DestroySubjectPublicKeyInfo(dc->spki); + SECITEM_FreeItem(&dc->derSpki, PR_FALSE); + SECITEM_FreeItem(&dc->signature, PR_FALSE); + PORT_ZFree(dc, sizeof(sslDelegatedCredential)); +} + +/* Sets |*certVerifyAlg| to the expected_cert_verify_algorithm field from the + * serialized DC |in|. Returns SECSuccess upon success; SECFailure indicates a + * decoding failure or the input wasn't long enough. + */ +static SECStatus +tls13_GetExpectedCertVerifyAlg(SECItem in, SSLSignatureScheme *certVerifyAlg) +{ + SECStatus rv; + PRUint64 n; + sslReader rdr = SSL_READER(in.data, in.len); + + if (in.len < 6) { /* Buffer too short to contain the first two params. */ + return SECFailure; + } + + rv = sslRead_ReadNumber(&rdr, 4, &n); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslRead_ReadNumber(&rdr, 2, &n); + if (rv != SECSuccess) { + return SECFailure; + } + *certVerifyAlg = n; + + return SECSuccess; +} + +/* Returns PR_TRUE if the host is verifying the handshake with a DC. */ +PRBool +tls13_IsVerifyingWithDelegatedCredential(const sslSocket *ss) +{ + /* We currently do not support client-delegated credentials. */ + if (ss->sec.isServer || + !ss->opt.enableDelegatedCredentials || + !ss->xtnData.peerDelegCred) { + return PR_FALSE; + } + + return PR_TRUE; +} + +/* Returns PR_TRUE if the host is signing the handshake with a DC. */ +PRBool +tls13_IsSigningWithDelegatedCredential(const sslSocket *ss) +{ + if (!ss->sec.isServer || + !ss->xtnData.sendingDelegCredToPeer || + !ss->xtnData.peerRequestedDelegCred) { + return PR_FALSE; + } + + return PR_TRUE; +} + +/* Commits to authenticating with a DC if all of the following conditions hold: + * - the negotiated protocol is TLS 1.3 or newer; + * - the selected certificate has a DC configured; + * - the peer has indicated support for this extension; + * - the peer has indicated support for the DC signature scheme; and + * - the host supports the DC signature scheme. + * + * It's the caller's responsibility to ensure that the version has been + * negotiated and the certificate has been selected. + */ +SECStatus +tls13_MaybeSetDelegatedCredential(sslSocket *ss) +{ + SECStatus rv; + PRBool doesRsaPss; + SECKEYPrivateKey *priv; + SSLSignatureScheme scheme; + + /* Assert that the host is the server (we do not currently support + * client-delegated credentials), the certificate has been + * chosen, TLS 1.3 or higher has been negotiated, and that the set of + * signature schemes supported by the client is known. + */ + PORT_Assert(ss->sec.isServer); + PORT_Assert(ss->sec.serverCert); + PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3); + PORT_Assert(ss->xtnData.peerRequestedDelegCred == !!ss->xtnData.delegCredSigSchemes); + + /* Check that the peer has indicated support and that a DC has been + * configured for the selected certificate. + */ + if (!ss->xtnData.peerRequestedDelegCred || + !ss->xtnData.delegCredSigSchemes || + !ss->sec.serverCert->delegCred.len || + !ss->sec.serverCert->delegCredKeyPair) { + return SECSuccess; + } + + /* Check that the host and peer both support the signing algorithm used with + * the DC. + */ + rv = tls13_GetExpectedCertVerifyAlg(ss->sec.serverCert->delegCred, + &scheme); + if (rv != SECSuccess) { + return SECFailure; + } + + priv = ss->sec.serverCert->delegCredKeyPair->privKey; + rv = ssl_PrivateKeySupportsRsaPss(priv, NULL, NULL, &doesRsaPss); + if (rv != SECSuccess) { + return SECFailure; + } + + if (!ssl_SignatureSchemeEnabled(ss, scheme) || + !ssl_CanUseSignatureScheme(scheme, + ss->xtnData.delegCredSigSchemes, + ss->xtnData.numDelegCredSigSchemes, + PR_FALSE /* requireSha1 */, + doesRsaPss)) { + return SECSuccess; + } + + /* Commit to sending a DC and set the handshake signature scheme to the + * indicated algorithm. + */ + ss->xtnData.sendingDelegCredToPeer = PR_TRUE; + ss->ssl3.hs.signatureScheme = scheme; + return SECSuccess; +} + +/* Serializes the DC up to the signature. */ +static SECStatus +tls13_AppendCredentialParams(sslBuffer *buf, sslDelegatedCredential *dc) +{ + SECStatus rv; + rv = sslBuffer_AppendNumber(buf, dc->validTime, 4); + if (rv != SECSuccess) { + return SECFailure; /* Error set by caller. */ + } + + rv = sslBuffer_AppendNumber(buf, dc->expectedCertVerifyAlg, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendVariable(buf, dc->derSpki.data, dc->derSpki.len, 3); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendNumber(buf, dc->alg, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + return SECSuccess; +} + +/* Serializes the DC signature. */ +static SECStatus +tls13_AppendCredentialSignature(sslBuffer *buf, sslDelegatedCredential *dc) +{ + SECStatus rv; + rv = sslBuffer_AppendVariable(buf, dc->signature.data, + dc->signature.len, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + return SECSuccess; +} + +/* Hashes the message used to sign/verify the DC. */ +static SECStatus +tls13_HashCredentialSignatureMessage(SSL3Hashes *hash, + SSLSignatureScheme scheme, + const CERTCertificate *cert, + const sslBuffer *dcBuf) +{ + SECStatus rv; + PK11Context *ctx = NULL; + unsigned int hashLen; + + /* Set up hash context. */ + hash->hashAlg = ssl_SignatureSchemeToHashType(scheme); + ctx = PK11_CreateDigestContext(ssl3_HashTypeToOID(hash->hashAlg)); + if (!ctx) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + static const PRUint8 kCtxStrPadding[64] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 + }; + + static const PRUint8 kCtxStr[] = "TLS, server delegated credentials"; + + /* Hash the message signed by the peer. */ + rv = SECSuccess; + rv |= PK11_DigestBegin(ctx); + rv |= PK11_DigestOp(ctx, kCtxStrPadding, sizeof kCtxStrPadding); + rv |= PK11_DigestOp(ctx, kCtxStr, 1 /* 0-byte */ + strlen((const char *)kCtxStr)); + rv |= PK11_DigestOp(ctx, cert->derCert.data, cert->derCert.len); + rv |= PK11_DigestOp(ctx, dcBuf->buf, dcBuf->len); + rv |= PK11_DigestFinal(ctx, hash->u.raw, &hashLen, sizeof hash->u.raw); + if (rv != SECSuccess) { + PORT_SetError(SSL_ERROR_SHA_DIGEST_FAILURE); + goto loser; + } + + hash->len = hashLen; + if (ctx) { + PK11_DestroyContext(ctx, PR_TRUE); + } + return SECSuccess; + +loser: + if (ctx) { + PK11_DestroyContext(ctx, PR_TRUE); + } + return SECFailure; +} + +/* Verifies the DC signature. */ +static SECStatus +tls13_VerifyCredentialSignature(sslSocket *ss, sslDelegatedCredential *dc) +{ + SECStatus rv = SECSuccess; + SSL3Hashes hash; + sslBuffer dcBuf = SSL_BUFFER_EMPTY; + CERTCertificate *cert = ss->sec.peerCert; + SECKEYPublicKey *pubKey = NULL; + + /* Serialize the DC parameters. */ + rv = tls13_AppendCredentialParams(&dcBuf, dc); + if (rv != SECSuccess) { + goto loser; /* Error set by caller. */ + } + + /* Hash the message that was signed by the delegator. */ + rv = tls13_HashCredentialSignatureMessage(&hash, dc->alg, cert, &dcBuf); + if (rv != SECSuccess) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); + goto loser; + } + + pubKey = SECKEY_ExtractPublicKey(&cert->subjectPublicKeyInfo); + if (pubKey == NULL) { + FATAL_ERROR(ss, SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE, internal_error); + goto loser; + } + + /* Verify the signature of the message. */ + rv = ssl_VerifySignedHashesWithPubKey(ss, pubKey, dc->alg, + &hash, &dc->signature); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_DC_BAD_SIGNATURE, illegal_parameter); + goto loser; + } + + SECOidTag spkiAlg = SECOID_GetAlgorithmTag(&(dc->spki->algorithm)); + if (spkiAlg == SEC_OID_PKCS1_RSA_ENCRYPTION) { + FATAL_ERROR(ss, SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM, illegal_parameter); + goto loser; + } + + SECKEY_DestroyPublicKey(pubKey); + sslBuffer_Clear(&dcBuf); + return SECSuccess; + +loser: + SECKEY_DestroyPublicKey(pubKey); + sslBuffer_Clear(&dcBuf); + return SECFailure; +} + +/* Checks that the peer's end-entity certificate has the correct key usage. */ +static SECStatus +tls13_CheckCertDelegationUsage(sslSocket *ss) +{ + int i; + PRBool found; + CERTCertExtension *ext; + SECItem delegUsageOid = { siBuffer, NULL, 0 }; + const CERTCertificate *cert = ss->sec.peerCert; + + /* 1.3.6.1.4.1.44363.44, as defined in draft-ietf-tls-subcerts. */ + static unsigned char kDelegationUsageOid[] = { + 0x2b, + 0x06, + 0x01, + 0x04, + 0x01, + 0x82, + 0xda, + 0x4b, + 0x2c + }; + + delegUsageOid.data = kDelegationUsageOid; + delegUsageOid.len = sizeof kDelegationUsageOid; + + /* The certificate must have the delegationUsage extension that authorizes + * it to negotiate delegated credentials. + */ + found = PR_FALSE; + for (i = 0; cert->extensions[i] != NULL; i++) { + ext = cert->extensions[i]; + if (SECITEM_CompareItem(&ext->id, &delegUsageOid) == SECEqual) { + found = PR_TRUE; + break; + } + } + + /* The certificate must also have the digitalSignature keyUsage set. */ + if (!found || + !cert->keyUsagePresent || + !(cert->keyUsage & KU_DIGITAL_SIGNATURE)) { + FATAL_ERROR(ss, SSL_ERROR_DC_INVALID_KEY_USAGE, illegal_parameter); + return SECFailure; + } + + return SECSuccess; +} + +static SECStatus +tls13_CheckCredentialExpiration(sslSocket *ss, sslDelegatedCredential *dc) +{ + SECStatus rv; + CERTCertificate *cert = ss->sec.peerCert; + /* 7 days in microseconds */ + static const PRTime kMaxDcValidity = ((PRTime)7 * 24 * 60 * 60 * PR_USEC_PER_SEC); + PRTime start, now, end; /* microseconds */ + + rv = DER_DecodeTimeChoice(&start, &cert->validity.notBefore); + if (rv != SECSuccess) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } + + end = start + ((PRTime)dc->validTime * PR_USEC_PER_SEC); + now = ssl_Time(ss); + if (now > end || end < 0) { + FATAL_ERROR(ss, SSL_ERROR_DC_EXPIRED, illegal_parameter); + return SECFailure; + } + + /* Not more than 7 days remaining in the validity period. */ + if (end - now > kMaxDcValidity) { + FATAL_ERROR(ss, SSL_ERROR_DC_INAPPROPRIATE_VALIDITY_PERIOD, illegal_parameter); + return SECFailure; + } + + return SECSuccess; +} + +/* Returns SECSucces if |dc| is a DC for the current handshake; otherwise it + * returns SECFailure. A valid DC meets three requirements: (1) the signature + * was produced by the peer's end-entity certificate, (2) the end-entity + * certificate must have the correct key usage, and (3) the DC must not be + * expired and its remaining TTL must be <= the maximum validity period (fixed + * as 7 days). + * + * This function calls FATAL_ERROR() when an error occurs. + */ +SECStatus +tls13_VerifyDelegatedCredential(sslSocket *ss, + sslDelegatedCredential *dc) +{ + SECStatus rv; + PRTime start; + PRExplodedTime end; + CERTCertificate *cert = ss->sec.peerCert; + char endStr[256]; + + rv = DER_DecodeTimeChoice(&start, &cert->validity.notBefore); + if (rv != SECSuccess) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } + + PR_ExplodeTime(start + (dc->validTime * PR_USEC_PER_SEC), + PR_GMTParameters, &end); + if (PR_FormatTime(endStr, sizeof(endStr), "%a %b %d %H:%M:%S %Y", &end)) { + SSL_TRC(20, ("%d: TLS13[%d]: Received delegated credential (expires %s)", + SSL_GETPID(), ss->fd, endStr)); + } else { + SSL_TRC(20, ("%d: TLS13[%d]: Received delegated credential", + SSL_GETPID(), ss->fd)); + } + + rv = SECSuccess; + rv |= tls13_VerifyCredentialSignature(ss, dc); + rv |= tls13_CheckCertDelegationUsage(ss); + rv |= tls13_CheckCredentialExpiration(ss, dc); + return rv; +} + +static CERTSubjectPublicKeyInfo * +tls13_MakePssSpki(const SECKEYPublicKey *pub, SECOidTag hashOid) +{ + SECStatus rv; + PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + goto loser; /* Code already set. */ + } + CERTSubjectPublicKeyInfo *spki = PORT_ArenaZNew(arena, CERTSubjectPublicKeyInfo); + if (!spki) { + goto loser; /* Code already set. */ + } + spki->arena = arena; + + SECKEYRSAPSSParams params = { 0 }; + params.hashAlg = PORT_ArenaZNew(arena, SECAlgorithmID); + rv = SECOID_SetAlgorithmID(arena, params.hashAlg, hashOid, NULL); + if (rv != SECSuccess) { + goto loser; /* Code already set. */ + } + + /* Set the mask hash algorithm too, which is an argument to + * a SEC_OID_PKCS1_MGF1 value. */ + SECAlgorithmID maskHashAlg; + memset(&maskHashAlg, 0, sizeof(maskHashAlg)); + rv = SECOID_SetAlgorithmID(arena, &maskHashAlg, hashOid, NULL); + if (rv != SECSuccess) { + goto loser; /* Code already set. */ + } + SECItem *maskHashAlgItem = + SEC_ASN1EncodeItem(arena, NULL, &maskHashAlg, + SEC_ASN1_GET(SECOID_AlgorithmIDTemplate)); + if (!maskHashAlgItem) { + /* Probably OOM, but not certain. */ + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + goto loser; + } + + params.maskAlg = PORT_ArenaZNew(arena, SECAlgorithmID); + rv = SECOID_SetAlgorithmID(arena, params.maskAlg, SEC_OID_PKCS1_MGF1, + maskHashAlgItem); + if (rv != SECSuccess) { + goto loser; /* Code already set. */ + } + + /* Always include saltLength: all hashes are larger than 20. */ + unsigned int saltLength = HASH_ResultLenByOidTag(hashOid); + PORT_Assert(saltLength > 20); + if (!SEC_ASN1EncodeInteger(arena, ¶ms.saltLength, saltLength)) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + goto loser; + } + /* Omit the trailerField always. */ + + SECItem *algorithmItem = + SEC_ASN1EncodeItem(arena, NULL, ¶ms, + SEC_ASN1_GET(SECKEY_RSAPSSParamsTemplate)); + if (!algorithmItem) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + goto loser; /* Code already set. */ + } + rv = SECOID_SetAlgorithmID(arena, &spki->algorithm, + SEC_OID_PKCS1_RSA_PSS_SIGNATURE, algorithmItem); + if (rv != SECSuccess) { + goto loser; /* Code already set. */ + } + + SECItem *pubItem = SEC_ASN1EncodeItem(arena, &spki->subjectPublicKey, pub, + SEC_ASN1_GET(SECKEY_RSAPublicKeyTemplate)); + if (!pubItem) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + goto loser; + } + spki->subjectPublicKey.len *= 8; /* Key length is in bits. */ + return spki; + +loser: + PORT_FreeArena(arena, PR_FALSE); + return NULL; +} + +static CERTSubjectPublicKeyInfo * +tls13_MakeDcSpki(const SECKEYPublicKey *dcPub, SSLSignatureScheme dcCertVerifyAlg) +{ + switch (SECKEY_GetPublicKeyType(dcPub)) { + case rsaKey: { + SECOidTag hashOid; + switch (dcCertVerifyAlg) { + /* Note: RSAE schemes are NOT permitted within DC SPKIs. However, + * support for their issuance remains so as to enable negative + * testing of client behavior. */ + case ssl_sig_rsa_pss_rsae_sha256: + case ssl_sig_rsa_pss_rsae_sha384: + case ssl_sig_rsa_pss_rsae_sha512: + return SECKEY_CreateSubjectPublicKeyInfo(dcPub); + case ssl_sig_rsa_pss_pss_sha256: + hashOid = SEC_OID_SHA256; + break; + case ssl_sig_rsa_pss_pss_sha384: + hashOid = SEC_OID_SHA384; + break; + case ssl_sig_rsa_pss_pss_sha512: + hashOid = SEC_OID_SHA512; + break; + + default: + PORT_SetError(SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM); + return NULL; + } + return tls13_MakePssSpki(dcPub, hashOid); + } + + case ecKey: { + const sslNamedGroupDef *group = ssl_ECPubKey2NamedGroup(dcPub); + if (!group) { + PORT_SetError(SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM); + return NULL; + } + SSLSignatureScheme keyScheme; + switch (group->name) { + case ssl_grp_ec_secp256r1: + keyScheme = ssl_sig_ecdsa_secp256r1_sha256; + break; + case ssl_grp_ec_secp384r1: + keyScheme = ssl_sig_ecdsa_secp384r1_sha384; + break; + case ssl_grp_ec_secp521r1: + keyScheme = ssl_sig_ecdsa_secp521r1_sha512; + break; + default: + PORT_SetError(SEC_ERROR_INVALID_KEY); + return NULL; + } + if (keyScheme != dcCertVerifyAlg) { + PORT_SetError(SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM); + return NULL; + } + return SECKEY_CreateSubjectPublicKeyInfo(dcPub); + } + + default: + break; + } + + PORT_SetError(SEC_ERROR_INVALID_KEY); + return NULL; +} + +/* Returns a serialized DC with the given parameters. + * + * Note that this function is meant primarily for testing. In particular, it + * DOES NOT verify any of the following: + * - |certPriv| is the private key corresponding to |cert|; + * - that |checkCertKeyUsage(cert) == SECSuccess|; + * - |dcValidFor| is less than 7 days (the maximum permitted by the spec); or + * - validTime doesn't overflow a PRUint32. + * + * These conditions are things we want to test for, which is why we allow them + * here. A real API for creating DCs would want to explicitly check ALL of these + * conditions are met. + */ +SECStatus +SSLExp_DelegateCredential(const CERTCertificate *cert, + const SECKEYPrivateKey *certPriv, + const SECKEYPublicKey *dcPub, + SSLSignatureScheme dcCertVerifyAlg, + PRUint32 dcValidFor, + PRTime now, + SECItem *out) +{ + SECStatus rv; + SSL3Hashes hash; + CERTSubjectPublicKeyInfo *spki = NULL; + SECKEYPrivateKey *tmpPriv = NULL; + sslDelegatedCredential *dc = NULL; + sslBuffer dcBuf = SSL_BUFFER_EMPTY; + + if (!cert || !certPriv || !dcPub || !out) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + dc = PORT_ZNew(sslDelegatedCredential); + if (!dc) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* Serialize the DC parameters. */ + PRTime start; + rv = DER_DecodeTimeChoice(&start, &cert->validity.notBefore); + if (rv != SECSuccess) { + goto loser; + } + dc->validTime = ((now - start) / PR_USEC_PER_SEC) + dcValidFor; + + /* Building the SPKI also validates |dcCertVerifyAlg|. */ + spki = tls13_MakeDcSpki(dcPub, dcCertVerifyAlg); + if (!spki) { + goto loser; + } + dc->expectedCertVerifyAlg = dcCertVerifyAlg; + + SECItem *spkiDer = + SEC_ASN1EncodeItem(NULL /*arena*/, &dc->derSpki, spki, + SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate)); + if (!spkiDer) { + goto loser; + } + + rv = ssl_SignatureSchemeFromSpki(&cert->subjectPublicKeyInfo, + PR_TRUE /* isTls13 */, &dc->alg); + if (rv != SECSuccess) { + goto loser; + } + + if (dc->alg == ssl_sig_none) { + SECOidTag spkiOid = SECOID_GetAlgorithmTag(&cert->subjectPublicKeyInfo.algorithm); + /* If the Cert SPKI contained an AlgorithmIdentifier of "rsaEncryption", set a + * default rsa_pss_rsae_sha256 scheme. NOTE: RSAE SPKIs are not permitted within + * "real" Delegated Credentials. However, since this function is primarily used for + * testing, we retain this support in order to verify that these DCs are rejected + * by tls13_VerifyDelegatedCredential. */ + if (spkiOid == SEC_OID_PKCS1_RSA_ENCRYPTION) { + SSLSignatureScheme scheme = ssl_sig_rsa_pss_rsae_sha256; + if (ssl_SignatureSchemeValid(scheme, spkiOid, PR_TRUE /* isTls13 */)) { + dc->alg = scheme; + } + } + } + PORT_Assert(dc->alg != ssl_sig_none); + + rv = tls13_AppendCredentialParams(&dcBuf, dc); + if (rv != SECSuccess) { + goto loser; + } + + /* Hash signature message. */ + rv = tls13_HashCredentialSignatureMessage(&hash, dc->alg, cert, &dcBuf); + if (rv != SECSuccess) { + goto loser; + } + + /* Sign the hash with the delegation key. + * + * The PK11 API discards const qualifiers, so we have to make a copy of + * |certPriv| and pass the copy to |ssl3_SignHashesWithPrivKey|. + */ + tmpPriv = SECKEY_CopyPrivateKey(certPriv); + rv = ssl3_SignHashesWithPrivKey(&hash, tmpPriv, dc->alg, + PR_TRUE /* isTls */, &dc->signature); + if (rv != SECSuccess) { + goto loser; + } + + /* Serialize the DC signature. */ + rv = tls13_AppendCredentialSignature(&dcBuf, dc); + if (rv != SECSuccess) { + goto loser; + } + + /* Copy the serialized DC to |out|. */ + rv = SECITEM_MakeItem(NULL, out, dcBuf.buf, dcBuf.len); + if (rv != SECSuccess) { + goto loser; + } + + PRINT_BUF(20, (NULL, "delegated credential", dcBuf.buf, dcBuf.len)); + + SECKEY_DestroySubjectPublicKeyInfo(spki); + SECKEY_DestroyPrivateKey(tmpPriv); + tls13_DestroyDelegatedCredential(dc); + sslBuffer_Clear(&dcBuf); + return SECSuccess; + +loser: + SECKEY_DestroySubjectPublicKeyInfo(spki); + SECKEY_DestroyPrivateKey(tmpPriv); + tls13_DestroyDelegatedCredential(dc); + sslBuffer_Clear(&dcBuf); + return SECFailure; +} |