-
-
Notifications
You must be signed in to change notification settings - Fork 318
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(web): check Uint8Array CEK lengths, refactor for better tree-shaking
- Loading branch information
Showing
27 changed files
with
346 additions
and
478 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,22 @@ | ||
import { JOSENotSupported } from '../util/errors.js' | ||
import random from '../runtime/random.js' | ||
|
||
const bitLengths = new Map<string, number>([ | ||
['A128CBC-HS256', 256], | ||
['A128GCM', 128], | ||
['A192CBC-HS384', 384], | ||
['A192GCM', 192], | ||
['A256CBC-HS512', 512], | ||
['A256GCM', 256], | ||
]) | ||
|
||
const generateCek = (alg: string): Uint8Array => { | ||
const bitLength = bitLengths.get(alg) | ||
if (!bitLength) { | ||
throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) | ||
export function bitLength(alg: string) { | ||
switch (alg) { | ||
case 'A128CBC-HS256': | ||
return 256 | ||
case 'A192CBC-HS384': | ||
return 384 | ||
case 'A256CBC-HS512': | ||
return 512 | ||
case 'A128GCM': | ||
return 128 | ||
case 'A192GCM': | ||
return 192 | ||
case 'A256GCM': | ||
return 256 | ||
default: | ||
throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) | ||
} | ||
|
||
return random(new Uint8Array(bitLength >> 3)) | ||
} | ||
|
||
export default generateCek | ||
export { bitLengths } | ||
export default (alg: string): Uint8Array => random(new Uint8Array(bitLength(alg) >> 3)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
import { isCloudflareWorkers, isNodeJs } from '../runtime/global.js' | ||
|
||
function getHashLength(hash: KeyAlgorithm) { | ||
return parseInt(hash?.name.substr(4), 10) | ||
} | ||
|
||
function getNamedCurve(alg: string) { | ||
switch (alg) { | ||
case 'ES256': | ||
return 'P-256' | ||
case 'ES384': | ||
return 'P-384' | ||
case 'ES512': | ||
return 'P-521' | ||
} | ||
} | ||
|
||
function checkUsage(key: CryptoKey, usages: KeyUsage[]) { | ||
if (usages.length && !usages.some((expected) => key.usages.includes(expected))) { | ||
let msg = 'CryptoKey does not support this operation, its usages must include ' | ||
if (usages.length > 2) { | ||
const last = usages.pop() | ||
msg += `one of ${usages.join(', ')}, or ${last}.` | ||
} else if (usages.length === 2) { | ||
msg += `one of ${usages[0]} or ${usages[1]}.` | ||
} else { | ||
msg += `${usages[0]}.` | ||
} | ||
|
||
throw new TypeError(msg) | ||
} | ||
} | ||
|
||
export function checkSigCryptoKey(key: CryptoKey, alg: string, ...usages: KeyUsage[]) { | ||
switch (alg) { | ||
case 'HS256': | ||
case 'HS384': | ||
case 'HS512': { | ||
if (key.algorithm.name !== 'HMAC') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be HMAC.`, | ||
) | ||
} | ||
|
||
const expected = parseInt(alg.substr(2), 10) | ||
const actual = getHashLength((<HmacKeyAlgorithm>key.algorithm).hash) | ||
if (actual !== expected) { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, | ||
) | ||
} | ||
break | ||
} | ||
case 'RS256': | ||
case 'RS384': | ||
case 'RS512': { | ||
if (key.algorithm.name !== 'RSASSA-PKCS1-v1_5') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be RSASSA-PKCS1-v1_5.`, | ||
) | ||
} | ||
|
||
const expected = parseInt(alg.substr(2), 10) | ||
const actual = getHashLength((<RsaHashedKeyAlgorithm>key.algorithm).hash) | ||
if (actual !== expected) { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, | ||
) | ||
} | ||
break | ||
} | ||
case 'PS256': | ||
case 'PS384': | ||
case 'PS512': { | ||
if (key.algorithm.name !== 'RSA-PSS') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be RSA-PSS.`, | ||
) | ||
} | ||
|
||
const expected = parseInt(alg.substr(2), 10) | ||
const actual = getHashLength((<RsaHashedKeyAlgorithm>key.algorithm).hash) | ||
if (actual !== expected) { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, | ||
) | ||
} | ||
break | ||
} | ||
case isNodeJs() && 'EdDSA': { | ||
if (key.algorithm.name !== 'NODE-ED25519' && key.algorithm.name !== 'NODE-ED448') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be NODE-ED25519 or NODE-ED448.`, | ||
) | ||
} | ||
break | ||
} | ||
case isCloudflareWorkers() && 'EdDSA': { | ||
if (key.algorithm.name !== 'NODE-ED25519') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be NODE-ED25519.`, | ||
) | ||
} | ||
break | ||
} | ||
case 'ES256': | ||
case 'ES384': | ||
case 'ES512': { | ||
if (key.algorithm.name !== 'ECDSA') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be ECDSA.`, | ||
) | ||
} | ||
|
||
const expected = getNamedCurve(alg) | ||
const actual = (<EcKeyAlgorithm>key.algorithm).namedCurve | ||
if (actual !== expected) { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.namedCurve must be ${expected}.`, | ||
) | ||
} | ||
break | ||
} | ||
default: | ||
throw new TypeError('CryptoKey does not support this operation') | ||
} | ||
|
||
checkUsage(key, usages) | ||
} | ||
|
||
export function checkEncCryptoKey(key: CryptoKey, alg: string, ...usages: KeyUsage[]) { | ||
switch (alg) { | ||
case 'A128GCM': | ||
case 'A192GCM': | ||
case 'A256GCM': { | ||
if (key.algorithm.name !== 'AES-GCM') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be AES-GCM.`, | ||
) | ||
} | ||
|
||
const expected = parseInt(alg.substr(1, 3), 10) | ||
const actual = (<AesKeyAlgorithm>key.algorithm).length | ||
if (actual !== expected) { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, | ||
) | ||
} | ||
break | ||
} | ||
case 'A128KW': | ||
case 'A192KW': | ||
case 'A256KW': { | ||
if (key.algorithm.name !== 'AES-KW') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be AES-KW.`, | ||
) | ||
} | ||
|
||
const expected = parseInt(alg.substr(1, 3), 10) | ||
const actual = (<AesKeyAlgorithm>key.algorithm).length | ||
if (actual !== expected) { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, | ||
) | ||
} | ||
break | ||
} | ||
case 'ECDH-ES': | ||
if (key.algorithm.name !== 'ECDH') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be ECDH.`, | ||
) | ||
} | ||
break | ||
case 'PBES2-HS256+A128KW': | ||
case 'PBES2-HS384+A192KW': | ||
case 'PBES2-HS512+A256KW': | ||
if (key.algorithm.name !== 'PBKDF2') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be PBKDF2.`, | ||
) | ||
} | ||
break | ||
case 'RSA-OAEP': | ||
case 'RSA-OAEP-256': | ||
case 'RSA-OAEP-384': | ||
case 'RSA-OAEP-512': { | ||
if (key.algorithm.name !== 'RSA-OAEP') { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.name must be RSA-OAEP.`, | ||
) | ||
} | ||
|
||
const expected = parseInt(alg.substr(9), 10) || 1 | ||
const actual = getHashLength((<RsaHashedKeyAlgorithm>key.algorithm).hash) | ||
if (actual !== expected) { | ||
throw new TypeError( | ||
`CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, | ||
) | ||
} | ||
break | ||
} | ||
default: | ||
throw new TypeError('CryptoKey does not support this operation') | ||
} | ||
|
||
checkUsage(key, usages) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,28 @@ | ||
import { JOSENotSupported } from '../util/errors.js' | ||
import random from '../runtime/random.js' | ||
|
||
const bitLengths = new Map<string, number>([ | ||
['A128CBC-HS256', 128], | ||
['A128GCM', 96], | ||
['A128GCMKW', 96], | ||
['A192CBC-HS384', 128], | ||
['A192GCM', 96], | ||
['A192GCMKW', 96], | ||
['A256CBC-HS512', 128], | ||
['A256GCM', 96], | ||
['A256GCMKW', 96], | ||
]) | ||
|
||
const generateIv = (alg: string): Uint8Array => { | ||
const bitLength = bitLengths.get(alg) | ||
if (!bitLength) { | ||
throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) | ||
export function bitLength(alg: string) { | ||
switch (alg) { | ||
case 'A128CBC-HS256': | ||
return 128 | ||
case 'A128GCM': | ||
return 96 | ||
case 'A128GCMKW': | ||
return 96 | ||
case 'A192CBC-HS384': | ||
return 128 | ||
case 'A192GCM': | ||
return 96 | ||
case 'A192GCMKW': | ||
return 96 | ||
case 'A256CBC-HS512': | ||
return 128 | ||
case 'A256GCM': | ||
return 96 | ||
case 'A256GCMKW': | ||
return 96 | ||
default: | ||
throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) | ||
} | ||
|
||
return random(new Uint8Array(bitLength >> 3)) | ||
} | ||
|
||
export default generateIv | ||
export { bitLengths } | ||
export default (alg: string): Uint8Array => random(new Uint8Array(bitLength(alg) >> 3)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { JWEInvalid } from '../../util/errors.js' | ||
|
||
const checkCekLength = (cek: Uint8Array, expected: number) => { | ||
if (cek.length << 3 !== expected) { | ||
throw new JWEInvalid('Invalid Content Encryption Key length') | ||
} | ||
} | ||
|
||
export default checkCekLength |
Oops, something went wrong.