|
@ -1,10 +1,9 @@ |
|
|
import asn1 from "asn1-ber"; |
|
|
import asn1 from "asn1-ber"; |
|
|
import { Buffer } from "buffer/"; |
|
|
import { Buffer } from "buffer/"; |
|
|
import { decode as cborDecode } from "cbor-x"; |
|
|
import { decode as cborDecode } from "cbor-x"; |
|
|
import { JWTPayload } from "did-jwt"; |
|
|
import { bytesToMultibase, JWTPayload, multibaseToBytes } from "did-jwt"; |
|
|
import { DIDResolutionResult } from "did-resolver"; |
|
|
import { DIDResolutionResult } from "did-resolver"; |
|
|
import { sha256 } from "ethereum-cryptography/sha256.js"; |
|
|
import { sha256 } from "ethereum-cryptography/sha256.js"; |
|
|
import { bytesToMultibase } from "@veramo/utils"; |
|
|
|
|
|
import { |
|
|
import { |
|
|
startAuthentication, |
|
|
startAuthentication, |
|
|
startRegistration, |
|
|
startRegistration, |
|
@ -24,6 +23,7 @@ import { |
|
|
|
|
|
|
|
|
import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers"; |
|
|
import { getWebCrypto, unwrapEC2Signature } from "@/libs/crypto/passkeyHelpers"; |
|
|
|
|
|
|
|
|
|
|
|
const PEER_DID_PREFIX = "did:peer:0"; |
|
|
export interface JWK { |
|
|
export interface JWK { |
|
|
kty: string; |
|
|
kty: string; |
|
|
crv: string; |
|
|
crv: string; |
|
@ -97,14 +97,16 @@ export function createPeerDid(publicKeyBytes: Uint8Array) { |
|
|
"base58btc", |
|
|
"base58btc", |
|
|
"p256-pub", |
|
|
"p256-pub", |
|
|
); |
|
|
); |
|
|
return "did:peer:0" + methodSpecificId; |
|
|
return PEER_DID_PREFIX + methodSpecificId; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function peerDidToPublicKeyBytes(did: string) { |
|
|
|
|
|
return multibaseToBytes(did.substring(PEER_DID_PREFIX.length)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export class PeerSetup { |
|
|
export class PeerSetup { |
|
|
public authenticatorData?: ArrayBuffer; |
|
|
public authenticatorData?: ArrayBuffer; |
|
|
public authenticatorDataBase64Url?: Base64URLString; |
|
|
|
|
|
public challenge?: Uint8Array; |
|
|
public challenge?: Uint8Array; |
|
|
public clientDataJsonDecoded?: object; |
|
|
|
|
|
public clientDataJsonBase64Url?: Base64URLString; |
|
|
public clientDataJsonBase64Url?: Base64URLString; |
|
|
public signature?: Base64URLString; |
|
|
public signature?: Base64URLString; |
|
|
|
|
|
|
|
@ -124,17 +126,12 @@ export class PeerSetup { |
|
|
const clientAuth = await startAuthentication(options); |
|
|
const clientAuth = await startAuthentication(options); |
|
|
// console.log("simple credential get", clientAuth);
|
|
|
// console.log("simple credential get", clientAuth);
|
|
|
|
|
|
|
|
|
this.authenticatorDataBase64Url = clientAuth.response.authenticatorData; |
|
|
const authenticatorDataBase64Url = clientAuth.response.authenticatorData; |
|
|
this.authenticatorData = Buffer.from( |
|
|
this.authenticatorData = Buffer.from( |
|
|
clientAuth.response.authenticatorData, |
|
|
clientAuth.response.authenticatorData, |
|
|
"base64", |
|
|
"base64", |
|
|
).buffer; |
|
|
).buffer; |
|
|
this.clientDataJsonBase64Url = clientAuth.response.clientDataJSON; |
|
|
this.clientDataJsonBase64Url = clientAuth.response.clientDataJSON; |
|
|
this.clientDataJsonDecoded = JSON.parse( |
|
|
|
|
|
Buffer.from(clientAuth.response.clientDataJSON, "base64").toString( |
|
|
|
|
|
"utf-8", |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
// console.log("simple authenticatorData for signing", this.authenticatorData);
|
|
|
// console.log("simple authenticatorData for signing", this.authenticatorData);
|
|
|
this.signature = clientAuth.response.signature; |
|
|
this.signature = clientAuth.response.signature; |
|
|
|
|
|
|
|
@ -147,7 +144,7 @@ export class PeerSetup { |
|
|
.replace(/=+$/, ""); |
|
|
.replace(/=+$/, ""); |
|
|
|
|
|
|
|
|
const dataInJwt = { |
|
|
const dataInJwt = { |
|
|
AuthenticationData: this.authenticatorDataBase64Url, |
|
|
AuthenticationData: authenticatorDataBase64Url, |
|
|
ClientDataJSON: this.clientDataJsonBase64Url, |
|
|
ClientDataJSON: this.clientDataJsonBase64Url, |
|
|
}; |
|
|
}; |
|
|
const dataInJwtString = JSON.stringify(dataInJwt); |
|
|
const dataInJwtString = JSON.stringify(dataInJwt); |
|
@ -175,20 +172,14 @@ export class PeerSetup { |
|
|
const credential = await navigator.credentials.get(options); |
|
|
const credential = await navigator.credentials.get(options); |
|
|
// console.log("nav credential get", credential);
|
|
|
// console.log("nav credential get", credential);
|
|
|
|
|
|
|
|
|
this.authenticatorDataBase64Url = bufferToBase64URLString( |
|
|
this.authenticatorData = credential?.response.authenticatorData; |
|
|
credential?.response.authenticatorData, |
|
|
const authenticatorDataBase64Url = bufferToBase64URLString( |
|
|
|
|
|
this.authenticatorData, |
|
|
); |
|
|
); |
|
|
this.authenticatorData = Buffer.from( |
|
|
|
|
|
this.authenticatorDataBase64Url as Base64URLString, |
|
|
|
|
|
"base64", |
|
|
|
|
|
).buffer; |
|
|
|
|
|
|
|
|
|
|
|
this.clientDataJsonBase64Url = bufferToBase64URLString( |
|
|
this.clientDataJsonBase64Url = bufferToBase64URLString( |
|
|
credential?.response.clientDataJSON, |
|
|
credential?.response.clientDataJSON, |
|
|
); |
|
|
); |
|
|
this.clientDataJsonDecoded = JSON.parse( |
|
|
|
|
|
new TextDecoder("utf-8").decode(credential?.response.clientDataJSON), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Our custom type of JWANT means the signature is based on a concatenation of the two Webauthn properties
|
|
|
// Our custom type of JWANT means the signature is based on a concatenation of the two Webauthn properties
|
|
|
const header: JWTPayload = { typ: "JWANT", alg: "ES256" }; |
|
|
const header: JWTPayload = { typ: "JWANT", alg: "ES256" }; |
|
@ -199,7 +190,7 @@ export class PeerSetup { |
|
|
.replace(/=+$/, ""); |
|
|
.replace(/=+$/, ""); |
|
|
|
|
|
|
|
|
const dataInJwt = { |
|
|
const dataInJwt = { |
|
|
AuthenticationData: this.authenticatorDataBase64Url, |
|
|
AuthenticationData: authenticatorDataBase64Url, |
|
|
ClientDataJSON: this.clientDataJsonBase64Url, |
|
|
ClientDataJSON: this.clientDataJsonBase64Url, |
|
|
}; |
|
|
}; |
|
|
const dataInJwtString = JSON.stringify(dataInJwt); |
|
|
const dataInJwtString = JSON.stringify(dataInJwt); |
|
@ -276,20 +267,51 @@ export class PeerSetup { |
|
|
// }
|
|
|
// }
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// I'd love to use this but it doesn't verify.
|
|
|
|
|
|
// Pequires:
|
|
|
|
|
|
// npm install @noble/curves
|
|
|
|
|
|
// ... and this import:
|
|
|
|
|
|
// import { p256 } from "@noble/curves/p256";
|
|
|
|
|
|
export async function verifyJwtP256( |
|
|
|
|
|
credId: Base64URLString, |
|
|
|
|
|
rawId: Uint8Array, |
|
|
|
|
|
did: string, |
|
|
|
|
|
authenticatorData: ArrayBuffer, |
|
|
|
|
|
challenge: Uint8Array, |
|
|
|
|
|
clientDataJsonBase64Url: Base64URLString, |
|
|
|
|
|
signature: Base64URLString, |
|
|
|
|
|
) { |
|
|
|
|
|
const authDataFromBase = Buffer.from(authenticatorData); |
|
|
|
|
|
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64"); |
|
|
|
|
|
const sigBuffer = Buffer.from(signature, "base64"); |
|
|
|
|
|
const finalSigBuffer = unwrapEC2Signature(sigBuffer); |
|
|
|
|
|
const publicKeyBytes = peerDidToPublicKeyBytes(did); |
|
|
|
|
|
|
|
|
|
|
|
// Hash the client data
|
|
|
|
|
|
const hash = sha256(clientDataFromBase); |
|
|
|
|
|
|
|
|
|
|
|
// Construct the preimage
|
|
|
|
|
|
const preimage = Buffer.concat([authDataFromBase, hash]); |
|
|
|
|
|
|
|
|
|
|
|
const isValid = p256.verify( |
|
|
|
|
|
finalSigBuffer, |
|
|
|
|
|
new Uint8Array(preimage), |
|
|
|
|
|
publicKeyBytes, |
|
|
|
|
|
); |
|
|
|
|
|
return isValid; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
export async function verifyJwtSimplewebauthn( |
|
|
export async function verifyJwtSimplewebauthn( |
|
|
jwt: string, |
|
|
|
|
|
credId: Base64URLString, |
|
|
credId: Base64URLString, |
|
|
rawId: Uint8Array, |
|
|
rawId: Uint8Array, |
|
|
|
|
|
did: string, |
|
|
authenticatorData: ArrayBuffer, |
|
|
authenticatorData: ArrayBuffer, |
|
|
authenticatorDataBase64Url: Base64URLString, |
|
|
|
|
|
challenge: Uint8Array, |
|
|
challenge: Uint8Array, |
|
|
clientDataJSON: object, |
|
|
|
|
|
clientDataJsonBase64Url: Base64URLString, |
|
|
clientDataJsonBase64Url: Base64URLString, |
|
|
publicKeyBytes: Uint8Array, |
|
|
|
|
|
publicKeyJwk: JWK, |
|
|
|
|
|
signature: Base64URLString, |
|
|
signature: Base64URLString, |
|
|
) { |
|
|
) { |
|
|
const authData = arrayToBase64Url(Buffer.from(authenticatorData)); |
|
|
const authData = arrayToBase64Url(Buffer.from(authenticatorData)); |
|
|
|
|
|
const publicKeyBytes = peerDidToPublicKeyBytes(did); |
|
|
const authOpts: VerifyAuthenticationResponseOpts = { |
|
|
const authOpts: VerifyAuthenticationResponseOpts = { |
|
|
authenticator: { |
|
|
authenticator: { |
|
|
credentialID: credId, |
|
|
credentialID: credId, |
|
@ -306,9 +328,7 @@ export async function verifyJwtSimplewebauthn( |
|
|
rawId: arrayToBase64Url(rawId), |
|
|
rawId: arrayToBase64Url(rawId), |
|
|
response: { |
|
|
response: { |
|
|
authenticatorData: authData, |
|
|
authenticatorData: authData, |
|
|
clientDataJSON: arrayToBase64Url( |
|
|
clientDataJSON: clientDataJsonBase64Url, |
|
|
Buffer.from(JSON.stringify(clientDataJSON)), |
|
|
|
|
|
), |
|
|
|
|
|
signature: signature, |
|
|
signature: signature, |
|
|
}, |
|
|
}, |
|
|
type: "public-key", |
|
|
type: "public-key", |
|
@ -318,57 +338,16 @@ export async function verifyJwtSimplewebauthn( |
|
|
return verification.verified; |
|
|
return verification.verified; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// I'd love to use this but it doesn't verify.
|
|
|
|
|
|
// Pequires:
|
|
|
|
|
|
// npm install @noble/curves
|
|
|
|
|
|
// ... and this import:
|
|
|
|
|
|
// import { p256 } from "@noble/curves/p256";
|
|
|
|
|
|
export async function verifyJwtP256( |
|
|
|
|
|
jwt: string, |
|
|
|
|
|
credId: Base64URLString, |
|
|
|
|
|
rawId: Uint8Array, |
|
|
|
|
|
authenticatorData: ArrayBuffer, |
|
|
|
|
|
authenticatorDataBase64Url: Base64URLString, |
|
|
|
|
|
challenge: Uint8Array, |
|
|
|
|
|
clientDataJSON: object, |
|
|
|
|
|
clientDataJsonBase64Url: Base64URLString, |
|
|
|
|
|
publicKeyBytes: Uint8Array, |
|
|
|
|
|
publicKeyJwk: JWK, |
|
|
|
|
|
signature: Base64URLString, |
|
|
|
|
|
) { |
|
|
|
|
|
const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64"); |
|
|
|
|
|
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64"); |
|
|
|
|
|
const sigBuffer = Buffer.from(signature, "base64"); |
|
|
|
|
|
const finalSigBuffer = unwrapEC2Signature(sigBuffer); |
|
|
|
|
|
|
|
|
|
|
|
// Hash the client data
|
|
|
|
|
|
const hash = sha256(clientDataFromBase); |
|
|
|
|
|
|
|
|
|
|
|
// Construct the preimage
|
|
|
|
|
|
const preimage = Buffer.concat([authDataFromBase, hash]); |
|
|
|
|
|
|
|
|
|
|
|
const isValid = p256.verify( |
|
|
|
|
|
finalSigBuffer, |
|
|
|
|
|
new Uint8Array(preimage), |
|
|
|
|
|
publicKeyBytes, |
|
|
|
|
|
); |
|
|
|
|
|
return isValid; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export async function verifyJwtWebCrypto( |
|
|
export async function verifyJwtWebCrypto( |
|
|
jwt: string, |
|
|
|
|
|
credId: Base64URLString, |
|
|
credId: Base64URLString, |
|
|
rawId: Uint8Array, |
|
|
rawId: Uint8Array, |
|
|
|
|
|
did: string, |
|
|
authenticatorData: ArrayBuffer, |
|
|
authenticatorData: ArrayBuffer, |
|
|
authenticatorDataBase64Url: Base64URLString, |
|
|
|
|
|
challenge: Uint8Array, |
|
|
challenge: Uint8Array, |
|
|
clientDataJSON: object, |
|
|
|
|
|
clientDataJsonBase64Url: Base64URLString, |
|
|
clientDataJsonBase64Url: Base64URLString, |
|
|
publicKeyBytes: Uint8Array, |
|
|
|
|
|
publicKeyJwk: JWK, |
|
|
|
|
|
signature: Base64URLString, |
|
|
signature: Base64URLString, |
|
|
) { |
|
|
) { |
|
|
const authDataFromBase = Buffer.from(authenticatorDataBase64Url, "base64"); |
|
|
const authDataFromBase = Buffer.from(authenticatorData); |
|
|
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64"); |
|
|
const clientDataFromBase = Buffer.from(clientDataJsonBase64Url, "base64"); |
|
|
const sigBuffer = Buffer.from(signature, "base64"); |
|
|
const sigBuffer = Buffer.from(signature, "base64"); |
|
|
const finalSigBuffer = unwrapEC2Signature(sigBuffer); |
|
|
const finalSigBuffer = unwrapEC2Signature(sigBuffer); |
|
@ -384,6 +363,8 @@ export async function verifyJwtWebCrypto( |
|
|
name: "ECDSA", |
|
|
name: "ECDSA", |
|
|
hash: { name: "SHA-256" }, |
|
|
hash: { name: "SHA-256" }, |
|
|
}; |
|
|
}; |
|
|
|
|
|
const publicKeyBytes = peerDidToPublicKeyBytes(did); |
|
|
|
|
|
const publicKeyJwk = cborToKeys(publicKeyBytes).publicKeyJwk; |
|
|
const keyAlgorithm = { |
|
|
const keyAlgorithm = { |
|
|
name: "ECDSA", |
|
|
name: "ECDSA", |
|
|
namedCurve: publicKeyJwk.crv, |
|
|
namedCurve: publicKeyJwk.crv, |
|
@ -479,19 +460,19 @@ function base64urlEncode(buffer: ArrayBuffer) { |
|
|
// from @simplewebauthn/browser
|
|
|
// from @simplewebauthn/browser
|
|
|
function bufferToBase64URLString(buffer) { |
|
|
function bufferToBase64URLString(buffer) { |
|
|
const bytes = new Uint8Array(buffer); |
|
|
const bytes = new Uint8Array(buffer); |
|
|
let str = ''; |
|
|
let str = ""; |
|
|
for (const charCode of bytes) { |
|
|
for (const charCode of bytes) { |
|
|
str += String.fromCharCode(charCode); |
|
|
str += String.fromCharCode(charCode); |
|
|
} |
|
|
} |
|
|
const base64String = btoa(str); |
|
|
const base64String = btoa(str); |
|
|
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); |
|
|
return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// from @simplewebauthn/browser
|
|
|
// from @simplewebauthn/browser
|
|
|
function base64URLStringToBuffer(base64URLString) { |
|
|
function base64URLStringToBuffer(base64URLString) { |
|
|
const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/'); |
|
|
const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/"); |
|
|
const padLength = (4 - (base64.length % 4)) % 4; |
|
|
const padLength = (4 - (base64.length % 4)) % 4; |
|
|
const padded = base64.padEnd(base64.length + padLength, '='); |
|
|
const padded = base64.padEnd(base64.length + padLength, "="); |
|
|
const binary = atob(padded); |
|
|
const binary = atob(padded); |
|
|
const buffer = new ArrayBuffer(binary.length); |
|
|
const buffer = new ArrayBuffer(binary.length); |
|
|
const bytes = new Uint8Array(buffer); |
|
|
const bytes = new Uint8Array(buffer); |
|
|