-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcrypto.ts
154 lines (124 loc) · 4.45 KB
/
crypto.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
/*
Copyright 2023 The Sigstore Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { base64ToUint8Array, hexToUint8Array, stringToUint8Array } from './encoding';
import { toDER } from './pem';
import { ASN1Obj } from './asn1/obj';
import { ECDSA_CURVE_NAMES } from './oid';
import { uint8ArrayToBase64 } from './encoding';
type KeyLike = string | Uint8Array;
const SHA256_ALGORITHM = 'sha256';
class Hash {
private algorithm: "SHA-256" | "SHA-384" | "SHA-512";
private value: ArrayBuffer;
constructor(algorithm: string) {
if (algorithm.includes("512")) {
this.algorithm = "SHA-512";
} else if (algorithm.includes("384")) {
this.algorithm = "SHA-384";
} else {
this.algorithm = "SHA-256";
}
this.value = new Uint8Array(0);
}
update(data: Uint8Array): void {
if (typeof data === 'string') {
data = stringToUint8Array(data);
}
var tmp = new Uint8Array(this.value.byteLength + data.byteLength);
tmp.set(new Uint8Array(this.value), 0);
tmp.set(new Uint8Array(data), this.value.byteLength);
this.value = tmp;
}
async digest(): Promise<Uint8Array> {
// This should fail if called multiple times; we con't care right now
return new Uint8Array(await crypto.subtle.digest(this.algorithm, this.value));
}
}
export async function createPublicKey(
key: KeyLike,
type: 'spki' | 'pkcs8' | 'raw' = 'spki'
): Promise<CryptoKey> {
let options: EcKeyImportParams;
let raw: Uint8Array;
if (typeof key === "string") {
raw = toDER(key);
} else if (key instanceof Uint8Array) {
raw = key;
} else {
throw new Error("Unsupported key format");
}
// We sadly have to find out the curve name manually
options = {name: 'ECDSA', namedCurve: ECDSA_CURVE_NAMES[ASN1Obj.parseBuffer(raw).subs[0].subs[1].toOID()]};
return await crypto.subtle.importKey(type, raw, options, true, ["verify"]);
}
function createHash(algorithm: string): Hash {
return new Hash(algorithm);
}
export async function digest(algorithm: string, data: Uint8Array | string): Promise<Uint8Array> {
const hash = createHash(algorithm);
if (typeof data === "string") {
hash.update(stringToUint8Array(data));
} else {
hash.update(data);
}
return hash.digest();
}
// TODO: deprecate this in favor of digest()
export async function hash(data: Uint8Array | string): Promise<Uint8Array> {
const hash = createHash(SHA256_ALGORITHM);
if (typeof data === "string") {
hash.update(stringToUint8Array(data));
} else {
hash.update(data);
}
return hash.digest();
}
export async function verify(
data: Uint8Array,
key: CryptoKey,
signature: Uint8Array,
algorithm?: string
): Promise<boolean> {
let options: EcdsaParams;
// Default to sha256
let hash: 'SHA-256' | 'SHA-384' | 'SHA-512' = 'SHA-256';
// Otherwise attempt another with this crappy heuristic
if (algorithm && algorithm.includes("512")) {
hash = "SHA-512";
} else if (algorithm && algorithm.includes("384")) {
hash = "SHA-384";
}
// For posterity: this mess is because the web crypto API supports only
// IEEE P1363, so we etract r and s from the DER sig and manually ancode
// big endian and append them one after each other
options = {name: 'ECDSA', hash: {name: hash}}
const asn1_sig = ASN1Obj.parseBuffer(signature);
let r = asn1_sig.subs[0].toInteger();
let s = asn1_sig.subs[1].toInteger();
const binr = hexToUint8Array(r.toString(16));
const bins = hexToUint8Array(s.toString(16));
let raw_signature = new Uint8Array(binr.length + bins.length);
raw_signature.set(binr, 0);
raw_signature.set(bins, binr.length);
return await crypto.subtle.verify(options, key, raw_signature, data);
}
// We can make this timesafe, but we are using it only to verify at this point :)
export function bufferEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.byteLength !== b.byteLength) {
return false;
}
for (let i = 0; i < a.byteLength; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}