Skip to content

Commit

Permalink
Bug 1534468 - Expose ChaCha20 primitive through PKCS#11, r=ekr
Browse files Browse the repository at this point in the history
Summary:
This adds a "CTR" mode for ChaCha20.  This takes a composite 16 octet "IV",
which is internally decomposed into a nonce and counter.

This operates like a CTR mode cipher on arbitrary input, up to the ChaCha20
limit of 2^32 x 64 octet blocks.  The counter provided is a starting counter and
it is incremented if more than 64 octets of input is provided.

Reviewers: ekr

Tags: #secure-revision

Bug #: 1534468

Differential Revision: https://phabricator.services.mozilla.com/D23060

--HG--
extra : rebase_source : 64ebd50bab6111d980569d5127882aa2c8444507
  • Loading branch information
martinthomson committed Mar 11, 2019
1 parent 013792d commit e9fdd32
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 23 deletions.
129 changes: 108 additions & 21 deletions gtests/pk11_gtest/pk11_chacha20poly1305_unittest.cc
Expand Up @@ -8,6 +8,7 @@
#include "nss.h"
#include "pk11pub.h"
#include "sechash.h"
#include "secerr.h"

#include "cpputil.h"
#include "nss_scoped_ptrs.h"
Expand All @@ -17,10 +18,17 @@

