From f9716bc8ab852abbb11f285dfb36b05e914702eb Mon Sep 17 00:00:00 2001 From: Kevin Jacobs Date: Tue, 26 Jan 2021 15:30:01 +0000 Subject: [PATCH] Bug 1688685 - land NSS 92dcda94c1d4 UPGRADE_NSS_RELEASE, r=bbeurdouche 2021-01-22 Kevin Jacobs * automation/abi-check/previous-nss-release, lib/nss/nss.h, lib/softoken/softkver.h, lib/util/nssutil.h: Set version numbers to 3.62 Beta [680ec01577b9] 2021-01-23 Kevin Jacobs * tests/chains/scenarios/nameconstraints.cfg, tests/libpkix/certs/NameConstraints.ipaca.cert, tests/libpkix/certs/NameConstraints.ocsp1.cert: Bug 1686134 - Renew two chains libpkix test certificates. r=rrelyea [3ddcd845704c] 2021-01-25 Kevin Jacobs * gtests/common/testvectors/hpke-vectors.h, gtests/pk11_gtest/pk11_hpke_unittest.cc, lib/pk11wrap/pk11hpke.c, lib/pk11wrap/pk11hpke.h, lib/pk11wrap/pk11pub.h: Bug 1678398 - Update HPKE to draft-07. r=mt This patch updates HPKE to draft-07. A few other minor changes are included: - Refactor HPKE gtests for increased parameterized testing. - Replace memcpy calls with PORT_Memcpy - Serialization tweaks to make way for context Export/Import (D99277). This should not be landed without an ECH update, as fixed ECH test vectors will otherwise fail to decrypt. [e0bf8cadadc7] * automation/abi-check/expected-report-libnss3.so.txt, gtests/pk11_gtest/pk11_hpke_unittest.cc, lib/nss/nss.def, lib/pk11wrap/pk11hpke.c, lib/pk11wrap/pk11pub.h: Bug 1678398 - Add Export/Import functions for HPKE context. r=mt This patch adds and exports two new HPKE functions: `PK11_HPKE_ExportContext` and `PK11_HPKE_ImportContext`, which are used to export a serialized HPKE context, then later reimport that context and resume Open and Export operations. Only receiver contexts are currently supported for export (see the rationale in pk11pub.h). One other change introduced here is that `PK11_HPKE_GetEncapPubKey` now works as expected on the receiver side. If the `wrapKey` argument is provided to the Export/Import functions, then the symmetric keys are wrapped with AES Key Wrap with Padding (SP800-38F, 6.3) prior to serialization. [8bcd12ab3b34] * automation/abi-check/expected-report-libssl3.so.txt, gtests/ssl_gtest/libssl_internals.c, gtests/ssl_gtest/libssl_internals.h, gtests/ssl_gtest/ssl_extension_unittest.cc, gtests/ssl_gtest/tls_ech_unittest.cc, lib/ssl/ssl3con.c, lib/ssl/ssl3ext.c, lib/ssl/ssl3ext.h, lib/ssl/sslexp.h, lib/ssl/sslimpl.h, lib/ssl/sslsecur.c, lib/ssl/sslsock.c, lib/ssl/sslt.h, lib/ssl/tls13con.c, lib/ssl/tls13con.h, lib/ssl/tls13ech.c, lib/ssl/tls13ech.h, lib/ssl/tls13exthandle.c, lib/ssl/tls13exthandle.h, lib/ssl/tls13hashstate.c, lib/ssl/tls13hashstate.h: Bug 1681585 - Update ECH to Draft-09. r=mt This patch updates ECH implementation to draft-09. Changes of note are: - Acceptance signal derivation is now based on the handshake secret. - `config_id` hint changes from 32B to 8B, trial decryption added on the server. - Duplicate code in HRR cookie handling has been consolidated into `tls13_HandleHrrCookie`. - `ech_is_inner` extension is added, which causes a server to indicate ECH acceptance. - Per the above, support signaling ECH acceptance when acting as a backend server in split-mode (i.e. when there is no other local Encrypted Client Hello state). [ed07a2e2a124] 2021-01-24 Kevin Jacobs * cmd/selfserv/selfserv.c: Bug 1681585 - Add ECH support to selfserv. r=mt Usage example: mkdir dbdir && cd dbdir certutil -N -d . certutil -S -s "CN=ech-public.com" -n ech-public.com -x -t "C,C,C" -m 1234 -d . certutil -S -s "CN=ech-private-backend.com" -n ech-private- backend.com -x -t "C,C,C" -m 2345 -d . ../dist/Debug/bin/selfserv -a ech-public.com -a ech-private-backend.com -n ech-public.com -n ech- private-backend.com -p 8443 -d dbdir/ -X publicname:ech-public.com (Copy echconfig from selfserv output and paste into the below command) ../dist/Debug/bin/tstclnt -D -p 8443 -v -A tests/ssl/sslreq.dat -h ech-private-backend.com -o -N -v [92dcda94c1d4] Differential Revision: https://phabricator.services.mozilla.com/D102982 --- build/moz.configure/nss.configure | 2 +- security/nss/TAG-INFO | 2 +- .../abi-check/expected-report-libnss3.so.txt | 5 + .../abi-check/expected-report-libssl3.so.txt | 11 + .../automation/abi-check/previous-nss-release | 2 +- security/nss/cmd/selfserv/selfserv.c | 221 ++++- security/nss/coreconf/coreconf.dep | 1 + .../gtests/common/testvectors/hpke-vectors.h | 250 +++--- .../gtests/pk11_gtest/pk11_hpke_unittest.cc | 604 ++++++++----- .../nss/gtests/ssl_gtest/libssl_internals.c | 23 +- .../nss/gtests/ssl_gtest/libssl_internals.h | 4 +- .../ssl_gtest/ssl_extension_unittest.cc | 16 - .../nss/gtests/ssl_gtest/tls_ech_unittest.cc | 471 ++++++---- security/nss/lib/nss/nss.def | 7 + security/nss/lib/nss/nss.h | 6 +- security/nss/lib/pk11wrap/pk11hpke.c | 457 ++++++++-- security/nss/lib/pk11wrap/pk11hpke.h | 6 +- security/nss/lib/pk11wrap/pk11pub.h | 23 +- security/nss/lib/softoken/softkver.h | 6 +- security/nss/lib/ssl/ssl3con.c | 163 ++-- security/nss/lib/ssl/ssl3ext.c | 10 +- security/nss/lib/ssl/ssl3ext.h | 10 +- security/nss/lib/ssl/sslexp.h | 8 + security/nss/lib/ssl/sslimpl.h | 11 +- security/nss/lib/ssl/sslsecur.c | 1 + security/nss/lib/ssl/sslsock.c | 15 +- security/nss/lib/ssl/sslt.h | 3 +- security/nss/lib/ssl/tls13con.c | 84 +- security/nss/lib/ssl/tls13con.h | 2 +- security/nss/lib/ssl/tls13ech.c | 809 ++++++++++-------- security/nss/lib/ssl/tls13ech.h | 25 +- security/nss/lib/ssl/tls13exthandle.c | 106 ++- security/nss/lib/ssl/tls13exthandle.h | 3 + security/nss/lib/ssl/tls13hashstate.c | 257 +++--- security/nss/lib/ssl/tls13hashstate.h | 15 +- security/nss/lib/util/nssutil.h | 6 +- .../chains/scenarios/nameconstraints.cfg | 12 +- .../libpkix/certs/NameConstraints.ipaca.cert | Bin 981 -> 1000 bytes .../libpkix/certs/NameConstraints.ocsp1.cert | Bin 898 -> 956 bytes 39 files changed, 2416 insertions(+), 1241 deletions(-) diff --git a/build/moz.configure/nss.configure b/build/moz.configure/nss.configure index 3cdee3306173..e66d63e546c1 100644 --- a/build/moz.configure/nss.configure +++ b/build/moz.configure/nss.configure @@ -9,7 +9,7 @@ option("--with-system-nss", help="Use system NSS") imply_option("--with-system-nspr", True, when="--with-system-nss") nss_pkg = pkg_check_modules( - "NSS", "nss >= 3.61", when="--with-system-nss", config=False + "NSS", "nss >= 3.62", when="--with-system-nss", config=False ) set_config("MOZ_SYSTEM_NSS", True, when="--with-system-nss") diff --git a/security/nss/TAG-INFO b/security/nss/TAG-INFO index 183f80acffaf..60e30d16e298 100644 --- a/security/nss/TAG-INFO +++ b/security/nss/TAG-INFO @@ -1 +1 @@ -NSS_3_61_RTM \ No newline at end of file +92dcda94c1d4 \ No newline at end of file diff --git a/security/nss/automation/abi-check/expected-report-libnss3.so.txt b/security/nss/automation/abi-check/expected-report-libnss3.so.txt index e69de29bb2d1..f758e8bec79e 100644 --- a/security/nss/automation/abi-check/expected-report-libnss3.so.txt +++ b/security/nss/automation/abi-check/expected-report-libnss3.so.txt @@ -0,0 +1,5 @@ + +2 Added functions: + + [A] 'function SECStatus PK11_HPKE_ExportContext(const HpkeContext*, PK11SymKey*, SECItem**)' {PK11_HPKE_ExportContext@@NSS_3.62} + [A] 'function HpkeContext* PK11_HPKE_ImportContext(const SECItem*, PK11SymKey*)' {PK11_HPKE_ImportContext@@NSS_3.62} diff --git a/security/nss/automation/abi-check/expected-report-libssl3.so.txt b/security/nss/automation/abi-check/expected-report-libssl3.so.txt index e69de29bb2d1..15e1f47da3b2 100644 --- a/security/nss/automation/abi-check/expected-report-libssl3.so.txt +++ b/security/nss/automation/abi-check/expected-report-libssl3.so.txt @@ -0,0 +1,11 @@ + +1 function with some indirect sub-type change: + + [C] 'function SECStatus SSL_HandshakeNegotiatedExtension(PRFileDesc*, SSLExtensionType, PRBool*)' at sslreveal.c:72:1 has some indirect sub-type changes: + parameter 2 of type 'typedef SSLExtensionType' has sub-type changes: + underlying type 'enum __anonymous_enum__' at sslt.h:519:1 changed: + type size hasn't changed + 1 enumerator insertion: + '__anonymous_enum__::ssl_tls13_ech_is_inner_xtn' value '55817' + 1 enumerator change: + '__anonymous_enum__::ssl_tls13_encrypted_client_hello_xtn' from value '65032' to '65033' at sslt.h:519:1 diff --git a/security/nss/automation/abi-check/previous-nss-release b/security/nss/automation/abi-check/previous-nss-release index 605d8a73f96a..e177689f5ad2 100644 --- a/security/nss/automation/abi-check/previous-nss-release +++ b/security/nss/automation/abi-check/previous-nss-release @@ -1 +1 @@ -NSS_3_60_BRANCH +NSS_3_61_BRANCH diff --git a/security/nss/cmd/selfserv/selfserv.c b/security/nss/cmd/selfserv/selfserv.c index 1584d7ee0224..be703318a175 100644 --- a/security/nss/cmd/selfserv/selfserv.c +++ b/security/nss/cmd/selfserv/selfserv.c @@ -42,6 +42,7 @@ #include "cert.h" #include "certt.h" #include "ocsp.h" +#include "nssb64.h" #ifndef PORT_Sprintf #define PORT_Sprintf sprintf @@ -140,6 +141,7 @@ static int configureReuseECDHE = -1; /* -1: don't configure, 0 refresh, >=1 reus static int configureWeakDHE = -1; /* -1: don't configure, 0 disable, >=1 enable*/ SECItem psk = { siBuffer, NULL, 0 }; SECItem pskLabel = { siBuffer, NULL, 0 }; +char *echParamsStr = NULL; static PRThread *acceptorThread; @@ -247,7 +249,14 @@ PrintParameterUsage() "-z Configure a TLS 1.3 External PSK with the given hex string for a key.\n" " To specify a label, use ':' as a delimiter. For example:\n" " 0xAAAABBBBCCCCDDDD:mylabel. Otherwise, the default label of\n" - " 'Client_identity' will be used.\n", + " 'Client_identity' will be used.\n" + "-X Configure the server for ECH via the given . ECHParams\n" + " are expected in one of two formats:\n" + " 1. A string containing the ECH public name prefixed by the substring\n" + " \"publicname:\". For example, \"publicname:example.com\". In this mode,\n" + " an ephemeral ECH keypair is generated and ECHConfigs are printed to stdout.\n" + " 2. As a Base64 tuple of || . In this mode, the\n" + " raw private key is used to bootstrap the HPKE context.\n", stderr); } @@ -1873,6 +1882,196 @@ importPsk(PRFileDesc *model_sock) return rv; } +static SECStatus +configureEchWithPublicName(PRFileDesc *model_sock, const char *public_name) +{ + SECStatus rv; + +#define OID_LEN 65 + unsigned char paramBuf[OID_LEN]; + SECItem ecParams = { siBuffer, paramBuf, sizeof(paramBuf) }; + SECKEYPublicKey *pubKey = NULL; + SECKEYPrivateKey *privKey = NULL; + SECOidData *oidData; + char *echConfigBase64 = NULL; + PRUint8 configBuf[1000]; + unsigned int len = 0; + unsigned int echCipherSuite = ((unsigned int)HpkeKdfHkdfSha256 << 16) | + HpkeAeadChaCha20Poly1305; + PK11SlotInfo *slot = PK11_GetInternalKeySlot(); + if (!slot) { + errWarn("PK11_GetInternalKeySlot failed"); + return SECFailure; + } + + oidData = SECOID_FindOIDByTag(SEC_OID_CURVE25519); + if (oidData && (2 + oidData->oid.len) < sizeof(paramBuf)) { + ecParams.data[0] = SEC_ASN1_OBJECT_ID; + ecParams.data[1] = oidData->oid.len; + memcpy(ecParams.data + 2, oidData->oid.data, oidData->oid.len); + ecParams.len = oidData->oid.len + 2; + } else { + errWarn("SECOID_FindOIDByTag failed"); + goto loser; + } + privKey = PK11_GenerateKeyPair(slot, CKM_EC_KEY_PAIR_GEN, &ecParams, + &pubKey, PR_FALSE, PR_FALSE, NULL); + + if (!privKey || !pubKey) { + errWarn("Failed to generate ECH keypair"); + goto loser; + } + rv = SSL_EncodeEchConfig(echParamsStr, &echCipherSuite, 1, + HpkeDhKemX25519Sha256, pubKey, 50, + configBuf, &len, sizeof(configBuf)); + if (rv != SECSuccess) { + errWarn("SSL_EncodeEchConfig failed"); + goto loser; + } + + rv = SSL_SetServerEchConfigs(model_sock, pubKey, privKey, configBuf, len); + if (rv != SECSuccess) { + errWarn("SSL_SetServerEchConfigs failed"); + goto loser; + } + + SECItem echConfigItem = { siBuffer, configBuf, len }; + echConfigBase64 = NSSBase64_EncodeItem(NULL, NULL, 0, &echConfigItem); + if (!echConfigBase64) { + errWarn("NSSBase64_EncodeItem failed"); + goto loser; + } + + // Remove the newline characters that NSSBase64_EncodeItem unhelpfully inserts. + char *newline = strstr(echConfigBase64, "\r\n"); + if (newline) { + memmove(newline, newline + 2, strlen(newline + 2) + 1); + } + + printf("%s\n", echConfigBase64); + PORT_Free(echConfigBase64); + SECKEY_DestroyPrivateKey(privKey); + SECKEY_DestroyPublicKey(pubKey); + PK11_FreeSlot(slot); + return SECSuccess; + +loser: + PORT_Free(echConfigBase64); + SECKEY_DestroyPrivateKey(privKey); + SECKEY_DestroyPublicKey(pubKey); + PK11_FreeSlot(slot); + return SECFailure; +} + +static SECStatus +configureEchWithData(PRFileDesc *model_sock) +{ +/* The input should be a Base64-encoded ECHKey struct: + * struct { + * opaque sk<0..2^16-1>; + * ECHConfig config<0..2^16>; // draft-ietf-tls-esni-09 + * } ECHKey; + * + * This is not a standardized format, rather it's designed for + * interoperability with https://github.com/xvzcf/tls-interop-runner. + */ + +#define REMAINING_BYTES(rdr, buf) \ + buf->len - (rdr - buf->data) + + SECStatus rv; + size_t len; + unsigned char *reader; + PK11SlotInfo *slot = NULL; + SECItem *decoded = NULL; + SECItem *pkcs8Key = NULL; + SECKEYPublicKey *pk = NULL; + SECKEYPrivateKey *sk = NULL; + + decoded = NSSBase64_DecodeBuffer(NULL, NULL, echParamsStr, PORT_Strlen(echParamsStr)); + if (!decoded || decoded->len < 2) { + errWarn("Couldn't decode ECHParams"); + goto loser; + }; + reader = decoded->data; + + len = (*(reader++) << 8); + len |= *(reader++); + if (len > (REMAINING_BYTES(reader, decoded) - 2)) { + errWarn("Bad ECHParams encoding"); + goto loser; + } + /* Importing a raw KEM private key is generally awful, + * however since we only support X25519, we can hardcode + * all the OID data. */ + const PRUint8 pkcs8Start[] = { 0x30, 0x67, 0x02, 0x01, 0x00, 0x30, 0x14, 0x06, + 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, + 0x47, 0x0F, 0x01, 0x04, 0x4C, 0x30, 0x4A, 0x02, + 0x01, 0x01, 0x04, 0x20 }; + const PRUint8 pkcs8End[] = { 0xA1, 0x23, 0x03, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 }; + pkcs8Key = SECITEM_AllocItem(NULL, NULL, sizeof(pkcs8Start) + len + sizeof(pkcs8End)); + if (!pkcs8Key) { + goto loser; + } + PORT_Memcpy(pkcs8Key->data, pkcs8Start, sizeof(pkcs8Start)); + PORT_Memcpy(&pkcs8Key->data[sizeof(pkcs8Start)], reader, len); + PORT_Memcpy(&pkcs8Key->data[sizeof(pkcs8Start) + len], pkcs8End, sizeof(pkcs8End)); + reader += len; + + /* Convert the key bytes to key handles */ + slot = PK11_GetInternalKeySlot(); + rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( + slot, pkcs8Key, NULL, NULL, PR_FALSE, PR_FALSE, KU_ALL, &sk, NULL); + if (rv != SECSuccess || !sk) { + errWarn("ECH key import failed"); + goto loser; + } + pk = SECKEY_ConvertToPublicKey(sk); + if (!pk) { + errWarn("ECH key conversion failed"); + goto loser; + } + + /* Remainder is the ECHConfig. */ + rv = SSL_SetServerEchConfigs(model_sock, pk, sk, reader, + REMAINING_BYTES(reader, decoded)); + if (rv != SECSuccess) { + errWarn("SSL_SetServerEchConfigs failed"); + goto loser; + } + + PK11_FreeSlot(slot); + SECKEY_DestroyPrivateKey(sk); + SECKEY_DestroyPublicKey(pk); + SECITEM_FreeItem(pkcs8Key, PR_TRUE); + SECITEM_FreeItem(decoded, PR_TRUE); + return SECSuccess; +loser: + if (slot) { + PK11_FreeSlot(slot); + } + SECKEY_DestroyPrivateKey(sk); + SECKEY_DestroyPublicKey(pk); + SECITEM_FreeItem(pkcs8Key, PR_TRUE); + SECITEM_FreeItem(decoded, PR_TRUE); + return SECFailure; +} + +static SECStatus +configureEch(PRFileDesc *model_sock) +{ + if (!PORT_Strncmp(echParamsStr, "publicname:", PORT_Strlen("publicname:"))) { + return configureEchWithPublicName(model_sock, + &echParamsStr[PORT_Strlen("publicname:")]); + } + return configureEchWithData(model_sock); +} + void server_main( PRFileDesc *listen_sock, @@ -2089,6 +2288,13 @@ server_main( } } + if (echParamsStr) { + rv = configureEch(model_sock); + if (rv != SECSuccess) { + errExit("configureEch failed"); + } + } + if (MakeCertOK) SSL_BadCertHook(model_sock, myBadCertHandler, NULL); @@ -2332,7 +2538,7 @@ main(int argc, char **argv) ** XXX: 'B', and 'q' were used in the past but removed ** in 3.28, please leave some time before resuing those. */ optstate = PL_CreateOptState(argc, argv, - "2:A:C:DEGH:I:J:L:M:NP:QRS:T:U:V:W:YZa:bc:d:e:f:g:hi:jk:lmn:op:rst:uvw:x:yz:"); + "2:A:C:DEGH:I:J:L:M:NP:QRS:T:U:V:W:X:YZa:bc:d:e:f:g:hi:jk:lmn:op:rst:uvw:x:yz:"); while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { ++optionsFound; switch (optstate->option) { @@ -2599,9 +2805,17 @@ main(int argc, char **argv) } break; + case 'X': + echParamsStr = PORT_Strdup(optstate->value); + if (echParamsStr == NULL) { + PL_DestroyOptState(optstate); + fprintf(stderr, "echParamsStr copy failed.\n"); + exit(5); + } + break; default: case '?': - fprintf(stderr, "Unrecognized or bad option specified.\n"); + fprintf(stderr, "Unrecognized or bad option specified: %c\n", optstate->option); fprintf(stderr, "Run '%s -h' for usage information.\n", progName); exit(4); break; @@ -2921,6 +3135,7 @@ cleanup: } SECITEM_ZfreeItem(&psk, PR_FALSE); SECITEM_ZfreeItem(&pskLabel, PR_FALSE); + PORT_Free(echParamsStr); if (NSS_Shutdown() != SECSuccess) { SECU_PrintError(progName, "NSS_Shutdown"); if (loggerThread) { diff --git a/security/nss/coreconf/coreconf.dep b/security/nss/coreconf/coreconf.dep index 5182f75552c8..590d1bfaeee3 100644 --- a/security/nss/coreconf/coreconf.dep +++ b/security/nss/coreconf/coreconf.dep @@ -10,3 +10,4 @@ */ #error "Do not include this header file." + diff --git a/security/nss/gtests/common/testvectors/hpke-vectors.h b/security/nss/gtests/common/testvectors/hpke-vectors.h index dd7b417b8ac1..afed3fea8c9d 100644 --- a/security/nss/gtests/common/testvectors/hpke-vectors.h +++ b/security/nss/gtests/common/testvectors/hpke-vectors.h @@ -52,48 +52,37 @@ const hpke_vector kHpkeTestVectors[] = { static_cast(1), static_cast(1), "4f6465206f6e2061204772656369616e2055726e", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a" - "02010104208c490e5b0c7dbe0c6d2192484d2b7a0423b3b4544f2481095a9" - "9dbf238fb350fa1230321008a07563949fac6232936ed6f36c4fa735930ecd" - "eaef6734e314aeac35a56fd0a", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a" - "02010104205a8aa0d2476b28521588e0c704b14db82cdd4970d340d293a957" - "6deaee9ec1c7a1230321008756e2580c07c1d2ffcb662f5fadc6d6ff13da85" - "abd7adfecf984aaa102c1269", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a02010104206" + "cee2e2755790708a2a1be22667883a5e3f9ec52810404a0d889a0ed3e28de00a123032100" + "950897e0d37a8bdb0f2153edf5fa580a64b399c39fbb3d014f80983352a63617", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a0201010420e" + "caf25b8485bcf40b9f013dbb96a6230f25733b8435bba0997a1dedbc7f78806a123032100" + "a5912b20892e36905bac635267e2353d58f8cc7525271a2bf57b9c48d2ec2c07", "", "", - "8a07563949fac6232936ed6f36c4fa735930ecdeaef6734e314aeac35a56fd0a", - "550ee0b7ec1ea2532f2e2bac87040a4c", - "2b855847756795a57229559a", + "950897e0d37a8bdb0f2153edf5fa580a64b399c39fbb3d014f80983352a63617", + "e20cee1bf5392ad2d3a442e231f187ae", + "5d99b2f03c452f7a9441933a", {// Encryptions {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d30", - "971ba65db526758ea30ae748cd769bc8d90579b62a037816057f24ce4274" - "16bd47c05ed1c2446ac8e19ec9ae79"}, + "9418f1ae06eddc43aa911032aed4a951754ee2286a786733761857f8d96a7ec8d852da9" + "3bc5eeab49623344aba"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d31", - "f18f1ec397667ca069b9a6ee0bebf0890cd5caa34bb9875b3600ca0142cb" - "a774dd35f2aafd79a02a08ca5f2806"}, + "74d69c61899b9158bb50e95d92fbad106f612ea67c61b3c4bef65c8bf3dc18e17bf41ec" + "4c408688aae58358d0e"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d32", - "51a8dea350fe6e753f743ec17c956de4cbdfa35f3018fc6a12752c51d137" - "2c5093959f18c7253da9c953c6cfbe"}}, + "e6602db9be05d81c4ab8fa621bc35993a7b759851075a34b3bffd257340011c70c9fa1f" + "5c11868a076fc3adb3b"}}, {// Exports - {"436f6e746578742d30", 32, - "0df04ac640d34a56561419bab20a68e6b7331070208004f89c7b973f4c47" - "2e92"}, - {"436f6e746578742d31", 32, - "723c2c8f80e6b827e72bd8e80973a801a05514afe3d4bc46e82e505dceb9" - "53aa"}, - {"436f6e746578742d32", 32, - "38010c7d5d81093a11b55e2403a258e9a195bcf066817b332dd996b0a9bc" - "bc9a"}, - {"436f6e746578742d33", 32, - "ebf6ab4c3186131de9b2c3c0bc3e2ad21dfcbc4efaf050cd0473f5b1535a" - "8b6d"}, - {"436f6e746578742d34", 32, - "c4823eeb3efd2d5216b2d3b16e542bf57470dc9b9ea9af6bce85b151a358" - "9d90"}}}, + {"", 32, + "be82c06bd83fd6edd74385de5a70859b9e03def4c7bb224a10cfae86087f8a25"}, + {"00", 32, + "82cbfd3c2b2db75e2311d457e569cf12b6387eb4309bca8e77adb2f2b599fc85"}, + {"54657374436f6e74657874", 32, + "c8387c1e6ec4f026c7f3577e3f29df51f46161295eec84c4f64a9174f7b64e4f"}}}, // A.1. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM, PSK mode {1, @@ -102,132 +91,153 @@ const hpke_vector kHpkeTestVectors[] = { static_cast(1), static_cast(1), "4f6465206f6e2061204772656369616e2055726e", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020" - "1010420e7d2b539792a48a24451303ccd0cfe77176b6cb06823c439edfd217458" - "a1398aa12303210008d39d3e7f9b586341b6004dafba9679d2bd9340066edb247" - "e3e919013efcd0f", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020" - "10104204b41ef269169090551fcea177ecdf622bca86d82298e21cd93119b804c" - "cc5eaba123032100a5c85773bed3a831e7096f7df4ff5d1d8bac48fc97bfac366" - "141efab91892a3a", - "5db3b80a81cb63ca59470c83414ef70a", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a02010104204" + "c1feed23e15ec6a55b8457e0c0f42a3a1ab3ccc309b7cbb7ac6165fc657bd3ba123032100" + "f16fa9440b2cb36c855b4b82fb87e1c02ce656dd132f7a7aec739294b6912768", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a02010104208" + "e5430f0d821407670e5e3f6eecc9f52b2cad27b15a5fad1f3d05359ae30d81ca123032100" + "13c789187a2dda71889e4b98dc5443624ae68f309cea91865561cfa207586e3a", + "0247fd33b913760fa1fa51e1892d9f307fbe65eb171e8132c2af18555a738b82", "456e6e796e20447572696e206172616e204d6f726961", - "08d39d3e7f9b586341b6004dafba9679d2bd9340066edb247e3e919013efcd0f", - "811e9b2d7a10f4f9d58786bf8a534ca6", - "b79b0c5a8c3808e238b10411", + "f16fa9440b2cb36c855b4b82fb87e1c02ce656dd132f7a7aec739294b6912768", + "70030b55bfb737d4f4355cf62302d281", + "746d5e6255902701c3e0b99f", {// Encryptions {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d30", - "fb68f911b4e4033d1547f646ea30c9cee987fb4b4a8c30918e5de6e96de32fc" - "63466f2fc05e09aeff552489741"}, + "63f7ed3d99e625d4a7373982b5f04daf0c3dfff39cac4b38eeb9d5c225cc3183bdbc91a" + "053db9b195319cc8c45"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d31", - "85e7472fbb7e2341af35fb2a0795df9a85caa99a8f584056b11d452bc160470" - "672e297f9892ce2c5020e794ae1"}, + "65e7160f80fdf47893a5abe1edcff46c85899f04acb97882e194ce6d4fceec2dc4cb2d3" + "abe5d969880722859b2"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d32", - "74229b7491102bcf94cf7633888bc48baa4e5a73cc544bfad4ff61585506fac" - "b44b359ade03c0b2b35c6430e4c"}}, + "915e08e6e340fca64982e90ad93490826bfb74af8f48062212c87105dad2b7569c83688" + "e564ed5862592b77cdc"}}, {// Exports - {"436f6e746578742d30", 32, - "bd292b132fae00243851451c3f3a87e9e11c3293c14d61b114b7e12e07245ffd"}, - {"436f6e746578742d31", 32, - "695de26bc9336caee01cb04826f6e224f4d2108066ab17fc18f0c993dce05f24"}, - {"436f6e746578742d32", 32, - "c53f26ef1bf4f5fd5469d807c418a0e103d035c76ccdbc6afb5bc42b24968f6c"}, - {"436f6e746578742d33", 32, - "8cea4a595dfe3de84644ca8ea7ea9401a345f0db29bb4beebc2c471afc602ec4"}, - {"436f6e746578742d34", 32, - "e6313f12f6c2054c69018f273211c54fcf2439d90173392eaa34b4caac929068"}}}, - - // A.2. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305, Base mode + {"", 32, + "7c40ceb745e14d19fceeac6e4756c796957fe5ff28709198c3f8cbdb5d368fe1"}, + {"00", 32, + "1ef0fd07bd40326f1b88f3545c92969cff202ca7186b9fd1315241f93fcc2edf"}, + {"54657374436f6e74657874", 32, + "997368419db9490aa96c977cdd90bda8fd6234054d4add3d2f31aaaa2f8c1172"}}}, + // A.2. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305, Base + // mode {2, static_cast(0), static_cast(32), static_cast(1), static_cast(3), "4f6465206f6e2061204772656369616e2055726e", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020" - "10104205006a9a0f0138b9b5d577ed4a67c4f795aee8fc146ac63d7a4167765be" - "3ad7dca123032100716281787b035b2fee90455d951fa70b3db6cc92f13bedfd7" - "58c3487994b7020", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020" - "101042062139576dcbf9878ccd56262d1b28dbea897821c03370d81971513cc74" - "aea3ffa1230321001ae26f65041b36ad69eb392c198bfd33df1c6ff17a910cb3e" - "49db7506b6a4e7f", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a0201010420e" + "fda8f0538ce6ab9f165aae26e02ad96dcb1775b248267174aeb3d140e002ee3a123032100" + "1440805f4e60cbd34835baf0813c3071d17def1dbd8c04e75889bb2271d7823a", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a02010104201" + "4365bb26500e7cf263720c4ab04bd45b8e146b4f724facd1fa01d58b63975e4a123032100" + "26147d5c2978bccc3cc03a4f9ac607560b5d83f852be4e9024f2cb7207d4c30e", "", "", - "716281787b035b2fee90455d951fa70b3db6cc92f13bedfd758c3487994b7020", - "1d5e71e2885ddadbcc479798cc65ea74d308f2a9e99c0cc7fe480adce66b5722", - "8354a7fcfef97d4bbef6d24e", + "1440805f4e60cbd34835baf0813c3071d17def1dbd8c04e75889bb2271d7823a", + "a17448a542d0d6d75e3b21be0a1f68607904b4802c6b19a7e7e90976aa00a5c8", + "6f6b832dba944a91e5684514", {// Encryptions {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d30", - "fa4632a400962c98143e58450e75d879365359afca81a5f5b5997c6555647ec" - "302045a80c57d3e2c2abe7e1ced"}, + "1b9ce69bd0e6b4242ac2dd841ef093fc9dfa9e684f81c2d1778fd3268ca5aa7d612cd87" + "f72acd2aeaee084dee2"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d31", - "8313fcbf760714f5a93b6864820e48dcec3ddd476ad4408ff1c1a1f7bfb8cb8" - "699fada4a9e59bf8086eb1c0635"}, + "f041fb8de275b5319587269cb39190029906b9267eb5619b7bec8a5e0b3b3a0bead1696" + "17f2c4d45d028b1b654"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d32", - "020f2856d95b85e1def9549bf327c484d327616f1e213045f117be4c287571a" - "b983958f74766cbc6f8197c8d8d"}}, + "0042c74002608a20e432ee9628e84cba76482aca29359e93d60067371be547355acca2c" + "271a2072b85a77a6237"}}, {// Exports - {"436f6e746578742d30", 32, - "22bbe971392c685b55e13544cdaf976f36b89dc1dbe1296c2884971a5aa9e331"}, - {"436f6e746578742d31", 32, - "5c0fa72053a2622d8999b726446db9ef743e725e2cb040afac2d83eae0d41981"}, - {"436f6e746578742d32", 32, - "72b0f9999fd37ac2b948a07dadd01132587501a5a9460d596c1f7383299a2442"}, - {"436f6e746578742d33", 32, - "73d2308ed5bdd63aacd236effa0db2d3a30742b6293a924d95a372e76d90486b"}, - {"436f6e746578742d34", 32, - "d4f8878dbc471935e86cdee08746e53837bbb4b6013003bebb0bc1cc3e074085"}}}, - - // A.2. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305, PSK mode + {"", 32, + "996dc6fda1dc47e687613e0e221d64a3598e1ead9585177d22f230716569c04d"}, + {"00", 32, + "6d07b4e3e06ace3dc3f1b2a0826a0f896aa828769ff993c2e3829ae40325c27d"}, + {"54657374436f6e74657874", 32, + "bb69068c4f7767331512d375e4ab0ca0c6c51446040096ea0ae1cc3f9a3f54bd"}}}, + // A.2. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305, PSK + // mode {3, static_cast(1), static_cast(32), static_cast(1), static_cast(3), "4f6465206f6e2061204772656369616e2055726e", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020" - "10104204bfdb62b95ae2a1f29f20ea49e24aa2673e0d240c6e967f668f55ed5de" - "e996dca123032100f4639297e3305b03d34dd5d86522ddc6ba11a608a0003670a" - "30734823cdd3763", - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020" - "1010420a6ab4e1bb782d580d837843089d65ebe271a0ee9b5a951777cecf1293c" - "58c150a123032100c49b46ed73ecb7d3a6a3e44f54b8f00f9ab872b57dd79ded6" - "6d7231a14c64144", - "5db3b80a81cb63ca59470c83414ef70a", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a0201010420d" + "b1c9dfba77e1e3b8687ea18af207cffca803bdd983f955376b8271ef9c78a46a123032100" + "8e4b29035c22b67b3a7a0f5a52f12b3ab17a9ae1f0c63b029137ba09f420224a", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a02010104204" + "e335da3ec60e68c156586b8217de6801cb83b5a4de413645fcb112c00b2228ba123032100" + "94ea1227a357dfd3548aadb9ef19d9974add594871498e123390a8bcb4db5d51", + "0247fd33b913760fa1fa51e1892d9f307fbe65eb171e8132c2af18555a738b82", "456e6e796e20447572696e206172616e204d6f726961", - "f4639297e3305b03d34dd5d86522ddc6ba11a608a0003670a30734823cdd3763", - "396c06a52b39d0930594aa2c6944561cc1741f638557a12bef1c1cad349157c9", - "baa4ecf96b5d6d536d0d7210", + "8e4b29035c22b67b3a7a0f5a52f12b3ab17a9ae1f0c63b029137ba09f420224a", + "a603fe0f9897dc6ce042a467d6bd430a01cd679e930f1b5706ad425e4153496d", + "318e48afae42913a928146e6", {// Encryptions {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d30", - "f97ca72675b8199e8ffec65b4c200d901110b177b246f241b6f9716fb60b35b" - "32a6d452675534b591e8141468a"}, + "c87f8158a501c7a2f31708bbdba10f9c5ad035624c3153eeb028e65b82f41f38cbe1cd9" + "aafb10e502d328b83c1"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d31", - "57796e2b9dd0ddf807f1a7cb5884dfc50e61468c4fd69fa03963731e51674ca" - "88fee94eeac3290734e1627ded6"}, + "aef7a0b0e3a58b177dac9628439b44d1e706724e265ab3b46d791612b51637342479ad9" + "45607b8b54112bd8c86"}, {"4265617574792069732074727574682c20747275746820626561757479", "436f756e742d32", - "b514150af1057151687d0036a9b4a3ad50fb186253f839d8433622baa85719e" - "d5d2532017a0ce7b9ca0007f276"}}, + "c00884a5c658213bd4381d65b54d93682692fef9408a6e437a97a904267727269b242d3" + "d81725ad8f0c764e082"}}, {// Exports - {"436f6e746578742d30", 32, - "735400cd9b9193daffe840f412074728ade6b1978e9ae27957aacd588dbd7c9e"}, - {"436f6e746578742d31", 32, - "cf4e351e1943d171ff2d88726f18160086ecbec52a8151dba8cf5ba0737a6097"}, - {"436f6e746578742d32", 32, - "8e23b44d4f23dd906d1c100580a670d171132c9786212c4ca2876a1541a84fae"}, - {"436f6e746578742d33", 32, - "56252a940ece53d4013eb619b444ee1d019a08eec427ded2b6dbf24be624a4a0"}, - {"436f6e746578742d34", 32, - "fc6cdca9ce8ab062401478ffd16ee1c07e2b15d7c781d4227f07c6043d937fad"}}}}; - + {"", 32, + "23c31ee2757bbecf105f74c90bf1e640b6ddc545dc8d80b1abbf2aa9dd1786ce"}, + {"00", 32, + "05af7597519945fe8443f7cb84cdb651a8dd18cd7bbbd65d31095d3c69c1257e"}, + {"54657374436f6e74657874", 32, + "5814619f842c7c328c9657854154e51b581c7bbd3b646bd773be67f93900a109"}}}, + // DHKEM(X25519, HKDF-SHA256), HKDF-SHA512, ChaCha20Poly1305, Base mode + // Tests KEM.hash != KDF.hash. + {4, + static_cast(0), + static_cast(32), + static_cast(3), + static_cast(3), + "4f6465206f6e2061204772656369616e2055726e", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a02010104200" + "6e74abcc8b65671d1ef4a6cb273662c6a3b3ff6590852bfebc7bc94887f5c4ea123032100" + "de2746f66f3e14a3389f570e8f8cc1de4e39a89d1cbb445fad711d7acf407e15", + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a02010104202" + "dc14b2f31b233963f0a2d9a836072f29666fdea84a5893d30254deb9183e0a9a123032100" + "318f92c9e96142c4ce9a06ea04f7099698ee4160044f2db585d9e2b02abd6041", + "", + "", + "de2746f66f3e14a3389f570e8f8cc1de4e39a89d1cbb445fad711d7acf407e15", + "4a54adb318d8a420506b0473815a32c2b1923a936fa7c735c8a038a38fcc80d2", + "9c6d83a59628e7327d19a3d8", + {// Encryptions + {"4265617574792069732074727574682c20747275746820626561757479", + "436f756e742d30", + "59cbc98df2d7640598377e3184e07c008dea1c264c72a8414028715960ab6d6909a3110" + "e633a23baf8b9b5e2f1"}, + {"4265617574792069732074727574682c20747275746820626561757479", + "436f756e742d31", + "c9a4b68ea349eea9fdf499f7577c9325e9b76f24308a81ac5dfbbee3489dd41c85d7fb3" + "5e585859ea5c790f155"}, + {"4265617574792069732074727574682c20747275746820626561757479", + "436f756e742d32", + "5d9f717b192b43bea1f6bd25ee63d7b88b06019132c31a4e262a4c1d4f01c7bd70d00df" + "0e2f858cf654ae86447"}}, + {// Exports + {"", 32, + "97b0ac016b9dedb5f115cf6fd24b927f8e75b48a2ab6069efe7fec6a18ff4272"}, + {"00", 32, + "c994b47854104e476d9e47bb15f9fb66f4879f68bc89a4cfccc259e80a30c913"}, + {"54657374436f6e74657874", 32, + "9199e5beeda45397b1bbee3dd13ad1afbd2963f83d9e5ebdf1e23b6c7e012317"}}}, +}; #endif // hpke_vectors_h__ diff --git a/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc b/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc index c8220621e23b..48de0a7af1a6 100644 --- a/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc +++ b/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc @@ -22,36 +22,8 @@ namespace nss_test { #ifdef NSS_ENABLE_DRAFT_HPKE #include "cpputil.h" -class Pkcs11HpkeTest : public ::testing::TestWithParam { +class HpkeTest { protected: - void ReadVector(const hpke_vector &vec) { - ScopedPK11SymKey vec_psk; - if (!vec.psk.empty()) { - ASSERT_FALSE(vec.psk_id.empty()); - vec_psk_id = hex_string_to_bytes(vec.psk_id); - - std::vector psk_bytes = hex_string_to_bytes(vec.psk); - SECItem psk_item = {siBuffer, toUcharPtr(psk_bytes.data()), - static_cast(psk_bytes.size())}; - ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); - ASSERT_TRUE(slot); - PK11SymKey *psk_key = - PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN, PK11_OriginUnwrap, - CKA_WRAP, &psk_item, nullptr); - ASSERT_NE(nullptr, psk_key); - vec_psk_key.reset(psk_key); - } - - vec_pkcs8_r = hex_string_to_bytes(vec.pkcs8_r); - vec_pkcs8_e = hex_string_to_bytes(vec.pkcs8_e); - vec_key = hex_string_to_bytes(vec.key); - vec_nonce = hex_string_to_bytes(vec.nonce); - vec_enc = hex_string_to_bytes(vec.enc); - vec_info = hex_string_to_bytes(vec.info); - vec_encryptions = vec.encrypt_vecs; - vec_exports = vec.export_vecs; - } - void CheckEquality(const std::vector &expected, SECItem *actual) { if (!actual) { EXPECT_TRUE(expected.empty()); @@ -102,55 +74,185 @@ class Pkcs11HpkeTest : public ::testing::TestWithParam { CheckEquality(expected_vec, actual); } - void SetupS(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkE, - const ScopedSECKEYPrivateKey &skE, - const ScopedSECKEYPublicKey &pkR, - const std::vector &info) { - SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()), - static_cast(vec_info.size())}; - SECStatus rv = - PK11_HPKE_SetupS(cx.get(), pkE.get(), skE.get(), pkR.get(), &info_item); - EXPECT_EQ(SECSuccess, rv); - } - - void SetupR(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkR, - const ScopedSECKEYPrivateKey &skR, - const std::vector &enc, - const std::vector &info) { - SECItem enc_item = {siBuffer, toUcharPtr(enc.data()), - static_cast(enc.size())}; - SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()), - static_cast(vec_info.size())}; - SECStatus rv = - PK11_HPKE_SetupR(cx.get(), pkR.get(), skR.get(), &enc_item, &info_item); - EXPECT_EQ(SECSuccess, rv); - } - void Seal(const ScopedHpkeContext &cx, std::vector &aad_vec, - std::vector &pt_vec, SECItem **out_ct) { + std::vector &pt_vec, std::vector &out_sealed) { SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()), static_cast(aad_vec.size())}; SECItem pt_item = {siBuffer, toUcharPtr(pt_vec.data()), static_cast(pt_vec.size())}; - SECStatus rv = PK11_HPKE_Seal(cx.get(), &aad_item, &pt_item, out_ct); - EXPECT_EQ(SECSuccess, rv); + SECItem *sealed_item = nullptr; + EXPECT_EQ(SECSuccess, + PK11_HPKE_Seal(cx.get(), &aad_item, &pt_item, &sealed_item)); + ASSERT_NE(nullptr, sealed_item); + ScopedSECItem sealed(sealed_item); + out_sealed.assign(sealed->data, sealed->data + sealed->len); } void Open(const ScopedHpkeContext &cx, std::vector &aad_vec, - std::vector &ct_vec, SECItem **out_pt) { + std::vector &ct_vec, std::vector &out_opened) { SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()), static_cast(aad_vec.size())}; SECItem ct_item = {siBuffer, toUcharPtr(ct_vec.data()), static_cast(ct_vec.size())}; - SECStatus rv = PK11_HPKE_Open(cx.get(), &aad_item, &ct_item, out_pt); - EXPECT_EQ(SECSuccess, rv); + SECItem *opened_item = nullptr; + EXPECT_EQ(SECSuccess, + PK11_HPKE_Open(cx.get(), &aad_item, &ct_item, &opened_item)); + ASSERT_NE(nullptr, opened_item); + ScopedSECItem opened(opened_item); + out_opened.assign(opened->data, opened->data + opened->len); + } + + void SealOpen(const ScopedHpkeContext &sender, + const ScopedHpkeContext &receiver, std::vector &msg, + std::vector &aad, const std::vector *expect) { + std::vector sealed; + std::vector opened; + Seal(sender, aad, msg, sealed); + if (expect) { + EXPECT_EQ(*expect, sealed); + } + Open(receiver, aad, sealed, opened); + EXPECT_EQ(msg, opened); + } + + void ExportSecret(const ScopedHpkeContext &receiver, + ScopedPK11SymKey &exported) { + std::vector context = {'c', 't', 'x', 't'}; + SECItem context_item = {siBuffer, context.data(), + static_cast(context.size())}; + PK11SymKey *tmp_exported = nullptr; + ASSERT_EQ(SECSuccess, PK11_HPKE_ExportSecret(receiver.get(), &context_item, + 64, &tmp_exported)); + exported.reset(tmp_exported); + } + + void ExportImportRecvContext(ScopedHpkeContext &scoped_cx, + PK11SymKey *wrapping_key) { + SECItem *tmp_exported = nullptr; + EXPECT_EQ(SECSuccess, PK11_HPKE_ExportContext(scoped_cx.get(), wrapping_key, + &tmp_exported)); + EXPECT_NE(nullptr, tmp_exported); + ScopedSECItem context(tmp_exported); + scoped_cx.reset(); + + HpkeContext *tmp_imported = + PK11_HPKE_ImportContext(context.get(), wrapping_key); + EXPECT_NE(nullptr, tmp_imported); + scoped_cx.reset(tmp_imported); + } + + bool GenerateKeyPair(ScopedSECKEYPublicKey &pub_key, + ScopedSECKEYPrivateKey &priv_key) { + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + ADD_FAILURE() << "Couldn't get slot"; + return false; + } + + unsigned char param_buf[65]; + SECItem ecdsa_params = {siBuffer, param_buf, sizeof(param_buf)}; + SECOidData *oid_data = SECOID_FindOIDByTag(SEC_OID_CURVE25519); + if (!oid_data) { + ADD_FAILURE() << "Couldn't get oid_data"; + return false; + } + ecdsa_params.data[0] = SEC_ASN1_OBJECT_ID; + ecdsa_params.data[1] = oid_data->oid.len; + memcpy(ecdsa_params.data + 2, oid_data->oid.data, oid_data->oid.len); + ecdsa_params.len = oid_data->oid.len + 2; + + SECKEYPublicKey *pub_tmp; + SECKEYPrivateKey *priv_tmp; + priv_tmp = + PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &ecdsa_params, + &pub_tmp, PR_FALSE, PR_TRUE, nullptr); + if (!pub_tmp || !priv_tmp) { + ADD_FAILURE() << "PK11_GenerateKeyPair failed"; + return false; + } + + pub_key.reset(pub_tmp); + priv_key.reset(priv_tmp); + return true; + } + + void SetUpEphemeralContexts(ScopedHpkeContext &sender, + ScopedHpkeContext &receiver, + HpkeModeId mode = HpkeModeBase, + HpkeKemId kem = HpkeDhKemX25519Sha256, + HpkeKdfId kdf = HpkeKdfHkdfSha256, + HpkeAeadId aead = HpkeAeadAes128Gcm) { + // Generate a PSK, if the mode calls for it. + PRUint8 psk_id_buf[] = {'p', 's', 'k', '-', 'i', 'd'}; + SECItem psk_id = {siBuffer, psk_id_buf, sizeof(psk_id_buf)}; + SECItem *psk_id_item = (mode == HpkeModePsk) ? &psk_id : nullptr; + ScopedPK11SymKey psk; + if (mode == HpkeModePsk) { + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(slot); + PK11SymKey *tmp_psk = + PK11_KeyGen(slot.get(), CKM_HKDF_DERIVE, nullptr, 16, nullptr); + ASSERT_NE(nullptr, tmp_psk); + psk.reset(tmp_psk); + } + + std::vector info = {'t', 'e', 's', 't', '-', 'i', 'n', 'f', 'o'}; + SECItem info_item = {siBuffer, info.data(), + static_cast(info.size())}; + sender.reset(PK11_HPKE_NewContext(kem, kdf, aead, psk.get(), psk_id_item)); + receiver.reset( + PK11_HPKE_NewContext(kem, kdf, aead, psk.get(), psk_id_item)); + ASSERT_TRUE(sender); + ASSERT_TRUE(receiver); + + ScopedSECKEYPublicKey pub_key_r; + ScopedSECKEYPrivateKey priv_key_r; + ASSERT_TRUE(GenerateKeyPair(pub_key_r, priv_key_r)); + EXPECT_EQ(SECSuccess, PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, + pub_key_r.get(), &info_item)); + + const SECItem *enc = PK11_HPKE_GetEncapPubKey(sender.get()); + EXPECT_NE(nullptr, enc); + EXPECT_EQ(SECSuccess, PK11_HPKE_SetupR( + receiver.get(), pub_key_r.get(), priv_key_r.get(), + const_cast(enc), &info_item)); + } +}; + +class TestVectors : public HpkeTest, + public ::testing::TestWithParam { + protected: + void ReadVector(const hpke_vector &vec) { + ScopedPK11SymKey vec_psk; + if (!vec.psk.empty()) { + ASSERT_FALSE(vec.psk_id.empty()); + vec_psk_id = hex_string_to_bytes(vec.psk_id); + + std::vector psk_bytes = hex_string_to_bytes(vec.psk); + SECItem psk_item = {siBuffer, toUcharPtr(psk_bytes.data()), + static_cast(psk_bytes.size())}; + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(slot); + PK11SymKey *psk_key = + PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN, PK11_OriginUnwrap, + CKA_WRAP, &psk_item, nullptr); + ASSERT_NE(nullptr, psk_key); + vec_psk_key.reset(psk_key); + } + + vec_pkcs8_r = hex_string_to_bytes(vec.pkcs8_r); + vec_pkcs8_e = hex_string_to_bytes(vec.pkcs8_e); + vec_key = hex_string_to_bytes(vec.key); + vec_nonce = hex_string_to_bytes(vec.nonce); + vec_enc = hex_string_to_bytes(vec.enc); + vec_info = hex_string_to_bytes(vec.info); + vec_encryptions = vec.encrypt_vecs; + vec_exports = vec.export_vecs; } void TestExports(const ScopedHpkeContext &sender, const ScopedHpkeContext &receiver) { - SECStatus rv; - for (auto &vec : vec_exports) { std::vector context = hex_string_to_bytes(vec.ctxt); std::vector expected = hex_string_to_bytes(vec.exported); @@ -158,12 +260,11 @@ class Pkcs11HpkeTest : public ::testing::TestWithParam { static_cast(context.size())}; PK11SymKey *actual_r = nullptr; PK11SymKey *actual_s = nullptr; - rv = PK11_HPKE_ExportSecret(sender.get(), &context_item, vec.len, - &actual_s); - ASSERT_EQ(SECSuccess, rv); - rv = PK11_HPKE_ExportSecret(receiver.get(), &context_item, vec.len, - &actual_r); - ASSERT_EQ(SECSuccess, rv); + ASSERT_EQ(SECSuccess, PK11_HPKE_ExportSecret(sender.get(), &context_item, + vec.len, &actual_s)); + ASSERT_EQ(SECSuccess, + PK11_HPKE_ExportSecret(receiver.get(), &context_item, vec.len, + &actual_r)); ScopedPK11SymKey scoped_act_s(actual_s); ScopedPK11SymKey scoped_act_r(actual_r); CheckEquality(expected, scoped_act_s.get()); @@ -177,15 +278,7 @@ class Pkcs11HpkeTest : public ::testing::TestWithParam { std::vector msg = hex_string_to_bytes(enc_vec.pt); std::vector aad = hex_string_to_bytes(enc_vec.aad); std::vector expect_ct = hex_string_to_bytes(enc_vec.ct); - SECItem *act_ct = nullptr; - Seal(sender, aad, msg, &act_ct); - CheckEquality(expect_ct, act_ct); - ScopedSECItem scoped_ct(act_ct); - - SECItem *act_pt = nullptr; - Open(receiver, aad, expect_ct, &act_pt); - CheckEquality(msg, act_pt); - ScopedSECItem scoped_pt(act_pt); + SealOpen(sender, receiver, msg, aad, &expect_ct); } } @@ -200,10 +293,9 @@ class Pkcs11HpkeTest : public ::testing::TestWithParam { SECItem pkcs8_e_item = {siBuffer, toUcharPtr(vec_pkcs8_e.data()), static_cast(vec_pkcs8_e.size())}; SECKEYPrivateKey *sk_e = nullptr; - SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( - slot.get(), &pkcs8_e_item, nullptr, nullptr, false, false, KU_ALL, - &sk_e, nullptr); - EXPECT_EQ(SECSuccess, rv); + EXPECT_EQ(SECSuccess, PK11_ImportDERPrivateKeyInfoAndReturnKey( + slot.get(), &pkcs8_e_item, nullptr, nullptr, + false, false, KU_ALL, &sk_e, nullptr)); skE_derived.reset(sk_e); SECKEYPublicKey *pk_e = SECKEY_ConvertToPublicKey(skE_derived.get()); ASSERT_NE(nullptr, pk_e); @@ -212,64 +304,49 @@ class Pkcs11HpkeTest : public ::testing::TestWithParam { SECItem pkcs8_r_item = {siBuffer, toUcharPtr(vec_pkcs8_r.data()), static_cast(vec_pkcs8_r.size())}; SECKEYPrivateKey *sk_r = nullptr; - rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( - slot.get(), &pkcs8_r_item, nullptr, nullptr, false, false, KU_ALL, - &sk_r, nullptr); - EXPECT_EQ(SECSuccess, rv); + EXPECT_EQ(SECSuccess, PK11_ImportDERPrivateKeyInfoAndReturnKey( + slot.get(), &pkcs8_r_item, nullptr, nullptr, + false, false, KU_ALL, &sk_r, nullptr)); skR_derived.reset(sk_r); SECKEYPublicKey *pk_r = SECKEY_ConvertToPublicKey(skR_derived.get()); ASSERT_NE(nullptr, pk_r); pkR_derived.reset(pk_r); } + void SetupS(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkE, + const ScopedSECKEYPrivateKey &skE, + const ScopedSECKEYPublicKey &pkR, + const std::vector &info) { + SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()), + static_cast(vec_info.size())}; + EXPECT_EQ(SECSuccess, PK11_HPKE_SetupS(cx.get(), pkE.get(), skE.get(), + pkR.get(), &info_item)); + } + + void SetupR(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkR, + const ScopedSECKEYPrivateKey &skR, + const std::vector &enc, + const std::vector &info) { + SECItem enc_item = {siBuffer, toUcharPtr(enc.data()), + static_cast(enc.size())}; + SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()), + static_cast(vec_info.size())}; + EXPECT_EQ(SECSuccess, PK11_HPKE_SetupR(cx.get(), pkR.get(), skR.get(), + &enc_item, &info_item)); + } + void SetupSenderReceiver(const ScopedHpkeContext &sender, const ScopedHpkeContext &receiver) { SetupS(sender, pkE_derived, skE_derived, pkR_derived, vec_info); uint8_t buf[32]; // Curve25519 only, fixed size. SECItem encap_item = {siBuffer, const_cast(buf), sizeof(buf)}; - SECStatus rv = PK11_HPKE_Serialize(pkE_derived.get(), encap_item.data, - &encap_item.len, encap_item.len); - ASSERT_EQ(SECSuccess, rv); + ASSERT_EQ(SECSuccess, + PK11_HPKE_Serialize(pkE_derived.get(), encap_item.data, + &encap_item.len, encap_item.len)); CheckEquality(vec_enc, &encap_item); SetupR(receiver, pkR_derived, skR_derived, vec_enc, vec_info); } - bool GenerateKeyPair(ScopedSECKEYPublicKey &pub_key, - ScopedSECKEYPrivateKey &priv_key) { - unsigned char param_buf[65]; - - ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); - if (!slot) { - ADD_FAILURE() << "Couldn't get slot"; - return false; - } - - SECItem ecdsa_params = {siBuffer, param_buf, sizeof(param_buf)}; - SECOidData *oid_data = SECOID_FindOIDByTag(SEC_OID_CURVE25519); - if (!oid_data) { - ADD_FAILURE() << "Couldn't get oid_data"; - return false; - } - - ecdsa_params.data[0] = SEC_ASN1_OBJECT_ID; - ecdsa_params.data[1] = oid_data->oid.len; - memcpy(ecdsa_params.data + 2, oid_data->oid.data, oid_data->oid.len); - ecdsa_params.len = oid_data->oid.len + 2; - SECKEYPublicKey *pub_tmp; - SECKEYPrivateKey *priv_tmp; - priv_tmp = - PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &ecdsa_params, - &pub_tmp, PR_FALSE, PR_TRUE, nullptr); - if (!pub_tmp || !priv_tmp) { - ADD_FAILURE() << "PK11_GenerateKeyPair failed"; - return false; - } - - pub_key.reset(pub_tmp); - priv_key.reset(priv_tmp); - return true; - } - void RunTestVector(const hpke_vector &vec) { ReadVector(vec); SECItem psk_id_item = {siBuffer, toUcharPtr(vec_psk_id.data()), @@ -307,12 +384,31 @@ class Pkcs11HpkeTest : public ::testing::TestWithParam { ScopedSECKEYPrivateKey skR_derived; }; -TEST_P(Pkcs11HpkeTest, TestVectors) { RunTestVector(GetParam()); } +TEST_P(TestVectors, TestVectors) { RunTestVector(GetParam()); } -INSTANTIATE_TEST_SUITE_P(Pkcs11HpkeTests, Pkcs11HpkeTest, +INSTANTIATE_TEST_SUITE_P(Pk11Hpke, TestVectors, ::testing::ValuesIn(kHpkeTestVectors)); -TEST_F(Pkcs11HpkeTest, BadEncapsulatedPubKey) { +class ModeParameterizedTest + : public HpkeTest, + public ::testing::TestWithParam< + std::tuple> {}; + +static const HpkeModeId kHpkeModesAll[] = {HpkeModeBase, HpkeModePsk}; +static const HpkeKemId kHpkeKemIdsAll[] = {HpkeDhKemX25519Sha256}; +static const HpkeKdfId kHpkeKdfIdsAll[] = {HpkeKdfHkdfSha256, HpkeKdfHkdfSha384, + HpkeKdfHkdfSha512}; +static const HpkeAeadId kHpkeAeadIdsAll[] = {HpkeAeadAes128Gcm, + HpkeAeadChaCha20Poly1305}; + +INSTANTIATE_TEST_SUITE_P( + Pk11Hpke, ModeParameterizedTest, + ::testing::Combine(::testing::ValuesIn(kHpkeModesAll), + ::testing::ValuesIn(kHpkeKemIdsAll), + ::testing::ValuesIn(kHpkeKdfIdsAll), + ::testing::ValuesIn(kHpkeAeadIdsAll))); + +TEST_F(ModeParameterizedTest, BadEncapsulatedPubKey) { ScopedHpkeContext sender( PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, HpkeAeadAes128Gcm, nullptr, nullptr)); @@ -331,157 +427,211 @@ TEST_F(Pkcs11HpkeTest, BadEncapsulatedPubKey) { ASSERT_TRUE(GenerateKeyPair(pub_key, priv_key)); // Decapsulating an empty buffer should fail. - SECStatus rv = - PK11_HPKE_Deserialize(sender.get(), empty.data, empty.len, &tmp_pub_key); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_Deserialize(sender.get(), empty.data, + empty.len, &tmp_pub_key)); EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); - // Decapsulating anything else will succeed, but the setup will fail. - rv = PK11_HPKE_Deserialize(sender.get(), short_encap.data, short_encap.len, - &tmp_pub_key); + // Decapsulating anything short will succeed, but the setup will fail. + EXPECT_EQ(SECSuccess, PK11_HPKE_Deserialize(sender.get(), short_encap.data, + short_encap.len, &tmp_pub_key)); ScopedSECKEYPublicKey bad_pub_key(tmp_pub_key); - EXPECT_EQ(SECSuccess, rv); - rv = PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(), - bad_pub_key.get(), &empty); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, + PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(), + bad_pub_key.get(), &empty)); EXPECT_EQ(SEC_ERROR_INVALID_KEY, PORT_GetError()); // Test the same for a receiver. - rv = PK11_HPKE_SetupR(sender.get(), pub_key.get(), priv_key.get(), &empty, - &empty); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_SetupR(sender.get(), pub_key.get(), + priv_key.get(), &empty, &empty)); EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); - - rv = PK11_HPKE_SetupR(sender.get(), pub_key.get(), priv_key.get(), - &short_encap, &empty); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_SetupR(sender.get(), pub_key.get(), + priv_key.get(), &short_encap, &empty)); EXPECT_EQ(SEC_ERROR_INVALID_KEY, PORT_GetError()); // Encapsulated key too long - rv = PK11_HPKE_Deserialize(sender.get(), long_encap.data, long_encap.len, - &tmp_pub_key); + EXPECT_EQ(SECSuccess, PK11_HPKE_Deserialize(sender.get(), long_encap.data, + long_encap.len, &tmp_pub_key)); bad_pub_key.reset(tmp_pub_key); - EXPECT_EQ(SECSuccess, rv); - - rv = PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(), - bad_pub_key.get(), &empty); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, + PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(), + bad_pub_key.get(), &empty)); EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); - rv = PK11_HPKE_SetupR(sender.get(), pub_key.get(), priv_key.get(), - &long_encap, &empty); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_SetupR(sender.get(), pub_key.get(), + priv_key.get(), &long_encap, &empty)); EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); } -// Vectors used fixed keypairs on each end. Make sure the -// ephemeral (particularly sender) path works. -TEST_F(Pkcs11HpkeTest, EphemeralKeys) { - unsigned char info[] = {"info"}; - unsigned char msg[] = {"secret"}; - unsigned char aad[] = {"aad"}; - SECItem info_item = {siBuffer, info, sizeof(info)}; - SECItem msg_item = {siBuffer, msg, sizeof(msg)}; - SECItem aad_item = {siBuffer, aad, sizeof(aad)}; +TEST_P(ModeParameterizedTest, ContextExportImportEncrypt) { + std::vector msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector aad = {'a', 'a', 'd'}; - ScopedHpkeContext sender( - PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, - HpkeAeadAes128Gcm, nullptr, nullptr)); - ScopedHpkeContext receiver( - PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, - HpkeAeadAes128Gcm, nullptr, nullptr)); - ASSERT_TRUE(sender); - ASSERT_TRUE(receiver); + ScopedHpkeContext sender; + ScopedHpkeContext receiver; + SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()), + std::get<1>(GetParam()), std::get<2>(GetParam()), + std::get<3>(GetParam())); + SealOpen(sender, receiver, msg, aad, nullptr); + ExportImportRecvContext(receiver, nullptr); + SealOpen(sender, receiver, msg, aad, nullptr); +} - ScopedSECKEYPublicKey pub_key_r; - ScopedSECKEYPrivateKey priv_key_r; - ASSERT_TRUE(GenerateKeyPair(pub_key_r, priv_key_r)); +TEST_P(ModeParameterizedTest, ContextExportImportExport) { + ScopedHpkeContext sender; + ScopedHpkeContext receiver; + ScopedPK11SymKey sender_export; + ScopedPK11SymKey receiver_export; + ScopedPK11SymKey receiver_reexport; + SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()), + std::get<1>(GetParam()), std::get<2>(GetParam()), + std::get<3>(GetParam())); + ExportSecret(sender, sender_export); + ExportSecret(receiver, receiver_export); + CheckEquality(sender_export.get(), receiver_export.get()); + ExportImportRecvContext(receiver, nullptr); + ExportSecret(receiver, receiver_reexport); + CheckEquality(receiver_export.get(), receiver_reexport.get()); +} - SECStatus rv = PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, - pub_key_r.get(), &info_item); - EXPECT_EQ(SECSuccess, rv); +TEST_P(ModeParameterizedTest, ContextExportImportWithWrap) { + std::vector msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector aad = {'a', 'a', 'd'}; - const SECItem *enc = PK11_HPKE_GetEncapPubKey(sender.get()); - EXPECT_NE(nullptr, enc); - rv = PK11_HPKE_SetupR(receiver.get(), pub_key_r.get(), priv_key_r.get(), - const_cast(enc), &info_item); - EXPECT_EQ(SECSuccess, rv); + // Generate a wrapping key, then use it for export. + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(slot); + ScopedPK11SymKey kek( + PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr)); + ASSERT_NE(nullptr, kek); - SECItem *tmp_sealed = nullptr; - rv = PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed); - EXPECT_EQ(SECSuccess, rv); - ScopedSECItem sealed(tmp_sealed); + ScopedHpkeContext sender; + ScopedHpkeContext receiver; + SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()), + std::get<1>(GetParam()), std::get<2>(GetParam()), + std::get<3>(GetParam())); + SealOpen(sender, receiver, msg, aad, nullptr); + ExportImportRecvContext(receiver, kek.get()); + SealOpen(sender, receiver, msg, aad, nullptr); +} - SECItem *tmp_unsealed = nullptr; - rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed); - EXPECT_EQ(SECSuccess, rv); - CheckEquality(&msg_item, tmp_unsealed); - ScopedSECItem unsealed(tmp_unsealed); +TEST_P(ModeParameterizedTest, ExportSenderContext) { + std::vector msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector aad = {'a', 'a', 'd'}; - // Once more - tmp_sealed = nullptr; - rv = PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed); - EXPECT_EQ(SECSuccess, rv); - ASSERT_NE(nullptr, sealed); - sealed.reset(tmp_sealed); - tmp_unsealed = nullptr; - rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed); - EXPECT_EQ(SECSuccess, rv); - CheckEquality(&msg_item, tmp_unsealed); - unsealed.reset(tmp_unsealed); + ScopedHpkeContext sender; + ScopedHpkeContext receiver; + SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()), + std::get<1>(GetParam()), std::get<2>(GetParam()), + std::get<3>(GetParam())); + + SECItem *tmp_exported = nullptr; + EXPECT_EQ(SECFailure, + PK11_HPKE_ExportContext(sender.get(), nullptr, &tmp_exported)); + EXPECT_EQ(nullptr, tmp_exported); + EXPECT_EQ(SEC_ERROR_NOT_A_RECIPIENT, PORT_GetError()); +} + +TEST_P(ModeParameterizedTest, ContextUnwrapBadKey) { + std::vector msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector aad = {'a', 'a', 'd'}; + + // Generate a wrapping key, then use it for export. + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(slot); + ScopedPK11SymKey kek( + PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr)); + ASSERT_NE(nullptr, kek); + ScopedPK11SymKey not_kek( + PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr)); + ASSERT_NE(nullptr, not_kek); + ScopedHpkeContext sender; + ScopedHpkeContext receiver; + + SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()), + std::get<1>(GetParam()), std::get<2>(GetParam()), + std::get<3>(GetParam())); + + SECItem *tmp_exported = nullptr; + EXPECT_EQ(SECSuccess, + PK11_HPKE_ExportContext(receiver.get(), kek.get(), &tmp_exported)); + EXPECT_NE(nullptr, tmp_exported); + ScopedSECItem context(tmp_exported); + + EXPECT_EQ(nullptr, PK11_HPKE_ImportContext(context.get(), not_kek.get())); + EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError()); +} + +TEST_P(ModeParameterizedTest, EphemeralKeys) { + std::vector msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector aad = {'a', 'a', 'd'}; + SECItem msg_item = {siBuffer, msg.data(), + static_cast(msg.size())}; + SECItem aad_item = {siBuffer, aad.data(), + static_cast(aad.size())}; + ScopedHpkeContext sender; + ScopedHpkeContext receiver; + SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()), + std::get<1>(GetParam()), std::get<2>(GetParam()), + std::get<3>(GetParam())); + + SealOpen(sender, receiver, msg, aad, nullptr); // Seal for negative tests - tmp_sealed = nullptr; - tmp_unsealed = nullptr; - rv = PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed); - EXPECT_EQ(SECSuccess, rv); - ASSERT_NE(nullptr, sealed); - sealed.reset(tmp_sealed); + SECItem *tmp_sealed = nullptr; + SECItem *tmp_unsealed = nullptr; + EXPECT_EQ(SECSuccess, + PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed)); + ASSERT_NE(nullptr, tmp_sealed); + ScopedSECItem sealed(tmp_sealed); // Drop AAD - rv = PK11_HPKE_Open(receiver.get(), nullptr, sealed.get(), &tmp_unsealed); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_Open(receiver.get(), nullptr, sealed.get(), + &tmp_unsealed)); + EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError()); EXPECT_EQ(nullptr, tmp_unsealed); // Modify AAD aad_item.data[0] ^= 0xff; - rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), + &tmp_unsealed)); + EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError()); EXPECT_EQ(nullptr, tmp_unsealed); aad_item.data[0] ^= 0xff; // Modify ciphertext sealed->data[0] ^= 0xff; - rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), + &tmp_unsealed)); + EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError()); EXPECT_EQ(nullptr, tmp_unsealed); sealed->data[0] ^= 0xff; - rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed); - EXPECT_EQ(SECSuccess, rv); + EXPECT_EQ(SECSuccess, PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), + &tmp_unsealed)); EXPECT_NE(nullptr, tmp_unsealed); - unsealed.reset(tmp_unsealed); + ScopedSECItem unsealed(tmp_unsealed); + CheckEquality(&msg_item, unsealed.get()); } -TEST_F(Pkcs11HpkeTest, InvalidContextParams) { +TEST_F(ModeParameterizedTest, InvalidContextParams) { HpkeContext *cx = - PK11_HPKE_NewContext(static_cast(1), HpkeKdfHkdfSha256, + PK11_HPKE_NewContext(static_cast(0xff), HpkeKdfHkdfSha256, HpkeAeadChaCha20Poly1305, nullptr, nullptr); EXPECT_EQ(nullptr, cx); EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); - cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, static_cast(2), + cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, static_cast(0xff), HpkeAeadChaCha20Poly1305, nullptr, nullptr); EXPECT_EQ(nullptr, cx); EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, - static_cast(4), nullptr, nullptr); + static_cast(0xff), nullptr, nullptr); EXPECT_EQ(nullptr, cx); EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); } -TEST_F(Pkcs11HpkeTest, InvalidReceiverKeyType) { +TEST_F(ModeParameterizedTest, InvalidReceiverKeyType) { ScopedHpkeContext sender( PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, HpkeAeadChaCha20Poly1305, nullptr, nullptr)); @@ -507,9 +657,8 @@ TEST_F(Pkcs11HpkeTest, InvalidReceiverKeyType) { pub_key.reset(pub_tmp); SECItem info_item = {siBuffer, nullptr, 0}; - SECStatus rv = PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, pub_key.get(), - &info_item); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, + pub_key.get(), &info_item)); EXPECT_EQ(SEC_ERROR_BAD_KEY, PORT_GetError()); // Try with an unexpected curve @@ -529,13 +678,12 @@ TEST_F(Pkcs11HpkeTest, InvalidReceiverKeyType) { ASSERT_NE(nullptr, priv_key); ASSERT_NE(nullptr, pub_tmp); pub_key.reset(pub_tmp); - rv = PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, pub_key.get(), - &info_item); - EXPECT_EQ(SECFailure, rv); + EXPECT_EQ(SECFailure, PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, + pub_key.get(), &info_item)); EXPECT_EQ(SEC_ERROR_BAD_KEY, PORT_GetError()); } #else -TEST(Pkcs11HpkeTest, EnsureNotImplemented) { +TEST(HpkeTest, EnsureNotImplemented) { ScopedHpkeContext cx( PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, HpkeAeadChaCha20Poly1305, nullptr, nullptr)); diff --git a/security/nss/gtests/ssl_gtest/libssl_internals.c b/security/nss/gtests/ssl_gtest/libssl_internals.c index 01d698e7179d..db0c9e86bc85 100644 --- a/security/nss/gtests/ssl_gtest/libssl_internals.c +++ b/security/nss/gtests/ssl_gtest/libssl_internals.c @@ -494,6 +494,27 @@ SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf, sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); SECITEM_FreeItem(&cfg->raw, PR_FALSE); SECITEM_AllocItem(NULL, &cfg->raw, len); - memcpy(cfg->raw.data, buf, len); + PORT_Memcpy(cfg->raw.data, buf, len); + return SECSuccess; +} + +// Zero the echConfig.config_id for all configured echConfigs. +// This mimics a collision on the 8B config ID so that we can +// test trial decryption. +SECStatus SSLInt_ZeroEchConfigIds(PRFileDesc *fd) { + if (!fd) { + return SECFailure; + } + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + + for (PRCList *cur_p = PR_LIST_HEAD(&ss->echConfigs); cur_p != &ss->echConfigs; + cur_p = PR_NEXT_LINK(cur_p)) { + PORT_Memset(((sslEchConfig *)cur_p)->configId, 0, + sizeof(((sslEchConfig *)cur_p)->configId)); + } + return SECSuccess; } diff --git a/security/nss/gtests/ssl_gtest/libssl_internals.h b/security/nss/gtests/ssl_gtest/libssl_internals.h index a6a3239d599c..372f254d6178 100644 --- a/security/nss/gtests/ssl_gtest/libssl_internals.h +++ b/security/nss/gtests/ssl_gtest/libssl_internals.h @@ -51,5 +51,5 @@ SECStatus SSLInt_SetDCAdvertisedSigSchemes(PRFileDesc *fd, SECStatus SSLInt_RemoveServerCertificates(PRFileDesc *fd); SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf, size_t len); - -#endif // ndef libssl_internals_h_ +SECStatus SSLInt_ZeroEchConfigIds(PRFileDesc *fd); +#endif // ifndef libssl_internals_h_ diff --git a/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc index b08eba59ad4f..f60697900afd 100644 --- a/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc +++ b/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc @@ -1098,22 +1098,6 @@ TEST_P(TlsExtensionTest13, HrrThenRemoveSupportedGroups) { SSL_ERROR_MISSING_SUPPORTED_GROUPS_EXTENSION); } -#ifdef NSS_ENABLE_DRAFT_HPKE -TEST_P(TlsExtensionTest13, HrrThenRemoveEch) { - if (variant_ == ssl_variant_datagram) { - // ECH not supported in DTLS. - GTEST_SKIP(); - } - - EnsureTlsSetup(); - SetupEch(client_, server_); - ExpectAlert(server_, kTlsAlertIllegalParameter); - HrrThenRemoveExtensionsTest(ssl_tls13_encrypted_client_hello_xtn, - SSL_ERROR_ILLEGAL_PARAMETER_ALERT, - SSL_ERROR_BAD_2ND_CLIENT_HELLO); -} -#endif - TEST_P(TlsExtensionTest13, EmptyVersionList) { static const uint8_t ext[] = {0x00, 0x00}; ConnectWithBogusVersionList(ext, sizeof(ext)); diff --git a/security/nss/gtests/ssl_gtest/tls_ech_unittest.cc b/security/nss/gtests/ssl_gtest/tls_ech_unittest.cc index b05224bda70b..64a270624556 100644 --- a/security/nss/gtests/ssl_gtest/tls_ech_unittest.cc +++ b/security/nss/gtests/ssl_gtest/tls_ech_unittest.cc @@ -19,9 +19,9 @@ namespace nss_test { class TlsAgentEchTest : public TlsAgentTestClient13 { protected: - void InstallEchConfig(const DataBuffer& record, PRErrorCode err = 0) { - SECStatus rv = - SSL_SetClientEchConfigs(agent_->ssl_fd(), record.data(), record.len()); + void InstallEchConfig(const DataBuffer& echconfig, PRErrorCode err = 0) { + SECStatus rv = SSL_SetClientEchConfigs(agent_->ssl_fd(), echconfig.data(), + echconfig.len()); if (err == 0) { ASSERT_EQ(SECSuccess, rv); } else { @@ -143,18 +143,6 @@ class TlsConnectStreamTls13Ech : public TlsConnectTestBase { Connect(); } - private: - // Testing certan invalid CHInner configurations is tricky, particularly - // since the CHOuter forms AAD and isn't available in filters. Instead of - // generating these inputs on the fly, use a fixed server keypair so that - // the input can be generated once (e.g. via a debugger) and replayed in - // each invocation of the test. - std::string kFixedServerPubkey = - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a" - "02010104205a8aa0d2476b28521588e0c704b14db82cdd4970d340d293a957" - "6deaee9ec1c7a1230321008756e2580c07c1d2ffcb662f5fadc6d6ff13da85" - "abd7adfecf984aaa102c1269"; - void ImportFixedEchKeypair(ScopedSECKEYPublicKey& pub, ScopedSECKEYPrivateKey& priv) { ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); @@ -176,16 +164,30 @@ class TlsConnectStreamTls13Ech : public TlsConnectTestBase { ASSERT_NE(nullptr, tmp_pub); } + private: + // Testing certan invalid CHInner configurations is tricky, particularly + // since the CHOuter forms AAD and isn't available in filters. Instead of + // generating these inputs on the fly, use a fixed server keypair so that + // the input can be generated once (e.g. via a debugger) and replayed in + // each invocation of the test. + std::string kFixedServerPubkey = + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a" + "02010104205a8aa0d2476b28521588e0c704b14db82cdd4970d340d293a957" + "6deaee9ec1c7a1230321008756e2580c07c1d2ffcb662f5fadc6d6ff13da85" + "abd7adfecf984aaa102c1269"; + void SetMutualEchConfigs(ScopedSECKEYPublicKey& pub, ScopedSECKEYPrivateKey& priv) { - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, + priv); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); } }; @@ -224,17 +226,17 @@ TEST_P(TlsAgentEchTest, EchConfigsSupportedYesNo) { // ECHConfig 2 cipher_suites are unsupported. const std::string mixed = - "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" - "444156E4E04D1BF0FFDA7783B6B457F75600200008000100030001000100640000FE0800" + "0086FE09003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F75600200008000100030001000100640000FE0900" "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" "4D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000"; std::vector config = hex_string_to_bytes(mixed); - DataBuffer record(config.data(), config.size()); + DataBuffer echconfig(config.data(), config.size()); EnsureInit(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE - InstallEchConfig(record, 0); + InstallEchConfig(echconfig, 0); auto filter = MakeTlsFilter( agent_, ssl_tls13_encrypted_client_hello_xtn); agent_->Handshake(); @@ -249,17 +251,17 @@ TEST_P(TlsAgentEchTest, EchConfigsSupportedNoYes) { // ECHConfig 1 cipher_suites are unsupported. const std::string mixed = - "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" - "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000FE0800" + "0086FE09003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000FE0900" "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" "4D1BF0FFDA7783B6B457F75600200008000100030001000100640000"; std::vector config = hex_string_to_bytes(mixed); - DataBuffer record(config.data(), config.size()); + DataBuffer echconfig(config.data(), config.size()); EnsureInit(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE - InstallEchConfig(record, 0); + InstallEchConfig(echconfig, 0); auto filter = MakeTlsFilter( agent_, ssl_tls13_encrypted_client_hello_xtn); agent_->Handshake(); @@ -274,17 +276,17 @@ TEST_P(TlsAgentEchTest, EchConfigsSupportedNoNo) { // ECHConfig 1 and 2 cipher_suites are unsupported. const std::string unsupported = - "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" - "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFF0001FFFF00640000FE0800" + "0086FE09003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFF0001FFFF00640000FE0900" "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" "4D1BF0FFDA7783B6B457F75600200008FFFF0003FFFF000100640000"; std::vector config = hex_string_to_bytes(unsupported); - DataBuffer record(config.data(), config.size()); + DataBuffer echconfig(config.data(), config.size()); EnsureInit(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); auto filter = MakeTlsFilter( agent_, ssl_tls13_encrypted_client_hello_xtn); agent_->Handshake(); @@ -296,11 +298,11 @@ TEST_P(TlsAgentEchTest, ShortEchConfig) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - record.Truncate(record.len() - 1); - InstallEchConfig(record, SEC_ERROR_BAD_DATA); + kPublicName, 100, echconfig, pub, priv); + echconfig.Truncate(echconfig.len() - 1); + InstallEchConfig(echconfig, SEC_ERROR_BAD_DATA); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter( @@ -314,11 +316,11 @@ TEST_P(TlsAgentEchTest, LongEchConfig) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - record.Write(record.len(), 1, 1); // Append one byte - InstallEchConfig(record, SEC_ERROR_BAD_DATA); + kPublicName, 100, echconfig, pub, priv); + echconfig.Write(echconfig.len(), 1, 1); // Append one byte + InstallEchConfig(echconfig, SEC_ERROR_BAD_DATA); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter( @@ -332,13 +334,13 @@ TEST_P(TlsAgentEchTest, UnsupportedEchConfigVersion) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; static const uint8_t bad_version[] = {0xff, 0xff}; DataBuffer bad_ver_buf(bad_version, sizeof(bad_version)); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - record.Splice(bad_ver_buf, 2, 2); - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + kPublicName, 100, echconfig, pub, priv); + echconfig.Splice(bad_ver_buf, 2, 2); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter( @@ -352,12 +354,12 @@ TEST_P(TlsAgentEchTest, UnsupportedHpkeKem) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; // SSL_EncodeEchConfig encodes without validation. TlsConnectTestBase::GenerateEchConfig(static_cast(0xff), kDefaultSuites, kPublicName, 100, - record, pub, priv); - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + echconfig, pub, priv); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter( @@ -371,10 +373,26 @@ TEST_P(TlsAgentEchTest, EchRejectIgnoreAllUnknownSuites) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kBogusSuite, - kPublicName, 100, record, pub, priv); - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + kPublicName, 100, echconfig, pub, priv); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, EchConfigRejectEmptyPublicName) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer echconfig; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kBogusSuite, "", + 100, echconfig, pub, priv); + InstallEchConfig(echconfig, SSL_ERROR_RX_MALFORMED_ECH_CONFIG); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter( @@ -385,17 +403,18 @@ TEST_P(TlsAgentEchTest, EchRejectIgnoreAllUnknownSuites) { TEST_F(TlsConnectStreamTls13, EchAcceptIgnoreSingleUnknownSuite) { EnsureTlsSetup(); - DataBuffer record; + DataBuffer echconfig; ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kUnknownFirstSuite, kPublicName, 100, - record, pub, priv); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig, pub, priv); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); @@ -488,10 +507,10 @@ TEST_P(TlsAgentEchTest, NoEarlyRetryConfigs) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - InstallEchConfig(record, 0); + kPublicName, 100, echconfig, pub, priv); + InstallEchConfig(echconfig, 0); EXPECT_EQ(SECFailure, SSL_GetEchRetryConfigs(agent_->ssl_fd(), &retry_configs)); @@ -502,11 +521,11 @@ TEST_P(TlsAgentEchTest, NoSniSoNoEch) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); SSL_SetURL(agent_->ssl_fd(), ""); - InstallEchConfig(record, 0); + InstallEchConfig(echconfig, 0); SSL_SetURL(agent_->ssl_fd(), ""); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE @@ -520,7 +539,7 @@ TEST_P(TlsAgentEchTest, NoEchConfigSoNoEch) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter( @@ -533,29 +552,29 @@ TEST_P(TlsAgentEchTest, EchConfigDuplicateExtensions) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); static const uint8_t duped_xtn[] = {0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}; DataBuffer buf(duped_xtn, sizeof(duped_xtn)); - record.Truncate(record.len() - 2); - record.Append(buf); + echconfig.Truncate(echconfig.len() - 2); + echconfig.Append(buf); uint32_t len; - ASSERT_TRUE(record.Read(0, 2, &len)); + ASSERT_TRUE(echconfig.Read(0, 2, &len)); len += buf.len() - 2; DataBuffer new_len; ASSERT_TRUE(new_len.Write(0, len, 2)); - record.Splice(new_len, 0, 2); + echconfig.Splice(new_len, 0, 2); new_len.Truncate(0); - ASSERT_TRUE(record.Read(4, 2, &len)); + ASSERT_TRUE(echconfig.Read(4, 2, &len)); len += buf.len() - 2; ASSERT_TRUE(new_len.Write(0, len, 2)); - record.Splice(new_len, 4, 2); + echconfig.Splice(new_len, 4, 2); - InstallEchConfig(record, SEC_ERROR_EXTENSION_VALUE_INVALID); + InstallEchConfig(echconfig, SEC_ERROR_EXTENSION_VALUE_INVALID); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter( @@ -570,17 +589,16 @@ TEST_P(TlsAgentEchTest, EchConfigDuplicateExtensions) { // extension in ClientHelloOuter. TEST_F(TlsConnectStreamTls13Ech, EchOuterExtensionsReferencesMissing) { std::string ch = - "01000170030374d616d97efe591bf9bee4496bcc1118145b4dd02f7d1ff979fd0cf61749" - "a91e0000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "010001580303dfff91b5e1ba00f29d2338419b3abf125ee1051a942ae25163bbf609a1ea" + "11920000061301130313020100012900000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "00204f346f86351b077492c83564c909d1aaab4f6f3ee2566af0e90a4684c793805d002b" + "0020d94c1590c261e9ea8ae55bc9581f397cc598115f8b70aec1b0236f4c8c555537002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b" - "a5f058dd5c5ab1ca9750ef9d28c70020924764b36fe5d4a985f9857ceb75edb10b5f4b5b" - "f9d59290db70743e3c582163006acea5d7785cc506ecf5c859a9cad18f2b1df1a32231fe" - "0330471ee0e88ece9047e6491a381bfabed58f7fc542f0ba78eb55030bcfe1d400f67275" - "eac8619d1e4237e9d6176dd4eb54f3f25865686756f313a4ba47901c83e5ad5413609d39" - "816346b940115fd68e534609"; + "020101001c00024001fe09009b0001000308fde4163c5c6e8bb6002067a895efa2721c88" + "63ecfa1bea1e520ae6f6cf938e3e37802688f7a83a871a04006aa693f053f87db87cf82a" + "7caa20670d79b92ccda97893fdf99352fc766fb3dd5570948311dddb6d41214234fae585" + "e354a048c072b3fb00a0a64e8e089e4a90152ee91a2c5b947c99d3dcebfb6334453b023d" + "4d725010996a290a0552e4b238ec91c21440adc0d51a4435"; ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, SSL_ERROR_ILLEGAL_PARAMETER_ALERT); @@ -589,17 +607,16 @@ TEST_F(TlsConnectStreamTls13Ech, EchOuterExtensionsReferencesMissing) { // Drop supported_versions from CHInner, make sure we don't negotiate 1.2+ECH. TEST_F(TlsConnectStreamTls13Ech, EchVersion12Inner) { std::string ch = - "0100017003034dd5bf4c12835e9be21f983953720e3595b3a8eeb4a44467678caceb7727" - "3be90000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "0100015103038fbe6f75b0123116fa5c4eccf0cf26c17ab1ded5529307e419c036ac7e9c" + "e8e30000061301130313020100012200000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "0020af7b976cdf69ffcd494ca5a93ae3ecde692b09be518ee033aad908c45b82c368002b" + "002078d644583b4f056bec4d8ae9bddd383aed6eb7cdb3294f88b0e37a4f26a02549002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c0002400100150003000000fe0800ac0001000320a10698ccbd4bd86df91f61" - "7e58dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020f5ece4c187b76f7e3d467c7506" - "215e73c27c918cd863c0e80d76a7987ec274320063e037492868eff5296a22dc50885e9d" - "f6964a5e26546f1bada043f8834988dfea5394b4c45a4d0b3afc52142d33f94161135a63" - "ed3c1b63f60d8133fb1cff17e1f9ced6c871984e412ed8ddb0f487c4d09d7aea80488004" - "c45a17cd3b5cdca316155fdb"; + "020101001c00024001fe0900940001000308fde4163c5c6e8bb600208958e66d1d4bbd46" + "4792f392e119dbce91ee3e65067899b45c83855dae61e67a00637df038e7b35483786707" + "dd1b25be5cd3dd07f1ca4b33a3595ddb959e5c0da3d2f0b3314417614968691700c05232" + "07c729b34f3b5de62728b3cb6b45b00e6f94b204a9504d0e7e24c66f42aacc73591c86ef" + "571e61cebd6ba671081150a2dae89e7493"; ReplayChWithMalformedInner(ch, kTlsAlertProtocolVersion, SSL_ERROR_UNSUPPORTED_VERSION, SSL_ERROR_PROTOCOL_VERSION_ALERT); @@ -608,40 +625,103 @@ TEST_F(TlsConnectStreamTls13Ech, EchVersion12Inner) { // Use CHInner supported_versions to negotiate 1.2. TEST_F(TlsConnectStreamTls13Ech, EchVersion12InnerSupportedVersions) { std::string ch = - "010001700303845c298db4017d2ed2584284b90e4ecba57a63663560c57aa0b1ac51203d" - "c8560000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "01000158030378a601a3f12229e53e0b8d92c3599bf1782e8261d2ecaec9bbe595d4c901" + "98770000061301130313020100012900000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "00203356719e88b539645438f645916aeeffe93c38803a59d6997938aa98eefbcf64002b" + "00201c8017d6970f3a92ac1c9919c3a26788052f84599fb0c3cb7bd381304148724e002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b" - "a5f058dd5c5ab1ca9750ef9d28c700208412c945c53624bcace5eda0dc1ad300a1620e86" - "5a0f4a27755a3477b115b65b006abf1dfd77ddc1b80c5976732174a5fe7ebcf9ff1a548b" - "097daa12a37f3e32a613a0798544ba1d96239431bc807ddd9055ac3fb3e32b2eb42cec30" - "e915357418a953027d73020fd739287414205349eeff376dd464750ca70a965141a88800" - "6a043fe1d6d882d9a2c2f6f3"; + "020101001c00024001fe09009b0001000308fde4163c5c6e8bb60020f7347d34f125e866" + "76b1cdc43455c6c00918a3c8a961335e1b9aa864da2b5313006a21e6ad81533e90cea24e" + "c2c3656f6b53114b4c63bf89462696f1c8ad4e1193d87062a5537edbe83c9b35c41e9763" + "1d2333270854758ee02548afb7f2264f904474465415a5085024487f22b017208e250ca4" + "7902d61d98fbd1cb8afc0a14dcd70a68343cf67c258758d9"; ReplayChWithMalformedInner(ch, kTlsAlertProtocolVersion, SSL_ERROR_UNSUPPORTED_VERSION, SSL_ERROR_PROTOCOL_VERSION_ALERT); } -// Replay a CH for which the ECH Inner lacks the required -// empty ECH extension. +// Replay a CH for which CHInner lacks the required ech_is_inner extension. TEST_F(TlsConnectStreamTls13Ech, EchInnerMissingEmptyEch) { std::string ch = - "0100017103032bf866cbd6d4abdec8ce23107eaef9af51b644043953e3b70f2f28f1898e" - "87880000061301130313020100014200000010000e00000b7075626c69632e6e616d65ff" + "010001540303033b3284790ada882445bfb38b8af3509659033c931e6ae97febbaa62b19" + "b4ac0000061301130313020100012500000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "00208f614d3017575332ca009a42d33bcaf876b4ba6d44b052e8019c31f6f1559e41002b" + "00209d1ed410ccb05ce9e424f52b1be3599bcc1efb0913ae14a24d9a69cbfbc39744002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c000240010015000100fe0800af0001000320a10698ccbd4bd86df91f617e58" - "dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020da1d5d9f183a5d5e49892e38eaae5e" - "9e3e6c5d404a5fdb672ca37f9cebabd57400660ea1d61917cc1049aab22506078ccecfc4" - "16a364a1beaa8915b250bb86ac2c725698c3c641830c4aa4e8b7f50152b5732b29b1ac43" - "45c97fc018855fd68e5600d0ef188e905b69997c3711b0ec0114a857177df728c7b84f52" - "2923f932838f7f15bb22644fd4"; - ReplayChWithMalformedInner(ch, kTlsAlertDecodeError, + "020101001c00024001fe0900970001000308fde4163c5c6e8bb600206321bdc543a23d47" + "7a7104ba69177cb722927c6c485117df4a077b8e82167f0b0066103d9aac7e5fc4ef990b" + "2ce38593589f7f6ba043847d7db6c9136adb811f63b956d56e6ca8cbe6864e3fc43a3bc5" + "94a332d4d63833e411c89ef14af63b5cd18c7adee99ffd1ad3112449ea18d6650bbaca66" + "528f7e4146fafbf338c27cf89b145a55022b26a3"; + ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter, SSL_ERROR_MISSING_ECH_EXTENSION, - SSL_ERROR_DECODE_ERROR_ALERT); + SSL_ERROR_ILLEGAL_PARAMETER_ALERT); +} + +// Replay a CH for which CHInner contains both an ECH and ech_is_inner +// extension. +TEST_F(TlsConnectStreamTls13Ech, InnerWithEchAndEchIsInner) { + std::string ch = + "0100015c030383fb49c98b62bcdf04cbbae418dd684f8f9512f40fca6861ba40555269a9" + "789f0000061301130313020100012d00000010000e00000b7075626c69632e6e616d65ff" + "01000100000a00140012001d00170018001901000101010201030104003300260024001d" + "00201e3d35a6755b7dddf7e481359429e9677baaa8dd99569c2bf0b0f7ea56e68b12002b" + "0003020304000d0018001604030503060302030804080508060401050106010201002d00" + "020101001c00024001fe09009f0001000308fde4163c5c6e8bb6002090110b89c1ba6618" + "942ea7aae8c472c22e97f10bef7dd490bee50cc108082b48006eed016fa2b3e3419cf5ef" + "9b41ab9ecffa84a4b60e2f4cc710cf31c739d1f6f88b48207aaf7ccabdd744a25a8f2a38" + "029d1b133e9d990681cf08c07a255d9242b3a002bc0865935cbb609b2b1996fab0626cb0" + "2ece6544bbde0d3218333ffd95c383a41854b76b1a254bb346a2702b"; + ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter, + SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, + SSL_ERROR_ILLEGAL_PARAMETER_ALERT); +} + +TEST_F(TlsConnectStreamTls13, OuterWithEchAndEchIsInner) { + static uint8_t empty_buf[1] = {0}; + DataBuffer empty(empty_buf, 0); + + EnsureTlsSetup(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_TRUE)); + MakeTlsFilter(client_, kTlsHandshakeClientHello, + ssl_tls13_ech_is_inner_xtn, empty); + ConnectExpectAlert(server_, kTlsAlertIllegalParameter); + client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_EXTENSION); +} + +// Apply two ECHConfigs on the server. They are identical with the exception +// of the public key: the first ECHConfig contains a public key for which we +// lack the private value. Use an SSLInt function to zero all the config_ids +// (client and server), then confirm that trial decryption works. +TEST_F(TlsConnectStreamTls13Ech, EchConfigsTrialDecrypt) { + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + EnsureTlsSetup(); + ImportFixedEchKeypair(pub, priv); + + const std::string two_configs_str = + "007EFE09003B000B7075626C69632E6E616D650020111111111111111111111111111111" + "1111111111111111111111111111111111002000040001000100640000fe09003B000B70" + "75626C69632E6E616D6500208756E2580C07C1D2FFCB662F5FADC6D6FF13DA85ABD7ADFE" + "CF984AAA102C1269002000040001000100640000"; + const std::string second_config_str = + "003FFE09003B000B7075626C69632E6E616D6500208756E2580C07C1D2FFCB662F5FADC6" + "D6FF13DA85ABD7ADFECF984AAA102C1269002000040001000100640000"; + std::vector two_configs = hex_string_to_bytes(two_configs_str); + std::vector second_config = hex_string_to_bytes(second_config_str); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), + two_configs.data(), two_configs.size())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), second_config.data(), + second_config.size())); + + ASSERT_EQ(SECSuccess, SSLInt_ZeroEchConfigIds(client_->ssl_fd())); + ASSERT_EQ(SECSuccess, SSLInt_ZeroEchConfigIds(server_->ssl_fd())); + client_->ExpectEch(); + server_->ExpectEch(); + Connect(); } // An empty config_id should prompt an alert. We don't support @@ -769,16 +849,17 @@ SSLHelloRetryRequestAction RetryEchHello(PRBool firstHello, TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); client_->SetAuthCertificateCallback(AuthCompleteSuccess); @@ -795,7 +876,7 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) { MakeNewServer(); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); client_->SetAuthCertificateCallback(AuthCompleteSuccess); @@ -805,6 +886,42 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) { SendReceive(); } +// Send GREASE ECH in CH1. CH2 must send exactly the same GREASE ECH contents. +TEST_F(TlsConnectStreamTls13, GreaseEchHrrMatches) { + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_TRUE)); // GREASE + auto capture = MakeTlsFilter( + client_, ssl_tls13_encrypted_client_hello_xtn); + + // Start the handshake. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); // Send CH1 + EXPECT_TRUE(capture->captured()); + DataBuffer ch1_grease = capture->extension(); + + server_->Handshake(); + MakeNewServer(); + capture = MakeTlsFilter( + client_, ssl_tls13_encrypted_client_hello_xtn); + + EXPECT_FALSE(capture->captured()); + client_->Handshake(); // Send CH2 + EXPECT_TRUE(capture->captured()); + EXPECT_EQ(ch1_grease, capture->extension()); + + EXPECT_EQ(1U, cb_called); + server_->StartConnect(); + Handshake(); + CheckConnected(); +} + // Fail to decrypt CH2. Unlike CH1, this generates an alert. TEST_F(TlsConnectStreamTls13, EchFailDecryptCH2) { EnsureTlsSetup(); @@ -848,9 +965,9 @@ TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingYN) { MakeNewServer(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE)); // Don't GREASE - ExpectAlert(server_, kTlsAlertIllegalParameter); + ExpectAlert(server_, kTlsAlertMissingExtension); Handshake(); - client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + client_->CheckErrorCode(SSL_ERROR_MISSING_EXTENSION_ALERT); server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO); EXPECT_EQ(1U, cb_called); } @@ -858,20 +975,47 @@ TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingYN) { TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingNY) { ConfigureSelfEncrypt(); EnsureTlsSetup(); + SetupEch(client_, server_); size_t cb_called = 0; EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( server_->ssl_fd(), RetryEchHello, &cb_called)); - EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), - PR_FALSE)); // Don't GREASE + MakeTlsFilter(client_, + ssl_tls13_encrypted_client_hello_xtn); // Start the handshake. client_->StartConnect(); server_->StartConnect(); client_->Handshake(); server_->Handshake(); MakeNewServer(); - EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), - PR_TRUE)); // Send GREASE + client_->ClearFilter(); // Let the second ECH offering through. + ExpectAlert(server_, kTlsAlertIllegalParameter); + Handshake(); + client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO); + EXPECT_EQ(1U, cb_called); +} + +// Change the ECHCipherSuite between CH1 and CH2. Expect alert. +TEST_F(TlsConnectStreamTls13, EchHrrChangeCipherSuite) { + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + SetupEch(client_, server_); + + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + // Start the handshake and trigger HRR. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + + // Damage the first byte of the ciphersuite (offset 0) + MakeTlsFilter(client_, + ssl_tls13_encrypted_client_hello_xtn, 0); + ExpectAlert(server_, kTlsAlertIllegalParameter); Handshake(); client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); @@ -884,17 +1028,18 @@ TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingNY) { TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); @@ -922,7 +1067,7 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) { MakeNewServer(); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); EXPECT_EQ(SECSuccess, @@ -940,7 +1085,7 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) { TEST_F(TlsConnectStreamTls13Ech, EchRejectWithHrr) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); SetupForEchRetry(); @@ -966,19 +1111,20 @@ TEST_F(TlsConnectStreamTls13Ech, EchRejectWithHrr) { EXPECT_EQ(1U, cb_called); } -// Reject ECH on CH1 and (HRR) CH2. PSKs are no longer allowed -// in CHOuter, but can still make sure the handshake succeeds. -// (prompting ech_required at the completion). +// Reject ECH on CH1 and CH2. PSKs are no longer allowed +// in CHOuter, but we can still make sure the handshake succeeds. +// This prompts an ech_required alert when the handshake completes. TEST_F(TlsConnectStreamTls13, EchRejectWithHrrAndPsk) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + kPublicName, 100, echconfig, pub, priv); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); size_t cb_called = 0; EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( @@ -1113,7 +1259,7 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { EnsureTlsSetup(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; DataBuffer crit_rec; DataBuffer len_buf; uint64_t tmp; @@ -1124,9 +1270,9 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { DataBuffer non_crit_exts(extensions, sizeof(extensions)); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha, - kPublicName, 100, record, pub, priv); - record.Truncate(record.len() - 2); // Eat the empty extensions. - crit_rec.Assign(record); + kPublicName, 100, echconfig, pub, priv); + echconfig.Truncate(echconfig.len() - 2); // Eat the empty extensions. + crit_rec.Assign(echconfig); ASSERT_TRUE(crit_rec.Read(0, 2, &tmp)); len_buf.Write(0, tmp + crit_exts.len() - 2, 2); // two bytes of length crit_rec.Splice(len_buf, 0, 2); @@ -1138,13 +1284,13 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { crit_rec.Splice(len_buf, 4, 2); len_buf.Truncate(0); - ASSERT_TRUE(record.Read(0, 2, &tmp)); + ASSERT_TRUE(echconfig.Read(0, 2, &tmp)); len_buf.Write(0, tmp + non_crit_exts.len() - 2, 2); - record.Append(non_crit_exts); - record.Splice(len_buf, 0, 2); - ASSERT_TRUE(record.Read(4, 2, &tmp)); + echconfig.Append(non_crit_exts); + echconfig.Splice(len_buf, 0, 2); + ASSERT_TRUE(echconfig.Read(4, 2, &tmp)); len_buf.Write(0, tmp + non_crit_exts.len() - 2, 2); - record.Splice(len_buf, 4, 2); + echconfig.Splice(len_buf, 4, 2); EXPECT_EQ(SECFailure, SSL_SetClientEchConfigs(client_->ssl_fd(), crit_rec.data(), @@ -1162,8 +1308,9 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { // Now try a variant with non-critical extensions, it should work. Reset(); EnsureTlsSetup(); - EXPECT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + EXPECT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); filter = MakeTlsFilter( client_, ssl_tls13_encrypted_client_hello_xtn); StartConnect(); @@ -1577,6 +1724,34 @@ TEST_F(TlsConnectStreamTls13, EchOuterExtensionsInCHOuter) { server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); } +// At draft-09: If a CH containing the ech_is_inner extension is received, the +// server acts as backend server in split-mode by responding with the ECH +// acceptance signal. The signal value itself depends on the handshake secret, +// which we've broken by appending ech_is_inner. For now, just check that the +// server negotiates ech_is_inner (which is what triggers sending the signal). +TEST_F(TlsConnectStreamTls13, EchBackendAcceptance) { + DataBuffer ch_buf; + static uint8_t empty_buf[1] = {0}; + DataBuffer empty(empty_buf, 0); + + EnsureTlsSetup(); + StartConnect(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE)); + MakeTlsFilter(client_, kTlsHandshakeClientHello, + ssl_tls13_ech_is_inner_xtn, empty); + + EXPECT_EQ(SECSuccess, SSL_EnableTls13BackendEch(server_->ssl_fd(), PR_TRUE)); + client_->Handshake(); + server_->Handshake(); + + ExpectAlert(client_, kTlsAlertBadRecordMac); + client_->Handshake(); + EXPECT_EQ(TlsAgent::STATE_ERROR, client_->state()); + EXPECT_EQ(PR_TRUE, SSLInt_ExtensionNegotiated(server_->ssl_fd(), + ssl_tls13_ech_is_inner_xtn)); + server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning); +} + INSTANTIATE_TEST_SUITE_P(EchAgentTest, TlsAgentEchTest, ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll, TlsConnectTestBase::kTlsV13)); diff --git a/security/nss/lib/nss/nss.def b/security/nss/lib/nss/nss.def index 5ad688b4e1ef..db912e1ec952 100644 --- a/security/nss/lib/nss/nss.def +++ b/security/nss/lib/nss/nss.def @@ -1213,3 +1213,10 @@ PK11_PubUnwrapSymKeyWithMechanism; ;+ local: ;+ *; ;+}; +;+NSS_3.62 { # NSS 3.62 release +;+ global: +PK11_HPKE_ExportContext; +PK11_HPKE_ImportContext; +;+ local: +;+ *; +;+}; \ No newline at end of file diff --git a/security/nss/lib/nss/nss.h b/security/nss/lib/nss/nss.h index e076270556df..64f0d12d1abd 100644 --- a/security/nss/lib/nss/nss.h +++ b/security/nss/lib/nss/nss.h @@ -22,12 +22,12 @@ * The format of the version string should be * ".[.[.]][ ][ ]" */ -#define NSS_VERSION "3.61" _NSS_CUSTOMIZED +#define NSS_VERSION "3.62" _NSS_CUSTOMIZED " Beta" #define NSS_VMAJOR 3 -#define NSS_VMINOR 61 +#define NSS_VMINOR 62 #define NSS_VPATCH 0 #define NSS_VBUILD 0 -#define NSS_BETA PR_FALSE +#define NSS_BETA PR_TRUE #ifndef RC_INVOKED diff --git a/security/nss/lib/pk11wrap/pk11hpke.c b/security/nss/lib/pk11wrap/pk11hpke.c index 7f8fe3b1b092..626989fd32ea 100644 --- a/security/nss/lib/pk11wrap/pk11hpke.c +++ b/security/nss/lib/pk11wrap/pk11hpke.c @@ -1,5 +1,5 @@ /* - * draft-irtf-cfrg-hpke-05 + * draft-irtf-cfrg-hpke-07 * * 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 @@ -53,12 +53,24 @@ PK11_HPKE_GetEncapPubKey(const HpkeContext *cx) return NULL; } SECStatus +PK11_HPKE_ExportContext(const HpkeContext *cx, PK11SymKey *wrapKey, SECItem **serialized) +{ + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; +} +SECStatus PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int L, PK11SymKey **outKey) { PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); return SECFailure; } +HpkeContext * +PK11_HPKE_ImportContext(const SECItem *serialized, PK11SymKey *wrapKey) +{ + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return NULL; +} SECStatus PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad, const SECItem *ct, SECItem **outPt) @@ -94,15 +106,16 @@ PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey * } #else -static const char *DRAFT_LABEL = "HPKE-05 "; +#define SERIALIZATION_VERSION 1 + +static const char *DRAFT_LABEL = "HPKE-07"; static const char *EXP_LABEL = "exp"; static const char *HPKE_LABEL = "HPKE"; static const char *INFO_LABEL = "info_hash"; static const char *KEM_LABEL = "KEM"; static const char *KEY_LABEL = "key"; -static const char *NONCE_LABEL = "nonce"; +static const char *NONCE_LABEL = "base_nonce"; static const char *PSK_ID_LABEL = "psk_id_hash"; -static const char *PSK_LABEL = "psk_hash"; static const char *SECRET_LABEL = "secret"; static const char *SEC_LABEL = "sec"; static const char *EAE_PRK_LABEL = "eae_prk"; @@ -114,7 +127,7 @@ struct HpkeContextStr { const hpkeAeadParams *aeadParams; PRUint8 mode; /* Base and PSK modes supported. */ SECItem *encapPubKey; /* Marshalled public key, sent to receiver. */ - SECItem *nonce; /* Deterministic nonce for AEAD. */ + SECItem *baseNonce; /* Deterministic nonce for AEAD. */ SECItem *pskId; /* PSK identifier (non-secret). */ PK11Context *aeadContext; /* AEAD context used by Seal/Open. */ PRUint64 sequenceNumber; /* seqNo for decrypt IV construction. */ @@ -129,10 +142,14 @@ static const hpkeKemParams kemParams[] = { { HpkeDhKemX25519Sha256, 32, 32, 32, SEC_OID_CURVE25519, CKM_SHA256 }, }; +#define MAX_WRAPPED_EXP_LEN 72 // Largest kdfParams->Nh + 8 static const hpkeKdfParams kdfParams[] = { /* KDF, Nh, mechanism */ { HpkeKdfHkdfSha256, SHA256_LENGTH, CKM_SHA256 }, + { HpkeKdfHkdfSha384, SHA384_LENGTH, CKM_SHA384 }, + { HpkeKdfHkdfSha512, SHA512_LENGTH, CKM_SHA512 }, }; +#define MAX_WRAPPED_KEY_LEN 40 // Largest aeadParams->Nk + 8 static const hpkeAeadParams aeadParams[] = { /* AEAD, Nk, Nn, tagLen, mechanism */ { HpkeAeadAes128Gcm, 16, 12, 16, CKM_AES_GCM }, @@ -156,6 +173,10 @@ kdfId2Params(HpkeKdfId kdfId) switch (kdfId) { case HpkeKdfHkdfSha256: return &kdfParams[0]; + case HpkeKdfHkdfSha384: + return &kdfParams[1]; + case HpkeKdfHkdfSha512: + return &kdfParams[2]; default: return NULL; } @@ -174,16 +195,30 @@ aeadId2Params(HpkeAeadId aeadId) } } -static SECStatus -encodeShort(PRUint32 val, PRUint8 *b) +static PRUint8 * +encodeNumber(PRUint64 value, PRUint8 *b, size_t count) { - if (val > 0xFFFF || !b) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); - return SECFailure; + PRUint64 encoded; + PORT_Assert(b && count > 0 && count <= sizeof(encoded)); + + encoded = PR_htonll(value); + PORT_Memcpy(b, ((unsigned char *)(&encoded)) + (sizeof(encoded) - count), + count); + return b + count; +} + +static PRUint8 * +decodeNumber(PRUint64 *value, PRUint8 *b, size_t count) +{ + unsigned int i; + PRUint64 number = 0; + PORT_Assert(b && value && count <= sizeof(*value)); + + for (i = 0; i < count; i++) { + number = (number << 8) + b[i]; } - b[0] = (val >> 8) & 0xff; - b[1] = val & 0xff; - return SECSuccess; + *value = number; + return b + count; } SECStatus @@ -268,20 +303,260 @@ PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit) PK11_FreeSymKey(cx->key); PK11_FreeSymKey(cx->psk); SECITEM_FreeItem(cx->pskId, PR_TRUE); - SECITEM_FreeItem(cx->nonce, PR_TRUE); + SECITEM_FreeItem(cx->baseNonce, PR_TRUE); SECITEM_FreeItem(cx->encapPubKey, PR_TRUE); cx->exporterSecret = NULL; cx->sharedSecret = NULL; cx->key = NULL; cx->psk = NULL; cx->pskId = NULL; - cx->nonce = NULL; + cx->baseNonce = NULL; cx->encapPubKey = NULL; if (freeit) { PORT_ZFree(cx, sizeof(HpkeContext)); } } +/* Export Format: + struct { + uint8 serilizationVersion; + uint8 hpkeVersion; + uint16 kemId; + uint16 kdfId; + uint16 aeadId; + uint16 modeId; + uint64 sequenceNumber; + opaque senderPubKey<1..2^16-1>; + opaque baseNonce<1..2^16-1>; + opaque key<1..2^16-1>; + opaque exporterSecret<1..2^16-1>; + } HpkeSerializedContext +*/ +#define EXPORTED_CTX_BASE_LEN 26 /* Fixed size plus 2B for each variable. */ +#define REMAINING_BYTES(walker, buf) \ + buf->len - (walker - buf->data) +SECStatus +PK11_HPKE_ExportContext(const HpkeContext *cx, PK11SymKey *wrapKey, SECItem **serialized) +{ + SECStatus rv; + size_t allocLen; + PRUint8 *walker; + SECItem *keyBytes = NULL; // Maybe wrapped + SECItem *exporterBytes = NULL; // Maybe wrapped + SECItem *serializedCx = NULL; + PRUint8 wrappedKeyBytes[MAX_WRAPPED_KEY_LEN] = { 0 }; + PRUint8 wrappedExpBytes[MAX_WRAPPED_EXP_LEN] = { 0 }; + SECItem wrappedKey = { siBuffer, wrappedKeyBytes, sizeof(wrappedKeyBytes) }; + SECItem wrappedExp = { siBuffer, wrappedExpBytes, sizeof(wrappedExpBytes) }; + + CHECK_FAIL_ERR((!cx || !cx->aeadContext || !serialized), SEC_ERROR_INVALID_ARGS); + CHECK_FAIL_ERR((cx->aeadContext->operation != (CKA_NSS_MESSAGE | CKA_DECRYPT)), + SEC_ERROR_NOT_A_RECIPIENT); + + /* If a wrapping key was provided, do the wrap first + * so that we know what size to allocate. */ + if (wrapKey) { + rv = PK11_WrapSymKey(CKM_AES_KEY_WRAP_KWP, NULL, wrapKey, + cx->key, &wrappedKey); + CHECK_RV(rv); + rv = PK11_WrapSymKey(CKM_AES_KEY_WRAP_KWP, NULL, wrapKey, + cx->exporterSecret, &wrappedExp); + CHECK_RV(rv); + + keyBytes = &wrappedKey; + exporterBytes = &wrappedExp; + } else { + rv = PK11_ExtractKeyValue(cx->key); + CHECK_RV(rv); + keyBytes = PK11_GetKeyData(cx->key); + CHECK_FAIL(!keyBytes); + PORT_Assert(keyBytes->len == cx->aeadParams->Nk); + + rv = PK11_ExtractKeyValue(cx->exporterSecret); + CHECK_RV(rv); + exporterBytes = PK11_GetKeyData(cx->exporterSecret); + CHECK_FAIL(!exporterBytes); + PORT_Assert(exporterBytes->len == cx->kdfParams->Nh); + } + + allocLen = EXPORTED_CTX_BASE_LEN + cx->baseNonce->len + cx->encapPubKey->len; + allocLen += wrapKey ? wrappedKey.len : cx->aeadParams->Nk; + allocLen += wrapKey ? wrappedExp.len : cx->kdfParams->Nh; + + serializedCx = SECITEM_AllocItem(NULL, NULL, allocLen); + CHECK_FAIL(!serializedCx); + + walker = &serializedCx->data[0]; + *(walker)++ = (PRUint8)SERIALIZATION_VERSION; + *(walker)++ = (PRUint8)HPKE_DRAFT_VERSION; + + walker = encodeNumber(cx->kemParams->id, walker, 2); + walker = encodeNumber(cx->kdfParams->id, walker, 2); + walker = encodeNumber(cx->aeadParams->id, walker, 2); + walker = encodeNumber(cx->mode, walker, 2); + walker = encodeNumber(cx->sequenceNumber, walker, 8); + + /* sender public key, serialized. */ + walker = encodeNumber(cx->encapPubKey->len, walker, 2); + PORT_Memcpy(walker, cx->encapPubKey->data, cx->encapPubKey->len); + walker += cx->encapPubKey->len; + + /* base nonce */ + walker = encodeNumber(cx->baseNonce->len, walker, 2); + PORT_Memcpy(walker, cx->baseNonce->data, cx->baseNonce->len); + walker += cx->baseNonce->len; + + /* key. */ + walker = encodeNumber(keyBytes->len, walker, 2); + PORT_Memcpy(walker, keyBytes->data, keyBytes->len); + walker += keyBytes->len; + + /* exporter_secret. */ + walker = encodeNumber(exporterBytes->len, walker, 2); + PORT_Memcpy(walker, exporterBytes->data, exporterBytes->len); + walker += exporterBytes->len; + + CHECK_FAIL_ERR(REMAINING_BYTES(walker, serializedCx) != 0, + SEC_ERROR_LIBRARY_FAILURE); + *serialized = serializedCx; + +CLEANUP: + if (rv != SECSuccess) { + SECITEM_ZfreeItem(serializedCx, PR_TRUE); + } + return rv; +} + +HpkeContext * +PK11_HPKE_ImportContext(const SECItem *serialized, PK11SymKey *wrapKey) +{ + SECStatus rv = SECSuccess; + HpkeContext *cx = NULL; + PRUint8 *walker; + PRUint64 tmpn; + PRUint8 tmp8; + HpkeKemId kem; + HpkeKdfId kdf; + HpkeAeadId aead; + PK11SlotInfo *slot = NULL; + PK11SymKey *tmpKey = NULL; + SECItem tmpItem = { siBuffer, NULL, 0 }; + SECItem emptyItem = { siBuffer, NULL, 0 }; + + CHECK_FAIL_ERR((!serialized || !serialized->data || serialized->len == 0), + SEC_ERROR_INVALID_ARGS); + CHECK_FAIL_ERR((serialized->len < EXPORTED_CTX_BASE_LEN), SEC_ERROR_BAD_DATA); + + walker = serialized->data; + + tmp8 = *(walker++); + CHECK_FAIL_ERR((tmp8 != SERIALIZATION_VERSION), SEC_ERROR_BAD_DATA); + tmp8 = *(walker++); + CHECK_FAIL_ERR((tmp8 != HPKE_DRAFT_VERSION), SEC_ERROR_INVALID_ALGORITHM); + + walker = decodeNumber(&tmpn, walker, 2); + kem = (HpkeKemId)tmpn; + + walker = decodeNumber(&tmpn, walker, 2); + kdf = (HpkeKdfId)tmpn; + + walker = decodeNumber(&tmpn, walker, 2); + aead = (HpkeAeadId)tmpn; + + /* Create context. We'll manually set the mode, though we + * no longer have the PSK and have no need for it. */ + cx = PK11_HPKE_NewContext(kem, kdf, aead, NULL, NULL); + CHECK_FAIL(!cx); + + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR((tmpn != HpkeModeBase && tmpn != HpkeModePsk), + SEC_ERROR_BAD_DATA); + cx->mode = (HpkeModeId)tmpn; + + walker = decodeNumber(&cx->sequenceNumber, walker, 8); + slot = PK11_GetBestSlot(CKM_HKDF_DERIVE, NULL); + CHECK_FAIL(!slot); + + /* Import sender public key (serialized). */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn >= REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + cx->encapPubKey = SECITEM_DupItem(&tmpItem); + CHECK_FAIL(!cx->encapPubKey); + walker += tmpItem.len; + + /* Import base_nonce. */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn != cx->aeadParams->Nn, SEC_ERROR_BAD_DATA); + CHECK_FAIL_ERR(tmpn >= REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + cx->baseNonce = SECITEM_DupItem(&tmpItem); + CHECK_FAIL(!cx->baseNonce); + walker += tmpItem.len; + + /* Import key */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn >= REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + walker += tmpItem.len; + if (wrapKey) { + cx->key = PK11_UnwrapSymKey(wrapKey, CKM_AES_KEY_WRAP_KWP, + NULL, &tmpItem, cx->aeadParams->mech, + CKA_NSS_MESSAGE | CKA_DECRYPT, 0); + CHECK_FAIL(!cx->key); + } else { + CHECK_FAIL_ERR(tmpn != cx->aeadParams->Nk, SEC_ERROR_BAD_DATA); + tmpKey = PK11_ImportSymKey(slot, cx->aeadParams->mech, + PK11_OriginUnwrap, CKA_NSS_MESSAGE | CKA_DECRYPT, + &tmpItem, NULL); + CHECK_FAIL(!tmpKey); + cx->key = tmpKey; + } + + /* Import exporter_secret. */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn != REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + walker += tmpItem.len; + + if (wrapKey) { + cx->exporterSecret = PK11_UnwrapSymKey(wrapKey, CKM_AES_KEY_WRAP_KWP, + NULL, &tmpItem, cx->kdfParams->mech, + CKM_HKDF_DERIVE, 0); + CHECK_FAIL(!cx->exporterSecret); + } else { + CHECK_FAIL_ERR(tmpn != cx->kdfParams->Nh, SEC_ERROR_BAD_DATA); + tmpKey = PK11_ImportSymKey(slot, CKM_HKDF_DERIVE, PK11_OriginUnwrap, + CKA_DERIVE, &tmpItem, NULL); + CHECK_FAIL(!tmpKey); + cx->exporterSecret = tmpKey; + } + + cx->aeadContext = PK11_CreateContextBySymKey(cx->aeadParams->mech, + CKA_NSS_MESSAGE | CKA_DECRYPT, + cx->key, &emptyItem); + +CLEANUP: + if (rv != SECSuccess) { + PK11_FreeSymKey(tmpKey); + PK11_HPKE_DestroyContext(cx, PR_TRUE); + cx = NULL; + } + if (slot) { + PK11_FreeSlot(slot); + } + + return cx; +} + SECStatus PK11_HPKE_Serialize(const SECKEYPublicKey *pk, PRUint8 *buf, unsigned int *len, unsigned int maxLen) { @@ -347,7 +622,7 @@ PK11_HPKE_Deserialize(const HpkeContext *cx, const PRUint8 *enc, // Set parameters. pubKey->u.ec.DEREncodedParams.data[0] = SEC_ASN1_OBJECT_ID; pubKey->u.ec.DEREncodedParams.data[1] = oidData->oid.len; - memcpy(pubKey->u.ec.DEREncodedParams.data + 2, oidData->oid.data, oidData->oid.len); + PORT_Memcpy(pubKey->u.ec.DEREncodedParams.data + 2, oidData->oid.data, oidData->oid.len); *outPubKey = pubKey; CLEANUP: @@ -403,7 +678,7 @@ pk11_hpke_GenerateKeyPair(const HpkeContext *cx, SECKEYPublicKey **pkE, ecp.type = siDEROID; ecp.data[0] = SEC_ASN1_OBJECT_ID; ecp.data[1] = oidData->oid.len; - memcpy(&ecp.data[2], oidData->oid.data, oidData->oid.len); + PORT_Memcpy(&ecp.data[2], oidData->oid.data, oidData->oid.len); slot = PK11_GetBestSlot(CKM_EC_KEY_PAIR_GEN, NULL); CHECK_FAIL(!slot); @@ -433,21 +708,21 @@ pk11_hpke_MakeExtractLabel(const char *prefix, unsigned int prefixLen, const SECItem *suiteId, const SECItem *ikm) { SECItem *out = NULL; - size_t off = 0; + PRUint8 *walker; out = SECITEM_AllocItem(NULL, NULL, prefixLen + labelLen + suiteId->len + (ikm ? ikm->len : 0)); if (!out) { return NULL; } - memcpy(&out->data[off], prefix, prefixLen); - off += prefixLen; - memcpy(&out->data[off], suiteId->data, suiteId->len); - off += suiteId->len; - memcpy(&out->data[off], label, labelLen); - off += labelLen; + walker = out->data; + PORT_Memcpy(walker, prefix, prefixLen); + walker += prefixLen; + PORT_Memcpy(walker, suiteId->data, suiteId->len); + walker += suiteId->len; + PORT_Memcpy(walker, label, labelLen); + walker += labelLen; if (ikm && ikm->data) { - memcpy(&out->data[off], ikm->data, ikm->len); - off += ikm->len; + PORT_Memcpy(walker, ikm->data, ikm->len); } return out; @@ -474,7 +749,7 @@ pk11_hpke_LabeledExtractData(const HpkeContext *cx, SECItem *salt, CHECK_FAIL(!labeledIkm); params.bExtract = CK_TRUE; params.bExpand = CK_FALSE; - params.prfHashMechanism = cx->kemParams->hashMech; + params.prfHashMechanism = cx->kdfParams->mech; params.ulSaltType = salt ? CKF_HKDF_SALT_DATA : CKF_HKDF_SALT_NULL; params.pSalt = salt ? (CK_BYTE_PTR)salt->data : NULL; params.ulSaltLen = salt ? salt->len : 0; @@ -511,7 +786,7 @@ CLEANUP: static SECStatus pk11_hpke_LabeledExtract(const HpkeContext *cx, PK11SymKey *salt, - const SECItem *suiteId, const char *label, + const SECItem *suiteId, const char *label, CK_MECHANISM_TYPE hashMech, unsigned int labelLen, PK11SymKey *ikm, PK11SymKey **out) { SECStatus rv = SECSuccess; @@ -537,7 +812,7 @@ pk11_hpke_LabeledExtract(const HpkeContext *cx, PK11SymKey *salt, params.bExtract = CK_TRUE; params.bExpand = CK_FALSE; - params.prfHashMechanism = cx->kemParams->hashMech; + params.prfHashMechanism = hashMech; params.ulSaltType = salt ? CKF_HKDF_SALT_KEY : CKF_HKDF_SALT_NULL; params.hSaltKey = salt ? PK11_GetSymKeyHandle(salt) : CK_INVALID_HANDLE; @@ -555,9 +830,10 @@ CLEANUP: static SECStatus pk11_hpke_LabeledExpand(const HpkeContext *cx, PK11SymKey *prk, const SECItem *suiteId, const char *label, unsigned int labelLen, const SECItem *info, - unsigned int L, PK11SymKey **outKey, SECItem **outItem) + unsigned int L, CK_MECHANISM_TYPE hashMech, PK11SymKey **outKey, + SECItem **outItem) { - SECStatus rv; + SECStatus rv = SECSuccess; CK_MECHANISM_TYPE keyMech; CK_MECHANISM_TYPE deriveMech; CK_HKDF_PARAMS params = { 0 }; @@ -567,34 +843,32 @@ pk11_hpke_LabeledExpand(const HpkeContext *cx, PK11SymKey *prk, const SECItem *s sizeof(params) }; SECItem *derivedKeyData; PRUint8 encodedL[2]; - size_t off = 0; + PRUint8 *walker = encodedL; size_t len; PORT_Assert(cx && prk && label && (!!outKey != !!outItem)); - rv = encodeShort(L, encodedL); - CHECK_RV(rv); - + walker = encodeNumber(L, walker, 2); len = info ? info->len : 0; len += sizeof(encodedL) + strlen(DRAFT_LABEL) + suiteId->len + labelLen; labeledInfoItem = SECITEM_AllocItem(NULL, NULL, len); CHECK_FAIL(!labeledInfoItem); - memcpy(&labeledInfoItem->data[off], encodedL, sizeof(encodedL)); - off += sizeof(encodedL); - memcpy(&labeledInfoItem->data[off], DRAFT_LABEL, strlen(DRAFT_LABEL)); - off += strlen(DRAFT_LABEL); - memcpy(&labeledInfoItem->data[off], suiteId->data, suiteId->len); - off += suiteId->len; - memcpy(&labeledInfoItem->data[off], label, labelLen); - off += labelLen; + walker = labeledInfoItem->data; + PORT_Memcpy(walker, encodedL, sizeof(encodedL)); + walker += sizeof(encodedL); + PORT_Memcpy(walker, DRAFT_LABEL, strlen(DRAFT_LABEL)); + walker += strlen(DRAFT_LABEL); + PORT_Memcpy(walker, suiteId->data, suiteId->len); + walker += suiteId->len; + PORT_Memcpy(walker, label, labelLen); + walker += labelLen; if (info) { - memcpy(&labeledInfoItem->data[off], info->data, info->len); - off += info->len; + PORT_Memcpy(walker, info->data, info->len); } params.bExtract = CK_FALSE; params.bExpand = CK_TRUE; - params.prfHashMechanism = cx->kemParams->hashMech; + params.prfHashMechanism = hashMech; params.ulSaltType = CKF_HKDF_SALT_NULL; params.pInfo = labeledInfoItem->data; params.ulInfoLen = labeledInfoItem->len; @@ -635,19 +909,22 @@ pk11_hpke_ExtractAndExpand(const HpkeContext *cx, PK11SymKey *ikm, PK11SymKey *eaePrk = NULL; PK11SymKey *sharedSecret = NULL; PRUint8 suiteIdBuf[5]; + PRUint8 *walker; PORT_Memcpy(suiteIdBuf, KEM_LABEL, strlen(KEM_LABEL)); SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) }; PORT_Assert(cx && ikm && kemContext && out); - rv = encodeShort(cx->kemParams->id, &suiteIdBuf[3]); - CHECK_RV(rv); + walker = &suiteIdBuf[3]; + walker = encodeNumber(cx->kemParams->id, walker, 2); rv = pk11_hpke_LabeledExtract(cx, NULL, &suiteIdItem, EAE_PRK_LABEL, - strlen(EAE_PRK_LABEL), ikm, &eaePrk); + cx->kemParams->hashMech, strlen(EAE_PRK_LABEL), + ikm, &eaePrk); CHECK_RV(rv); rv = pk11_hpke_LabeledExpand(cx, eaePrk, &suiteIdItem, SH_SEC_LABEL, strlen(SH_SEC_LABEL), - kemContext, cx->kemParams->Nsecret, &sharedSecret, NULL); + kemContext, cx->kemParams->Nsecret, cx->kemParams->hashMech, + &sharedSecret, NULL); CHECK_RV(rv); *out = sharedSecret; @@ -698,7 +975,7 @@ pk11_hpke_Encap(HpkeContext *cx, const SECKEYPublicKey *pkE, SECKEYPrivateKey *s kemContext = SECITEM_AllocItem(NULL, NULL, cx->encapPubKey->len + tmpLen); CHECK_FAIL(!kemContext); - memcpy(kemContext->data, cx->encapPubKey->data, cx->encapPubKey->len); + PORT_Memcpy(kemContext->data, cx->encapPubKey->data, cx->encapPubKey->len); rv = PK11_HPKE_Serialize(pkR, &kemContext->data[cx->encapPubKey->len], &tmpLen, tmpLen); CHECK_RV(rv); @@ -723,6 +1000,7 @@ PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int SECStatus rv; PK11SymKey *exported; PRUint8 suiteIdBuf[10]; + PRUint8 *walker; PORT_Memcpy(suiteIdBuf, HPKE_LABEL, strlen(HPKE_LABEL)); SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) }; @@ -733,15 +1011,14 @@ PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int return SECFailure; } - rv = encodeShort(cx->kemParams->id, &suiteIdBuf[4]); - CHECK_RV(rv); - rv = encodeShort(cx->kdfParams->id, &suiteIdBuf[6]); - CHECK_RV(rv); - rv = encodeShort(cx->aeadParams->id, &suiteIdBuf[8]); - CHECK_RV(rv); + walker = &suiteIdBuf[4]; + walker = encodeNumber(cx->kemParams->id, walker, 2); + walker = encodeNumber(cx->kdfParams->id, walker, 2); + walker = encodeNumber(cx->aeadParams->id, walker, 2); rv = pk11_hpke_LabeledExpand(cx, cx->exporterSecret, &suiteIdItem, SEC_LABEL, - strlen(SEC_LABEL), info, L, &exported, NULL); + strlen(SEC_LABEL), info, L, cx->kdfParams->mech, + &exported, NULL); CHECK_RV(rv); *out = exported; @@ -785,12 +1062,18 @@ pk11_hpke_Decap(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *s kemContext = SECITEM_AllocItem(NULL, NULL, encS->len + tmpLen); CHECK_FAIL(!kemContext); - memcpy(kemContext->data, encS->data, encS->len); + PORT_Memcpy(kemContext->data, encS->data, encS->len); rv = PK11_HPKE_Serialize(pkR, &kemContext->data[encS->len], &tmpLen, kemContext->len - encS->len); CHECK_RV(rv); rv = pk11_hpke_ExtractAndExpand(cx, dh, kemContext, &cx->sharedSecret); CHECK_RV(rv); + + /* Store the sender serialized public key, which + * may be required by application use cases. */ + cx->encapPubKey = SECITEM_DupItem(encS); + CHECK_FAIL(!cx->encapPubKey); + CLEANUP: if (rv != SECSuccess) { PK11_FreeSymKey(cx->sharedSecret); @@ -809,7 +1092,6 @@ PK11_HPKE_GetEncapPubKey(const HpkeContext *cx) if (!cx) { return NULL; } - /* Will be NULL on receiver. */ return cx->encapPubKey; } @@ -820,21 +1102,19 @@ pk11_hpke_KeySchedule(HpkeContext *cx, const SECItem *info) SECItem contextItem = { siBuffer, NULL, 0 }; unsigned int len; unsigned int off; - PK11SymKey *pskHash = NULL; PK11SymKey *secret = NULL; SECItem *pskIdHash = NULL; SECItem *infoHash = NULL; PRUint8 suiteIdBuf[10]; + PRUint8 *walker; PORT_Memcpy(suiteIdBuf, HPKE_LABEL, strlen(HPKE_LABEL)); SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) }; PORT_Assert(cx && info && cx->psk && cx->pskId); - rv = encodeShort(cx->kemParams->id, &suiteIdBuf[4]); - CHECK_RV(rv); - rv = encodeShort(cx->kdfParams->id, &suiteIdBuf[6]); - CHECK_RV(rv); - rv = encodeShort(cx->aeadParams->id, &suiteIdBuf[8]); - CHECK_RV(rv); + walker = &suiteIdBuf[4]; + walker = encodeNumber(cx->kemParams->id, walker, 2); + walker = encodeNumber(cx->kdfParams->id, walker, 2); + walker = encodeNumber(cx->aeadParams->id, walker, 2); rv = pk11_hpke_LabeledExtractData(cx, NULL, &suiteIdItem, PSK_ID_LABEL, strlen(PSK_ID_LABEL), cx->pskId, &pskIdHash); @@ -847,33 +1127,33 @@ pk11_hpke_KeySchedule(HpkeContext *cx, const SECItem *info) len = sizeof(cx->mode) + pskIdHash->len + infoHash->len; CHECK_FAIL(!SECITEM_AllocItem(NULL, &contextItem, len)); off = 0; - memcpy(&contextItem.data[off], &cx->mode, sizeof(cx->mode)); + PORT_Memcpy(&contextItem.data[off], &cx->mode, sizeof(cx->mode)); off += sizeof(cx->mode); - memcpy(&contextItem.data[off], pskIdHash->data, pskIdHash->len); + PORT_Memcpy(&contextItem.data[off], pskIdHash->data, pskIdHash->len); off += pskIdHash->len; - memcpy(&contextItem.data[off], infoHash->data, infoHash->len); + PORT_Memcpy(&contextItem.data[off], infoHash->data, infoHash->len); off += infoHash->len; // Compute the keys - rv = pk11_hpke_LabeledExtract(cx, NULL, &suiteIdItem, PSK_LABEL, - strlen(PSK_LABEL), cx->psk, &pskHash); - CHECK_RV(rv); - rv = pk11_hpke_LabeledExtract(cx, pskHash, &suiteIdItem, SECRET_LABEL, - strlen(SECRET_LABEL), cx->sharedSecret, &secret); + rv = pk11_hpke_LabeledExtract(cx, cx->sharedSecret, &suiteIdItem, SECRET_LABEL, + cx->kdfParams->mech, strlen(SECRET_LABEL), + cx->psk, &secret); CHECK_RV(rv); rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, KEY_LABEL, strlen(KEY_LABEL), - &contextItem, cx->aeadParams->Nk, &cx->key, NULL); + &contextItem, cx->aeadParams->Nk, cx->kdfParams->mech, + &cx->key, NULL); CHECK_RV(rv); rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, NONCE_LABEL, strlen(NONCE_LABEL), - &contextItem, cx->aeadParams->Nn, NULL, &cx->nonce); + &contextItem, cx->aeadParams->Nn, cx->kdfParams->mech, + NULL, &cx->baseNonce); CHECK_RV(rv); rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, EXP_LABEL, strlen(EXP_LABEL), - &contextItem, cx->kdfParams->Nh, &cx->exporterSecret, NULL); + &contextItem, cx->kdfParams->Nh, cx->kdfParams->mech, + &cx->exporterSecret, NULL); CHECK_RV(rv); CLEANUP: /* If !SECSuccess, callers will tear down the context. */ - PK11_FreeSymKey(pskHash); PK11_FreeSymKey(secret); SECITEM_FreeItem(&contextItem, PR_FALSE); SECITEM_FreeItem(infoHash, PR_TRUE); @@ -886,7 +1166,7 @@ PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey * const SECItem *enc, const SECItem *info) { SECStatus rv; - SECItem nullParams = { siBuffer, NULL, 0 }; + SECItem empty = { siBuffer, NULL, 0 }; CHECK_FAIL_ERR((!cx || !skR || !info || !enc || !enc->data || !enc->len), SEC_ERROR_INVALID_ARGS); @@ -903,7 +1183,7 @@ PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey * PORT_Assert(cx->key); cx->aeadContext = PK11_CreateContextBySymKey(cx->aeadParams->mech, CKA_NSS_MESSAGE | CKA_DECRYPT, - cx->key, &nullParams); + cx->key, &empty); CHECK_FAIL_ERR((!cx->aeadContext), SEC_ERROR_LIBRARY_FAILURE); CLEANUP: @@ -973,8 +1253,8 @@ PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, unsigned char tagBuf[HASH_LENGTH_MAX]; size_t tagLen; unsigned int fixedBits; - PORT_Assert(cx->nonce->len == sizeof(ivOut)); - memcpy(ivOut, cx->nonce->data, cx->nonce->len); + PORT_Assert(cx->baseNonce->len == sizeof(ivOut)); + PORT_Memcpy(ivOut, cx->baseNonce->data, cx->baseNonce->len); /* aad may be NULL, PT may be zero-length but not NULL. */ if (!cx || !cx->aeadContext || @@ -987,7 +1267,7 @@ PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, tagLen = cx->aeadParams->tagLen; maxOut = pt->len + tagLen; - fixedBits = (cx->nonce->len - 8) * 8; + fixedBits = (cx->baseNonce->len - 8) * 8; ct = SECITEM_AllocItem(NULL, NULL, maxOut); CHECK_FAIL(!ct); @@ -1003,7 +1283,7 @@ PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, CHECK_FAIL_ERR((ct->len > maxOut - tagLen), SEC_ERROR_LIBRARY_FAILURE); /* Append the tag to the ciphertext. */ - memcpy(&ct->data[ct->len], tagBuf, tagLen); + PORT_Memcpy(&ct->data[ct->len], tagBuf, tagLen); ct->len += tagLen; *out = ct; @@ -1023,7 +1303,7 @@ static SECStatus pk11_hpke_makeIv(HpkeContext *cx, PRUint8 *iv, size_t ivLen) { unsigned int counterLen = sizeof(cx->sequenceNumber); - PORT_Assert(cx->nonce->len == ivLen); + PORT_Assert(cx->baseNonce->len == ivLen); PORT_Assert(counterLen == 8); if (cx->sequenceNumber == PR_UINT64(0xffffffffffffffff)) { /* Overflow */ @@ -1031,9 +1311,9 @@ pk11_hpke_makeIv(HpkeContext *cx, PRUint8 *iv, size_t ivLen) return SECFailure; } - memcpy(iv, cx->nonce->data, cx->nonce->len); + PORT_Memcpy(iv, cx->baseNonce->data, cx->baseNonce->len); for (size_t i = 0; i < counterLen; i++) { - iv[cx->nonce->len - 1 - i] ^= + iv[cx->baseNonce->len - 1 - i] ^= PORT_GET_BYTE_BE(cx->sequenceNumber, counterLen - 1 - i, counterLen); } @@ -1082,4 +1362,5 @@ CLEANUP: } return rv; } + #endif // NSS_ENABLE_DRAFT_HPKE diff --git a/security/nss/lib/pk11wrap/pk11hpke.h b/security/nss/lib/pk11wrap/pk11hpke.h index 95a55fd336de..e86d235b43d4 100644 --- a/security/nss/lib/pk11wrap/pk11hpke.h +++ b/security/nss/lib/pk11wrap/pk11hpke.h @@ -9,7 +9,7 @@ #include "seccomon.h" #ifdef NSS_ENABLE_DRAFT_HPKE -#define HPKE_DRAFT_VERSION 5 +#define HPKE_DRAFT_VERSION 7 #define CLEANUP \ PORT_Assert(rv == SECSuccess); \ @@ -42,13 +42,15 @@ typedef enum { HpkeModePsk = 1, } HpkeModeId; -/* https://tools.ietf.org/html/draft-irtf-cfrg-hpke-05#section-7.1 */ +/* https://tools.ietf.org/html/draft-irtf-cfrg-hpke-07#section-7.1 */ typedef enum { HpkeDhKemX25519Sha256 = 0x20, } HpkeKemId; typedef enum { HpkeKdfHkdfSha256 = 1, + HpkeKdfHkdfSha384 = 2, + HpkeKdfHkdfSha512 = 3, } HpkeKdfId; typedef enum { diff --git a/security/nss/lib/pk11wrap/pk11pub.h b/security/nss/lib/pk11wrap/pk11pub.h index e6961e024429..1a074219b0a3 100644 --- a/security/nss/lib/pk11wrap/pk11pub.h +++ b/security/nss/lib/pk11wrap/pk11pub.h @@ -728,7 +728,7 @@ CK_BBOOL PK11_HasAttributeSet(PK11SlotInfo *slot, PRBool haslock /* must be set to PR_FALSE */); /********************************************************************** - * Hybrid Public Key Encryption (draft-05) + * Hybrid Public Key Encryption (draft-07) **********************************************************************/ /* * NOTE: All HPKE functions will fail with SEC_ERROR_INVALID_ALGORITHM @@ -746,9 +746,28 @@ HpkeContext *PK11_HPKE_NewContext(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId a SECStatus PK11_HPKE_Deserialize(const HpkeContext *cx, const PRUint8 *enc, unsigned int encLen, SECKEYPublicKey **outPubKey); void PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit); -const SECItem *PK11_HPKE_GetEncapPubKey(const HpkeContext *cx); + +/* Serialize an initialized receiver context. This only retains the keys and + * associated information necessary to resume Export and Open operations after + * import. Serialization is currently supported for receiver contexts only. + * This is done for two reasons: 1) it avoids having to move the encryption + * sequence number outside of the token (or adding encryption context + * serialization support to softoken), and 2) we don't have to worry about IV + * reuse due to sequence number cloning. + * + * |wrapKey| is required when exporting in FIPS mode. If exported with a + * wrapping key, that same key must be provided to the import function, + * otherwise behavior is undefined. + * + * Even when exported with key wrap, HPKE expects the nonce to also be kept + * secret and that value is not protected by wrapKey. Applications are + * responsible for maintaining the confidentiality of the exported information. + */ +SECStatus PK11_HPKE_ExportContext(const HpkeContext *cx, PK11SymKey *wrapKey, SECItem **serialized); SECStatus PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int L, PK11SymKey **outKey); +const SECItem *PK11_HPKE_GetEncapPubKey(const HpkeContext *cx); +HpkeContext *PK11_HPKE_ImportContext(const SECItem *serialized, PK11SymKey *wrapKey); SECStatus PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad, const SECItem *ct, SECItem **outPt); SECStatus PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, SECItem **outCt); SECStatus PK11_HPKE_Serialize(const SECKEYPublicKey *pk, PRUint8 *buf, unsigned int *len, unsigned int maxLen); diff --git a/security/nss/lib/softoken/softkver.h b/security/nss/lib/softoken/softkver.h index dcbee9252302..06e77242dfaf 100644 --- a/security/nss/lib/softoken/softkver.h +++ b/security/nss/lib/softoken/softkver.h @@ -17,11 +17,11 @@ * The format of the version string should be * ".[.[.]][ ][ ]" */ -#define SOFTOKEN_VERSION "3.61" SOFTOKEN_ECC_STRING +#define SOFTOKEN_VERSION "3.62" SOFTOKEN_ECC_STRING " Beta" #define SOFTOKEN_VMAJOR 3 -#define SOFTOKEN_VMINOR 61 +#define SOFTOKEN_VMINOR 62 #define SOFTOKEN_VPATCH 0 #define SOFTOKEN_VBUILD 0 -#define SOFTOKEN_BETA PR_FALSE +#define SOFTOKEN_BETA PR_TRUE #endif /* _SOFTKVER_H_ */ diff --git a/security/nss/lib/ssl/ssl3con.c b/security/nss/lib/ssl/ssl3con.c index cd36b80a5fd1..fd491f89639a 100644 --- a/security/nss/lib/ssl/ssl3con.c +++ b/security/nss/lib/ssl/ssl3con.c @@ -70,6 +70,9 @@ PRBool ssl_IsRsaPssSignatureScheme(SSLSignatureScheme scheme); PRBool ssl_IsRsaeSignatureScheme(SSLSignatureScheme scheme); PRBool ssl_IsRsaPkcs1SignatureScheme(SSLSignatureScheme scheme); PRBool ssl_IsDsaSignatureScheme(SSLSignatureScheme scheme); +static SECStatus ssl3_UpdateDefaultHandshakeHashes(sslSocket *ss, + const unsigned char *b, + unsigned int l); const PRUint8 ssl_hello_retry_random[] = { 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, @@ -3848,11 +3851,18 @@ ssl3_InitHandshakeHashes(sslSocket *ss) if (ss->ssl3.hs.hashType != handshake_hash_record && ss->ssl3.hs.messages.len > 0) { - if (ssl3_UpdateHandshakeHashes(ss, ss->ssl3.hs.messages.buf, - ss->ssl3.hs.messages.len) != SECSuccess) { + /* When doing ECH, ssl3_UpdateHandshakeHashes will store outer messages into + * the both the outer and inner transcripts. ssl3_UpdateDefaultHandshakeHashes + * uses only the default context (which is the outer when doing ECH). */ + if (ssl3_UpdateDefaultHandshakeHashes(ss, ss->ssl3.hs.messages.buf, + ss->ssl3.hs.messages.len) != SECSuccess) { return SECFailure; } - sslBuffer_Clear(&ss->ssl3.hs.messages); + /* When doing ECH, deriving accept_confirmation requires all messages + * up to SH, then a synthetic SH. Don't free the buffers just yet. */ + if (!ss->ssl3.hs.echHpkeCtx) { + sslBuffer_Clear(&ss->ssl3.hs.messages); + } } if (ss->ssl3.hs.shaEchInner && ss->ssl3.hs.echInnerMessages.len > 0) { @@ -3861,7 +3871,9 @@ ssl3_InitHandshakeHashes(sslSocket *ss) ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); return SECFailure; } - sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); + if (!ss->ssl3.hs.echHpkeCtx) { + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); + } } return SECSuccess; @@ -3893,52 +3905,28 @@ ssl3_RestartHandshakeHashes(sslSocket *ss) } } -/* For TLS 1.3 EncryptedClientHello, add the provided buffer to the - * given hash context. This is only needed for the initial CH, - * after which ssl3_UpdateHandshakeHashes will update both contexts - * until ssl3_CoalesceEchHandshakeHashes. */ +/* Add the provided bytes to the handshake hash context. When doing + * TLS 1.3 ECH, |target| may be provided to specify only the inner/outer + * transcript, else the input is added to both contexts. This happens + * only on the client. On the server, only the default context is used. */ SECStatus -ssl3_UpdateExplicitHandshakeTranscript(sslSocket *ss, const unsigned char *b, - unsigned int l, sslBuffer *target) -{ - PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); - PORT_Assert(ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3); - if (ss->sec.isServer) { - /* Only the client maintains two states at the outset. */ - PORT_Assert(target != &ss->ssl3.hs.echInnerMessages); - } - return sslBuffer_Append(target, b, l); -} -static SECStatus -ssl3_UpdateOuterHandshakeHashes(sslSocket *ss, const unsigned char *b, - unsigned int l) -{ - return ssl3_UpdateExplicitHandshakeTranscript(ss, b, l, - &ss->ssl3.hs.messages); -} -static SECStatus -ssl3_UpdateInnerHandshakeHashes(sslSocket *ss, const unsigned char *b, - unsigned int l) -{ - return ssl3_UpdateExplicitHandshakeTranscript(ss, b, l, - &ss->ssl3.hs.echInnerMessages); -} -/* - * Handshake messages - */ -/* Called from ssl3_InitHandshakeHashes() -** ssl3_AppendHandshake() -** ssl3_HandleV2ClientHello() -** ssl3_HandleHandshakeMessage() -** Caller must hold the ssl3Handshake lock. -*/ -SECStatus -ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l) +ssl3_UpdateHandshakeHashesInt(sslSocket *ss, const unsigned char *b, + unsigned int l, sslBuffer *target) { + SECStatus rv = SECSuccess; - + PRBool explicit = (target != NULL); + PRBool appendToEchInner = !ss->sec.isServer && + ss->ssl3.hs.echHpkeCtx && + !explicit; PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); + PORT_Assert(target != &ss->ssl3.hs.echInnerMessages || + !ss->sec.isServer); + if (target == NULL) { + /* Default context. */ + target = &ss->ssl3.hs.messages; + } /* With TLS 1.3, and versions TLS.1.1 and older, we keep the hash(es) * always up to date. However, we must initially buffer the handshake * messages, until we know what to do. @@ -3950,15 +3938,14 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l * and never update the hash, because the hash function we must use for * certificate_verify might be different from the hash function we use * when signing other handshake hashes. */ - if (ss->ssl3.hs.hashType == handshake_hash_unknown || ss->ssl3.hs.hashType == handshake_hash_record) { - rv = sslBuffer_Append(&ss->ssl3.hs.messages, b, l); + rv = sslBuffer_Append(target, b, l); if (rv != SECSuccess) { return SECFailure; } - if (!ss->sec.isServer && ss->ssl3.hs.echHpkeCtx) { - return ssl3_UpdateInnerHandshakeHashes(ss, b, l); + if (appendToEchInner) { + return sslBuffer_Append(&ss->ssl3.hs.echInnerMessages, b, l); } return SECSuccess; } @@ -3967,10 +3954,20 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l if (ss->ssl3.hs.hashType == handshake_hash_single) { PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3); - rv = PK11_DigestOp(ss->ssl3.hs.sha, b, l); - if (rv != SECSuccess) { - ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); - return rv; + if (target == &ss->ssl3.hs.messages) { + rv = PK11_DigestOp(ss->ssl3.hs.sha, b, l); + if (rv != SECSuccess) { + ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); + return rv; + } + } + if (ss->ssl3.hs.shaEchInner && + (target == &ss->ssl3.hs.echInnerMessages || !explicit)) { + rv = PK11_DigestOp(ss->ssl3.hs.shaEchInner, b, l); + if (rv != SECSuccess) { + ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); + return rv; + } } } else if (ss->ssl3.hs.hashType == handshake_hash_combo) { rv = PK11_DigestOp(ss->ssl3.hs.md5, b, l); @@ -3987,6 +3984,37 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l return rv; } +static SECStatus +ssl3_UpdateDefaultHandshakeHashes(sslSocket *ss, const unsigned char *b, + unsigned int l) +{ + return ssl3_UpdateHandshakeHashesInt(ss, b, l, + &ss->ssl3.hs.messages); +} + +static SECStatus +ssl3_UpdateInnerHandshakeHashes(sslSocket *ss, const unsigned char *b, + unsigned int l) +{ + return ssl3_UpdateHandshakeHashesInt(ss, b, l, + &ss->ssl3.hs.echInnerMessages); +} + +/* + * Handshake messages + */ +/* Called from ssl3_InitHandshakeHashes() +** ssl3_AppendHandshake() +** ssl3_HandleV2ClientHello() +** ssl3_HandleHandshakeMessage() +** Caller must hold the ssl3Handshake lock. +*/ +SECStatus +ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l) +{ + return ssl3_UpdateHandshakeHashesInt(ss, b, l, NULL); +} + SECStatus ssl3_UpdatePostHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l) { @@ -5513,7 +5541,7 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) if (rv != SECSuccess) { goto loser; /* code set */ } - rv = ssl3_UpdateOuterHandshakeHashes(ss, chBuf.buf, chBuf.len); + rv = ssl3_UpdateDefaultHandshakeHashes(ss, chBuf.buf, chBuf.len); if (rv != SECSuccess) { goto loser; /* code set */ } @@ -7064,11 +7092,6 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length) return SECSuccess; } - rv = tls13_MaybeHandleEchSignal(ss); - if (rv != SECSuccess) { - goto alert_loser; - } - rv = ssl3_HandleParsedExtensions(ss, ssl_hs_server_hello); ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions); if (rv != SECSuccess) { @@ -7082,7 +7105,7 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length) } if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) { - rv = tls13_HandleServerHelloPart2(ss); + rv = tls13_HandleServerHelloPart2(ss, savedMsg, savedLength); if (rv != SECSuccess) { errCode = PORT_GetError(); goto loser; @@ -7093,6 +7116,7 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length) goto loser; } + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; return SECSuccess; alert_loser: @@ -8628,13 +8652,6 @@ ssl_GenerateServerRandom(sslSocket *ss) return SECFailure; } - if (ss->ssl3.hs.echAccepted) { - rv = tls13_WriteServerEchSignal(ss); - if (rv != SECSuccess) { - return SECFailure; - } - } - if (ss->version == ss->vrange.max) { return SECSuccess; } @@ -9775,6 +9792,15 @@ ssl_ConstructServerHello(sslSocket *ss, PRBool helloRetry, } } + if (!helloRetry && ssl3_ExtensionNegotiated(ss, ssl_tls13_ech_is_inner_xtn)) { + /* Signal ECH acceptance if we handled handled both CHOuter/CHInner (i.e. + * in shared mode), or if we received a CHInner in split/backend mode. */ + if (ss->ssl3.hs.echAccepted || ss->opt.enableTls13BackendEch) { + return tls13_WriteServerEchSignal(ss, SSL_BUFFER_BASE(messageBuf), + SSL_BUFFER_LEN(messageBuf)); + } + } + return SECSuccess; } @@ -12284,7 +12310,7 @@ ssl_HashHandshakeMessageDefault(sslSocket *ss, SSLHandshakeType ct, const PRUint8 *b, PRUint32 length) { return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq, - b, length, ssl3_UpdateOuterHandshakeHashes); + b, length, ssl3_UpdateDefaultHandshakeHashes); } SECStatus ssl_HashHandshakeMessageEchInner(sslSocket *ss, SSLHandshakeType ct, @@ -13847,6 +13873,7 @@ ssl3_DestroySSL3Info(sslSocket *ss) /* TLS 1.3 ECH state. */ PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ + sslBuffer_Clear(&ss->ssl3.hs.greaseEchBuf); } /* diff --git a/security/nss/lib/ssl/ssl3ext.c b/security/nss/lib/ssl/ssl3ext.c index 78c2b901a99e..199cf459ae7c 100644 --- a/security/nss/lib/ssl/ssl3ext.c +++ b/security/nss/lib/ssl/ssl3ext.c @@ -15,6 +15,7 @@ #include "sslimpl.h" #include "sslproto.h" #include "ssl3exthandle.h" +#include "tls13ech.h" #include "tls13err.h" #include "tls13exthandle.h" #include "tls13subcerts.h" @@ -54,6 +55,7 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = { { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn }, { ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn }, { ssl_tls13_post_handshake_auth_xtn, &tls13_ServerHandlePostHandshakeAuthXtn }, + { ssl_tls13_ech_is_inner_xtn, &tls13_ServerHandleEchIsInnerXtn }, { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn }, { 0, NULL } }; @@ -1020,12 +1022,8 @@ ssl3_DestroyExtensionData(TLSExtensionData *xtnData) PORT_Free(xtnData->advertised); tls13_DestroyDelegatedCredential(xtnData->peerDelegCred); - /* ECH State */ - SECITEM_FreeItem(&xtnData->innerCh, PR_FALSE); - SECITEM_FreeItem(&xtnData->echSenderPubKey, PR_FALSE); - SECITEM_FreeItem(&xtnData->echConfigId, PR_FALSE); - SECITEM_FreeItem(&xtnData->echRetryConfigs, PR_FALSE); - xtnData->echRetryConfigsValid = PR_FALSE; + tls13_DestroyEchXtnState(xtnData->ech); + xtnData->ech = NULL; } /* Free everything that has been allocated and then reset back to diff --git a/security/nss/lib/ssl/ssl3ext.h b/security/nss/lib/ssl/ssl3ext.h index 45510041e913..685a7a99c668 100644 --- a/security/nss/lib/ssl/ssl3ext.h +++ b/security/nss/lib/ssl/ssl3ext.h @@ -131,13 +131,9 @@ struct TLSExtensionDataStr { * rather through tls13_DestoryPskList(). */ sslPsk *selectedPsk; - /* ECH working state. */ - SECItem innerCh; /* Server: "payload value of ClientECH. */ - SECItem echSenderPubKey; /* Server: "enc value of ClientECH, required for CHInner decryption. */ - SECItem echConfigId; /* Server: "config_id" value of ClientECH. */ - PRUint32 echCipherSuite; /* Server: "cipher_suite" value of ClientECH. */ - SECItem echRetryConfigs; /* Client: Retry_configs from ServerEncryptedCH. */ - PRBool echRetryConfigsValid; /* Client: Permits retry_configs to be extracted. */ + /* ECH working state. Non-null when a valid Encrypted Client Hello extension + * was received. */ + sslEchXtnState *ech; }; typedef struct TLSExtensionStr { diff --git a/security/nss/lib/ssl/sslexp.h b/security/nss/lib/ssl/sslexp.h index a02f0f35160b..8bacc6b4217d 100644 --- a/security/nss/lib/ssl/sslexp.h +++ b/security/nss/lib/ssl/sslexp.h @@ -509,6 +509,14 @@ typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)( SSL_EXPERIMENTAL_API("SSL_EnableTls13GreaseEch", \ (PRFileDesc * _fd, PRBool _enabled), (fd, enabled)) +/* If |enabled|, a server receiving a Client Hello containing the ech_is_inner + * (and not encrypted_client_hello) extension will respond with the ECH + * acceptance signal. This signals the client to continue with the inner + * transcript rather than outer. */ +#define SSL_EnableTls13BackendEch(fd, enabled) \ + SSL_EXPERIMENTAL_API("SSL_EnableTls13BackendEch", \ + (PRFileDesc * _fd, PRBool _enabled), (fd, enabled)) + /* Called by the client after an initial ECH connection fails with * SSL_ERROR_ECH_RETRY_WITH_ECH. Returns compatible ECHConfigs, which * are configured via SetClientEchConfigs for an ECH retry attempt. diff --git a/security/nss/lib/ssl/sslimpl.h b/security/nss/lib/ssl/sslimpl.h index a126cb8c3bfc..1b7dfb107fb8 100644 --- a/security/nss/lib/ssl/sslimpl.h +++ b/security/nss/lib/ssl/sslimpl.h @@ -36,9 +36,9 @@ typedef struct sslSocketStr sslSocket; typedef struct sslNamedGroupDefStr sslNamedGroupDef; -typedef struct sslEsniKeysStr sslEsniKeys; typedef struct sslEchConfigStr sslEchConfig; typedef struct sslEchConfigContentsStr sslEchConfigContents; +typedef struct sslEchXtnStateStr sslEchXtnState; typedef struct sslPskStr sslPsk; typedef struct sslDelegatedCredentialStr sslDelegatedCredential; typedef struct sslEphemeralKeyPairStr sslEphemeralKeyPair; @@ -287,6 +287,7 @@ typedef struct sslOptionsStr { unsigned int enableDtls13VersionCompat : 1; unsigned int suppressEndOfEarlyData : 1; unsigned int enableTls13GreaseEch : 1; + unsigned int enableTls13BackendEch : 1; } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -748,6 +749,7 @@ typedef struct SSL3HandshakeStateStr { HpkeContext *echHpkeCtx; /* Client/Server: HPKE context for ECH. */ const char *echPublicName; /* Client: If rejected, the ECHConfig.publicName to * use for certificate verification. */ + sslBuffer greaseEchBuf; /* Client: Remember GREASE ECH, as advertised, for CH2 (HRR case). */ } SSL3HandshakeState; @@ -1122,8 +1124,9 @@ struct sslSocketStr { SSLProtocolVariant protocolVariant; /* TLS 1.3 Encrypted Client Hello. */ - PRCList echConfigs; /* Client/server: Must not change while hs is in-progress. */ - SECKEYPublicKey *echPubKey; /* Server: The ECH keypair used in HPKE setup */ + PRCList echConfigs; /* Client/server: Must not change while hs + * is in-progress. */ + SECKEYPublicKey *echPubKey; /* Server: The ECH keypair used in HPKE. */ SECKEYPrivateKey *echPrivKey; /* As above. */ /* Anti-replay for TLS 1.3 0-RTT. */ @@ -1948,6 +1951,8 @@ SECStatus SSLExp_DestroyMaskingContext(SSLMaskingContext *ctx); SECStatus SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled); +SECStatus SSLExp_EnableTls13BackendEch(PRFileDesc *fd, PRBool enabled); + SEC_END_PROTOS #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS) diff --git a/security/nss/lib/ssl/sslsecur.c b/security/nss/lib/ssl/sslsecur.c index 162fc66d0c2f..2c9a4dbf94f0 100644 --- a/security/nss/lib/ssl/sslsecur.c +++ b/security/nss/lib/ssl/sslsecur.c @@ -183,6 +183,7 @@ SSL_ResetHandshake(PRFileDesc *s, PRBool asServer) PORT_Assert(ss->ssl3.hs.echPublicName); PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ ss->ssl3.hs.echPublicName = NULL; + sslBuffer_Clear(&ss->ssl3.hs.greaseEchBuf); } if (!ss->TCPconnected) diff --git a/security/nss/lib/ssl/sslsock.c b/security/nss/lib/ssl/sslsock.c index e075e23c8175..b698d8f43985 100644 --- a/security/nss/lib/ssl/sslsock.c +++ b/security/nss/lib/ssl/sslsock.c @@ -93,7 +93,8 @@ static sslOptions ssl_defaults = { .enableV2CompatibleHello = PR_FALSE, .enablePostHandshakeAuth = PR_FALSE, .suppressEndOfEarlyData = PR_FALSE, - .enableTls13GreaseEch = PR_FALSE + .enableTls13GreaseEch = PR_FALSE, + .enableTls13BackendEch = PR_FALSE }; /* @@ -4293,6 +4294,7 @@ struct { EXP(DestroyAead), EXP(DestroyMaskingContext), EXP(DestroyResumptionTokenInfo), + EXP(EnableTls13BackendEch), EXP(EnableTls13GreaseEch), EXP(EncodeEchConfig), EXP(GetCurrentEpoch), @@ -4371,6 +4373,17 @@ SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled) return SECSuccess; } +SECStatus +SSLExp_EnableTls13BackendEch(PRFileDesc *fd, PRBool enabled) +{ + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + ss->opt.enableTls13BackendEch = enabled; + return SECSuccess; +} + SECStatus SSLExp_SetDtls13VersionWorkaround(PRFileDesc *fd, PRBool enabled) { diff --git a/security/nss/lib/ssl/sslt.h b/security/nss/lib/ssl/sslt.h index f1713069bd07..1d5c4d17907d 100644 --- a/security/nss/lib/ssl/sslt.h +++ b/security/nss/lib/ssl/sslt.h @@ -545,8 +545,9 @@ typedef enum { ssl_next_proto_nego_xtn = 13172, /* Deprecated. */ ssl_renegotiation_info_xtn = 0xff01, ssl_tls13_short_header_xtn = 0xff03, /* Deprecated. */ + ssl_tls13_ech_is_inner_xtn = 0xda09, ssl_tls13_outer_extensions_xtn = 0xfd00, - ssl_tls13_encrypted_client_hello_xtn = 0xfe08, + ssl_tls13_encrypted_client_hello_xtn = 0xfe09, ssl_tls13_encrypted_sni_xtn = 0xffce, /* Deprecated. */ } SSLExtensionType; diff --git a/security/nss/lib/ssl/tls13con.c b/security/nss/lib/ssl/tls13con.c index a10962981ca5..1347f3fe2af8 100644 --- a/security/nss/lib/ssl/tls13con.c +++ b/security/nss/lib/ssl/tls13con.c @@ -61,7 +61,7 @@ tls13_DeriveSecretWrap(sslSocket *ss, PK11SymKey *key, const char *suffix, const char *keylogLabel, PK11SymKey **dest); -static SECStatus +SECStatus tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key, const char *label, unsigned int labelLen, @@ -1184,13 +1184,12 @@ tls13_DeriveEarlySecrets(sslSocket *ss) } static SECStatus -tls13_ComputeHandshakeSecrets(sslSocket *ss) +tls13_ComputeHandshakeSecret(sslSocket *ss) { SECStatus rv; PK11SymKey *derivedSecret = NULL; PK11SymKey *newSecret = NULL; - - SSL_TRC(5, ("%d: TLS13[%d]: compute handshake secrets (%s)", + SSL_TRC(5, ("%d: TLS13[%d]: compute handshake secret (%s)", SSL_GETPID(), ss->fd, SSL_ROLE(ss))); /* If no PSK, generate the default early secret. */ @@ -1205,7 +1204,7 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss) PORT_Assert(ss->ssl3.hs.currentSecret); PORT_Assert(ss->ssl3.hs.dheSecret); - /* Expand before we extract. */ + /* Derive-Secret(., "derived", "") */ rv = tls13_DeriveSecretNullHash(ss, ss->ssl3.hs.currentSecret, kHkdfLabelDerivedSecret, strlen(kHkdfLabelDerivedSecret), @@ -1215,18 +1214,32 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss) return rv; } + /* HKDF-Extract(ECDHE, .) = Handshake Secret */ rv = tls13_HkdfExtract(derivedSecret, ss->ssl3.hs.dheSecret, tls13_GetHash(ss), &newSecret); PK11_FreeSymKey(derivedSecret); - if (rv != SECSuccess) { LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); return rv; } - PK11_FreeSymKey(ss->ssl3.hs.dheSecret); - ss->ssl3.hs.dheSecret = NULL; + PK11_FreeSymKey(ss->ssl3.hs.currentSecret); ss->ssl3.hs.currentSecret = newSecret; + return SECSuccess; +} + +static SECStatus +tls13_ComputeHandshakeSecrets(sslSocket *ss) +{ + SECStatus rv; + PK11SymKey *derivedSecret = NULL; + PK11SymKey *newSecret = NULL; + + PK11_FreeSymKey(ss->ssl3.hs.dheSecret); + ss->ssl3.hs.dheSecret = NULL; + + SSL_TRC(5, ("%d: TLS13[%d]: compute handshake secrets (%s)", + SSL_GETPID(), ss->fd, SSL_ROLE(ss))); /* Now compute |*HsTrafficSecret| */ rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret, @@ -1865,22 +1878,16 @@ tls13_HandleClientHelloPart2(sslSocket *ss, PRINT_BUF(50, (ss, "Client sent cookie", ss->xtnData.cookie.data, ss->xtnData.cookie.len)); - rv = tls13_RecoverHashState(ss, ss->xtnData.cookie.data, - ss->xtnData.cookie.len, - &previousCipherSuite, - &previousGroup, - &previousEchOffered); + rv = tls13_HandleHrrCookie(ss, ss->xtnData.cookie.data, + ss->xtnData.cookie.len, + &previousCipherSuite, + &previousGroup, + &previousEchOffered, + NULL, NULL, NULL, NULL, PR_TRUE); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, illegal_parameter); goto loser; } - - /* CH1/CH2 must either both include ECH, or both exclude it. */ - if ((ss->xtnData.echConfigId.len > 0) != previousEchOffered) { - FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, - illegal_parameter); - goto loser; - } } /* Now merge the ClientHello into the hash state. */ @@ -1936,6 +1943,13 @@ tls13_HandleClientHelloPart2(sslSocket *ss, goto loser; } + /* CH1/CH2 must either both include ECH, or both exclude it. */ + if (previousEchOffered != (ss->xtnData.ech != NULL)) { + FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, + previousEchOffered ? missing_extension : illegal_parameter); + goto loser; + } + /* If we requested a new key share, check that the client provided just * one of the right type. */ if (previousGroup) { @@ -2825,6 +2839,11 @@ tls13_SendServerHelloSequence(sslSocket *ss) return SECFailure; } + rv = tls13_ComputeHandshakeSecret(ss); + if (rv != SECSuccess) { + return SECFailure; /* error code is set. */ + } + rv = ssl3_SendServerHello(ss); if (rv != SECSuccess) { return rv; /* err code is set. */ @@ -2909,7 +2928,7 @@ tls13_SendServerHelloSequence(sslSocket *ss) } SECStatus -tls13_HandleServerHelloPart2(sslSocket *ss) +tls13_HandleServerHelloPart2(sslSocket *ss, const PRUint8 *savedMsg, PRUint32 savedLength) { SECStatus rv; sslSessionID *sid = ss->sec.ci.sid; @@ -2991,6 +3010,17 @@ tls13_HandleServerHelloPart2(sslSocket *ss) if (rv != SECSuccess) { return SECFailure; } + + rv = tls13_ComputeHandshakeSecret(ss); + if (rv != SECSuccess) { + return SECFailure; /* error code is set. */ + } + + rv = tls13_MaybeHandleEchSignal(ss, savedMsg, savedLength); + if (rv != SECSuccess) { + return SECFailure; /* error code is set. */ + } + rv = tls13_ComputeHandshakeSecrets(ss); if (rv != SECSuccess) { return SECFailure; /* error code is set. */ @@ -4960,15 +4990,16 @@ tls13_FinishHandshake(sslSocket *ss) TLS13_SET_HS_STATE(ss, idle_handshake); - if (offeredEch && - !ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_client_hello_xtn)) { + PORT_Assert(ss->ssl3.hs.echAccepted == + ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_client_hello_xtn)); + if (offeredEch && !ss->ssl3.hs.echAccepted) { SSL3_SendAlert(ss, alert_fatal, ech_required); - /* "If [one, none] of the values contains a supported version, the client can + /* "If [one, none] of the retry_configs contains a supported version, the client can * regard ECH as securely [replaced, disabled] by the server." */ - if (ss->xtnData.echRetryConfigs.len) { + if (ss->xtnData.ech && ss->xtnData.ech->retryConfigs.len) { PORT_SetError(SSL_ERROR_ECH_RETRY_WITH_ECH); - ss->xtnData.echRetryConfigsValid = PR_TRUE; + ss->xtnData.ech->retryConfigsValid = PR_TRUE; } else { PORT_SetError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); } @@ -5520,6 +5551,7 @@ static const struct { hello_retry_request) }, { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }, { ssl_tls13_encrypted_client_hello_xtn, _M2(client_hello, encrypted_extensions) }, + { ssl_tls13_ech_is_inner_xtn, _M1(client_hello) }, { ssl_tls13_outer_extensions_xtn, _M_NONE /* Encoding/decoding only */ }, { ssl_tls13_post_handshake_auth_xtn, _M1(client_hello) } }; diff --git a/security/nss/lib/ssl/tls13con.h b/security/nss/lib/ssl/tls13con.h index ae0b4ae332a1..8c648ac307b8 100644 --- a/security/nss/lib/ssl/tls13con.h +++ b/security/nss/lib/ssl/tls13con.h @@ -76,7 +76,7 @@ SECStatus tls13_HandleClientHelloPart2(sslSocket *ss, sslSessionID *sid, const PRUint8 *msg, unsigned int len); -SECStatus tls13_HandleServerHelloPart2(sslSocket *ss); +SECStatus tls13_HandleServerHelloPart2(sslSocket *ss, const PRUint8 *savedMsg, PRUint32 savedLength); SECStatus tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length); SECStatus tls13_ConstructHelloRetryRequest(sslSocket *ss, diff --git a/security/nss/lib/ssl/tls13ech.c b/security/nss/lib/ssl/tls13ech.c index a42bda01a3f1..7b6c2f0a4a54 100644 --- a/security/nss/lib/ssl/tls13ech.c +++ b/security/nss/lib/ssl/tls13ech.c @@ -14,14 +14,22 @@ #include "ssl3exthandle.h" #include "tls13ech.h" #include "tls13exthandle.h" +#include "tls13hashstate.h" #include "tls13hkdf.h" extern SECStatus -ssl3_UpdateExplicitHandshakeTranscript(sslSocket *ss, const unsigned char *b, - unsigned int l, sslBuffer *transcriptBuf); +ssl3_UpdateHandshakeHashesInt(sslSocket *ss, const unsigned char *b, + unsigned int l, sslBuffer *transcriptBuf); extern SECStatus ssl3_HandleClientHelloPreamble(sslSocket *ss, PRUint8 **b, PRUint32 *length, SECItem *sidBytes, SECItem *cookieBytes, SECItem *suites, SECItem *comps); +extern SECStatus +tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key, + const char *label, + unsigned int labelLen, + const SSL3Hashes *hashes, + PK11SymKey **dest, + SSLHashType hash); void tls13_DestroyEchConfig(sslEchConfig *config) @@ -48,6 +56,19 @@ tls13_DestroyEchConfigs(PRCList *list) } } +void +tls13_DestroyEchXtnState(sslEchXtnState *state) +{ + if (!state) { + return; + } + SECITEM_FreeItem(&state->innerCh, PR_FALSE); + SECITEM_FreeItem(&state->senderPubKey, PR_FALSE); + SECITEM_FreeItem(&state->configId, PR_FALSE); + SECITEM_FreeItem(&state->retryConfigs, PR_FALSE); + PORT_ZFree(state, sizeof(*state)); +} + SECStatus tls13_CopyEchConfigs(PRCList *oConfigs, PRCList *configs) { @@ -86,6 +107,7 @@ tls13_CopyEchConfigs(PRCList *oConfigs, PRCList *configs) newConfig->contents.kdfId = config->contents.kdfId; newConfig->contents.aeadId = config->contents.aeadId; newConfig->contents.maxNameLen = config->contents.maxNameLen; + newConfig->version = config->version; PORT_Memcpy(newConfig->configId, config->configId, sizeof(newConfig->configId)); PR_APPEND_LINK(&newConfig->link, configs); } @@ -127,7 +149,7 @@ tls13_DigestEchConfig(const sslEchConfig *cfg, PRUint8 *digest, size_t maxDigest params.pInfo = CONST_CAST(CK_BYTE, hHkdfInfoEchConfigID); params.ulInfoLen = strlen(hHkdfInfoEchConfigID); derived = PK11_DeriveWithFlags(configKey, CKM_HKDF_DATA, - ¶msi, CKM_HKDF_DERIVE, CKA_DERIVE, 32, + ¶msi, CKM_HKDF_DERIVE, CKA_DERIVE, 8, CKF_SIGN | CKF_VERIFY); rv = PK11_ExtractKeyValue(derived); @@ -182,8 +204,11 @@ tls13_DecodeEchConfigContents(const sslReadBuffer *rawConfig, if (rv != SECSuccess) { goto loser; } - /* Make sure the public name doesn't contain any NULLs. - * TODO: Just store the SECItem instead. */ + + if (tmpBuf.len == 0) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + goto loser; + } for (tmpn = 0; tmpn < tmpBuf.len; tmpn++) { if (tmpBuf.buf[tmpn] == '\0') { PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); @@ -193,7 +218,6 @@ tls13_DecodeEchConfigContents(const sslReadBuffer *rawConfig, contents.publicName = PORT_ZAlloc(tmpBuf.len + 1); if (!contents.publicName) { - PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); goto loser; } PORT_Memcpy(contents.publicName, (PRUint8 *)tmpBuf.buf, tmpBuf.len); @@ -430,8 +454,8 @@ SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites, PRUint8 tmpBuf[66]; // Large enough for an EC public key, currently only X25519. unsigned int tmpLen; - if (!publicName || PORT_Strlen(publicName) == 0 || !hpkeSuites || - hpkeSuiteCount == 0 || !pubKey || maxNameLen == 0 || !out || !outlen) { + if (!publicName || !hpkeSuites || hpkeSuiteCount == 0 || + !pubKey || maxNameLen == 0 || !out || !outlen) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } @@ -535,12 +559,18 @@ SSLExp_GetEchRetryConfigs(PRFileDesc *fd, SECItem *retryConfigs) PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } - if (!ss->xtnData.echRetryConfigsValid) { + + /* We don't distinguish between "handshake completed + * without retry configs", and "handshake not completed". + * An application should only call this after receiving a + * RETRY_WITH_ECH error code, which implies retry_configs. */ + if (!ss->xtnData.ech || !ss->xtnData.ech->retryConfigsValid) { PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED); return SECFailure; } + /* May be empty. */ - rv = SECITEM_CopyItem(NULL, &out, &ss->xtnData.echRetryConfigs); + rv = SECITEM_CopyItem(NULL, &out, &ss->xtnData.ech->retryConfigs); if (rv == SECFailure) { return SECFailure; } @@ -571,8 +601,8 @@ SSLExp_RemoveEchConfigs(PRFileDesc *fd) } /* Also remove any retry_configs and handshake context. */ - if (ss->xtnData.echRetryConfigs.len) { - SECITEM_FreeItem(&ss->xtnData.echRetryConfigs, PR_FALSE); + if (ss->xtnData.ech && ss->xtnData.ech->retryConfigs.len) { + SECITEM_FreeItem(&ss->xtnData.ech->retryConfigs, PR_FALSE); } if (ss->ssl3.hs.echHpkeCtx) { @@ -704,14 +734,7 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) HpkeContext *cx = NULL; SECKEYPublicKey *pkR = NULL; SECItem hpkeInfo = { siBuffer, NULL, 0 }; - PK11SymKey *hrrPsk = NULL; sslEchConfig *cfg = NULL; - const SECItem kEchHrrInfoItem = { siBuffer, - (unsigned char *)kHpkeInfoEchHrr, - strlen(kHpkeInfoEchHrr) }; - const SECItem kEchHrrPskLabelItem = { siBuffer, - (unsigned char *)kHpkeLabelHrrPsk, - strlen(kHpkeLabelHrrPsk) }; if (PR_CLIST_IS_EMPTY(&ss->echConfigs) || !ssl_ShouldSendSNIExtension(ss, ss->url) || @@ -739,20 +762,12 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) cfg->contents.aeadId, NULL, NULL); break; case client_hello_retry: - PORT_Assert(ss->ssl3.hs.echHpkeCtx && ss->ssl3.hs.echPublicName); - rv = PK11_HPKE_ExportSecret(ss->ssl3.hs.echHpkeCtx, - &kEchHrrInfoItem, 32, &hrrPsk); - if (rv != SECSuccess) { - goto loser; + if (!ss->ssl3.hs.echHpkeCtx || !ss->ssl3.hs.echPublicName) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + return SECFailure; } - - PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); - PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ - ss->ssl3.hs.echHpkeCtx = NULL; - ss->ssl3.hs.echPublicName = NULL; - cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId, - cfg->contents.aeadId, hrrPsk, &kEchHrrPskLabelItem); - break; + /* Nothing else to do. */ + return SECSuccess; default: PORT_Assert(0); goto loser; @@ -779,11 +794,9 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) goto loser; } - if (!ss->ssl3.hs.helloRetry) { - rv = ssl3_GetNewRandom(ss->ssl3.hs.client_inner_random); - if (rv != SECSuccess) { - goto loser; /* code set */ - } + rv = ssl3_GetNewRandom(ss->ssl3.hs.client_inner_random); + if (rv != SECSuccess) { + goto loser; /* code set */ } /* If ECH is rejected, the application will use SSLChannelInfo @@ -794,22 +807,21 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) } ss->ssl3.hs.echHpkeCtx = cx; - PK11_FreeSymKey(hrrPsk); SECKEY_DestroyPublicKey(pkR); SECITEM_FreeItem(&hpkeInfo, PR_FALSE); return SECSuccess; loser: PK11_HPKE_DestroyContext(cx, PR_TRUE); - PK11_FreeSymKey(hrrPsk); SECKEY_DestroyPublicKey(pkR); SECITEM_FreeItem(&hpkeInfo, PR_FALSE); + PORT_Assert(PORT_GetError() != 0); return SECFailure; } /* * enum { - * encrypted_client_hello(0xfe08), (65535) + * encrypted_client_hello(0xfe09), (65535) * } ExtensionType; * * struct { @@ -871,13 +883,22 @@ tls13_EncryptClientHello(sslSocket *ss, sslBuffer *outerAAD, sslBuffer *chInner) if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_AppendVariable(chInner, cfg->configId, sizeof(cfg->configId), 1); - if (rv != SECSuccess) { - goto loser; - } - rv = sslBuffer_AppendVariable(chInner, hpkeEnc->data, hpkeEnc->len, 2); - if (rv != SECSuccess) { - goto loser; + + if (!ss->ssl3.hs.helloRetry) { + rv = sslBuffer_AppendVariable(chInner, cfg->configId, sizeof(cfg->configId), 1); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(chInner, hpkeEnc->data, hpkeEnc->len, 2); + if (rv != SECSuccess) { + goto loser; + } + } else { + /* one byte for empty configId, two for empty Enc. */ + rv = sslBuffer_AppendNumber(chInner, 0, 3); + if (rv != SECSuccess) { + goto loser; + } } rv = sslBuffer_AppendVariable(chInner, chCt->data, chCt->len, 2); if (rv != SECSuccess) { @@ -892,14 +913,14 @@ loser: } SECStatus -tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, - const SECItem *configId, sslEchConfig **cfg) +tls13_GetMatchingEchConfigs(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, + const SECItem *configId, const sslEchConfig *cur, sslEchConfig **next) { - sslEchConfig *candidate; PRINT_BUF(50, (ss, "Server GetMatchingEchConfig with digest:", configId->data, configId->len)); - for (PRCList *cur_p = PR_LIST_HEAD(&ss->echConfigs); + /* If |cur|, resume the search at that node, else the list head. */ + for (PRCList *cur_p = cur ? ((PRCList *)cur)->next : PR_LIST_HEAD(&ss->echConfigs); cur_p != &ss->echConfigs; cur_p = PR_NEXT_LINK(cur_p)) { sslEchConfig *echConfig = (sslEchConfig *)cur_p; @@ -907,139 +928,17 @@ tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, PORT_Memcmp(echConfig->configId, configId->data, sizeof(echConfig->configId))) { continue; } - candidate = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); - if (candidate->contents.aeadId != aead || - candidate->contents.kdfId != kdf) { - continue; + if (echConfig->contents.aeadId == aead && + echConfig->contents.kdfId == kdf) { + *next = echConfig; + return SECSuccess; } - *cfg = candidate; - return SECSuccess; } - SSL_TRC(50, ("%d: TLS13[%d]: Server found no matching ECHConfig", - SSL_GETPID(), ss->fd)); - - *cfg = NULL; + *next = NULL; return SECSuccess; } -/* This is unfortunate in that it requires a second decryption of the cookie. - * This is largely copied from tls13hashstate.c as HRR handling is still in flux. - * TODO: Consolidate this code no later than -09. */ -/* struct { - * uint8 indicator = 0xff; // To disambiguate from tickets. - * uint16 cipherSuite; // Selected cipher suite. - * uint16 keyShare; // Requested key share group (0=none) - * opaque applicationToken<0..65535>; // Application token - * opaque echHrrPsk<0..255>; // Encrypted ClientHello HRR PSK - * opaque echConfigId<0..255>; // ECH config ID selected in CH1, to decrypt the CH2 ECH payload. - * opaque ch_hash[rest_of_buffer]; // H(ClientHello) - * } CookieInner; - */ -SECStatus -tls13_GetEchInfoFromCookie(sslSocket *ss, const TLSExtension *hrrCookie, PK11SymKey **echHrrPsk, SECItem *echConfigId) -{ - SECStatus rv; - PK11SymKey *hrrKey = NULL; - PRUint64 tmpn; - sslReadBuffer tmpReader = { 0 }; - PK11SlotInfo *slot = NULL; - unsigned char plaintext[1024]; - unsigned int plaintextLen = 0; - SECItem hrrPskItem = { siBuffer, NULL, 0 }; - SECItem hrrCookieData = { siBuffer, NULL, 0 }; - SECItem saveHrrCookieData = hrrCookieData; - SECItem previousEchConfigId = { siBuffer, NULL, 0 }; - - /* Copy the extension data so as to not consume it in the handler. - * The extension handler walks the pointer, so save a copy to free. */ - rv = SECITEM_CopyItem(NULL, &hrrCookieData, &hrrCookie->data); - if (rv != SECSuccess) { - goto loser; - } - saveHrrCookieData = hrrCookieData; - - rv = tls13_ServerHandleCookieXtn(ss, &ss->xtnData, &hrrCookieData); - if (rv != SECSuccess) { - goto loser; - } - - rv = ssl_SelfEncryptUnprotect(ss, ss->xtnData.cookie.data, ss->xtnData.cookie.len, - plaintext, &plaintextLen, sizeof(plaintext)); - if (rv != SECSuccess) { - goto loser; - } - - sslReader reader = SSL_READER(plaintext, plaintextLen); - - /* Should start with 0xff. */ - rv = sslRead_ReadNumber(&reader, 1, &tmpn); - if ((rv != SECSuccess) || (tmpn != 0xff)) { - rv = SECFailure; - goto loser; - } - rv = sslRead_ReadNumber(&reader, 2, &tmpn); - if (rv != SECSuccess) { - goto loser; - } - /* The named group, if any. */ - rv = sslRead_ReadNumber(&reader, 2, &tmpn); - if (rv != SECSuccess) { - goto loser; - } - /* Application token. */ - rv = sslRead_ReadNumber(&reader, 2, &tmpn); - if (rv != SECSuccess) { - goto loser; - } - rv = sslRead_Read(&reader, tmpn, &tmpReader); - if (rv != SECSuccess) { - goto loser; - } - - /* ECH Config ID */ - rv = sslRead_ReadVariable(&reader, 1, &tmpReader); - if (rv != SECSuccess) { - goto loser; - } - rv = SECITEM_MakeItem(NULL, &previousEchConfigId, - tmpReader.buf, tmpReader.len); - if (rv != SECSuccess) { - goto loser; - } - - /* ECH HRR key. */ - rv = sslRead_ReadVariable(&reader, 1, &tmpReader); - if (rv != SECSuccess) { - goto loser; - } - if (tmpReader.len) { - slot = PK11_GetInternalSlot(); - if (!slot) { - rv = SECFailure; - goto loser; - } - hrrPskItem.len = tmpReader.len; - hrrPskItem.data = CONST_CAST(PRUint8, tmpReader.buf); - hrrKey = PK11_ImportSymKey(slot, CKM_HKDF_KEY_GEN, PK11_OriginUnwrap, - CKA_DERIVE, &hrrPskItem, NULL); - PK11_FreeSlot(slot); - if (!hrrKey) { - rv = SECFailure; - goto loser; - } - } - *echConfigId = previousEchConfigId; - *echHrrPsk = hrrKey; - SECITEM_FreeItem(&saveHrrCookieData, PR_FALSE); - return SECSuccess; - -loser: - SECITEM_FreeItem(&previousEchConfigId, PR_FALSE); - SECITEM_FreeItem(&saveHrrCookieData, PR_FALSE); - return SECFailure; -} - /* Given a CH with extensions, copy from the start up to the extensions * into |writer| and return the extensions themselves in |extensions|. * If |explicitSid|, place this value into |writer| as the SID. Else, @@ -1110,19 +1009,55 @@ tls13_CopyChPreamble(sslReader *reader, const SECItem *explicitSid, sslBuffer *w return SECSuccess; } +/* + * struct { + * HpkeKdfId kdfId; // ClientECH.cipher_suite.kdf + * HpkeAeadId aeadId; // ClientECH.cipher_suite.aead + * opaque config_id<0..255>; // ClientECH.config_id + * opaque enc<1..2^16-1>; // ClientECH.enc + * opaque outer_hello<1..2^24-1>; + * } ClientHelloOuterAAD; + */ static SECStatus -tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD) +tls13_MakeChOuterAAD(sslSocket *ss, const SECItem *outer, SECItem *outerAAD) { SECStatus rv; sslBuffer aad = SSL_BUFFER_EMPTY; - sslReadBuffer aadXtns; + sslReadBuffer aadXtns = { 0 }; sslReader chReader = SSL_READER(outer->data, outer->len); PRUint64 tmpn; - sslReadBuffer tmpvar; + sslReadBuffer tmpvar = { 0 }; unsigned int offset; - unsigned int preambleLen; + unsigned int savedOffset; + PORT_Assert(ss->xtnData.ech); - rv = sslBuffer_Skip(&aad, 4, NULL); + rv = sslBuffer_AppendNumber(&aad, ss->xtnData.ech->kdfId, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(&aad, ss->xtnData.ech->aeadId, 2); + if (rv != SECSuccess) { + goto loser; + } + + if (!ss->ssl3.hs.helloRetry) { + rv = sslBuffer_AppendVariable(&aad, ss->xtnData.ech->configId.data, + ss->xtnData.ech->configId.len, 1); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(&aad, ss->xtnData.ech->senderPubKey.data, + ss->xtnData.ech->senderPubKey.len, 2); + } else { + /* 1B config_id length, 2B enc length. */ + rv = sslBuffer_AppendNumber(&aad, 0, 3); + } + if (rv != SECSuccess) { + goto loser; + } + + /* Skip 3 bytes for the CHOuter length. */ + rv = sslBuffer_Skip(&aad, 3, &savedOffset); if (rv != SECSuccess) { goto loser; } @@ -1132,9 +1067,7 @@ tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD) if (rv != SECSuccess) { goto loser; } - sslReader xtnsReader = SSL_READER(aadXtns.buf, aadXtns.len); - preambleLen = SSL_BUFFER_LEN(&aad); /* Save room for extensions length. */ rv = sslBuffer_Skip(&aad, 2, &offset); @@ -1165,22 +1098,18 @@ tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD) } } - rv = sslBuffer_InsertNumber(&aad, offset, SSL_BUFFER_LEN(&aad) - preambleLen - 2, 2); + rv = sslBuffer_InsertLength(&aad, offset, 2); if (rv != SECSuccess) { goto loser; } - /* Give it a message header. */ - rv = sslBuffer_InsertNumber(&aad, 0, ssl_hs_client_hello, 1); + rv = sslBuffer_InsertLength(&aad, savedOffset, 3); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_InsertLength(&aad, 1, 3); - if (rv != SECSuccess) { - goto loser; - } - *outerAAD = aad; + outerAAD->data = aad.buf; + outerAAD->len = aad.len; return SECSuccess; loser: @@ -1189,55 +1118,47 @@ loser: } SECStatus -tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, sslEchConfig *cfg, PK11SymKey *echHrrPsk, SECItem **chInner) +tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, const SECItem *outerAAD, sslEchConfig *cfg, SECItem **chInner) { SECStatus rv; - sslBuffer outerAAD = SSL_BUFFER_EMPTY; HpkeContext *cx = NULL; SECItem *decryptedChInner = NULL; SECItem hpkeInfo = { siBuffer, NULL, 0 }; - SECItem outerAADItem = { siBuffer, NULL, 0 }; - const SECItem kEchHrrPskLabelItem = { siBuffer, - (unsigned char *)kHpkeLabelHrrPsk, - strlen(kHpkeLabelHrrPsk) }; SSL_TRC(50, ("%d: TLS13[%d]: Server opening ECH Inner%s", SSL_GETPID(), ss->fd, ss->ssl3.hs.helloRetry ? " after HRR" : "")); - cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId, - cfg->contents.aeadId, echHrrPsk, - echHrrPsk ? &kEchHrrPskLabelItem : NULL); - if (!cx) { - goto loser; - } + if (!ss->ssl3.hs.helloRetry) { + PORT_Assert(!ss->ssl3.hs.echHpkeCtx); + cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId, + cfg->contents.aeadId, NULL, NULL); + if (!cx) { + goto loser; + } - if (!SECITEM_AllocItem(NULL, &hpkeInfo, strlen(kHpkeInfoEch) + 1 + cfg->raw.len)) { - goto loser; - } - PORT_Memcpy(&hpkeInfo.data[0], kHpkeInfoEch, strlen(kHpkeInfoEch)); - PORT_Memset(&hpkeInfo.data[strlen(kHpkeInfoEch)], 0, 1); - PORT_Memcpy(&hpkeInfo.data[strlen(kHpkeInfoEch) + 1], cfg->raw.data, cfg->raw.len); + if (!SECITEM_AllocItem(NULL, &hpkeInfo, strlen(kHpkeInfoEch) + 1 + cfg->raw.len)) { + goto loser; + } + PORT_Memcpy(&hpkeInfo.data[0], kHpkeInfoEch, strlen(kHpkeInfoEch)); + PORT_Memset(&hpkeInfo.data[strlen(kHpkeInfoEch)], 0, 1); + PORT_Memcpy(&hpkeInfo.data[strlen(kHpkeInfoEch) + 1], cfg->raw.data, cfg->raw.len); - rv = PK11_HPKE_SetupR(cx, ss->echPubKey, ss->echPrivKey, - &ss->xtnData.echSenderPubKey, &hpkeInfo); - if (rv != SECSuccess) { - goto loser; /* code set */ + rv = PK11_HPKE_SetupR(cx, ss->echPubKey, ss->echPrivKey, + &ss->xtnData.ech->senderPubKey, &hpkeInfo); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + } else { + PORT_Assert(ss->ssl3.hs.echHpkeCtx); + cx = ss->ssl3.hs.echHpkeCtx; } - rv = tls13_MakeChOuterAAD(outer, &outerAAD); - if (rv != SECSuccess) { - goto loser; /* code set */ - } - - outerAADItem.data = outerAAD.buf; - outerAADItem.len = outerAAD.len; - #ifndef UNSAFE_FUZZER_MODE - rv = PK11_HPKE_Open(cx, &outerAADItem, &ss->xtnData.innerCh, &decryptedChInner); + rv = PK11_HPKE_Open(cx, outerAAD, &ss->xtnData.ech->innerCh, &decryptedChInner); if (rv != SECSuccess) { goto loser; /* code set */ } #else - rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.innerCh); + rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.ech->innerCh); if (rv != SECSuccess) { goto loser; } @@ -1248,14 +1169,15 @@ tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, sslEchConfig *cf ss->ssl3.hs.echHpkeCtx = cx; *chInner = decryptedChInner; SECITEM_FreeItem(&hpkeInfo, PR_FALSE); - sslBuffer_Clear(&outerAAD); return SECSuccess; loser: SECITEM_FreeItem(decryptedChInner, PR_TRUE); - PK11_HPKE_DestroyContext(cx, PR_TRUE); SECITEM_FreeItem(&hpkeInfo, PR_FALSE); - sslBuffer_Clear(&outerAAD); + if (cx != ss->ssl3.hs.echHpkeCtx) { + /* Don't double-free if it's already global. */ + PK11_HPKE_DestroyContext(cx, PR_TRUE); + } return SECFailure; } @@ -1289,7 +1211,7 @@ tls13_ConstructInnerExtensionsFromOuter(sslSocket *ss, sslBuffer *chOuterXtnsBuf /* When offering the "encrypted_client_hello" extension in its * ClientHelloOuter, the client MUST also offer an empty * "encrypted_client_hello" extension in its ClientHelloInner. */ - rv = sslBuffer_AppendNumber(chInnerXtns, ssl_tls13_encrypted_client_hello_xtn, 2); + rv = sslBuffer_AppendNumber(chInnerXtns, ssl_tls13_ech_is_inner_xtn, 2); if (rv != SECSuccess) { goto loser; } @@ -1508,9 +1430,11 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool sslBuffer encodedChInner = SSL_BUFFER_EMPTY; sslBuffer chInnerXtns = SSL_BUFFER_EMPTY; sslBuffer pskXtn = SSL_BUFFER_EMPTY; - sslBuffer outerAAD = SSL_BUFFER_EMPTY; + sslBuffer aad = SSL_BUFFER_EMPTY; unsigned int encodedChLen; unsigned int preambleLen; + const SECItem *hpkeEnc = NULL; + unsigned int savedOffset; SSL_TRC(50, ("%d: TLS13[%d]: Constructing ECH inner", SSL_GETPID())); /* Create the full (uncompressed) inner extensions and steal any PSK extension. @@ -1551,8 +1475,8 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool goto loser; } - rv = ssl3_UpdateExplicitHandshakeTranscript(ss, chInner.buf, chInner.len, - &ss->ssl3.hs.echInnerMessages); + rv = ssl3_UpdateHandshakeHashesInt(ss, chInner.buf, chInner.len, + &ss->ssl3.hs.echInnerMessages); if (rv != SECSuccess) { goto loser; /* code set */ } @@ -1566,7 +1490,6 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool goto loser; } - /* TODO: Pad CHInner */ rv = tls13_EncodeClientHelloInner(ss, &chInner, &chInnerXtns, &encodedChInner); if (rv != SECSuccess) { goto loser; @@ -1575,29 +1498,67 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool /* Pad the outer prior to appending ECH (for the AAD). * Encoded extension size is (echCipherSuite + enc + configId + payload + tag). * Post-encryption, we'll assert that this was correct. */ - encodedChLen = 4 + 33 + 34 + 2 + encodedChInner.len + 16; + encodedChLen = 4 + 1 + 2 + 2 + encodedChInner.len + 16; + if (!ss->ssl3.hs.helloRetry) { + encodedChLen += 8 + 32; /* configId || enc */ + } rv = ssl_InsertPaddingExtension(ss, chOuter->len + encodedChLen, chOuterXtnsBuf); if (rv != SECSuccess) { goto loser; } - /* Make the ClientHelloOuterAAD value, which is complete - * chOuter minus encrypted_client_hello xtn. */ - rv = sslBuffer_Append(&outerAAD, chOuter->buf, chOuter->len); + PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->echConfigs)); + sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); + rv = sslBuffer_AppendNumber(&aad, cfg->contents.kdfId, 2); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_AppendBufferVariable(&outerAAD, chOuterXtnsBuf, 2); + rv = sslBuffer_AppendNumber(&aad, cfg->contents.aeadId, 2); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_InsertLength(&outerAAD, 1, 3); + + if (!ss->ssl3.hs.helloRetry) { + rv = sslBuffer_AppendVariable(&aad, cfg->configId, sizeof(cfg->configId), 1); + if (rv != SECSuccess) { + goto loser; + } + hpkeEnc = PK11_HPKE_GetEncapPubKey(ss->ssl3.hs.echHpkeCtx); + if (!hpkeEnc) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + goto loser; + } + rv = sslBuffer_AppendVariable(&aad, hpkeEnc->data, hpkeEnc->len, 2); + } else { + /* 1B config_id length, 2B enc length. */ + rv = sslBuffer_AppendNumber(&aad, 0, 3); + } + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_Skip(&aad, 3, &savedOffset); + if (rv != SECSuccess) { + goto loser; + } + + /* Skip the handshake header. */ + PORT_Assert(chOuter->len > 4); + rv = sslBuffer_Append(&aad, &chOuter->buf[4], chOuter->len - 4); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendBufferVariable(&aad, chOuterXtnsBuf, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_InsertLength(&aad, savedOffset, 3); if (rv != SECSuccess) { goto loser; } /* Insert the encrypted_client_hello xtn and coalesce. */ - rv = tls13_EncryptClientHello(ss, &outerAAD, &encodedChInner); + rv = tls13_EncryptClientHello(ss, &aad, &encodedChInner); if (rv != SECSuccess) { goto loser; } @@ -1618,64 +1579,123 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool if (rv != SECSuccess) { goto loser; } + sslBuffer_Clear(&chInner); + sslBuffer_Clear(&encodedChInner); + sslBuffer_Clear(&chInnerXtns); + sslBuffer_Clear(&pskXtn); + sslBuffer_Clear(&aad); + return SECSuccess; loser: sslBuffer_Clear(&chInner); sslBuffer_Clear(&encodedChInner); sslBuffer_Clear(&chInnerXtns); sslBuffer_Clear(&pskXtn); - sslBuffer_Clear(&outerAAD); - return rv; + sslBuffer_Clear(&aad); + PORT_Assert(PORT_GetError() != 0); + return SECFailure; } +/* Compute the ECH signal using the transcript (up to, excluding) Server Hello. + * We'll append an artificial SH (ServerHelloECHConf). The server sources + * this transcript prefix from ss->ssl3.hs.messages, as it never uses + * ss->ssl3.hs.echInnerMessages. The client uses the inner transcript, echInnerMessages. */ static SECStatus -tls13_ComputeEchSignal(sslSocket *ss, PRUint8 *out) +tls13_ComputeEchSignal(sslSocket *ss, const PRUint8 *sh, unsigned int shLen, PRUint8 *out) { SECStatus rv; - PRUint8 derived[64]; - SECItem randItem = { siBuffer, - ss->sec.isServer ? ss->ssl3.hs.client_random : ss->ssl3.hs.client_inner_random, - SSL3_RANDOM_LENGTH }; - SSLHashType hashAlg = tls13_GetHash(ss); - PK11SymKey *extracted = NULL; - PK11SymKey *randKey = NULL; - PK11SlotInfo *slot = PK11_GetInternalSlot(); - if (!slot) { - goto loser; - } + PK11SymKey *confirmationKey = NULL; + sslBuffer confMsgs = SSL_BUFFER_EMPTY; + sslBuffer *chSource = ss->sec.isServer ? &ss->ssl3.hs.messages : &ss->ssl3.hs.echInnerMessages; + SSL3Hashes hashes; + SECItem *confirmationBytes; + unsigned int offset = sizeof(SSL3ProtocolVersion) + + SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN; + PORT_Assert(sh && shLen > offset); + PORT_Assert(TLS13_ECH_SIGNAL_LEN <= SSL3_RANDOM_LENGTH); - randKey = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap, - CKA_DERIVE, &randItem, NULL); - if (!randKey) { - goto loser; - } - - rv = tls13_HkdfExtract(NULL, randKey, hashAlg, &extracted); + rv = sslBuffer_AppendBuffer(&confMsgs, chSource); if (rv != SECSuccess) { goto loser; } - rv = tls13_HkdfExpandLabelRaw(extracted, hashAlg, ss->ssl3.hs.server_random, 24, - kHkdfInfoEchConfirm, strlen(kHkdfInfoEchConfirm), - ss->protocolVariant, derived, TLS13_ECH_SIGNAL_LEN); + /* Re-create the message header. */ + rv = sslBuffer_AppendNumber(&confMsgs, ssl_hs_server_hello, 1); if (rv != SECSuccess) { goto loser; } - PORT_Memcpy(out, derived, TLS13_ECH_SIGNAL_LEN); + rv = sslBuffer_AppendNumber(&confMsgs, shLen, 3); + if (rv != SECSuccess) { + goto loser; + } + + /* Copy the version and 24B of server_random. */ + rv = sslBuffer_Append(&confMsgs, sh, offset); + if (rv != SECSuccess) { + goto loser; + } + + /* Zero the signal placeholder. */ + rv = sslBuffer_AppendNumber(&confMsgs, 0, TLS13_ECH_SIGNAL_LEN); + if (rv != SECSuccess) { + goto loser; + } + offset += TLS13_ECH_SIGNAL_LEN; + + /* Use the remainder of SH. */ + rv = sslBuffer_Append(&confMsgs, &sh[offset], shLen - offset); + if (rv != SECSuccess) { + goto loser; + } + + rv = tls13_ComputeHash(ss, &hashes, confMsgs.buf, confMsgs.len, + tls13_GetHash(ss)); + if (rv != SECSuccess) { + goto loser; + } + + /* accept_confirmation = + * Derive-Secret(Handshake Secret, + * "ech accept confirmation", + * ClientHelloInner...ServerHelloECHConf) + */ + rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret, + kHkdfInfoEchConfirm, strlen(kHkdfInfoEchConfirm), + &hashes, &confirmationKey, tls13_GetHash(ss)); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = PK11_ExtractKeyValue(confirmationKey); + if (rv != SECSuccess) { + goto loser; + } + confirmationBytes = PK11_GetKeyData(confirmationKey); + if (!confirmationBytes) { + rv = SECFailure; + PORT_SetError(SSL_ERROR_ECH_FAILED); + goto loser; + } + if (confirmationBytes->len < TLS13_ECH_SIGNAL_LEN) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + goto loser; + } SSL_TRC(50, ("%d: TLS13[%d]: %s computed ECH signal", SSL_GETPID(), ss->fd, SSL_ROLE(ss))); PRINT_BUF(50, (ss, "", out, TLS13_ECH_SIGNAL_LEN)); - PK11_FreeSymKey(extracted); - PK11_FreeSymKey(randKey); - PK11_FreeSlot(slot); + + PORT_Memcpy(out, confirmationBytes->data, TLS13_ECH_SIGNAL_LEN); + PK11_FreeSymKey(confirmationKey); + sslBuffer_Clear(&confMsgs); + sslBuffer_Clear(&ss->ssl3.hs.messages); + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); return SECSuccess; loser: - PK11_FreeSymKey(extracted); - PK11_FreeSymKey(randKey); - if (slot) { - PK11_FreeSlot(slot); - } + PK11_FreeSymKey(confirmationKey); + sslBuffer_Clear(&confMsgs); + sslBuffer_Clear(&ss->ssl3.hs.messages); + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); return SECFailure; } @@ -1695,6 +1715,8 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) SECItem *rawData; CK_HKDF_PARAMS params; SECItem paramsi; + /* 1B aead determinant (don't send), 8B config_id, 32B enc, payload */ + const int kNonPayloadLen = 41; if (!ss->opt.enableTls13GreaseEch || ss->ssl3.hs.echHpkeCtx) { return SECSuccess; @@ -1705,6 +1727,13 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) return SECSuccess; } + /* In draft-09, CH2 sends exactly the same GREASE ECH extension. */ + if (ss->ssl3.hs.helloRetry) { + return ssl3_EmplaceExtension(ss, buf, ssl_tls13_encrypted_client_hello_xtn, + ss->ssl3.hs.greaseEchBuf.buf, + ss->ssl3.hs.greaseEchBuf.len, PR_TRUE); + } + /* Compress the extensions for payload length. */ rv = tls13_ConstructInnerExtensionsFromOuter(ss, buf, &chInnerXtns, NULL, PR_TRUE); @@ -1734,7 +1763,7 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) paramsi.len = sizeof(params); derivedData = PK11_DeriveWithFlags(hmacPrk, CKM_HKDF_DATA, ¶msi, CKM_HKDF_DATA, - CKA_DERIVE, 65 + payloadLen, + CKA_DERIVE, kNonPayloadLen + payloadLen, CKF_VERIFY); if (!derivedData) { goto loser; @@ -1745,12 +1774,11 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) goto loser; } - /* 1B aead determinant (don't send), 32B config_id, 32B enc, payload */ rawData = PK11_GetKeyData(derivedData); if (!rawData) { goto loser; } - PORT_Assert(rawData->len == 65 + payloadLen); + PORT_Assert(rawData->len == kNonPayloadLen + payloadLen); /* struct { HpkeKdfId kdf_id; @@ -1773,31 +1801,34 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) goto loser; } - rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[1], 32, 1); + /* config_id, 8B */ + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[1], 8, 1); if (rv != SECSuccess) { goto loser; } /* enc len is fixed 32B for X25519. */ - rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[33], 32, 2); + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[9], 32, 2); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[65], payloadLen, 2); + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[kNonPayloadLen], payloadLen, 2); if (rv != SECSuccess) { goto loser; } /* Mark ECH as advertised so that we can validate any response. - * We'll use echHpkeCtx to determine if we sent real or GREASE ECH. - * TODO: Maybe a broader need to similarly track GREASED extensions? */ + * We'll use echHpkeCtx to determine if we sent real or GREASE ECH. */ rv = ssl3_EmplaceExtension(ss, buf, ssl_tls13_encrypted_client_hello_xtn, greaseBuf.buf, greaseBuf.len, PR_TRUE); if (rv != SECSuccess) { goto loser; } - sslBuffer_Clear(&greaseBuf); + + /* Stash the GREASE ECH extension - in the case of HRR, CH2 must echo it. */ + ss->ssl3.hs.greaseEchBuf = greaseBuf; + sslBuffer_Clear(&chInnerXtns); PK11_FreeSymKey(hmacPrk); PK11_FreeSymKey(derivedData); @@ -1805,7 +1836,6 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) return SECSuccess; loser: - sslBuffer_Clear(&greaseBuf); sslBuffer_Clear(&chInnerXtns); PK11_FreeSymKey(hmacPrk); PK11_FreeSymKey(derivedData); @@ -1861,15 +1891,11 @@ tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem /* Since in Outer we explicitly call the ECH handler, do the same on Inner. * Extensions are already parsed in tls13_MaybeAcceptEch. */ - echExtension = ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn); + echExtension = ssl3_FindExtension(ss, ssl_tls13_ech_is_inner_xtn); if (!echExtension) { - FATAL_ERROR(ss, SSL_ERROR_MISSING_ECH_EXTENSION, decode_error); + FATAL_ERROR(ss, SSL_ERROR_MISSING_ECH_EXTENSION, illegal_parameter); goto loser; } - rv = tls13_ServerHandleEchXtn(ss, &ss->xtnData, &echExtension->data); - if (rv != SECSuccess) { - goto loser; /* code set, alert sent. */ - } versionExtension = ssl3_FindExtension(ss, ssl_tls13_supported_versions_xtn); if (!versionExtension) { @@ -1895,11 +1921,12 @@ tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem loser: SECITEM_FreeItem(tmpEchInner, PR_TRUE); + PORT_Assert(PORT_GetError() != 0); return SECFailure; } SECStatus -tls13_MaybeHandleEchSignal(sslSocket *ss) +tls13_MaybeHandleEchSignal(sslSocket *ss, const PRUint8 *sh, PRUint32 shLen) { SECStatus rv; PRUint8 computed[TLS13_ECH_SIGNAL_LEN]; @@ -1907,29 +1934,41 @@ tls13_MaybeHandleEchSignal(sslSocket *ss) PORT_Assert(!ss->sec.isServer); /* If !echHpkeCtx, we either didn't advertise or sent GREASE ECH. */ - if (ss->ssl3.hs.echHpkeCtx) { - PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn)); - rv = tls13_ComputeEchSignal(ss, computed); - if (rv != SECSuccess) { - return SECFailure; - } - - ss->ssl3.hs.echAccepted = !PORT_Memcmp(computed, signal, TLS13_ECH_SIGNAL_LEN); - if (ss->ssl3.hs.echAccepted) { - if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter); - return SECFailure; - } - ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; - PORT_Memcpy(ss->ssl3.hs.client_random, ss->ssl3.hs.client_inner_random, SSL3_RANDOM_LENGTH); - } - /* If rejected, leave echHpkeCtx and echPublicName for rejection paths. */ - ssl3_CoalesceEchHandshakeHashes(ss); - SSL_TRC(50, ("%d: TLS13[%d]: ECH %s accepted by server", - SSL_GETPID(), ss->fd, ss->ssl3.hs.echAccepted ? "is" : "is not")); + if (!ss->ssl3.hs.echHpkeCtx) { + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; + return SECSuccess; } + PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn)); + rv = tls13_ComputeEchSignal(ss, sh, shLen, computed); + if (rv != SECSuccess) { + return SECFailure; + } + + ss->ssl3.hs.echAccepted = !PORT_Memcmp(computed, signal, TLS13_ECH_SIGNAL_LEN); ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; + if (ss->ssl3.hs.echAccepted) { + if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter); + return SECFailure; + } + if (ss->ssl3.hs.helloRetry && ss->sec.isServer) { + /* Enc and ConfigId are stored in the cookie and must not + * be included in CH2.ClientECH. */ + if (ss->xtnData.ech->senderPubKey.len || ss->xtnData.ech->configId.len) { + ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); + PORT_SetError(SSL_ERROR_BAD_2ND_CLIENT_HELLO); + return SECFailure; + } + } + + ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; + PORT_Memcpy(ss->ssl3.hs.client_random, ss->ssl3.hs.client_inner_random, SSL3_RANDOM_LENGTH); + } + /* If rejected, leave echHpkeCtx and echPublicName for rejection paths. */ + ssl3_CoalesceEchHandshakeHashes(ss); + SSL_TRC(50, ("%d: TLS13[%d]: ECH %s accepted by server", + SSL_GETPID(), ss->fd, ss->ssl3.hs.echAccepted ? "is" : "is not")); return SECSuccess; } @@ -2092,75 +2131,111 @@ tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOu SECStatus rv; SECItem outer = { siBuffer, CONST_CAST(PRUint8, chOuter), chOuterLen }; SECItem *decryptedChInner = NULL; - PK11SymKey *echHrrPsk = NULL; SECItem hrrCh1ConfigId = { siBuffer, NULL, 0 }; - HpkeKdfId kdf; - HpkeAeadId aead; + SECItem outerAAD = { siBuffer, NULL, 0 }; + SECItem cookieData = { siBuffer, NULL, 0 }; + HpkeContext *ch1EchHpkeCtx = NULL; + HpkeKdfId echKdfId; + HpkeAeadId echAeadId; sslEchConfig *candidate = NULL; /* non-owning */ TLSExtension *hrrXtn; - SECItem *configId = ss->ssl3.hs.helloRetry ? &hrrCh1ConfigId : &ss->xtnData.echConfigId; - if (!ss->xtnData.innerCh.len) { + + if (!ss->xtnData.ech) { return SECSuccess; } - PORT_Assert(ss->xtnData.echSenderPubKey.data); - PORT_Assert(ss->xtnData.echConfigId.data); - PORT_Assert(ss->xtnData.echCipherSuite); + PORT_Assert(ss->xtnData.ech->innerCh.data); if (ss->ssl3.hs.helloRetry) { + PORT_Assert(!ss->ssl3.hs.echHpkeCtx); hrrXtn = ssl3_FindExtension(ss, ssl_tls13_cookie_xtn); if (!hrrXtn) { /* If the client doesn't echo cookie, we can't decrypt. */ return SECSuccess; } - rv = tls13_GetEchInfoFromCookie(ss, hrrXtn, &echHrrPsk, &hrrCh1ConfigId); + PORT_Assert(!ss->xtnData.ech->configId.data); + PORT_Assert(!ss->ssl3.hs.echHpkeCtx); + + PRUint8 *tmp = hrrXtn->data.data; + PRUint32 len = hrrXtn->data.len; + rv = ssl3_ExtConsumeHandshakeVariable(ss, &cookieData, 2, + &tmp, &len); if (rv != SECSuccess) { - /* If we failed due to an issue with the cookie, continue without - * ECH and let the HRR code handle the problem. */ - goto exit_success; + return SECFailure; } - /* No CH1 config_id means ECH wasn't advertised in CH1. - * No CH1 HRR PSK means that ECH was not accepted in CH1, and the - * HRR was generated off CH1Outer. */ - if (hrrCh1ConfigId.len == 0) { + /* Extract ECH info without restoring hash state. If there's + * something wrong with the cookie, continue without ECH + * and let HRR code handle the problem. */ + rv = tls13_HandleHrrCookie(ss, cookieData.data, cookieData.len, + NULL, NULL, NULL, &echKdfId, &echAeadId, + &hrrCh1ConfigId, &ch1EchHpkeCtx, PR_FALSE); + if (rv != SECSuccess) { + return SECSuccess; + } + + ss->xtnData.ech->configId = hrrCh1ConfigId; + ss->ssl3.hs.echHpkeCtx = ch1EchHpkeCtx; + + if (echKdfId != ss->xtnData.ech->kdfId || + echAeadId != ss->xtnData.ech->aeadId) { FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, illegal_parameter); - goto loser; + return SECFailure; } - if (!echHrrPsk) { - goto exit_success; + + if (!ss->ssl3.hs.echHpkeCtx) { + return SECSuccess; } } - kdf = (HpkeKdfId)(ss->xtnData.echCipherSuite & 0xFFFF); - aead = (HpkeAeadId)(((ss->xtnData.echCipherSuite) >> 16) & 0xFFFF); - rv = tls13_GetMatchingEchConfig(ss, kdf, aead, configId, &candidate); - if (rv != SECSuccess) { - goto loser; - } - if (!candidate || candidate->contents.kdfId != kdf || - candidate->contents.aeadId != aead) { - /* Send retry_configs if we have any. - * This does *not* count as negotiating ECH. */ - rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData, - ssl_tls13_encrypted_client_hello_xtn, - tls13_ServerSendEchXtn); - goto exit_success; - } - rv = tls13_OpenClientHelloInner(ss, &outer, candidate, echHrrPsk, &decryptedChInner); + /* Cookie data was good, proceed with ECH. */ + PORT_Assert(ss->xtnData.ech->configId.data); + rv = tls13_GetMatchingEchConfigs(ss, ss->xtnData.ech->kdfId, ss->xtnData.ech->aeadId, + &ss->xtnData.ech->configId, candidate, &candidate); if (rv != SECSuccess) { - if (ss->ssl3.hs.helloRetry) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, decrypt_error); - goto loser; - } else { - rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData, - ssl_tls13_encrypted_client_hello_xtn, - tls13_ServerSendEchXtn); - goto exit_success; + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + return SECFailure; + } + + if (candidate) { + rv = tls13_MakeChOuterAAD(ss, &outer, &outerAAD); + if (rv != SECSuccess) { + return SECFailure; } } + + while (candidate) { + rv = tls13_OpenClientHelloInner(ss, &outer, &outerAAD, candidate, &decryptedChInner); + if (rv != SECSuccess) { + /* Get the next matching config */ + rv = tls13_GetMatchingEchConfigs(ss, ss->xtnData.ech->kdfId, ss->xtnData.ech->aeadId, + &ss->xtnData.ech->configId, candidate, &candidate); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + SECITEM_FreeItem(&outerAAD, PR_FALSE); + return SECFailure; + } + continue; + } + break; + } + SECITEM_FreeItem(&outerAAD, PR_FALSE); + + if (rv != SECSuccess || !decryptedChInner) { + if (ss->ssl3.hs.helloRetry) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, decrypt_error); + return SECFailure; + } else { + /* Send retry_configs (if we have any) when we fail to decrypt or + * found no candidates. This does *not* count as negotiating ECH. */ + return ssl3_RegisterExtensionSender(ss, &ss->xtnData, + ssl_tls13_encrypted_client_hello_xtn, + tls13_ServerSendEchXtn); + } + } + SSL_TRC(20, ("%d: TLS13[%d]: Successfully opened ECH inner CH", SSL_GETPID(), ss->fd)); ss->ssl3.hs.echAccepted = PR_TRUE; @@ -2172,31 +2247,33 @@ tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOu rv = tls13_UnencodeChInner(ss, sidBytes, &decryptedChInner); if (rv != SECSuccess) { SECITEM_FreeItem(decryptedChInner, PR_TRUE); - goto loser; /* code set */ + return SECFailure; /* code set */ } *chInner = decryptedChInner; - -exit_success: - PK11_FreeSymKey(echHrrPsk); - SECITEM_FreeItem(&hrrCh1ConfigId, PR_FALSE); return SECSuccess; - -loser: - PK11_FreeSymKey(echHrrPsk); - SECITEM_FreeItem(&hrrCh1ConfigId, PR_FALSE); - return SECFailure; } SECStatus -tls13_WriteServerEchSignal(sslSocket *ss) +tls13_WriteServerEchSignal(sslSocket *ss, PRUint8 *sh, unsigned int shLen) { SECStatus rv; PRUint8 signal[TLS13_ECH_SIGNAL_LEN]; - rv = tls13_ComputeEchSignal(ss, signal); + PRUint8 *msg_random = &sh[sizeof(SSL3ProtocolVersion)]; + + PORT_Assert(shLen > sizeof(SSL3ProtocolVersion) + SSL3_RANDOM_LENGTH); + PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3); + + rv = tls13_ComputeEchSignal(ss, sh, shLen, signal); if (rv != SECSuccess) { return SECFailure; } - PRUint8 *dest = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN]; + PRUint8 *dest = &msg_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN]; PORT_Memcpy(dest, signal, TLS13_ECH_SIGNAL_LEN); + + /* Keep the socket copy consistent. */ + PORT_Assert(0 == memcmp(msg_random, &ss->ssl3.hs.server_random, SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN)); + dest = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN]; + PORT_Memcpy(dest, signal, TLS13_ECH_SIGNAL_LEN); + return SECSuccess; } diff --git a/security/nss/lib/ssl/tls13ech.h b/security/nss/lib/ssl/tls13ech.h index 5c53b3f8a532..a39a0295c00a 100644 --- a/security/nss/lib/ssl/tls13ech.h +++ b/security/nss/lib/ssl/tls13ech.h @@ -11,7 +11,7 @@ #include "pk11hpke.h" -/* draft-08, shared-mode only. +/* draft-09, supporting shared-mode and split-mode as a backend server only. * Notes on the implementation status: * - Padding (https://tools.ietf.org/html/draft-ietf-tls-esni-08#section-6.2), * is not implemented (see bug 1677181). @@ -21,12 +21,10 @@ * - Some of the buffering (construction/compression/decompression) could likely * be optimized, but the spec is still evolving so that work is deferred. */ -#define TLS13_ECH_VERSION 0xfe08 +#define TLS13_ECH_VERSION 0xfe09 #define TLS13_ECH_SIGNAL_LEN 8 static const char kHpkeInfoEch[] = "tls ech"; -static const char kHpkeInfoEchHrr[] = "tls ech hrr key"; -static const char kHpkeLabelHrrPsk[] = "hrr key"; static const char hHkdfInfoEchConfigID[] = "tls ech config id"; static const char kHkdfInfoEchConfirm[] = "ech accept confirmation"; @@ -45,11 +43,23 @@ struct sslEchConfigContentsStr { struct sslEchConfigStr { PRCList link; SECItem raw; - PRUint8 configId[32]; + PRUint8 configId[8]; PRUint16 version; sslEchConfigContents contents; }; +struct sslEchXtnStateStr { + SECItem innerCh; /* Server: ClientECH.payload */ + SECItem senderPubKey; /* Server: ClientECH.enc */ + SECItem configId; /* Server: ClientECH.config_id */ + HpkeKdfId kdfId; /* Server: ClientECH.cipher_suite.kdf */ + HpkeAeadId aeadId; /* Server: ClientECH.cipher_suite.aead */ + SECItem retryConfigs; /* Client: ServerECH.retry_configs*/ + PRBool retryConfigsValid; /* Client: Extraction of retry_configss is allowed. + * This is set once the handshake completes (having + * verified to the ECHConfig public name). */ +}; + SECStatus SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites, unsigned int hpkeSuiteCount, HpkeKemId kemId, const SECKEYPublicKey *pubKey, PRUint16 maxNameLen, @@ -69,14 +79,15 @@ SECStatus tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *s SECStatus tls13_CopyEchConfigs(PRCList *oconfigs, PRCList *configs); SECStatus tls13_DecodeEchConfigs(const SECItem *data, PRCList *configs); void tls13_DestroyEchConfigs(PRCList *list); +void tls13_DestroyEchXtnState(sslEchXtnState *state); SECStatus tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, const SECItem *configId, sslEchConfig **cfg); SECStatus tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem *sidBytes, SECItem *comps, SECItem *cookieBytes, SECItem *suites, SECItem **echInner); -SECStatus tls13_MaybeHandleEchSignal(sslSocket *ss); +SECStatus tls13_MaybeHandleEchSignal(sslSocket *ss, const PRUint8 *savedMsg, PRUint32 savedLength); SECStatus tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOuter, unsigned int chOuterLen, SECItem **chInner); SECStatus tls13_MaybeGreaseEch(sslSocket *ss, unsigned int prefixLen, sslBuffer *buf); -SECStatus tls13_WriteServerEchSignal(sslSocket *ss); +SECStatus tls13_WriteServerEchSignal(sslSocket *ss, PRUint8 *sh, unsigned int shLen); #endif diff --git a/security/nss/lib/ssl/tls13exthandle.c b/security/nss/lib/ssl/tls13exthandle.c index 54a4abb099e3..7991a12c26c2 100644 --- a/security/nss/lib/ssl/tls13exthandle.c +++ b/security/nss/lib/ssl/tls13exthandle.c @@ -1211,6 +1211,12 @@ tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, PRCList parsedConfigs; PR_INIT_CLIST(&parsedConfigs); + PORT_Assert(!xtnData->ech); + xtnData->ech = PORT_ZNew(sslEchXtnState); + if (!xtnData->ech) { + return SECFailure; + } + /* Parse the list to determine 1) That the configs are valid * and properly encoded, and 2) If any are compatible. */ rv = tls13_DecodeEchConfigs(data, &parsedConfigs); @@ -1219,11 +1225,11 @@ tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); return SECFailure; } - /* Don't mark ECH negotiated on retry. Save the the raw - * configs so the application can retry. If we sent GREASE - * ECH (no echHpkeCtx), don't apply returned retry_configs. */ + /* Don't mark ECH negotiated on rejection with retry_config. + * Save the the raw configs so the application can retry. If + * we sent GREASE ECH (no echHpkeCtx), don't apply retry_configs. */ if (ss->ssl3.hs.echHpkeCtx && !PR_CLIST_IS_EMPTY(&parsedConfigs)) { - rv = SECITEM_CopyItem(NULL, &xtnData->echRetryConfigs, data); + rv = SECITEM_CopyItem(NULL, &xtnData->ech->retryConfigs, data); } tls13_DestroyEchConfigs(&parsedConfigs); @@ -1465,14 +1471,22 @@ tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, return SECSuccess; } - /* On CHInner, the extension must be empty. */ - if (ss->ssl3.hs.echAccepted && data->len > 0) { + if (ss->ssl3.hs.echAccepted) { ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); - PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION); + return SECFailure; + } + + if (ssl3_FindExtension(CONST_CAST(sslSocket, ss), ssl_tls13_ech_is_inner_xtn)) { + ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION); + return SECFailure; + } + + PORT_Assert(!xtnData->ech); + xtnData->ech = PORT_ZNew(sslEchXtnState); + if (!xtnData->ech) { return SECFailure; - } else if (ss->ssl3.hs.echAccepted) { - xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; - return SECSuccess; } /* Parse the KDF and AEAD. */ @@ -1503,37 +1517,40 @@ tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, goto alert_loser; } - /* payload */ + /* payload, which must be final and non-empty. */ rv = ssl3_ExtConsumeHandshakeVariable(ss, &encryptedCh, 2, &data->data, &data->len); if (rv != SECSuccess) { goto alert_loser; } - - if (data->len) { + if (data->len || !encryptedCh.len) { goto alert_loser; } - /* All fields required. */ - if (!configId.len || !senderPubKey.len || !encryptedCh.len) { - goto alert_loser; + if (!ss->ssl3.hs.helloRetry) { + /* In the real ECH HRR case, config_id and enc should be empty. This + * is checked after acceptance, because it might be GREASE ECH. */ + if (!configId.len || !senderPubKey.len) { + goto alert_loser; + } + + rv = SECITEM_CopyItem(NULL, &xtnData->ech->senderPubKey, &senderPubKey); + if (rv == SECFailure) { + return SECFailure; + } + + rv = SECITEM_CopyItem(NULL, &xtnData->ech->configId, &configId); + if (rv == SECFailure) { + return SECFailure; + } } - rv = SECITEM_CopyItem(NULL, &xtnData->echSenderPubKey, &senderPubKey); + rv = SECITEM_CopyItem(NULL, &xtnData->ech->innerCh, &encryptedCh); if (rv == SECFailure) { return SECFailure; } - - rv = SECITEM_CopyItem(NULL, &xtnData->innerCh, &encryptedCh); - if (rv == SECFailure) { - return SECFailure; - } - - rv = SECITEM_CopyItem(NULL, &xtnData->echConfigId, &configId); - if (rv == SECFailure) { - return SECFailure; - } - xtnData->echCipherSuite = (aead & 0xFFFF) << 16 | (kdf & 0xFFFF); + xtnData->ech->kdfId = kdf; + xtnData->ech->aeadId = aead; /* Not negotiated until tls13_MaybeAcceptEch. */ return SECSuccess; @@ -1543,3 +1560,36 @@ alert_loser: PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); return SECFailure; } + +SECStatus +tls13_ServerHandleEchIsInnerXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data) +{ + SSL_TRC(3, ("%d: TLS13[%d]: handle ech_is_inner extension", + SSL_GETPID(), ss->fd)); + + if (data->len) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + return SECFailure; + } + + if (ssl3_FindExtension(CONST_CAST(sslSocket, ss), ssl_tls13_encrypted_client_hello_xtn)) { + ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION); + return SECFailure; + } + + /* Consider encrypted_client_hello_xtn negotiated if we performed the + * CHOuter decryption. This is only supported in shared mode, so we'll also + * handle ech_is_inner in that case. We might, however, receive a CHInner + * that was forwarded by a different client-facing server. In this case, + * mark ech_is_inner as negotiated, which triggers sending of the ECH + * acceptance signal. ech_is_inner_xtn being negotiated does not imply + * that any other ECH state actually exists. */ + if (ss->ssl3.hs.echAccepted) { + xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; + } + xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_ech_is_inner_xtn; + return SECSuccess; +} diff --git a/security/nss/lib/ssl/tls13exthandle.h b/security/nss/lib/ssl/tls13exthandle.h index 556737210149..ae79ecba8210 100644 --- a/security/nss/lib/ssl/tls13exthandle.h +++ b/security/nss/lib/ssl/tls13exthandle.h @@ -94,6 +94,9 @@ SECStatus tls13_ServerSendEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, sslBuffer *buf, PRBool *added); SECStatus tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, SECItem *data); +SECStatus tls13_ServerHandleEchIsInnerXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data); SECStatus tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss, TLSExtensionData *xtnData, sslBuffer *buf, PRBool *added); diff --git a/security/nss/lib/ssl/tls13hashstate.c b/security/nss/lib/ssl/tls13hashstate.c index 011b4838e737..f2f55ba0fd74 100644 --- a/security/nss/lib/ssl/tls13hashstate.c +++ b/security/nss/lib/ssl/tls13hashstate.c @@ -24,9 +24,11 @@ * uint8 indicator = 0xff; // To disambiguate from tickets. * uint16 cipherSuite; // Selected cipher suite. * uint16 keyShare; // Requested key share group (0=none) + * HpkeKdfId kdfId; // ECH KDF (uint16) + * HpkeAeadId aeadId; // ECH AEAD (uint16) + * opaque echConfigId<0..255>; // ECH config_id + * opaque echHpkeCtx<0..65535>; // ECH serialized HPKE context * opaque applicationToken<0..65535>; // Application token - * echConfigId<0..255>; // Encrypted Client Hello config_id - * echHrrPsk<0..255>; // Encrypted Client Hello HRR PSK * opaque ch_hash[rest_of_buffer]; // H(ClientHello) * } CookieInner; * @@ -43,10 +45,7 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, PRUint8 cookie[1024]; sslBuffer cookieBuf = SSL_BUFFER(cookie); static const PRUint8 indicator = 0xff; - SECItem hrrNonceInfoItem = { siBuffer, (unsigned char *)kHpkeInfoEchHrr, - strlen(kHpkeInfoEchHrr) }; - PK11SymKey *echHrrPsk = NULL; - SECItem *rawEchPsk = NULL; + SECItem *echHpkeCtx = NULL; /* Encode header. */ rv = sslBuffer_Append(&cookieBuf, &indicator, 1); @@ -63,54 +62,53 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, return SECFailure; } + if (ss->xtnData.ech) { + rv = sslBuffer_AppendNumber(&cookieBuf, ss->xtnData.ech->kdfId, 2); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendNumber(&cookieBuf, ss->xtnData.ech->aeadId, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + /* Received ECH config_id, regardless of acceptance or possession + * of a matching ECHConfig. */ + PORT_Assert(ss->xtnData.ech->configId.len == 8); + rv = sslBuffer_AppendVariable(&cookieBuf, ss->xtnData.ech->configId.data, + ss->xtnData.ech->configId.len, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + /* There might be no HPKE Context, e.g. when we lack a matching ECHConfig. */ + if (ss->ssl3.hs.echHpkeCtx) { + rv = PK11_HPKE_ExportContext(ss->ssl3.hs.echHpkeCtx, NULL, &echHpkeCtx); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendVariable(&cookieBuf, echHpkeCtx->data, echHpkeCtx->len, 2); + SECITEM_ZfreeItem(echHpkeCtx, PR_TRUE); + } else { + /* Zero length HPKE context. */ + rv = sslBuffer_AppendNumber(&cookieBuf, 0, 2); + } + if (rv != SECSuccess) { + return SECFailure; + } + } else { + rv = sslBuffer_AppendNumber(&cookieBuf, 0, 7); + if (rv != SECSuccess) { + return SECFailure; + } + } + /* Application token. */ rv = sslBuffer_AppendVariable(&cookieBuf, appToken, appTokenLen, 2); if (rv != SECSuccess) { return SECFailure; } - /* Received ECH config_id, regardless of acceptance or possession - * of a matching ECHConfig. If rejecting ECH, this is essentially a boolean - * indicating that ECH was offered in CH1. If accepting ECH, this config_id - * will be used for the ECH decryption in CH2. */ - if (ss->xtnData.echConfigId.len) { - rv = sslBuffer_AppendVariable(&cookieBuf, ss->xtnData.echConfigId.data, - ss->xtnData.echConfigId.len, 1); - } else { - PORT_Assert(!ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn)); - rv = sslBuffer_AppendNumber(&cookieBuf, 0, 1); - } - if (rv != SECSuccess) { - return SECFailure; - } - - /* Extract and encode the ech-hrr-key, if ECH was accepted - * (i.e. an Open() succeeded. */ - if (ss->ssl3.hs.echAccepted) { - rv = PK11_HPKE_ExportSecret(ss->ssl3.hs.echHpkeCtx, &hrrNonceInfoItem, 32, &echHrrPsk); - if (rv != SECSuccess) { - return SECFailure; - } - rv = PK11_ExtractKeyValue(echHrrPsk); - if (rv != SECSuccess) { - PK11_FreeSymKey(echHrrPsk); - return SECFailure; - } - rawEchPsk = PK11_GetKeyData(echHrrPsk); - if (!rawEchPsk) { - PK11_FreeSymKey(echHrrPsk); - return SECFailure; - } - rv = sslBuffer_AppendVariable(&cookieBuf, rawEchPsk->data, rawEchPsk->len, 1); - PK11_FreeSymKey(echHrrPsk); - } else { - /* Zero length ech_hrr_key. */ - rv = sslBuffer_AppendNumber(&cookieBuf, 0, 1); - } - if (rv != SECSuccess) { - return SECFailure; - } - /* Compute and encode hashes. */ rv = tls13_ComputeHandshakeHashes(ss, &hashes); if (rv != SECSuccess) { @@ -131,23 +129,34 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, return SECSuccess; } -/* Recover the hash state from the cookie. */ +/* Given a cookie and cookieLen, decrypt and parse, returning + * any values that were requested via the "previous_" params. If + * recoverHashState is true, the transcript state is recovered */ SECStatus -tls13_RecoverHashState(sslSocket *ss, - unsigned char *cookie, unsigned int cookieLen, - ssl3CipherSuite *previousCipherSuite, - const sslNamedGroupDef **previousGroup, - PRBool *previousEchOffered) +tls13_HandleHrrCookie(sslSocket *ss, + unsigned char *cookie, unsigned int cookieLen, + ssl3CipherSuite *previousCipherSuite, + const sslNamedGroupDef **previousGroup, + PRBool *previousEchOffered, + HpkeKdfId *previousEchKdfId, + HpkeAeadId *previousEchAeadId, + SECItem *previousEchConfigId, + HpkeContext **previousEchHpkeCtx, + PRBool recoverHashState) { SECStatus rv; unsigned char plaintext[1024]; unsigned int plaintextLen = 0; sslBuffer messageBuf = SSL_BUFFER_EMPTY; - sslReadBuffer echPskBuf; - sslReadBuffer echConfigIdBuf; + sslReadBuffer echHpkeBuf = { 0 }; + sslReadBuffer echConfigIdBuf = { 0 }; PRUint64 sentinel; PRUint64 cipherSuite; + HpkeContext *hpkeContext = NULL; + HpkeKdfId echKdfId; + HpkeAeadId echAeadId; PRUint64 group; + PRUint64 tmp64; const sslNamedGroupDef *selectedGroup; PRUint64 appTokenLen; @@ -180,6 +189,33 @@ tls13_RecoverHashState(sslSocket *ss, } selectedGroup = ssl_LookupNamedGroup(group); + /* ECH Ciphersuite */ + rv = sslRead_ReadNumber(&reader, 2, &tmp64); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + echKdfId = (HpkeKdfId)tmp64; + + rv = sslRead_ReadNumber(&reader, 2, &tmp64); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + echAeadId = (HpkeAeadId)tmp64; + + /* ECH Config ID and HPKE context may be empty. */ + rv = sslRead_ReadVariable(&reader, 1, &echConfigIdBuf); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + rv = sslRead_ReadVariable(&reader, 2, &echHpkeBuf); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + /* Application token. */ PORT_Assert(ss->xtnData.applicationToken.len == 0); rv = sslRead_ReadNumber(&reader, 2, &appTokenLen); @@ -202,55 +238,80 @@ tls13_RecoverHashState(sslSocket *ss, PORT_Assert(appTokenReader.len == appTokenLen); PORT_Memcpy(ss->xtnData.applicationToken.data, appTokenReader.buf, appTokenLen); - /* ECH Config ID, which may be empty. */ - rv = sslRead_ReadVariable(&reader, 1, &echConfigIdBuf); - if (rv != SECSuccess) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); - return SECFailure; - } - /* ECH HRR PSK, if present, is already used by tls13_GetEchInfoFromCookie */ - rv = sslRead_ReadVariable(&reader, 1, &echPskBuf); - if (rv != SECSuccess) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); - return SECFailure; - } - /* The remainder is the hash. */ - unsigned int hashLen = SSL_READER_REMAINING(&reader); - if (hashLen != tls13_GetHashSize(ss)) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); - return SECFailure; + if (recoverHashState) { + unsigned int hashLen = SSL_READER_REMAINING(&reader); + if (hashLen != tls13_GetHashSize(ss)) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + + /* Now reinject the message. */ + SSL_ASSERT_HASHES_EMPTY(ss); + rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_message_hash, 0, + SSL_READER_CURRENT(&reader), hashLen, + ssl3_UpdateHandshakeHashes); + if (rv != SECSuccess) { + return SECFailure; + } + + /* And finally reinject the HRR. */ + rv = tls13_ConstructHelloRetryRequest(ss, cipherSuite, + selectedGroup, + cookie, cookieLen, + &messageBuf); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_server_hello, 0, + SSL_BUFFER_BASE(&messageBuf), + SSL_BUFFER_LEN(&messageBuf), + ssl3_UpdateHandshakeHashes); + sslBuffer_Clear(&messageBuf); + if (rv != SECSuccess) { + return SECFailure; + } } - /* Now reinject the message. */ - SSL_ASSERT_HASHES_EMPTY(ss); - rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_message_hash, 0, - SSL_READER_CURRENT(&reader), hashLen, - ssl3_UpdateHandshakeHashes); - if (rv != SECSuccess) { - return SECFailure; + if (previousEchHpkeCtx && echHpkeBuf.len) { + const SECItem hpkeItem = { siBuffer, CONST_CAST(unsigned char, echHpkeBuf.buf), + echHpkeBuf.len }; + hpkeContext = PK11_HPKE_ImportContext(&hpkeItem, NULL); + if (!hpkeContext) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } } - /* And finally reinject the HRR. */ - rv = tls13_ConstructHelloRetryRequest(ss, cipherSuite, - selectedGroup, - cookie, cookieLen, - &messageBuf); - if (rv != SECSuccess) { - return SECFailure; + if (previousEchConfigId && echConfigIdBuf.len) { + SECItem tmp = { siBuffer, NULL, 0 }; + rv = SECITEM_MakeItem(NULL, &tmp, echConfigIdBuf.buf, echConfigIdBuf.len); + if (rv != SECSuccess) { + PK11_HPKE_DestroyContext(hpkeContext, PR_TRUE); + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } + *previousEchConfigId = tmp; } - rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_server_hello, 0, - SSL_BUFFER_BASE(&messageBuf), - SSL_BUFFER_LEN(&messageBuf), - ssl3_UpdateHandshakeHashes); - sslBuffer_Clear(&messageBuf); - if (rv != SECSuccess) { - return SECFailure; + if (previousEchKdfId) { + *previousEchKdfId = echKdfId; + } + if (previousEchAeadId) { + *previousEchAeadId = echAeadId; + } + if (previousEchHpkeCtx) { + *previousEchHpkeCtx = hpkeContext; + } + if (previousCipherSuite) { + *previousCipherSuite = cipherSuite; + } + if (previousGroup) { + *previousGroup = selectedGroup; + } + if (previousEchOffered) { + *previousEchOffered = echConfigIdBuf.len > 0; } - - *previousCipherSuite = cipherSuite; - *previousGroup = selectedGroup; - *previousEchOffered = echConfigIdBuf.len > 0; return SECSuccess; } diff --git a/security/nss/lib/ssl/tls13hashstate.h b/security/nss/lib/ssl/tls13hashstate.h index 8126bd0db37c..48832f04a408 100644 --- a/security/nss/lib/ssl/tls13hashstate.h +++ b/security/nss/lib/ssl/tls13hashstate.h @@ -17,9 +17,14 @@ SECStatus tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGro const PRUint8 *appToken, unsigned int appTokenLen, PRUint8 *buf, unsigned int *len, unsigned int maxlen); SECStatus tls13_GetHrrCookieLength(sslSocket *ss, unsigned int *length); -SECStatus tls13_RecoverHashState(sslSocket *ss, - unsigned char *cookie, unsigned int cookieLen, - ssl3CipherSuite *previousCipherSuite, - const sslNamedGroupDef **previousGroup, - PRBool *previousEchOffered); +SECStatus tls13_HandleHrrCookie(sslSocket *ss, + unsigned char *cookie, unsigned int cookieLen, + ssl3CipherSuite *previousCipherSuite, + const sslNamedGroupDef **previousGroup, + PRBool *previousEchOffered, + HpkeKdfId *previousEchKdfId, + HpkeAeadId *previousEchAeadId, + SECItem *previousEchConfigId, + HpkeContext **previousEchHpkeCtx, + PRBool recoverHashState); #endif diff --git a/security/nss/lib/util/nssutil.h b/security/nss/lib/util/nssutil.h index 9503a5491450..3785f77236ff 100644 --- a/security/nss/lib/util/nssutil.h +++ b/security/nss/lib/util/nssutil.h @@ -19,12 +19,12 @@ * The format of the version string should be * ".[.[.]][ ]" */ -#define NSSUTIL_VERSION "3.61" +#define NSSUTIL_VERSION "3.62 Beta" #define NSSUTIL_VMAJOR 3 -#define NSSUTIL_VMINOR 61 +#define NSSUTIL_VMINOR 62 #define NSSUTIL_VPATCH 0 #define NSSUTIL_VBUILD 0 -#define NSSUTIL_BETA PR_FALSE +#define NSSUTIL_BETA PR_TRUE SEC_BEGIN_PROTOS diff --git a/security/nss/tests/chains/scenarios/nameconstraints.cfg b/security/nss/tests/chains/scenarios/nameconstraints.cfg index 4a149032b194..a2de4be44668 100644 --- a/security/nss/tests/chains/scenarios/nameconstraints.cfg +++ b/security/nss/tests/chains/scenarios/nameconstraints.cfg @@ -159,12 +159,20 @@ verify NameConstraints.dcissblocked:x verify NameConstraints.dcissallowed:x result pass -# Subject: "O = IPA.LOCAL 201901211552, CN = OCSP Subsystem" +# Subject: "O = IPA.LOCAL 20200120, CN = OCSP and IPSEC" +# EKUs: OCSPSigning,ipsecUser # # This tests that a non server certificate (i.e. id-kp-serverAuth # not present in EKU) does *NOT* have CN treated as dnsName for -# purposes of Name Constraints validation +# purposes of Name Constraints validation (certificateUsageStatusResponder) +# https://hg.mozilla.org/projects/nss/rev/0b30eb1c3650 verify NameConstraints.ocsp1:x usage 10 result pass +# This tests that a non server certificate (i.e. id-kp-serverAuth +# not present in EKU) does *NOT* have CN treated as dnsName for +# purposes of Name Constraints validation (certificateUsageIPsec) +verify NameConstraints.ocsp1:x + usage 12 + result pass diff --git a/security/nss/tests/libpkix/certs/NameConstraints.ipaca.cert b/security/nss/tests/libpkix/certs/NameConstraints.ipaca.cert index 6c7d68c770062d12f3a90693142e0a08f0f1e8d5..4a451f3429d25ab6d3a9cb00b2f005118f7cac08 100644 GIT binary patch literal 1000 zcmXqLVt!)K#B^o>GZP~d6DPy|^k+KryEhsc@Un4gwRyCC=VfH%W@Rw&GL$xuWMd9x z;Sv_|3~ss2{VNT8_F5TfH=&;qRy#BC7EfN$%!SY z3XY{E8Tmz-C6xvW;=EvOMg|6kmc}Mg68uIWGmMNZp#oI3t%*?y*)xo+49rc8{0s(7 zj9g4jjEoG=^1s)l`)r?{?G*K{efrz@^y51jSY#?Qrv7h?HS|xJwyR@Kb#XoSS;k@` zsnyQM3ryGy#WsHSVY$Dg^{G_h`%Mr31m-(W;crQ1^qduS#_$YF{p|^7gJ%S~&q!HW zm(3G@CaCp?<;oYmA#W!&GCx=N{6P0c-oNwp#+pmdoLYZ0GCC^kL&QoMpHn;Sj(>Qe z^hQm=O0D0%H-6Ih3kIU0fhJcnWwxL0RlXc-l2#_+w#?P(S@YRe@9B#!@pBZMNXePs z#64l9Oa=R2fe+QeDM|AR{Ml+s&wNX=u{yg}=;L43N9z-pu=4KysD4%b=6dyoUg<0! z52apDsQdXg`^iRm&1dslnV1eyHr_^;KV3M8$}QfyGzfK|alza>7@jL9aX)Co#pvT{I2vIrRn zEZ{TXY2wn$EJ)PL$xlwqL5^-<+66{8BSWu1di8`|O}m!dVwtxpYDH(l^O)n^EA4l3 z2Rcm5V9HPnT;@5OTT`YgmH*njX#oeAOsrSN+As3`-O|vv$fQhr`L8dpcAUF7(@-ym zp*GO$s#)TcbR|ob^_L{HZ|84+p?QqM+2n2c1(x(BcbIl*#%b-UdvaBi_t078nTmLQ-nt@LY0vHRrzwT}_?+z8Sn%C=^~-miMw^$+=01`oQa<@mMQC2xlwb3s zR5$K8d`&sCuu8 oe}au0v=w%&75+F|gzv*HC4Yeh^Q0HMr%t--6)E;wBf+r-0MfH^bpQYW literal 981 zcmXqLV!mk5#I$n(GZP~d6DPyP?%>QT)${udc-c6$+C196^D;8>ure4#8gd(OvN4CU zun99ch8hYR2!c2qJY0dLsi}FzIf;2GhJptCAVGE>4yU5b;-tj9R6_v+K9CR>4|`B* zih@UC2~32Uhdm^lEJ~c$2*@?GfN~8S4do4F!KMg{ zc?LM@`S?3K`Y0F~Kr|Veni?6(8OVSXFbj)1rxulDre!84mZT~;mX>7X7iE@I8ZF)m|AX~LmnPfe%eM6I^GMeF zcXz#9-ka~cH{1SyHu>Z82Xk&jXV2?gmv>9~vH$H98N)co%k1+79?ojx&Pp&oa_!Y= zrC*7=TGx7AC@x|>5yiUf(}j?NRLQ27cTR3Q>T2-z{hpf**1Heh^qI8tQ&5{=eUI(& zX(j)e*XWAATcndR_0*kHS^T22H{OxBV)g5N)8`%jtG@HID4t~vFx&0AXtK?fCU&ib zrT5PNj5L=pvl9tl5_Qb)_oQO>Q&XDezMK^6A$*pJnUR5UapPu##tq;&loe)V{LjK_ zzzn3oVK2+aBE}+;ACb16d+|br33?}XOux$iTjQ$rWq944Whmjji{otW#VY3>OxsQmaN=Ba+17VpG#&Sh2GVp$$WSsO6qZ{1W< zpJz8oXQjixf)hV~rt8Sadg-ivEXJ5T!Kr6c_bo@c4t?dtm8{mFZ!TOF4bwW?p&E-ydkx6^rp z?Q730In6YYgUp+EyLXxXtGP7ogR|!9t*-C0ZI$$-lpgPnYq_V-E%D;~%RQ%Ej?PWh l|Glxtu~Lg@4SqR+^h@+UWU>Jl5EVOEL_4u zo&k<}KK{;*J_<$#MurB4Mh1r52Am*CHesgFU_&_r84!nASkyVSs3bEjGdZy&Rl%{e zBqP5lv!v2ML7W$?4XE45(AXkMg5N0E2xy3rfhknPz!}v?A4HGcGgd4K(v^@jv) zmL50~D-!xco_7y#SzcoQfjs6(U%sr_&f9W2M)KsdhbtT7uEoeO)mImpQ+sj{igLVB>AI)1XefRPfudQ48_0+3n*=&Ei1qg3=lTl<~Y(B}1 zQL*!=#+QeytEbB_-aewy*Lluo!=Lw=UrrTWbEv+Ny0h&?Pmkq~32E1hg16uDJ@dL> zocVRdz6jpTUhaFf`#wp`nzwRF=G@0j%!~|-iyPM(G_C^2k*qKalL3PPA27&d`577i zv#>BTu^li_0P$5pd@%zNHV$nzMpjmKW==SZ-9Q$kf{#UvMMQOqLUvAW)HD%U@n}Oa z*$q-Ti906CFxl1fvvEQ+8Zff5Fac8yY9eB8Vq|34v3JKzi3PhN%-=a~n7-`jv3=pZ zU5iS!TOaW2G?ZU>^FV0Ew>x=7x*DB3Th70^F^!L3Grp1QB&YZ#u1#xXb~%T)XIHFF z_+lWvjFaW$4CiBlm)~^f>Byfxb?{tEz2TCK1xY88?-cCm8M^cx~~< xGP=@QO*h&|H7lts^nSpsmgGGLB|D~1KK)>OyPQb$tbn42PZpPpbPKr$001YfNPqwU delta 822 zcmdnP-o)-?(8OG4(8QFsfSHMriHVWhfR~L^tIebBJ1-+6H!FjIqoKTkEE{tu3zx8% zXMm%gkH52{kAjhbp{0SLk)fffsnJAl#dP14&5s*6u_R$DsM z{aE4ama4g6#l2(a?JmCFckJ)F`%|B=DXmC7aj3G_FMnA_V%+tu>!vcyYxJ*sa`}HT zkKd9jTpOlcRf^S(GT?G*;CNEIzT=(~|0UmfHYN3D{;4r4meVS4l?D0xcYP54$eh~w zWbMQpPlv);;m7BkpY(+Nfr{FZ3+azz*I9Tjd3Gr~;OE8n7A$_d)Hmz>ZnIyQx#zg; z-R5BVR}MB4Pi3vCo0Y$mp-T7L_NtO~S<6>`7oN0MbxKUd)CCW!FI-78cH=xzX~_Oc z@aLYYA_<*FCL3l(2FAsW?FNl42J*mAlvQStFc51H$&W}|&%Jn|!UVk&JEmXd|E=-X zX@>zn8>coKBP%Pr0V6956Dz~yC?>mlVUR9W79ImGHV%j^GbeJW1Cs(U)EOCca+k{I z{(O-oubQ^(^7)9Lj8T?{PdI89w5&XlcmClLNs%yt8o4jL54A3CyBM-!^9IkUPL95e zpV(Vgc5Gw*zRqT6zTd+O%XpJE?y;D7QDnl8X)zDA^`@+?|5&@e;^3(n|L1Di`>HhcKqS|(N-