summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/ssl/tls13subcerts.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/ssl/tls13subcerts.c')
-rw-r--r--security/nss/lib/ssl/tls13subcerts.c801
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, &params.saltLength, saltLength)) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto loser;
+ }
+ /* Omit the trailerField always. */
+
+ SECItem *algorithmItem =
+ SEC_ASN1EncodeItem(arena, NULL, &params,
+ 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;
+}