-
Notifications
You must be signed in to change notification settings - Fork 63
/
Copy pathbackend-factory.ts
151 lines (129 loc) · 5.09 KB
/
backend-factory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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 =
| FullSupportWebCryptoBackend
| MixedSupportWebCryptoBackend
export type FullSupportWebCryptoBackend = {
subtle: SubtleCrypto
randomValues: (byteLength: number) => Uint8Array
}
export type MixedSupportWebCryptoBackend = {
zeroByteSubtle: SubtleCrypto
nonZeroByteSubtle: SubtleCrypto
randomValues: (byteLength: number) => Uint8Array
}
export function webCryptoBackendFactory(window: Window) {
const fallbackRequiredPromise = windowRequiresFallback(window)
const randomValues = generateSynchronousRandomValues(window)
let webCryptoFallbackPromise: Promise<SubtleCrypto> | false = false
return { getWebCryptoBackend, configureFallback }
async function getWebCryptoBackend(): Promise<WebCryptoBackend> {
/* Precondition: Access to a secure random source is required. */
try {
randomValues(1)
} catch (ex) {
throw new Error('No supported secure random')
}
const fallbackRequired = await fallbackRequiredPromise
const subtle = pluckSubtleCrypto(window)
const webCryptoFallback = await webCryptoFallbackPromise
/* Postcondition: If a a subtle backend exists and a fallback is required, one must be configured.
* In this case the subtle backend does not support zero byte GCM operations.
*/
if (subtle && fallbackRequired && !webCryptoFallback) {
throw new Error(
'A Fallback is required for zero byte AES-GCM operations.'
)
}
/* Postcondition: If no SubtleCrypto exists, a fallback must configured. */
if (!subtle && !webCryptoFallback) {
throw new Error(
'A Fallback is required because no subtle backend exists.'
)
}
if (!fallbackRequired && subtle) {
return { subtle, randomValues }
}
if (fallbackRequired && subtle && webCryptoFallback) {
return {
nonZeroByteSubtle: subtle,
randomValues,
zeroByteSubtle: webCryptoFallback,
}
}
if (fallbackRequired && !subtle && webCryptoFallback) {
return { subtle: webCryptoFallback, randomValues }
}
throw new Error('unknown error')
}
async function configureFallback(fallback: SubtleCrypto) {
const fallbackRequired = await fallbackRequiredPromise
/* Precondition: If a fallback is not required, do not configure one. */
if (!fallbackRequired) {
return
}
/* Precondition: Can not reconfigure fallback. */
if (webCryptoFallbackPromise)
throw new Error('Fallback reconfiguration denied')
/* Precondition: Fallback must look like it supports the required operations. */
if (!supportsSubtleCrypto(fallback))
throw new Error('Fallback does not support WebCrypto')
// This if to lock the fallback.
// when using the fallback, it is simpler
// for the customer to not await the success
// of configuration so we handle it for them
// I still return in case they want to await
webCryptoFallbackPromise = supportsZeroByteGCM(fallback).then(
(zeroByteGCMSupport) => {
/* Postcondition: The fallback must specifically support ZeroByteGCM. */
if (!zeroByteGCMSupport)
throw new Error('Fallback does not support zero byte AES-GCM')
return fallback
}
)
return webCryptoFallbackPromise
}
}
export function getNonZeroByteBackend(backend: WebCryptoBackend | false) {
/* Precondition: A backend must be passed to get a non zero byte backend. */
if (!backend) throw new Error('No supported backend.')
return (
(backend as FullSupportWebCryptoBackend).subtle ||
(backend as MixedSupportWebCryptoBackend).nonZeroByteSubtle
)
}
export function getZeroByteSubtle(backend: WebCryptoBackend | false) {
/* Precondition: A backend must be passed to get a zero byte backend. */
if (!backend) throw new Error('No supported backend.')
return (
(backend as FullSupportWebCryptoBackend).subtle ||
(backend as MixedSupportWebCryptoBackend).zeroByteSubtle
)
}
export async function windowRequiresFallback(window: Window) {
const subtle = pluckSubtleCrypto(window)
if (!subtle) return true
const zeroByteSupport = await supportsZeroByteGCM(subtle)
return !zeroByteSupport
}
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
}
export function isFullSupportWebCryptoBackend(
backend: WebCryptoBackend
): backend is FullSupportWebCryptoBackend {
return !!(backend as FullSupportWebCryptoBackend).subtle
}