summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/ssl/sslprimitive.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/ssl/sslprimitive.c')
-rw-r--r--security/nss/lib/ssl/sslprimitive.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/security/nss/lib/ssl/sslprimitive.c b/security/nss/lib/ssl/sslprimitive.c
new file mode 100644
index 0000000000..2afecfb165
--- /dev/null
+++ b/security/nss/lib/ssl/sslprimitive.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * SSL Primitives: Public HKDF and AEAD Functions
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "blapit.h"
+#include "keyhi.h"
+#include "pk11pub.h"
+#include "sechash.h"
+#include "ssl.h"
+#include "sslexp.h"
+#include "sslerr.h"
+#include "sslproto.h"
+
+#include "sslimpl.h"
+#include "tls13con.h"
+#include "tls13hkdf.h"
+
+struct SSLAeadContextStr {
+ /* sigh, the API creates a single context, but then uses either encrypt
+ * and decrypt on that context. We should take an encrypt/decrypt
+ * variable here, but for now create two contexts. */
+ PK11Context *encryptContext;
+ PK11Context *decryptContext;
+ int tagLen;
+ int ivLen;
+ unsigned char iv[MAX_IV_LENGTH];
+};
+
+SECStatus
+SSLExp_MakeVariantAead(PRUint16 version, PRUint16 cipherSuite, SSLProtocolVariant variant,
+ PK11SymKey *secret, const char *labelPrefix,
+ unsigned int labelPrefixLen, SSLAeadContext **ctx)
+{
+ SSLAeadContext *out = NULL;
+ char label[255]; // Maximum length label.
+ static const char *const keySuffix = "key";
+ static const char *const ivSuffix = "iv";
+ CK_MECHANISM_TYPE mech;
+ SECItem nullParams = { siBuffer, NULL, 0 };
+ PK11SymKey *key = NULL;
+
+ PORT_Assert(strlen(keySuffix) >= strlen(ivSuffix));
+ if (secret == NULL || ctx == NULL ||
+ (labelPrefix == NULL && labelPrefixLen > 0) ||
+ labelPrefixLen + strlen(keySuffix) > sizeof(label)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ goto loser;
+ }
+
+ SSLHashType hash;
+ const ssl3BulkCipherDef *cipher;
+ SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+ &hash, &cipher);
+ if (rv != SECSuccess) {
+ goto loser; /* Code already set. */
+ }
+
+ out = PORT_ZNew(SSLAeadContext);
+ if (out == NULL) {
+ goto loser;
+ }
+ mech = ssl3_Alg2Mech(cipher->calg);
+ out->ivLen = cipher->iv_size + cipher->explicit_nonce_size;
+ out->tagLen = cipher->tag_size;
+
+ memcpy(label, labelPrefix, labelPrefixLen);
+ memcpy(label + labelPrefixLen, ivSuffix, strlen(ivSuffix));
+ unsigned int labelLen = labelPrefixLen + strlen(ivSuffix);
+ unsigned int ivLen = cipher->iv_size + cipher->explicit_nonce_size;
+ rv = tls13_HkdfExpandLabelRaw(secret, hash,
+ NULL, 0, // Handshake hash.
+ label, labelLen, variant,
+ out->iv, ivLen);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ memcpy(label + labelPrefixLen, keySuffix, strlen(keySuffix));
+ labelLen = labelPrefixLen + strlen(keySuffix);
+ rv = tls13_HkdfExpandLabel(secret, hash,
+ NULL, 0, // Handshake hash.
+ label, labelLen, mech, cipher->key_size,
+ variant, &key);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* We really need to change the API to Create a context for each
+ * encrypt and decrypt rather than a single call that does both. it's
+ * almost certain that the underlying application tries to use the same
+ * context for both. */
+ out->encryptContext = PK11_CreateContextBySymKey(mech,
+ CKA_NSS_MESSAGE | CKA_ENCRYPT,
+ key, &nullParams);
+ if (out->encryptContext == NULL) {
+ goto loser;
+ }
+
+ out->decryptContext = PK11_CreateContextBySymKey(mech,
+ CKA_NSS_MESSAGE | CKA_DECRYPT,
+ key, &nullParams);
+ if (out->decryptContext == NULL) {
+ goto loser;
+ }
+
+ PK11_FreeSymKey(key);
+ *ctx = out;
+ return SECSuccess;
+
+loser:
+ PK11_FreeSymKey(key);
+ SSLExp_DestroyAead(out);
+ return SECFailure;
+}
+
+SECStatus
+SSLExp_MakeAead(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *secret,
+ const char *labelPrefix, unsigned int labelPrefixLen, SSLAeadContext **ctx)
+{
+ return SSLExp_MakeVariantAead(version, cipherSuite, ssl_variant_stream, secret,
+ labelPrefix, labelPrefixLen, ctx);
+}
+
+SECStatus
+SSLExp_DestroyAead(SSLAeadContext *ctx)
+{
+ if (!ctx) {
+ return SECSuccess;
+ }
+ if (ctx->encryptContext) {
+ PK11_DestroyContext(ctx->encryptContext, PR_TRUE);
+ }
+ if (ctx->decryptContext) {
+ PK11_DestroyContext(ctx->decryptContext, PR_TRUE);
+ }
+
+ PORT_ZFree(ctx, sizeof(*ctx));
+ return SECSuccess;
+}
+
+/* Bug 1529440 exists to refactor this and the other AEAD uses. */
+static SECStatus
+ssl_AeadInner(const SSLAeadContext *ctx, PK11Context *context,
+ PRBool decrypt, PRUint64 counter,
+ const PRUint8 *aad, unsigned int aadLen,
+ const PRUint8 *in, unsigned int inLen,
+ PRUint8 *out, unsigned int *outLen, unsigned int maxOut)
+{
+ if (ctx == NULL || (aad == NULL && aadLen > 0) || in == NULL ||
+ out == NULL || outLen == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ // Setup the nonce.
+ PRUint8 nonce[sizeof(counter)] = { 0 };
+ sslBuffer nonceBuf = SSL_BUFFER_FIXED(nonce, sizeof(counter));
+ SECStatus rv = sslBuffer_AppendNumber(&nonceBuf, counter, sizeof(counter));
+ if (rv != SECSuccess) {
+ PORT_Assert(0);
+ return SECFailure;
+ }
+ /* at least on encrypt, we should not be using CKG_NO_GENERATE, but
+ * the current experimental API has the application tracking the counter
+ * rather than token. We should look at the QUIC code and see if the
+ * counter can be moved internally where it belongs. That would
+ * also get rid of the formatting code above and have the API
+ * call tls13_AEAD directly in SSLExp_Aead* */
+ return tls13_AEAD(context, decrypt, CKG_NO_GENERATE, 0, ctx->iv, NULL,
+ ctx->ivLen, nonce, sizeof(counter), aad, aadLen,
+ out, outLen, maxOut, ctx->tagLen, in, inLen);
+}
+
+SECStatus
+SSLExp_AeadEncrypt(const SSLAeadContext *ctx, PRUint64 counter,
+ const PRUint8 *aad, unsigned int aadLen,
+ const PRUint8 *plaintext, unsigned int plaintextLen,
+ PRUint8 *out, unsigned int *outLen, unsigned int maxOut)
+{
+ // false == encrypt
+ return ssl_AeadInner(ctx, ctx->encryptContext, PR_FALSE, counter,
+ aad, aadLen, plaintext, plaintextLen,
+ out, outLen, maxOut);
+}
+
+SECStatus
+SSLExp_AeadDecrypt(const SSLAeadContext *ctx, PRUint64 counter,
+ const PRUint8 *aad, unsigned int aadLen,
+ const PRUint8 *ciphertext, unsigned int ciphertextLen,
+ PRUint8 *out, unsigned int *outLen, unsigned int maxOut)
+{
+ // true == decrypt
+ return ssl_AeadInner(ctx, ctx->decryptContext, PR_TRUE, counter,
+ aad, aadLen, ciphertext, ciphertextLen,
+ out, outLen, maxOut);
+}
+
+SECStatus
+SSLExp_HkdfExtract(PRUint16 version, PRUint16 cipherSuite,
+ PK11SymKey *salt, PK11SymKey *ikm, PK11SymKey **keyp)
+{
+ if (keyp == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ SSLHashType hash;
+ SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+ &hash, NULL);
+ if (rv != SECSuccess) {
+ return SECFailure; /* Code already set. */
+ }
+ return tls13_HkdfExtract(salt, ikm, hash, keyp);
+}
+
+SECStatus
+SSLExp_HkdfExpandLabel(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *prk,
+ const PRUint8 *hsHash, unsigned int hsHashLen,
+ const char *label, unsigned int labelLen, PK11SymKey **keyp)
+{
+ return SSLExp_HkdfVariantExpandLabel(version, cipherSuite, prk, hsHash, hsHashLen,
+ label, labelLen, ssl_variant_stream, keyp);
+}
+
+SECStatus
+SSLExp_HkdfVariantExpandLabel(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *prk,
+ const PRUint8 *hsHash, unsigned int hsHashLen,
+ const char *label, unsigned int labelLen,
+ SSLProtocolVariant variant, PK11SymKey **keyp)
+{
+ if (prk == NULL || keyp == NULL ||
+ label == NULL || labelLen == 0) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ SSLHashType hash;
+ SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+ &hash, NULL);
+ if (rv != SECSuccess) {
+ return SECFailure; /* Code already set. */
+ }
+ return tls13_HkdfExpandLabel(prk, hash, hsHash, hsHashLen, label, labelLen,
+ CKM_HKDF_DERIVE,
+ tls13_GetHashSizeForHash(hash), variant, keyp);
+}
+
+SECStatus
+SSLExp_HkdfExpandLabelWithMech(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *prk,
+ const PRUint8 *hsHash, unsigned int hsHashLen,
+ const char *label, unsigned int labelLen,
+ CK_MECHANISM_TYPE mech, unsigned int keySize,
+ PK11SymKey **keyp)
+{
+ return SSLExp_HkdfVariantExpandLabelWithMech(version, cipherSuite, prk, hsHash, hsHashLen,
+ label, labelLen, mech, keySize,
+ ssl_variant_stream, keyp);
+}
+
+SECStatus
+SSLExp_HkdfVariantExpandLabelWithMech(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *prk,
+ const PRUint8 *hsHash, unsigned int hsHashLen,
+ const char *label, unsigned int labelLen,
+ CK_MECHANISM_TYPE mech, unsigned int keySize,
+ SSLProtocolVariant variant, PK11SymKey **keyp)
+{
+ if (prk == NULL || keyp == NULL ||
+ label == NULL || labelLen == 0 ||
+ mech == CKM_INVALID_MECHANISM || keySize == 0) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ SSLHashType hash;
+ SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+ &hash, NULL);
+ if (rv != SECSuccess) {
+ return SECFailure; /* Code already set. */
+ }
+ return tls13_HkdfExpandLabel(prk, hash, hsHash, hsHashLen, label, labelLen,
+ mech, keySize, variant, keyp);
+}
+
+SECStatus
+ssl_CreateMaskingContextInner(PRUint16 version, PRUint16 cipherSuite,
+ SSLProtocolVariant variant,
+ PK11SymKey *secret,
+ const char *label,
+ unsigned int labelLen,
+ SSLMaskingContext **ctx)
+{
+ if (!secret || !ctx || (!label && labelLen)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ SSLMaskingContext *out = PORT_ZNew(SSLMaskingContext);
+ if (out == NULL) {
+ goto loser;
+ }
+
+ SSLHashType hash;
+ const ssl3BulkCipherDef *cipher;
+ SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+ &hash, &cipher);
+ if (rv != SECSuccess) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ goto loser; /* Code already set. */
+ }
+
+ out->mech = tls13_SequenceNumberEncryptionMechanism(cipher->calg);
+ if (out->mech == CKM_INVALID_MECHANISM) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ goto loser;
+ }
+
+ // Derive the masking key
+ rv = tls13_HkdfExpandLabel(secret, hash,
+ NULL, 0, // Handshake hash.
+ label, labelLen,
+ out->mech,
+ cipher->key_size, variant,
+ &out->secret);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ out->version = version;
+ out->cipherSuite = cipherSuite;
+
+ *ctx = out;
+ return SECSuccess;
+loser:
+ SSLExp_DestroyMaskingContext(out);
+ return SECFailure;
+}
+
+SECStatus
+ssl_CreateMaskInner(SSLMaskingContext *ctx, const PRUint8 *sample,
+ unsigned int sampleLen, PRUint8 *outMask,
+ unsigned int maskLen)
+{
+ if (!ctx || !sample || !sampleLen || !outMask || !maskLen) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (ctx->secret == NULL) {
+ PORT_SetError(SEC_ERROR_NO_KEY);
+ return SECFailure;
+ }
+
+ SECStatus rv = SECFailure;
+ unsigned int outMaskLen = 0;
+ int paramLen = 0;
+
+ /* Internal output len/buf, for use if the caller allocated and requested
+ * less than one block of output. |oneBlock| should have size equal to the
+ * largest block size supported below. */
+ PRUint8 oneBlock[AES_BLOCK_SIZE];
+ PRUint8 *outMask_ = outMask;
+ unsigned int maskLen_ = maskLen;
+
+ switch (ctx->mech) {
+ case CKM_AES_ECB:
+ if (sampleLen < AES_BLOCK_SIZE) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (maskLen_ < AES_BLOCK_SIZE) {
+ outMask_ = oneBlock;
+ maskLen_ = sizeof(oneBlock);
+ }
+ rv = PK11_Encrypt(ctx->secret,
+ ctx->mech,
+ NULL,
+ outMask_, &outMaskLen, maskLen_,
+ sample, AES_BLOCK_SIZE);
+ if (rv == SECSuccess &&
+ maskLen < AES_BLOCK_SIZE) {
+ memcpy(outMask, outMask_, maskLen);
+ }
+ break;
+ case CKM_NSS_CHACHA20_CTR:
+ paramLen = 16;
+ /* fall through */
+ case CKM_CHACHA20:
+ paramLen = (paramLen) ? paramLen : sizeof(CK_CHACHA20_PARAMS);
+ if (sampleLen < paramLen) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ SECItem param;
+ param.type = siBuffer;
+ param.len = paramLen;
+ param.data = (PRUint8 *)sample; // const-cast :(
+ unsigned char zeros[128] = { 0 };
+
+ if (maskLen > sizeof(zeros)) {
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+
+ rv = PK11_Encrypt(ctx->secret,
+ ctx->mech,
+ &param,
+ outMask, &outMaskLen,
+ maskLen,
+ zeros, maskLen);
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (rv != SECSuccess) {
+ PORT_SetError(SEC_ERROR_PKCS11_FUNCTION_FAILED);
+ return SECFailure;
+ }
+
+ // Ensure we produced at least as much material as requested.
+ if (outMaskLen < maskLen) {
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus
+ssl_DestroyMaskingContextInner(SSLMaskingContext *ctx)
+{
+ if (!ctx) {
+ return SECSuccess;
+ }
+
+ PK11_FreeSymKey(ctx->secret);
+ PORT_ZFree(ctx, sizeof(*ctx));
+ return SECSuccess;
+}
+
+SECStatus
+SSLExp_CreateMask(SSLMaskingContext *ctx, const PRUint8 *sample,
+ unsigned int sampleLen, PRUint8 *outMask,
+ unsigned int maskLen)
+{
+ return ssl_CreateMaskInner(ctx, sample, sampleLen, outMask, maskLen);
+}
+
+SECStatus
+SSLExp_CreateMaskingContext(PRUint16 version, PRUint16 cipherSuite,
+ PK11SymKey *secret,
+ const char *label,
+ unsigned int labelLen,
+ SSLMaskingContext **ctx)
+{
+ return ssl_CreateMaskingContextInner(version, cipherSuite, ssl_variant_stream, secret,
+ label, labelLen, ctx);
+}
+
+SECStatus
+SSLExp_CreateVariantMaskingContext(PRUint16 version, PRUint16 cipherSuite,
+ SSLProtocolVariant variant,
+ PK11SymKey *secret,
+ const char *label,
+ unsigned int labelLen,
+ SSLMaskingContext **ctx)
+{
+ return ssl_CreateMaskingContextInner(version, cipherSuite, variant, secret,
+ label, labelLen, ctx);
+}
+
+SECStatus
+SSLExp_DestroyMaskingContext(SSLMaskingContext *ctx)
+{
+ return ssl_DestroyMaskingContextInner(ctx);
+}