Skip to content

Commit

Permalink
fix(web): checking cryptokey applicability early
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Oct 15, 2021
1 parent 4b296f0 commit 89dc2aa
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 86 deletions.
9 changes: 5 additions & 4 deletions src/runtime/browser/aeskw.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AesKwUnwrapFunction, AesKwWrapFunction } from '../interfaces.d'
import bogusWebCrypto from './bogus.js'
import crypto, { isCryptoKey } from './webcrypto.js'
import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js'
import invalidKeyInput from './invalid_key_input.js'

function checkKeySize(key: CryptoKey, alg: string) {
Expand All @@ -9,8 +9,9 @@ function checkKeySize(key: CryptoKey, alg: string) {
}
}

function getCryptoKey(key: unknown, usage: KeyUsage) {
function getCryptoKey(key: unknown, alg: string, usage: KeyUsage) {
if (isCryptoKey(key)) {
checkCryptoKey(key, alg, usage)
return key
}

Expand All @@ -22,7 +23,7 @@ function getCryptoKey(key: unknown, usage: KeyUsage) {
}

export const wrap: AesKwWrapFunction = async (alg: string, key: unknown, cek: Uint8Array) => {
const cryptoKey = await getCryptoKey(key, 'wrapKey')
const cryptoKey = await getCryptoKey(key, alg, 'wrapKey')

checkKeySize(cryptoKey, alg)

Expand All @@ -37,7 +38,7 @@ export const unwrap: AesKwUnwrapFunction = async (
key: unknown,
encryptedKey: Uint8Array,
) => {
const cryptoKey = await getCryptoKey(key, 'unwrapKey')
const cryptoKey = await getCryptoKey(key, alg, 'unwrapKey')

checkKeySize(cryptoKey, alg)

Expand Down
45 changes: 0 additions & 45 deletions src/runtime/browser/check_cek_length.ts

This file was deleted.

25 changes: 15 additions & 10 deletions src/runtime/browser/decrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ import { concat, uint64be } from '../../lib/buffer_utils.js'

import type { DecryptFunction } from '../interfaces.d'
import checkIvLength from '../../lib/check_iv_length.js'
import checkCekLength from './check_cek_length.js'
import timingSafeEqual from './timing_safe_equal.js'
import { JOSENotSupported, JWEDecryptionFailed } from '../../util/errors.js'
import crypto, { isCryptoKey } from './webcrypto.js'
import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js'
import invalidKeyInput from './invalid_key_input.js'

async function cbcDecrypt(
enc: string,
cek: Uint8Array,
cek: Uint8Array | CryptoKey,
ciphertext: Uint8Array,
iv: Uint8Array,
tag: Uint8Array,
aad: Uint8Array,
) {
if (!(cek instanceof Uint8Array)) {
throw new TypeError(invalidKeyInput(cek, 'Uint8Array'))
}
const keySize = parseInt(enc.substr(1, 3), 10)
const encKey = await crypto.subtle.importKey(
'raw',
Expand Down Expand Up @@ -66,16 +68,20 @@ async function cbcDecrypt(
}

async function gcmDecrypt(
enc: string,
cek: Uint8Array | CryptoKey,
ciphertext: Uint8Array,
iv: Uint8Array,
tag: Uint8Array,
aad: Uint8Array,
) {
const encKey =
cek instanceof Uint8Array
? await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['decrypt'])
: cek
let encKey: CryptoKey
if (cek instanceof Uint8Array) {
encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['decrypt'])
} else {
checkCryptoKey(cek, enc, 'decrypt')
encKey = cek
}

try {
return new Uint8Array(
Expand Down Expand Up @@ -108,18 +114,17 @@ const decrypt: DecryptFunction = async (
throw new TypeError(invalidKeyInput(cek, 'CryptoKey', 'Uint8Array'))
}

checkCekLength(enc, cek)
checkIvLength(enc, iv)

switch (enc) {
case 'A128CBC-HS256':
case 'A192CBC-HS384':
case 'A256CBC-HS512':
return cbcDecrypt(enc, <Uint8Array>cek, ciphertext, iv, tag, aad)
return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad)
case 'A128GCM':
case 'A192GCM':
case 'A256GCM':
return gcmDecrypt(cek, ciphertext, iv, tag, aad)
return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad)
default:
throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm')
}
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/browser/ecdhes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
GenerateEpkFunction,
} from '../interfaces.d'
import { encoder, concat, uint32be, lengthAndInput, concatKdf } from '../../lib/buffer_utils.js'
import crypto, { isCryptoKey } from './webcrypto.js'
import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js'
import digest from './digest.js'
import invalidKeyInput from './invalid_key_input.js'

Expand All @@ -19,9 +19,11 @@ export const deriveKey: EcdhESDeriveKeyFunction = async (
if (!isCryptoKey(publicKey)) {
throw new TypeError(invalidKeyInput(publicKey, 'CryptoKey'))
}
checkCryptoKey(publicKey, 'ECDH-ES')
if (!isCryptoKey(privateKey)) {
throw new TypeError(invalidKeyInput(privateKey, 'CryptoKey'))
}
checkCryptoKey(privateKey, 'ECDH-ES', 'deriveBits', 'deriveKey')

const value = concat(
lengthAndInput(encoder.encode(algorithm)),
Expand Down
23 changes: 14 additions & 9 deletions src/runtime/browser/encrypt.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { concat, uint64be } from '../../lib/buffer_utils.js'
import type { EncryptFunction } from '../interfaces.d'
import checkIvLength from '../../lib/check_iv_length.js'
import checkCekLength from './check_cek_length.js'
import crypto, { isCryptoKey } from './webcrypto.js'
import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js'
import invalidKeyInput from './invalid_key_input.js'
import { JOSENotSupported } from '../../util/errors.js'

async function cbcEncrypt(
enc: string,
plaintext: Uint8Array,
cek: Uint8Array,
cek: Uint8Array | CryptoKey,
iv: Uint8Array,
aad: Uint8Array,
) {
if (!(cek instanceof Uint8Array)) {
throw new TypeError(invalidKeyInput(cek, 'Uint8Array'))
}
const keySize = parseInt(enc.substr(1, 3), 10)
const encKey = await crypto.subtle.importKey(
'raw',
Expand Down Expand Up @@ -52,15 +54,19 @@ async function cbcEncrypt(
}

async function gcmEncrypt(
enc: string,
plaintext: Uint8Array,
cek: Uint8Array | CryptoKey,
iv: Uint8Array,
aad: Uint8Array,
) {
const encKey =
cek instanceof Uint8Array
? await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['encrypt'])
: cek
let encKey: CryptoKey
if (cek instanceof Uint8Array) {
encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['encrypt'])
} else {
checkCryptoKey(cek, enc, 'encrypt')
encKey = cek
}

const encrypted = new Uint8Array(
await crypto.subtle.encrypt(
Expand Down Expand Up @@ -93,7 +99,6 @@ const encrypt: EncryptFunction = async (
throw new TypeError(invalidKeyInput(cek, 'CryptoKey', 'Uint8Array'))
}

checkCekLength(enc, cek)
checkIvLength(enc, iv)

switch (enc) {
Expand All @@ -104,7 +109,7 @@ const encrypt: EncryptFunction = async (
case 'A128GCM':
case 'A192GCM':
case 'A256GCM':
return gcmEncrypt(plaintext, cek, iv, aad)
return gcmEncrypt(enc, plaintext, cek, iv, aad)
default:
throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm')
}
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/browser/get_sign_verify_key.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import crypto, { isCryptoKey } from './webcrypto.js'
import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js'
import invalidKeyInput from './invalid_key_input.js'

export default function getCryptoKey(alg: string, key: unknown, usage: KeyUsage) {
if (isCryptoKey(key)) {
checkCryptoKey(key, alg, usage)
return key
}

Expand Down
9 changes: 5 additions & 4 deletions src/runtime/browser/pbes2kw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { p2s as concatSalt } from '../../lib/buffer_utils.js'
import { encode as base64url } from './base64url.js'
import { wrap, unwrap } from './aeskw.js'
import checkP2s from '../../lib/check_p2s.js'
import crypto, { isCryptoKey } from './webcrypto.js'
import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js'
import invalidKeyInput from './invalid_key_input.js'

function getCryptoKey(key: unknown) {
function getCryptoKey(key: unknown, alg: string) {
if (key instanceof Uint8Array) {
return crypto.subtle.importKey('raw', key, 'PBKDF2', false, ['deriveBits'])
}

if (isCryptoKey(key)) {
checkCryptoKey(key, alg, 'deriveBits', 'deriveKey')
return key
}

Expand Down Expand Up @@ -41,7 +42,7 @@ export const encrypt: Pbes2KWEncryptFunction = async (
name: 'AES-KW',
}

const cryptoKey = await getCryptoKey(key)
const cryptoKey = await getCryptoKey(key, alg)

let derived: CryptoKey | Uint8Array
if (cryptoKey.usages.includes('deriveBits')) {
Expand Down Expand Up @@ -79,7 +80,7 @@ export const decrypt: Pbes2KWDecryptFunction = async (
name: 'AES-KW',
}

const cryptoKey = await getCryptoKey(key)
const cryptoKey = await getCryptoKey(key, alg)

let derived: CryptoKey | Uint8Array
if (cryptoKey.usages.includes('deriveBits')) {
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/browser/rsaes.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { RsaEsDecryptFunction, RsaEsEncryptFunction } from '../interfaces.d'
import subtleAlgorithm from './subtle_rsaes.js'
import bogusWebCrypto from './bogus.js'
import crypto, { isCryptoKey } from './webcrypto.js'
import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js'
import checkKeyLength from './check_key_length.js'
import invalidKeyInput from './invalid_key_input.js'

export const encrypt: RsaEsEncryptFunction = async (alg: string, key: unknown, cek: Uint8Array) => {
if (!isCryptoKey(key)) {
throw new TypeError(invalidKeyInput(key, 'CryptoKey'))
}
checkCryptoKey(key, alg, 'encrypt', 'wrapKey')
checkKeyLength(alg, key)

if (key.usages.includes('encrypt')) {
Expand Down Expand Up @@ -36,6 +37,7 @@ export const decrypt: RsaEsDecryptFunction = async (
if (!isCryptoKey(key)) {
throw new TypeError(invalidKeyInput(key, 'CryptoKey'))
}
checkCryptoKey(key, alg, 'decrypt', 'unwrapKey')
checkKeyLength(alg, key)

if (key.usages.includes('decrypt')) {
Expand Down
Loading

0 comments on commit 89dc2aa

Please sign in to comment.