This repository has been archived by the owner on Aug 27, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
aes.ts
153 lines (134 loc) · 4.87 KB
/
aes.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
152
153
import { objectenc, aes } from './pb'
import { IEncryptionImpl } from './impl'
import { ResourceResolverFunc } from './resource'
import { IKeySaltMultihash, IKeyResource } from './resource-key'
import { crypto } from './crypto'
import * as multihashing from 'multihashing'
import * as toBuffer from 'typedarray-to-buffer'
// aesIvLen is the length of the AES IV in bytes
const aesIvLen = 16
// keyHashSaltLen is the length of the key hash salt in bytes
const keyHashSaltLen = 32
// newAESKeySize builds a AES key size from a key length in bytes.
export function newAESKeySize(keyLen: number): aes.KeySize {
switch (keyLen) {
case 16:
return aes.KeySize.KeySize_AES128
case 24:
return aes.KeySize.KeySize_AES192
case 32:
return aes.KeySize.KeySize_AES192
default:
throw new Error(`invalid aes key length: ${keyLen}`)
}
}
// validateAESMetadata checks AES metadata.
export function validateAESMetadata(meta: aes.AESMetadata): Error | null {
if (!(meta.keySize in aes.KeySize)) {
return new Error(`key size unknown: ${meta.keySize}`)
}
try {
multihashing.decode(meta.keyHash)
} catch (e) {
return e
}
if (meta.iv.length !== aesIvLen) {
return new Error(`expected iv length ${aesIvLen} != actual ${meta.iv.length}`)
}
if (meta.keyHashSalt.length !== keyHashSaltLen) {
return new Error(
`expected key hash salt length ${keyHashSaltLen} != actual ${meta.keyHashSalt.length}`
)
}
return null
}
// AES implements the AES encryption algorithms.
export class AES implements IEncryptionImpl {
// GetEncryptionType returns the encryption type this implementation satisfies.
public getEncryptionType(): objectenc.EncryptionType {
return objectenc.EncryptionType.EncryptionType_AES
}
// ValidateMetadata checks the metadata field.
// If metadata is not expected, this should check that it doesn't exist.
public validateMetadata(metadata: Uint8Array): Error | null {
try {
let aesMetadata = aes.AESMetadata.decode(metadata)
return validateAESMetadata(aesMetadata)
} catch (e) {
return e
}
}
// DecryptBlob decrypts an encryptedblob.
public async decryptBlob(
resolver: ResourceResolverFunc | null,
blob: objectenc.EncryptedBlob
): Promise<Uint8Array> {
let aesMetadata = aes.AESMetadata.decode(blob.encMetadata)
let keyResource = await this.resolveKey(resolver, blob, aesMetadata.iv, null, aesMetadata)
if (!keyResource.keyData) {
throw new Error('key resource did not return a decryption key')
}
// let aesCtr = new aesjs.ModeOfOperation.ctr(keyResource.keyData)
let decryptedBytes = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: aesMetadata.iv },
keyResource.keyData,
blob.encData
)
return new Uint8Array(decryptedBytes)
}
// EncryptBlob encrypts a blob.
public async encryptBlob(
resolver: ResourceResolverFunc,
data: Uint8Array,
dataUncompressed: Uint8Array
): Promise<objectenc.EncryptedBlob> {
let iv = crypto.getRandomValues(new Uint8Array(aesIvLen)) as Uint8Array
let meta = new aes.AESMetadata({ iv })
let blob = new objectenc.EncryptedBlob({
encType: objectenc.EncryptionType.EncryptionType_AES
})
let keyResource = await this.resolveKey(resolver, blob, iv, data, meta)
if (!keyResource.keyData) {
throw new Error('key resource did not return an encryption key')
}
// build key metadata
let keyRaw = new Uint8Array(await crypto.subtle.exportKey('raw', keyResource.keyData))
meta.keySize = newAESKeySize(keyRaw.length)
meta.keyHashSalt = <Uint8Array>crypto.getRandomValues(new Uint8Array(keyHashSaltLen))
let hashedKeyData = new Uint8Array(keyRaw.length + keyHashSaltLen)
hashedKeyData.set(meta.keyHashSalt, 0)
hashedKeyData.set(keyRaw, keyHashSaltLen)
meta.keyHash = multihashing.digest(toBuffer(hashedKeyData), 'sha1')
blob.encMetadata = aes.AESMetadata.encode(meta).finish()
// encrypt data
blob.encData = new Uint8Array(
await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, keyResource.keyData, data)
)
return blob
}
private async resolveKey(
resolver: ResourceResolverFunc | null,
blob: objectenc.EncryptedBlob,
iv: Uint8Array | null,
encryptDat: Uint8Array | null,
keySaltMultihash: IKeySaltMultihash
): Promise<IKeyResource> {
if (!resolver) {
throw new Error('aes requires a resolver to lookup keys')
}
let res: IKeyResource = {
resourceId: 'IKeyResource',
keySaltMultihash: keySaltMultihash,
encryptionType: objectenc.EncryptionType.EncryptionType_AES,
encrypting: !!(encryptDat && encryptDat.length)
}
if (encryptDat) {
res.encryptingData = encryptDat
}
await resolver(blob, res) // throws e
if (!res.keyData || !res.keyData.extractable) {
throw new Error('resolver failed to return an extractable key')
}
return res
}
}