Skip to content

Commit

Permalink
crypto: allow length=0 for HKDF and PBKDF2 in SubtleCrypto.deriveBits
Browse files Browse the repository at this point in the history
PR-URL: #55866
Reviewed-By: Matthew Aitken <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: Jason Zhang <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Tobias Nießen <[email protected]>
  • Loading branch information
panva authored Nov 17, 2024
1 parent b1ee186 commit 1618463
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 31 deletions.
3 changes: 2 additions & 1 deletion lib/internal/crypto/hkdf.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
ArrayBuffer,
FunctionPrototypeCall,
} = primordials;

Expand Down Expand Up @@ -141,7 +142,7 @@ async function hkdfDeriveBits(algorithm, baseKey, length) {
const { hash, salt, info } = algorithm;

if (length === 0)
throw lazyDOMException('length cannot be zero', 'OperationError');
return new ArrayBuffer(0);
if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError');
if (length % 8) {
Expand Down
7 changes: 3 additions & 4 deletions lib/internal/crypto/pbkdf2.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
ArrayBuffer,
FunctionPrototypeCall,
} = primordials;

Expand Down Expand Up @@ -98,10 +99,8 @@ async function pbkdf2DeriveBits(algorithm, baseKey, length) {
'iterations cannot be zero',
'OperationError');

const raw = baseKey[kKeyObject].export();

if (length === 0)
throw lazyDOMException('length cannot be zero', 'OperationError');
return new ArrayBuffer(0);
if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError');
if (length % 8) {
Expand All @@ -113,7 +112,7 @@ async function pbkdf2DeriveBits(algorithm, baseKey, length) {
let result;
try {
result = await pbkdf2Promise(
raw, salt, iterations, length / 8, normalizeHashName(hash.name),
baseKey[kKeyObject].export(), salt, iterations, length / 8, normalizeHashName(hash.name),
);
} catch (err) {
throw lazyDOMException(
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/wpt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Last update:
- user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing
- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi
- wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/5f0f4ac1af/WebCryptoAPI
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/b81831169b/WebCryptoAPI
- webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions
- webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/6495c91853/webmessaging/broadcastchannel
- webstorage: https://github.com/web-platform-tests/wpt/tree/9dafa89214/webstorage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// META: title=WebCryptoAPI: CryptoKey.algorithm getter returns cached object

// https://w3c.github.io/webcrypto/#dom-cryptokey-algorithm
// https://github.com/servo/servo/issues/33908

promise_test(function() {
return self.crypto.subtle.generateKey(
{
name: "AES-CTR",
length: 256,
},
true,
["encrypt"],
).then(
function(key) {
let a = key.algorithm;
let b = key.algorithm;
assert_true(a === b);
},
function(err) {
assert_unreached("generateKey threw an unexpected error: " + err.toString());
}
);
}, "CryptoKey.algorithm getter returns cached object");
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var testCases = {
{length: 256, expected: algorithms["HKDF"].derivation},
{length: 384, expected: algorithms["HKDF"].derivation384},
{length: 230, expected: undefined}, // should throw an exception, not multiple of 8
{length: 0, expected: undefined}, // explicitly disallowed, so should throw
{length: 0, expected: emptyArray},
{length: null, expected: undefined }, // should throw an exception
{length: undefined, expected: undefined }, // should throw an exception
{length: "omitted", expected: undefined }, // default value is null, so should throw
Expand All @@ -12,7 +12,7 @@ var testCases = {
{length: 256, expected: algorithms["PBKDF2"].derivation},
{length: 384, expected: algorithms["PBKDF2"].derivation384},
{length: 230, expected: undefined}, // should throw an exception, not multiple of 8
{length: 0, expected: undefined}, // explicitly disallowed, so should throw
{length: 0, expected: emptyArray},
{length: null, expected: undefined }, // should throw an exception
{length: undefined, expected: undefined }, // should throw an exception
{length: "omitted", expected: undefined }, // default value is null, so should throw
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ function define_tests() {
});
}, testName);

// 0 length (OperationError)
// 0 length
subsetTest(promise_test, function(test) {
return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 0)
.then(function(derivation) {
assert_equals(derivation.byteLength, 0, "Derived correctly empty key");
}, function(err) {
assert_equals(err.name, "OperationError", "deriveBits with 0 length correctly threw OperationError: " + err.message);
assert_unreached("deriveBits failed with error " + err.name + ": " + err.message);
});
}, testName + " with 0 length");

Expand Down
20 changes: 10 additions & 10 deletions test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/pbkdf2.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ function define_tests() {
});
}, testName);