namespace nss_test {

static const CK_MECHANISM_TYPE kMech = CKM_NSS_CHACHA20_POLY1305;
static const CK_MECHANISM_TYPE kMechXor = CKM_NSS_CHACHA20_CTR;
// Some test data for simple tests.
static const uint8_t kKeyData[32] = {'k'};
static const uint8_t kCtrNonce[16] = {'c', 0, 0, 0, 'n'};
static const uint8_t kData[16] = {'d'};

class Pkcs11ChaCha20Poly1305Test
: public ::testing::TestWithParam<chacha_testvector> {
public:
void EncryptDecrypt(PK11SymKey* symKey, const bool invalid_iv,
void EncryptDecrypt(const ScopedPK11SymKey& key, const bool invalid_iv,
const bool invalid_tag, const uint8_t* data,
size_t data_len, const uint8_t* aad, size_t aad_len,
const uint8_t* iv, size_t iv_len,
Expand All @@ -39,7 +47,7 @@ class Pkcs11ChaCha20Poly1305Test
// Encrypt.
unsigned int outputLen = 0;
std::vector<uint8_t> output(data_len + aead_params.ulTagLen);
SECStatus rv = PK11_Encrypt(symKey, mech, &params, output.data(),
SECStatus rv = PK11_Encrypt(key.get(), kMech, &params, output.data(),
&outputLen, output.size(), data, data_len);

// Return if encryption failure was expected due to invalid IV.
Expand All @@ -60,8 +68,9 @@ class Pkcs11ChaCha20Poly1305Test
// Decrypt.
unsigned int decryptedLen = 0;
std::vector<uint8_t> decrypted(data_len);
rv = PK11_Decrypt(symKey, mech, &params, decrypted.data(), &decryptedLen,
decrypted.size(), output.data(), outputLen);
rv =
PK11_Decrypt(key.get(), kMech, &params, decrypted.data(), &decryptedLen,
decrypted.size(), output.data(), outputLen);
EXPECT_EQ(rv, SECSuccess);

// Check the plaintext.
Expand All @@ -73,8 +82,9 @@ class Pkcs11ChaCha20Poly1305Test
if (outputLen != 0) {
std::vector<uint8_t> bogusCiphertext(output);
bogusCiphertext[0] ^= 0xff;
rv = PK11_Decrypt(symKey, mech, &params, decrypted.data(), &decryptedLen,
decrypted.size(), bogusCiphertext.data(), outputLen);
rv = PK11_Decrypt(key.get(), kMech, &params, decrypted.data(),
&decryptedLen, decrypted.size(), bogusCiphertext.data(),
outputLen);
EXPECT_NE(rv, SECSuccess);
}

Expand All @@ -83,8 +93,9 @@ class Pkcs11ChaCha20Poly1305Test
if (outputLen != 0) {
std::vector<uint8_t> bogusTag(output);
bogusTag[outputLen - 1] ^= 0xff;
rv = PK11_Decrypt(symKey, mech, &params, decrypted.data(), &decryptedLen,
decrypted.size(), bogusTag.data(), outputLen);
rv = PK11_Decrypt(key.get(), kMech, &params, decrypted.data(),
&decryptedLen, decrypted.size(), bogusTag.data(),
outputLen);
EXPECT_NE(rv, SECSuccess);
}

Expand All @@ -100,7 +111,7 @@ class Pkcs11ChaCha20Poly1305Test
bogusAeadParams.pNonce = toUcharPtr(bogusIV.data());
bogusIV[0] ^= 0xff;

rv = PK11_Decrypt(symKey, mech, &bogusParams, decrypted.data(),
rv = PK11_Decrypt(key.get(), kMech, &bogusParams, decrypted.data(),
&decryptedLen, data_len, output.data(), outputLen);
EXPECT_NE(rv, SECSuccess);
}
Expand All @@ -117,39 +128,38 @@ class Pkcs11ChaCha20Poly1305Test
bogusAeadParams.pAAD = toUcharPtr(bogusAAD.data());
bogusAAD[0] ^= 0xff;

rv = PK11_Decrypt(symKey, mech, &bogusParams, decrypted.data(),
rv = PK11_Decrypt(key.get(), kMech, &bogusParams, decrypted.data(),
&decryptedLen, data_len, output.data(), outputLen);
EXPECT_NE(rv, SECSuccess);
}
}

void EncryptDecrypt(const chacha_testvector testvector) {
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
SECItem keyItem = {siBuffer, toUcharPtr(testvector.Key.data()),
static_cast<unsigned int>(testvector.Key.size())};
SECItem key_item = {siBuffer, toUcharPtr(testvector.Key.data()),
static_cast<unsigned int>(testvector.Key.size())};

// Import key.
ScopedPK11SymKey symKey(PK11_ImportSymKey(
slot.get(), mech, PK11_OriginUnwrap, CKA_ENCRYPT, &keyItem, nullptr));
EXPECT_TRUE(!!symKey);
ScopedPK11SymKey key(PK11_ImportSymKey(slot.get(), kMech, PK11_OriginUnwrap,
CKA_ENCRYPT, &key_item, nullptr));
EXPECT_TRUE(!!key);

// Check.
EncryptDecrypt(symKey.get(), testvector.invalid_iv, testvector.invalid_tag,
EncryptDecrypt(key, testvector.invalid_iv, testvector.invalid_tag,
testvector.Data.data(), testvector.Data.size(),
testvector.AAD.data(), testvector.AAD.size(),
testvector.IV.data(), testvector.IV.size(),
testvector.CT.data(), testvector.CT.size());
}

protected:
CK_MECHANISM_TYPE mech = CKM_NSS_CHACHA20_POLY1305;
};

TEST_F(Pkcs11ChaCha20Poly1305Test, GenerateEncryptDecrypt) {
// Generate a random key.
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ScopedPK11SymKey symKey(PK11_KeyGen(slot.get(), mech, nullptr, 32, nullptr));
EXPECT_TRUE(!!symKey);
ScopedPK11SymKey key(PK11_KeyGen(slot.get(), kMech, nullptr, 32, nullptr));
EXPECT_TRUE(!!key);

// Generate random data.
std::vector<uint8_t> data(512);
Expand All @@ -168,8 +178,85 @@ TEST_F(Pkcs11ChaCha20Poly1305Test, GenerateEncryptDecrypt) {
EXPECT_EQ(rv, SECSuccess);

// Check.
EncryptDecrypt(symKey.get(), false, false, data.data(), data.size(),
aad.data(), aad.size(), iv.data(), iv.size());
EncryptDecrypt(key, false, false, data.data(), data.size(), aad.data(),
aad.size(), iv.data(), iv.size());
}

TEST_F(Pkcs11ChaCha20Poly1305Test, Xor) {
static const uint8_t kExpected[sizeof(kData)] = {
0xd8, 0x15, 0xd3, 0xb3, 0xe9, 0x34, 0x3b, 0x7a,
0x24, 0xf6, 0x5f, 0xd7, 0x95, 0x3d, 0xd3, 0x51};

ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
SECItem keyItem = {siBuffer, toUcharPtr(kKeyData),
static_cast<unsigned int>(sizeof(kKeyData))};
ScopedPK11SymKey key(PK11_ImportSymKey(
slot.get(), kMechXor, PK11_OriginUnwrap, CKA_ENCRYPT, &keyItem, nullptr));
EXPECT_TRUE(!!key);

SECItem ctr_nonce_item = {siBuffer, toUcharPtr(kCtrNonce),
static_cast<unsigned int>(sizeof(kCtrNonce))};
uint8_t output[sizeof(kData)];
unsigned int output_len = 88; // This should be overwritten.
SECStatus rv =
PK11_Encrypt(key.get(), kMechXor, &ctr_nonce_item, output, &output_len,
sizeof(output), kData, sizeof(kData));
ASSERT_EQ(SECSuccess, rv);
ASSERT_EQ(sizeof(kExpected), static_cast<size_t>(output_len));
EXPECT_EQ(0, memcmp(kExpected, output, sizeof(kExpected)));

// Decrypting has the same effect.
rv = PK11_Decrypt(key.get(), kMechXor, &ctr_nonce_item, output, &output_len,
sizeof(output), kData, sizeof(kData));
ASSERT_EQ(SECSuccess, rv);
ASSERT_EQ(sizeof(kData), static_cast<size_t>(output_len));
EXPECT_EQ(0, memcmp(kExpected, output, sizeof(kExpected)));

// Operating in reverse too.
rv = PK11_Encrypt(key.get(), kMechXor, &ctr_nonce_item, output, &output_len,
sizeof(output), kExpected, sizeof(kExpected));
ASSERT_EQ(SECSuccess, rv);
ASSERT_EQ(sizeof(kExpected), static_cast<size_t>(output_len));
EXPECT_EQ(0, memcmp(kData, output, sizeof(kData)));
}

// This test just ensures that a key can be generated for use with the XOR
// function. The result is random and therefore cannot be checked.
TEST_F(Pkcs11ChaCha20Poly1305Test, GenerateXor) {
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ScopedPK11SymKey key(PK11_KeyGen(slot.get(), kMech, nullptr, 32, nullptr));
EXPECT_TRUE(!!key);

SECItem ctr_nonce_item = {siBuffer, toUcharPtr(kCtrNonce),
static_cast<unsigned int>(sizeof(kCtrNonce))};
uint8_t output[sizeof(kData)];
unsigned int output_len = 88; // This should be overwritten.
SECStatus rv =
PK11_Encrypt(key.get(), kMechXor, &ctr_nonce_item, output, &output_len,
sizeof(output), kData, sizeof(kData));
ASSERT_EQ(SECSuccess, rv);
ASSERT_EQ(sizeof(kData), static_cast<size_t>(output_len));
}

TEST_F(Pkcs11ChaCha20Poly1305Test, XorInvalidParams) {
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ScopedPK11SymKey key(PK11_KeyGen(slot.get(), kMech, nullptr, 32, nullptr));
EXPECT_TRUE(!!key);

SECItem ctr_nonce_item = {siBuffer, toUcharPtr(kCtrNonce),
static_cast<unsigned int>(sizeof(kCtrNonce)) - 1};
uint8_t output[sizeof(kData)];
unsigned int output_len = 88;
SECStatus rv =
PK11_Encrypt(key.get(), kMechXor, &ctr_nonce_item, output, &output_len,
sizeof(output), kData, sizeof(kData));
EXPECT_EQ(SECFailure, rv);

ctr_nonce_item.data = nullptr;
rv = PK11_Encrypt(key.get(), kMechXor, &ctr_nonce_item, output, &output_len,
sizeof(output), kData, sizeof(kData));
EXPECT_EQ(SECFailure, rv);
EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
}

TEST_P(Pkcs11ChaCha20Poly1305Test, TestVectors) { EncryptDecrypt(GetParam()); }
Expand Down
4 changes: 4 additions & 0 deletions lib/freebl/blapi.h
Expand Up @@ -1013,6 +1013,10 @@ extern SECStatus ChaCha20Poly1305_Open(
const unsigned char *nonce, unsigned int nonceLen,
const unsigned char *ad, unsigned int adLen);

extern SECStatus ChaCha20_Xor(
unsigned char *output, const unsigned char *block, unsigned int len,
const unsigned char *k, const unsigned char *nonce, PRUint32 ctr);

/******************************************/
/*
** MD5 secure hash function
Expand Down
23 changes: 23 additions & 0 deletions lib/freebl/chacha20poly1305.c
Expand Up @@ -170,6 +170,24 @@ ChaCha20Xor(uint8_t *output, uint8_t *block, uint32_t len, uint8_t *k,
}
#endif /* NSS_DISABLE_CHACHAPOLY */

SECStatus
ChaCha20_Xor(unsigned char *output, const unsigned char *block, unsigned int len,
const unsigned char *k, const unsigned char *nonce, PRUint32 ctr)
{
#ifdef NSS_DISABLE_CHACHAPOLY
return SECFailure;
#else
// ChaCha has a 64 octet block, with a 32-bit block counter.
if (len >= (1ULL << (6 + 32))) {
PORT_SetError(SEC_ERROR_INPUT_LEN);
return SECFailure;
}
ChaCha20Xor(output, (uint8_t *)block, len, (uint8_t *)k,
(uint8_t *)nonce, ctr);
return SECSuccess;
#endif
}

SECStatus
ChaCha20Poly1305_Seal(const ChaCha20Poly1305Context *ctx, unsigned char *output,
unsigned int *outputLen, unsigned int maxOutputLen,
Expand All @@ -187,6 +205,11 @@ ChaCha20Poly1305_Seal(const ChaCha20Poly1305Context *ctx, unsigned char *output,
PORT_SetError(SEC_ERROR_INPUT_LEN);
return SECFailure;
}
// ChaCha has a 64 octet block, with a 32-bit block counter.
if (inputLen >= (1ULL << (6 + 32))) {
PORT_SetError(SEC_ERROR_INPUT_LEN);
return SECFailure;
}
*outputLen = inputLen + ctx->tagLen;
if (maxOutputLen < *outputLen) {
PORT_SetError(SEC_ERROR_OUTPUT_LEN);
Expand Down
6 changes: 5 additions & 1 deletion lib/freebl/ldvector.c
Expand Up @@ -313,10 +313,14 @@ static const struct FREEBLVectorStr vector =
BLAKE2B_End,
BLAKE2B_FlattenSize,
BLAKE2B_Flatten,
BLAKE2B_Resurrect
BLAKE2B_Resurrect,

/* End of Version 3.020 */

ChaCha20_Xor

/* End of version 3.021 */

};

const FREEBLVector*
Expand Down
10 changes: 10 additions & 0 deletions lib/freebl/loader.c
Expand Up @@ -2060,6 +2060,16 @@ EC_CopyParams(PLArenaPool *arena, ECParams *dstParams,
return (vector->p_EC_CopyParams)(arena, dstParams, srcParams);
}

SECStatus
ChaCha20_Xor(unsigned char *output, const unsigned char *block, unsigned int len,
const unsigned char *k, const unsigned char *nonce, PRUint32 ctr)
{
if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) {
return SECFailure;
}
return (vector->p_ChaCha20_Xor)(output, block, len, k, nonce, ctr);
}

SECStatus
ChaCha20Poly1305_InitContext(ChaCha20Poly1305Context *ctx,
const unsigned char *key, unsigned int keyLen,
Expand Down
8 changes: 7 additions & 1 deletion lib/freebl/loader.h
Expand Up @@ -10,7 +10,7 @@

#include "blapi.h"

#define FREEBL_VERSION 0x0314
#define FREEBL_VERSION 0x0315

struct FREEBLVectorStr {

Expand Down Expand Up @@ -759,6 +759,12 @@ struct FREEBLVectorStr {

/* Version 3.020 came to here */

SECStatus (*p_ChaCha20_Xor)(unsigned char *output, const unsigned char *block,
unsigned int len, const unsigned char *k,
const unsigned char *nonce, PRUint32 ctr);

/* Version 3.021 came to here */

/* Add new function pointers at the end of this struct and bump
* FREEBL_VERSION at the beginning of this file. */
};
Expand Down
9 changes: 9 additions & 0 deletions lib/pk11wrap/pk11mech.c
Expand Up @@ -226,6 +226,7 @@ PK11_GetKeyType(CK_MECHANISM_TYPE type, unsigned long len)
return CKK_CAMELLIA;
case CKM_NSS_CHACHA20_POLY1305:
case CKM_NSS_CHACHA20_KEY_GEN:
case CKM_NSS_CHACHA20_CTR:
return CKK_NSS_CHACHA20;
case CKM_AES_ECB:
case CKM_AES_CBC:
Expand Down Expand Up @@ -440,6 +441,7 @@ PK11_GetKeyGenWithSize(CK_MECHANISM_TYPE type, int size)
case CKM_CAMELLIA_KEY_GEN:
return CKM_CAMELLIA_KEY_GEN;
case CKM_NSS_CHACHA20_POLY1305:
case CKM_NSS_CHACHA20_CTR:
return CKM_NSS_CHACHA20_KEY_GEN;
case CKM_AES_ECB:
case CKM_AES_CBC:
Expand Down Expand Up @@ -730,6 +732,9 @@ PK11_GetBlockSize(CK_MECHANISM_TYPE type, SECItem *params)
case CKM_RSA_X_509:
/*actually it's the modulus length of the key!*/
return -1; /* failure */
case CKM_NSS_CHACHA20_POLY1305:
case CKM_NSS_CHACHA20_CTR:
return 64;
default:
return pk11_lookup(type)->blockSize;
}
Expand Down Expand Up @@ -784,12 +789,16 @@ PK11_GetIVLength(CK_MECHANISM_TYPE type)
case CKM_CAST3_CBC_PAD:
case CKM_CAST5_CBC_PAD:
return 8;
case CKM_AES_GCM:
case CKM_NSS_CHACHA20_POLY1305:
return 12;
case CKM_SEED_CBC:
case CKM_SEED_CBC_PAD:
case CKM_CAMELLIA_CBC:
case CKM_CAMELLIA_CBC_PAD:
case CKM_AES_CBC:
case CKM_AES_CBC_PAD:
case CKM_NSS_CHACHA20_CTR:
return 16;
case CKM_SKIPJACK_CBC64:
case CKM_SKIPJACK_ECB64:
Expand Down
1 change: 1 addition & 0 deletions lib/softoken/pkcs11.c
Expand Up @@ -346,6 +346,7 @@ static const struct mechanismList mechanisms[] = {
/* ------------------------- ChaCha20 Operations ---------------------- */
{ CKM_NSS_CHACHA20_KEY_GEN, { 32, 32, CKF_GENERATE }, PR_TRUE },
{ CKM_NSS_CHACHA20_POLY1305, { 32, 32, CKF_EN_DE }, PR_TRUE },
{ CKM_NSS_CHACHA20_CTR, { 32, 32, CKF_EN_DE }, PR_TRUE },
#endif /* NSS_DISABLE_CHACHAPOLY */
/* ------------------------- Hashing Operations ----------------------- */
{ CKM_MD2, { 0, 0, CKF_DIGEST }, PR_FALSE },
Expand Down

0 comments on commit e9fdd32

Please sign in to comment.