Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/shared-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ['22.x', '24.x', 'latest']
node-version: ['18.x', '20.x', '22.x', '24.x', 'latest']
test-type: ['node', 'browser']
# Determine test categories based on whether testing published packages or source code:
# - Testing published packages: only run vector tests (don't have build artifacts to test coverage or compliance)
Expand Down
1 change: 1 addition & 0 deletions modules/web-crypto-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/ie11-detection": "4.0.0",
"@aws-crypto/supports-web-crypto": "5.2.0",
"@aws-sdk/util-locate-window": "3.310.0",
"tslib": "^2.2.0"
Expand Down
3 changes: 3 additions & 0 deletions modules/web-crypto-backend/src/backend-factory.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { isMsWindow } from '@aws-crypto/ie11-detection'
import {
supportsWebCrypto,
supportsSubtleCrypto,
supportsZeroByteGCM,
} from '@aws-crypto/supports-web-crypto'
import { generateSynchronousRandomValues } from './synchronous_random_values'
import promisifyMsSubtleCrypto from './promisify-ms-crypto'

type MaybeSubtleCrypto = SubtleCrypto | false
export type WebCryptoBackend =
Expand Down Expand Up @@ -138,6 +140,7 @@ export function pluckSubtleCrypto(window: Window): MaybeSubtleCrypto {
// if needed webkitSubtle check should be added here
// see: https://webkit.org/blog/7790/update-on-web-cryptography/
if (supportsWebCrypto(window)) return window.crypto.subtle
if (isMsWindow(window)) return promisifyMsSubtleCrypto(window.msCrypto.subtle)
return false
}

Expand Down
38 changes: 38 additions & 0 deletions modules/web-crypto-backend/src/promisify-ms-crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { MsSubtleCrypto } from '@aws-crypto/ie11-detection'

type MsSubtleFunctions = keyof MsSubtleCrypto

export default function promisifyMsSubtleCrypto(backend: MsSubtleCrypto) {
const usages: MsSubtleFunctions[] = [
'decrypt',
'digest',
'encrypt',
'exportKey',
'generateKey',
'importKey',
'sign',
'verify',
]
const decorateUsage = (fakeBackend: any, usage: MsSubtleFunctions) =>
decorate(backend, fakeBackend, usage)
return usages.reduce(decorateUsage, {}) as SubtleCrypto
}

function decorate(
subtle: MsSubtleCrypto,
fakeBackend: any,
name: MsSubtleFunctions
) {
fakeBackend[name] = async (...args: any[]) => {
return new Promise((resolve, reject) => {
// @ts-ignore
const operation = subtle[name](...args)
operation.oncomplete = () => resolve(operation.result)
operation.onerror = reject
})
}
return fakeBackend
}
5 changes: 5 additions & 0 deletions modules/web-crypto-backend/src/synchronous_random_values.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { isMsWindow } from '@aws-crypto/ie11-detection'
import { supportsSecureRandom } from '@aws-crypto/supports-web-crypto'
import { locateWindow } from '@aws-sdk/util-locate-window'

Expand All @@ -18,6 +19,10 @@ export function generateSynchronousRandomValues(
return function synchronousRandomValues(byteLength: number): Uint8Array {
if (supportsSecureRandom(globalScope)) {
return globalScope.crypto.getRandomValues(new Uint8Array(byteLength))
} else if (isMsWindow(globalScope)) {
const values = new Uint8Array(byteLength)
globalScope.msCrypto.getRandomValues(values)
return values
}

throw new Error(`Unable to locate a secure random source.`)
Expand Down
149 changes: 143 additions & 6 deletions modules/web-crypto-backend/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@

export const fakeWindowWebCryptoSupportsZeroByteGCM: Window = {
crypto: {
getRandomValues: (array: Uint8Array) => {
for (let i = 0; i < array.length; i++) {
array[i] = Math.floor(Math.random() * 256)
}
return array
},
getRandomValues: () => {},
subtle: {
async decrypt() {
return {} as any
Expand Down Expand Up @@ -147,3 +142,145 @@ export const subtleFallbackZeroByteEncryptFail = {
} as any

export const subtleFallbackNoWebCrypto = {} as any

export const fakeWindowIE11OnComplete = {
msCrypto: {
getRandomValues: (values: Uint8Array) => {
return values.fill(1)
},
subtle: {
decrypt() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
digest() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
encrypt() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
exportKey() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
generateKey() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
importKey() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
sign() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
verify() {
const obj = {} as any
setTimeout(() => {
obj.result = true
obj.oncomplete()
})
return obj
},
},
},
MSInputMethodContext: {} as any,
} as any

export const fakeWindowIE11OnError = {
msCrypto: {
getRandomValues: (values: Uint8Array) => {
return values.fill(1)
},
subtle: {
decrypt() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
digest() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
encrypt() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
exportKey() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
generateKey() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
importKey() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
sign() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
verify() {
const obj = {} as any
setTimeout(() => {
obj.onerror(new Error('stub error'))
})
return obj
},
},
},
MSInputMethodContext: {} as any,
} as any
36 changes: 36 additions & 0 deletions modules/web-crypto-backend/test/promisify-ms-crypto.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

/* eslint-env mocha */

import * as chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import promisifyMsSubtleCrypto from '../src/promisify-ms-crypto'
import * as fixtures from './fixtures'

chai.use(chaiAsPromised)
const { expect } = chai

/* These tests are very simple
* I am not testing every subtle function
* because the promisify code is all the same.
*/
describe('promisifyMsSubtleCrypto', () => {
const backendComplete = promisifyMsSubtleCrypto(
fixtures.fakeWindowIE11OnComplete.msCrypto.subtle
)
const backendError = promisifyMsSubtleCrypto(
fixtures.fakeWindowIE11OnError.msCrypto.subtle
)

it('backendComplete:decrypt', async () => {
// @ts-ignore These methods are stubs, ignore ts errors
const test = await backendComplete.decrypt()
expect(test).to.equal(true)
})

it('backendError:decrypt', async () => {
// @ts-ignore These methods are stubs, ignore ts errors
await expect(backendError.decrypt()).to.rejectedWith(Error)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@

import { expect } from 'chai'
import { generateSynchronousRandomValues } from '../src/synchronous_random_values'
import { synchronousRandomValues } from '../src/index'
import * as fixtures from './fixtures'

describe('synchronousRandomValues', () => {
it('should return random values', () => {
const test = synchronousRandomValues(5)
expect(test).to.be.instanceOf(Uint8Array)
expect(test).lengthOf(5)
})

it('should return msCrypto random values', () => {
const synchronousRandomValues = generateSynchronousRandomValues(
fixtures.fakeWindowWebCryptoSupportsZeroByteGCM
fixtures.fakeWindowIE11OnComplete
)

const test = synchronousRandomValues(5)
expect(test).to.be.instanceOf(Uint8Array)
expect(test).lengthOf(5)
// The random is a stub, so I know the value
expect(test).to.deep.equal(new Uint8Array(5).fill(1))
})
})
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.