// 0 length
subsetTest(promise_test, function(test) {
return subtle.deriveBits({name: "PBKDF2", salt: salts[saltSize], hash: hashName, iterations: parseInt(iterations)}, baseKeys[passwordSize], 0)
.then(function(derivation) {
assert_true(equalBuffers(derivation.byteLength, 0, "Derived correctly empty key"));
}, function(err) {
assert_unreached("deriveBits failed with error " + err.name + ": " + err.message);
});
}, testName + " with 0 length");

// Check for correct deriveKey results for every kind of
// key that can be created by the deriveKeys operation.
derivedKeyTypes.forEach(function(derivedKeyType) {
Expand Down Expand Up @@ -103,16 +113,6 @@ function define_tests() {

});

// 0 length (OperationError)
subsetTest(promise_test, function(test) {
return subtle.deriveBits({name: "PBKDF2", salt: salts[saltSize], hash: hashName, iterations: parseInt(iterations)}, baseKeys[passwordSize], 0)
.then(function(derivation) {
assert_unreached("0 length should have thrown an OperationError");
}, function(err) {
assert_equals(err.name, "OperationError", "deriveBits with 0 length correctly threw OperationError: " + err.message);
});
}, testName + " with 0 length");

// length not multiple of 8 (OperationError)
subsetTest(promise_test, function(test) {
return subtle.deriveBits({name: "PBKDF2", salt: salts[saltSize], hash: hashName, iterations: parseInt(iterations)}, baseKeys[passwordSize], 44)
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/wpt/versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"path": "wasm/webapi"
},
"WebCryptoAPI": {
"commit": "5f0f4ac1af4848480406621fac99163c8ba0e242",
"commit": "b81831169b8527a6c569a4ad92cf8a1baf4a7118",
"path": "WebCryptoAPI"
},
"webidl/ecmascript-binding/es-exceptions": {
Expand Down
20 changes: 15 additions & 5 deletions test/parallel/test-webcrypto-derivebits-hkdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,6 @@ async function testDeriveBitsBadLengths(
subtle.deriveBits(algorithm, baseKeys[size], undefined), {
name: 'OperationError',
}),
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 0), {
message: /length cannot be zero/,
name: 'OperationError',
}),
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], null), {
message: 'length cannot be null',
Expand Down Expand Up @@ -562,3 +557,18 @@ async function testWrongKeyType(
await Promise.all(variations);

})().then(common.mustCall());

// https://github.com/w3c/webcrypto/pull/380
{
crypto.subtle.importKey('raw', new Uint8Array(0), 'HKDF', false, ['deriveBits']).then((key) => {
return crypto.subtle.deriveBits({
name: 'HKDF',
hash: { name: 'SHA-256' },
info: new Uint8Array(0),
salt: new Uint8Array(0),
}, key, 0);
}).then((bits) => {
assert.deepStrictEqual(bits, new ArrayBuffer(0));
})
.then(common.mustCall());
}
21 changes: 16 additions & 5 deletions test/pummel/test-webcrypto-derivebits-pbkdf2.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,11 +449,6 @@ async function testDeriveBitsBadLengths(
subtle.deriveBits(algorithm, baseKeys[size], undefined), {
name: 'OperationError',
}),
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 0), {
message: /length cannot be zero/,
name: 'OperationError',
}),
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], null), {
message: 'length cannot be null',
Expand Down Expand Up @@ -693,3 +688,19 @@ async function testWrongKeyType(

await Promise.all(variations);
})().then(common.mustCall());


// https://github.com/w3c/webcrypto/pull/380
{
crypto.subtle.importKey('raw', new Uint8Array(0), 'PBKDF2', false, ['deriveBits']).then((key) => {
return crypto.subtle.deriveBits({
name: 'PBKDF2',
hash: { name: 'SHA-256' },
iterations: 10,
salt: new Uint8Array(0),
}, key, 0);
}).then((bits) => {
assert.deepStrictEqual(bits, new ArrayBuffer(0));
})
.then(common.mustCall());
}

0 comments on commit 1618463

Please sign in to comment.