This repository has been archived by the owner on Aug 2, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 461
/
Copy patheosjs-webauthn-sig.ts
104 lines (94 loc) · 4.39 KB
/
eosjs-webauthn-sig.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
/**
* @module WebAuthn-Sig
*/
// copyright defined in eosjs/LICENSE.txt
import { SignatureProvider, SignatureProviderArgs } from './eosjs-api-interfaces';
import { PushTransactionArgs } from './eosjs-rpc-interfaces';
import * as ser from './eosjs-serialize';
import * as numeric from './eosjs-numeric';
import { ec } from 'elliptic';
/** Signs transactions using WebAuthn */
export class WebAuthnSignatureProvider implements SignatureProvider {
/** Map public key to credential ID (hex). User must populate this. */
public keys = new Map<string, string>();
/** Public keys that the `SignatureProvider` holds */
public async getAvailableKeys(): Promise<string[]> {
return Array.from(this.keys.keys());
}
/** Sign a transaction */
public async sign(
{ chainId, requiredKeys, serializedTransaction, serializedContextFreeData }: SignatureProviderArgs,
): Promise<PushTransactionArgs> {
const signBuf = new ser.SerialBuffer();
signBuf.pushArray(ser.hexToUint8Array(chainId));
signBuf.pushArray(serializedTransaction);
if (serializedContextFreeData) {
signBuf.pushArray(new Uint8Array(await crypto.subtle.digest('SHA-256', serializedContextFreeData.buffer)));
} else {
signBuf.pushArray(new Uint8Array(32));
}
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', signBuf.asUint8Array().slice().buffer));
const signatures = [] as string[];
for (const key of requiredKeys) {
const id = ser.hexToUint8Array(this.keys.get(key));
const assertion = await (navigator as any).credentials.get({
publicKey: {
timeout: 60000,
allowCredentials: [{
id,
type: 'public-key',
}],
challenge: digest.buffer,
},
});
const e = new ec('p256') as any; // /~https://github.com/indutny/elliptic/pull/232
const pubKey = e.keyFromPublic(numeric.stringToPublicKey(key).data.subarray(0, 33)).getPublic();
const fixup = (x: Uint8Array): Uint8Array => {
const a = Array.from(x);
while (a.length < 32) {
a.unshift(0);
}
while (a.length > 32) {
if (a.shift() !== 0) {
throw new Error('Signature has an r or s that is too big');
}
}
return new Uint8Array(a);
};
const der = new ser.SerialBuffer({ array: new Uint8Array(assertion.response.signature) });
if (der.get() !== 0x30) {
throw new Error('Signature missing DER prefix');
}
if (der.get() !== der.array.length - 2) {
throw new Error('Signature has bad length');
}
if (der.get() !== 0x02) {
throw new Error('Signature has bad r marker');
}
const r = fixup(der.getUint8Array(der.get()));
if (der.get() !== 0x02) {
throw new Error('Signature has bad s marker');
}
const s = fixup(der.getUint8Array(der.get()));
const whatItReallySigned = new ser.SerialBuffer();
whatItReallySigned.pushArray(new Uint8Array(assertion.response.authenticatorData));
whatItReallySigned.pushArray(new Uint8Array(
await crypto.subtle.digest('SHA-256', assertion.response.clientDataJSON)));
const hash = new Uint8Array(
await crypto.subtle.digest('SHA-256', whatItReallySigned.asUint8Array().slice()));
const recid = e.getKeyRecoveryParam(hash, new Uint8Array(assertion.response.signature), pubKey);
const sigData = new ser.SerialBuffer();
sigData.push(recid + 27 + 4);
sigData.pushArray(r);
sigData.pushArray(s);
sigData.pushBytes(new Uint8Array(assertion.response.authenticatorData));
sigData.pushBytes(new Uint8Array(assertion.response.clientDataJSON));
const sig = numeric.signatureToString({
type: numeric.KeyType.wa,
data: sigData.asUint8Array().slice(),
});
signatures.push(sig);
}
return { signatures, serializedTransaction, serializedContextFreeData };
}
}