@1shotapi/ows-signer 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/crypto/sign.js +3 -1
- package/src/handlers.js +3 -3
- package/src/webauthn.js +103 -6
package/package.json
CHANGED
package/src/crypto/sign.js
CHANGED
|
@@ -27,10 +27,12 @@ export async function signWithScheme(
|
|
|
27
27
|
lowS: true,
|
|
28
28
|
});
|
|
29
29
|
const compact = sig.toCompactRawBytes();
|
|
30
|
+
// Ethereum ecrecover / OpenZeppelin ECDSA require v ∈ {27, 28}.
|
|
31
|
+
// Noble returns recovery ∈ {0,1} (rarely 2,3); mask to yParity then +27.
|
|
30
32
|
const recovery = sig.recovery ?? 0;
|
|
31
33
|
const out = new Uint8Array(65);
|
|
32
34
|
out.set(compact, 0);
|
|
33
|
-
out[64] = recovery;
|
|
35
|
+
out[64] = 27 + (recovery & 1);
|
|
34
36
|
return to0xHex(out);
|
|
35
37
|
}
|
|
36
38
|
case "secp256k1-bip340":
|
package/src/handlers.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
getAssertionSignatureBase64Url,
|
|
37
37
|
getCredentialId,
|
|
38
38
|
getPasskeyAssertion,
|
|
39
|
-
|
|
39
|
+
getCosePublicKeyBase64Url,
|
|
40
40
|
} from "./webauthn.js";
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -227,7 +227,7 @@ async function handleCreateCredential(params, correlationId, targetOrigin) {
|
|
|
227
227
|
|
|
228
228
|
emitEvent(window.parent, targetOrigin, "CredentialCreated", correlationId, {
|
|
229
229
|
credentialId,
|
|
230
|
-
|
|
230
|
+
cosePublicKey: getCosePublicKeyBase64Url(credential),
|
|
231
231
|
secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
|
|
232
232
|
});
|
|
233
233
|
zeroize(keys.secp256k1PrivateKey);
|
|
@@ -626,7 +626,7 @@ async function handleGetPublicKey(params, correlationId, targetOrigin) {
|
|
|
626
626
|
|
|
627
627
|
emitEvent(window.parent, targetOrigin, "PublicKey", correlationId, {
|
|
628
628
|
credentialId: getCredentialId(credential),
|
|
629
|
-
|
|
629
|
+
cosePublicKey: getCosePublicKeyBase64Url(credential),
|
|
630
630
|
secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
|
|
631
631
|
ed25519PublicKey: to0xHex(keys.ed25519PublicKey),
|
|
632
632
|
});
|
package/src/webauthn.js
CHANGED
|
@@ -126,15 +126,112 @@ export function getCredentialId(credential) {
|
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
128
|
* @param {PublicKeyCredential} credential
|
|
129
|
-
* @returns {string | null}
|
|
129
|
+
* @returns {string | null} COSE credentialPublicKey as base64url, or null
|
|
130
130
|
*/
|
|
131
|
-
export function
|
|
131
|
+
export function getCosePublicKeyBase64Url(credential) {
|
|
132
132
|
const response = credential.response;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
// COSE lives in attestation authenticator data only — not on assertions,
|
|
134
|
+
// and not via AuthenticatorAttestationResponse.getPublicKey() (that is SPKI).
|
|
135
|
+
if (
|
|
136
|
+
!("getAuthenticatorData" in response) ||
|
|
137
|
+
typeof response.getAuthenticatorData !== "function"
|
|
138
|
+
) {
|
|
139
|
+
return null;
|
|
136
140
|
}
|
|
137
|
-
|
|
141
|
+
const authData = response.getAuthenticatorData();
|
|
142
|
+
if (!authData) return null;
|
|
143
|
+
const cose = extractCosePublicKeyFromAuthenticatorData(
|
|
144
|
+
new Uint8Array(authData),
|
|
145
|
+
);
|
|
146
|
+
return cose ? bufferToBase64Url(cose) : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Slice the COSE credentialPublicKey from attested credential data.
|
|
151
|
+
* @param {Uint8Array} authData
|
|
152
|
+
* @returns {Uint8Array | null}
|
|
153
|
+
*/
|
|
154
|
+
function extractCosePublicKeyFromAuthenticatorData(authData) {
|
|
155
|
+
if (authData.length < 37) return null;
|
|
156
|
+
const flags = authData[32];
|
|
157
|
+
const hasAttestedCredentialData = (flags & 0x40) !== 0;
|
|
158
|
+
if (!hasAttestedCredentialData) return null;
|
|
159
|
+
|
|
160
|
+
let offset = 37; // rpIdHash(32) + flags(1) + signCount(4)
|
|
161
|
+
offset += 16; // aaguid
|
|
162
|
+
if (offset + 2 > authData.length) return null;
|
|
163
|
+
const credentialIdLength = (authData[offset] << 8) | authData[offset + 1];
|
|
164
|
+
offset += 2 + credentialIdLength;
|
|
165
|
+
if (offset >= authData.length) return null;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const length = cborFirstItemLength(authData, offset);
|
|
169
|
+
return authData.subarray(offset, offset + length);
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Byte length of the first definite-length CBOR item at `offset`.
|
|
177
|
+
* Sufficient for WebAuthn COSE public keys (maps of ints / bstrs).
|
|
178
|
+
* @param {Uint8Array} buf
|
|
179
|
+
* @param {number} offset
|
|
180
|
+
* @returns {number}
|
|
181
|
+
*/
|
|
182
|
+
function cborFirstItemLength(buf, offset) {
|
|
183
|
+
if (offset >= buf.length) throw new Error("cborTruncated");
|
|
184
|
+
const initial = buf[offset];
|
|
185
|
+
const major = initial >> 5;
|
|
186
|
+
const additional = initial & 31;
|
|
187
|
+
let pos = offset + 1;
|
|
188
|
+
let argument = additional;
|
|
189
|
+
|
|
190
|
+
if (additional === 24) {
|
|
191
|
+
argument = buf[pos];
|
|
192
|
+
pos += 1;
|
|
193
|
+
} else if (additional === 25) {
|
|
194
|
+
argument = (buf[pos] << 8) | buf[pos + 1];
|
|
195
|
+
pos += 2;
|
|
196
|
+
} else if (additional === 26) {
|
|
197
|
+
argument =
|
|
198
|
+
(buf[pos] << 24) |
|
|
199
|
+
(buf[pos + 1] << 16) |
|
|
200
|
+
(buf[pos + 2] << 8) |
|
|
201
|
+
buf[pos + 3];
|
|
202
|
+
pos += 4;
|
|
203
|
+
} else if (additional === 27) {
|
|
204
|
+
// 64-bit; WebAuthn keys never need this size
|
|
205
|
+
throw new Error("cborUint64Unsupported");
|
|
206
|
+
} else if (additional >= 28) {
|
|
207
|
+
throw new Error("cborIndefiniteUnsupported");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (major === 0 || major === 1) {
|
|
211
|
+
return pos - offset;
|
|
212
|
+
}
|
|
213
|
+
if (major === 2 || major === 3) {
|
|
214
|
+
return pos - offset + argument;
|
|
215
|
+
}
|
|
216
|
+
if (major === 7) {
|
|
217
|
+
if (additional < 24) return pos - offset;
|
|
218
|
+
if (additional === 25) return pos - offset + 2;
|
|
219
|
+
if (additional === 26) return pos - offset + 4;
|
|
220
|
+
if (additional === 27) return pos - offset + 8;
|
|
221
|
+
return pos - offset;
|
|
222
|
+
}
|
|
223
|
+
if (major === 6) {
|
|
224
|
+
const inner = cborFirstItemLength(buf, pos);
|
|
225
|
+
return pos - offset + inner;
|
|
226
|
+
}
|
|
227
|
+
if (major === 4 || major === 5) {
|
|
228
|
+
const valueCount = major === 5 ? argument * 2 : argument;
|
|
229
|
+
for (let i = 0; i < valueCount; i++) {
|
|
230
|
+
pos += cborFirstItemLength(buf, pos);
|
|
231
|
+
}
|
|
232
|
+
return pos - offset;
|
|
233
|
+
}
|
|
234
|
+
throw new Error("cborUnsupportedMajor");
|
|
138
235
|
}
|
|
139
236
|
|
|
140
237
|
/**
|