Commit 7320df08 authored by padolph@netflix.com's avatar padolph@netflix.com

[webcrypto] Add raw symmetric key AES-KW wrap/unwrap for NSS.

BUG=245025
TEST=content_unittests --gtest_filter="WebCryptoImpl*"

Review URL: https://codereview.chromium.org/118623002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@255270 0039d316-1c4b-4281-b951-d872f2087c98
parent f1f523f9
......@@ -176,6 +176,25 @@ Status ExportKeyRaw(SymKey* key, blink::WebArrayBuffer* buffer);
// * |key| is non-null.
Status ExportKeySpki(PublicKey* key, blink::WebArrayBuffer* buffer);
// Preconditions:
// * |wrapping_key| is non-null
// * |key| is non-null
Status WrapSymKeyAesKw(SymKey* wrapping_key,
SymKey* key,
blink::WebArrayBuffer* buffer);
// Preconditions:
// * |wrapping_key| is non-null
// * |key| is non-null
// * |algorithm.id()| is for a symmetric key algorithm.
// * |wrapped_key_data| is at least 24 bytes and a multiple of 8 bytes
Status UnwrapSymKeyAesKw(const CryptoData& wrapped_key_data,
SymKey* wrapping_key,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key);
} // namespace platform
} // namespace webcrypto
......
......@@ -445,57 +445,67 @@ bool CreatePrivateKeyAlgorithm(const blink::WebCryptoAlgorithm& algorithm,
return CreatePublicKeyAlgorithm(algorithm, public_key.get(), key_algorithm);
}
} // namespace
Status ImportKeyRaw(const blink::WebCryptoAlgorithm& algorithm,
const CryptoData& key_data,
bool extractable,
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key) {
DCHECK(!algorithm.isNull());
// TODO(bryaneyler): Need to split handling for symmetric and asymmetric keys.
// Currently only supporting symmetric.
CK_MECHANISM_TYPE mechanism = CKM_INVALID_MECHANISM;
// The Default IV for AES-KW. See http://www.ietf.org/rfc/rfc3394.txt
// Section 2.2.3.1.
// TODO(padolph): Move to common place to be shared with OpenSSL implementation.
const unsigned char kAesIv[] = {0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6};
// Sets NSS CK_MECHANISM_TYPE and CK_FLAGS corresponding to the input Web Crypto
// algorithm ID.
Status WebCryptoAlgorithmToNssMechFlags(
const blink::WebCryptoAlgorithm& algorithm,
CK_MECHANISM_TYPE* mechanism,
CK_FLAGS* flags) {
// Flags are verified at the Blink layer; here the flags are set to all
// possible operations for this key type.
CK_FLAGS flags = 0;
// possible operations of a key for the input algorithm type.
switch (algorithm.id()) {
case blink::WebCryptoAlgorithmIdHmac: {
const blink::WebCryptoAlgorithm& hash = GetInnerHashAlgorithm(algorithm);
mechanism = WebCryptoHashToHMACMechanism(hash);
if (mechanism == CKM_INVALID_MECHANISM)
const blink::WebCryptoAlgorithm hash = GetInnerHashAlgorithm(algorithm);
*mechanism = WebCryptoHashToHMACMechanism(hash);
if (*mechanism == CKM_INVALID_MECHANISM)
return Status::ErrorUnsupported();
flags |= CKF_SIGN | CKF_VERIFY;
*flags = CKF_SIGN | CKF_VERIFY;
break;
}
case blink::WebCryptoAlgorithmIdAesCbc: {
mechanism = CKM_AES_CBC;
flags |= CKF_ENCRYPT | CKF_DECRYPT;
*mechanism = CKM_AES_CBC;
*flags = CKF_ENCRYPT | CKF_DECRYPT;
break;
}
case blink::WebCryptoAlgorithmIdAesKw: {
mechanism = CKM_NSS_AES_KEY_WRAP;
flags |= CKF_WRAP | CKF_WRAP;
*mechanism = CKM_NSS_AES_KEY_WRAP;
*flags = CKF_WRAP | CKF_WRAP;
break;
}
case blink::WebCryptoAlgorithmIdAesGcm: {
if (!g_aes_gcm_support.Get().IsSupported())
return Status::ErrorUnsupported();
mechanism = CKM_AES_GCM;
flags |= CKF_ENCRYPT | CKF_DECRYPT;
*mechanism = CKM_AES_GCM;
*flags = CKF_ENCRYPT | CKF_DECRYPT;
break;
}
default:
return Status::ErrorUnsupported();
}
return Status::Success();
}
} // namespace
Status ImportKeyRaw(const blink::WebCryptoAlgorithm& algorithm,
const CryptoData& key_data,
bool extractable,
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key) {
DCHECK(!algorithm.isNull());
DCHECK_NE(CKM_INVALID_MECHANISM, mechanism);
DCHECK_NE(0ul, flags);
CK_MECHANISM_TYPE mechanism;
CK_FLAGS flags;
Status status =
WebCryptoAlgorithmToNssMechFlags(algorithm, &mechanism, &flags);
if (status.IsError())
return status;
SECItem key_item = MakeSECItemForBuffer(key_data);
......@@ -1100,6 +1110,99 @@ Status ImportRsaPublicKey(const blink::WebCryptoAlgorithm& algorithm,
return Status::Success();
}
Status WrapSymKeyAesKw(SymKey* wrapping_key,
SymKey* key,
blink::WebArrayBuffer* buffer) {
// The data size must be at least 16 bytes and a multiple of 8 bytes.
// RFC 3394 does not specify a maximum allowed data length, but since only
// keys are being wrapped in this application (which are small), a reasonable
// max limit is whatever will fit into an unsigned. For the max size test,
// note that AES Key Wrap always adds 8 bytes to the input data size.
const unsigned int input_length = PK11_GetKeyLength(key->key());
if (input_length < 16)
return Status::ErrorDataTooSmall();
if (input_length > UINT_MAX - 8)
return Status::ErrorDataTooLarge();
if (input_length % 8)
return Status::ErrorInvalidAesKwDataLength();
SECItem iv_item = MakeSECItemForBuffer(CryptoData(kAesIv, sizeof(kAesIv)));
crypto::ScopedSECItem param_item(
PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP, &iv_item));
if (!param_item)
return Status::ErrorUnexpected();
const unsigned int output_length = input_length + 8;
*buffer = blink::WebArrayBuffer::create(output_length, 1);
unsigned char* buffer_data = reinterpret_cast<unsigned char*>(buffer->data());
SECItem wrapped_key_item = {siBuffer, buffer_data, output_length};
if (SECSuccess != PK11_WrapSymKey(CKM_NSS_AES_KEY_WRAP,
param_item.get(),
wrapping_key->key(),
key->key(),
&wrapped_key_item)) {
return Status::Error();
}
if (output_length != wrapped_key_item.len)
return Status::ErrorUnexpected();
return Status::Success();
}
Status UnwrapSymKeyAesKw(const CryptoData& wrapped_key_data,
SymKey* wrapping_key,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key) {
DCHECK_GE(wrapped_key_data.byte_length(), 24u);
DCHECK_EQ(wrapped_key_data.byte_length() % 8, 0u);
SECItem iv_item = MakeSECItemForBuffer(CryptoData(kAesIv, sizeof(kAesIv)));
crypto::ScopedSECItem param_item(
PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP, &iv_item));
if (!param_item)
return Status::ErrorUnexpected();
SECItem cipher_text = MakeSECItemForBuffer(wrapped_key_data);
// The plaintext length is always 64 bits less than the data size.
const unsigned int plaintext_length = wrapped_key_data.byte_length() - 8;
// Determine the proper NSS key properties from the input algorithm.
CK_MECHANISM_TYPE mechanism;
CK_FLAGS flags;
Status status =
WebCryptoAlgorithmToNssMechFlags(algorithm, &mechanism, &flags);
if (status.IsError())
return status;
crypto::ScopedPK11SymKey unwrapped_key(PK11_UnwrapSymKey(wrapping_key->key(),
CKM_NSS_AES_KEY_WRAP,
param_item.get(),
&cipher_text,
mechanism,
flags,
plaintext_length));
// TODO(padolph): Use NSS PORT_GetError() and friends to report a more
// accurate error, providing if doesn't leak any information to web pages
// about other web crypto users, key details, etc.
if (!unwrapped_key)
return Status::Error();
blink::WebCryptoKeyAlgorithm key_algorithm;
if (!CreateSecretKeyAlgorithm(algorithm, plaintext_length, &key_algorithm))
return Status::ErrorUnexpected();
*key = blink::WebCryptoKey::create(new SymKey(unwrapped_key.Pass()),
blink::WebCryptoKeyTypeSecret,
extractable,
key_algorithm,
usage_mask);
return Status::Success();
}
} // namespace platform
} // namespace webcrypto
......
......@@ -388,6 +388,23 @@ Status ExportKeySpki(PublicKey* key, blink::WebArrayBuffer* buffer) {
return Status::ErrorUnsupported();
}
Status WrapSymKeyAesKw(SymKey* wrapping_key,
SymKey* key,
blink::WebArrayBuffer* buffer) {
// TODO(eroman): http://crbug.com/267888
return Status::ErrorUnsupported();
}
Status UnwrapSymKeyAesKw(const CryptoData& wrapped_key_data,
SymKey* wrapping_key,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key) {
// TODO(eroman): http://crbug.com/267888
return Status::ErrorUnsupported();
}
} // namespace platform
} // namespace webcrypto
......
......@@ -480,6 +480,89 @@ Status VerifySignature(const blink::WebCryptoAlgorithm& algorithm,
}
}
Status WrapKey(blink::WebCryptoKeyFormat format,
const blink::WebCryptoKey& wrapping_key,
const blink::WebCryptoKey& key_to_wrap,
const blink::WebCryptoAlgorithm& wrapping_algorithm,
blink::WebArrayBuffer* buffer) {
if (!KeyUsageAllows(wrapping_key, blink::WebCryptoKeyUsageUnwrapKey))
return Status::ErrorUnexpected();
if (wrapping_algorithm.id() != wrapping_key.algorithm().id())
return Status::ErrorUnexpected();
// TODO (padolph): Handle formats other than raw
if (format != blink::WebCryptoKeyFormatRaw)
return Status::ErrorUnsupported();
// TODO (padolph): Handle key-to-wrap types other than secret/symmetric
if (key_to_wrap.type() != blink::WebCryptoKeyTypeSecret)
return Status::ErrorUnsupported();
platform::SymKey* platform_wrapping_key;
Status status = ToPlatformSymKey(wrapping_key, &platform_wrapping_key);
if (status.IsError())
return status;
platform::SymKey* platform_key;
status = ToPlatformSymKey(key_to_wrap, &platform_key);
if (status.IsError())
return status;
// TODO(padolph): Handle other wrapping algorithms
switch (wrapping_algorithm.id()) {
case blink::WebCryptoAlgorithmIdAesKw:
return platform::WrapSymKeyAesKw(
platform_wrapping_key, platform_key, buffer);
default:
return Status::ErrorUnsupported();
}
}
Status UnwrapKey(blink::WebCryptoKeyFormat format,
const CryptoData& wrapped_key_data,
const blink::WebCryptoKey& wrapping_key,
const blink::WebCryptoAlgorithm& wrapping_algorithm,
const blink::WebCryptoAlgorithm& algorithm_or_null,
bool extractable,
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key) {
if (!KeyUsageAllows(wrapping_key, blink::WebCryptoKeyUsageUnwrapKey))
return Status::ErrorUnexpected();
if (wrapping_algorithm.id() != wrapping_key.algorithm().id())
return Status::ErrorUnexpected();
// TODO(padolph): Handle formats other than raw
if (format != blink::WebCryptoKeyFormatRaw)
return Status::ErrorUnsupported();
// Must provide an algorithm when unwrapping a raw key
if (format == blink::WebCryptoKeyFormatRaw && algorithm_or_null.isNull())
return Status::ErrorMissingAlgorithmUnwrapRawKey();
platform::SymKey* platform_wrapping_key;
Status status = ToPlatformSymKey(wrapping_key, &platform_wrapping_key);
if (status.IsError())
return status;
// TODO(padolph): Handle other wrapping algorithms
switch (wrapping_algorithm.id()) {
case blink::WebCryptoAlgorithmIdAesKw: {
// AES-KW requires the wrapped key data size must be at least 24 bytes and
// also a multiple of 8 bytes.
if (wrapped_key_data.byte_length() < 24)
return Status::ErrorDataTooSmall();
if (wrapped_key_data.byte_length() % 8)
return Status::ErrorInvalidAesKwDataLength();
return platform::UnwrapSymKeyAesKw(wrapped_key_data,
platform_wrapping_key,
algorithm_or_null,
extractable,
usage_mask,
key);
}
default:
return Status::ErrorUnsupported();
}
}
} // namespace webcrypto
} // namespace content
......@@ -131,6 +131,23 @@ CONTENT_EXPORT Status
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key);
CONTENT_EXPORT Status
WrapKey(blink::WebCryptoKeyFormat format,
const blink::WebCryptoKey& wrapping_key,
const blink::WebCryptoKey& key_to_wrap,
const blink::WebCryptoAlgorithm& wrapping_algorithm,
blink::WebArrayBuffer* buffer);
CONTENT_EXPORT Status
UnwrapKey(blink::WebCryptoKeyFormat format,
const CryptoData& wrapped_key_data,
const blink::WebCryptoKey& wrapping_key,
const blink::WebCryptoAlgorithm& wrapping_algorithm,
const blink::WebCryptoAlgorithm& algorithm_or_null,
bool extractable,
blink::WebCryptoKeyUsageMask usage_mask,
blink::WebCryptoKey* key);
} // namespace webcrypto
} // namespace content
......
......@@ -112,6 +112,10 @@ Status Status::ErrorDataTooLarge() {
return Status("The provided data is too large");
}
Status Status::ErrorDataTooSmall() {
return Status("The provided data is too small");
}
Status Status::ErrorUnsupported() {
return Status("The requested operation is unsupported");
}
......@@ -126,6 +130,12 @@ Status Status::ErrorInvalidAesGcmTagLength() {
"bits");
}
Status Status::ErrorInvalidAesKwDataLength() {
return Status(
"The AES-KW input data length is invalid: not a multiple of 8 "
"bytes");
}
Status Status::ErrorGenerateKeyPublicExponent() {
return Status("The \"publicExponent\" is either empty, zero, or too large");
}
......@@ -136,6 +146,12 @@ Status Status::ErrorMissingAlgorithmImportRawKey() {
"raw-formatted key.");
}
Status Status::ErrorMissingAlgorithmUnwrapRawKey() {
return Status(
"The key's algorithm must be specified when unwrapping a "
"raw-formatted key.");
}
Status Status::ErrorImportRsaEmptyModulus() {
return Status("The modulus is empty");
}
......
......@@ -135,6 +135,11 @@ class CONTENT_EXPORT Status {
// key's modulus).
static Status ErrorDataTooLarge();
// The data provided to an encrypt/decrypt/sign/verify operation was too
// small. This usually represents an algorithm restriction (for instance
// AES-KW requires a minimum of 24 bytes input data).
static Status ErrorDataTooSmall();
// Something was unsupported or unimplemented. This can mean the algorithm in
// question was unsupported, some parameter combination was unsupported, or
// something has not yet been implemented.
......@@ -149,6 +154,10 @@ class CONTENT_EXPORT Status {
// not 32, 64, 96, 104, 112, 120, or 128.
static Status ErrorInvalidAesGcmTagLength();
// The input data given to an AES-KW encrypt/decrypt operation was not a
// multiple of 8 bytes, as required by RFC 3394.
static Status ErrorInvalidAesKwDataLength();
// The "publicExponent" used to generate a key was invalid: either no bytes
// were specified, or the number was too large to fit into an "unsigned long"
// (implemention limitation), or the exponent was zero.
......@@ -158,6 +167,10 @@ class CONTENT_EXPORT Status {
// is required.
static Status ErrorMissingAlgorithmImportRawKey();
// The algorithm was null when unwrapping a raw-formatted key. In this case it
// is required.
static Status ErrorMissingAlgorithmUnwrapRawKey();
// The modulus bytes were empty when importing an RSA public key.
static Status ErrorImportRsaEmptyModulus();
......
[
// AES-KW test vectors from http://www.ietf.org/rfc/rfc3394.txt
// 4.1 Wrap 128 bits of Key Data with a 128-bit KEK
{
"kek": "000102030405060708090A0B0C0D0E0F",
"key": "00112233445566778899AABBCCDDEEFF",
"ciphertext": "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5"
},
// 4.2 Wrap 128 bits of Key Data with a 192-bit KEK
{
"kek": "000102030405060708090A0B0C0D0E0F1011121314151617",
"key": "00112233445566778899AABBCCDDEEFF",
"ciphertext": "96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D"
},
// 4.3 Wrap 128 bits of Key Data with a 256-bit KEK
{
"kek": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"key": "00112233445566778899AABBCCDDEEFF",
"ciphertext": "64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7"
},
// 4.4 Wrap 192 bits of Key Data with a 192-bit KEK
{
"kek": "000102030405060708090A0B0C0D0E0F1011121314151617",
"key": "00112233445566778899AABBCCDDEEFF0001020304050607",
"ciphertext": "031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2"
},
// 4.5 Wrap 192 bits of Key Data with a 256-bit KEK
{
"kek": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"key": "00112233445566778899AABBCCDDEEFF0001020304050607",
"ciphertext": "A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1"
},
// 4.6 Wrap 256 bits of Key Data with a 256-bit KEK
{
"kek": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"key": "00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F",
"ciphertext": "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21"
}
]
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment