|
@ -3,6 +3,7 @@ import { Buffer } from "buffer/"; |
|
|
import { decode as cborDecode } from "cbor-x"; |
|
|
import { decode as cborDecode } from "cbor-x"; |
|
|
import { createJWS, JWTPayload, verifyJWT } from "did-jwt"; |
|
|
import { createJWS, JWTPayload, verifyJWT } from "did-jwt"; |
|
|
import { DIDResolutionResult, Resolver } from "did-resolver"; |
|
|
import { DIDResolutionResult, Resolver } from "did-resolver"; |
|
|
|
|
|
import { sha256 } from "ethereum-cryptography/sha256.js"; |
|
|
import { bytesToMultibase } from "@veramo/utils"; |
|
|
import { bytesToMultibase } from "@veramo/utils"; |
|
|
import { |
|
|
import { |
|
|
startAuthentication, |
|
|
startAuthentication, |
|
@ -22,6 +23,8 @@ import { |
|
|
PublicKeyCredentialRequestOptionsJSON, |
|
|
PublicKeyCredentialRequestOptionsJSON, |
|
|
} from "@simplewebauthn/types"; |
|
|
} from "@simplewebauthn/types"; |
|
|
|
|
|
|
|
|
|
|
|
import { generateRandomBytes } from "@/libs/crypto"; |
|
|
|
|
|
|
|
|
export interface JWK { |
|
|
export interface JWK { |
|
|
kty: string; |
|
|
kty: string; |
|
|
crv: string; |
|
|
crv: string; |
|
@ -33,15 +36,15 @@ export interface PublicKeyCredential { |
|
|
jwt: JWK; |
|
|
jwt: JWK; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toBase64Url(anything: Uint8Array) { |
|
|
function toBase64Url(anythingB64: string) { |
|
|
return Buffer.from(anything) |
|
|
return anythingB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); |
|
|
.toString("base64") |
|
|
} |
|
|
.replace(/\+/g, "-") |
|
|
|
|
|
.replace(/\//g, "_") |
|
|
function arrayToBase64Url(anything: Uint8Array) { |
|
|
.replace(/=+$/, ""); |
|
|
return toBase64Url(Buffer.from(anything).toString("base64")); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export async function registerCredential() { |
|
|
export async function registerCredential(userId: Uint8Array) { |
|
|
const options: PublicKeyCredentialCreationOptionsJSON = |
|
|
const options: PublicKeyCredentialCreationOptionsJSON = |
|
|
await generateRegistrationOptions({ |
|
|
await generateRegistrationOptions({ |
|
|
rpName: "Time Safari", |
|
|
rpName: "Time Safari", |
|
@ -85,10 +88,8 @@ export async function registerCredential() { |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export async function registerCredential2( |
|
|
export async function registerCredential2(userId: Uint8Array) { |
|
|
userId: Uint8Array, |
|
|
const challenge = generateRandomBytes(32); |
|
|
challenge: Uint8Array, |
|
|
|
|
|
) { |
|
|
|
|
|
const publicKeyOptions: PublicKeyCredentialCreationOptions = { |
|
|
const publicKeyOptions: PublicKeyCredentialCreationOptions = { |
|
|
challenge: challenge, |
|
|
challenge: challenge, |
|
|
rp: { |
|
|
rp: { |
|
@ -120,20 +121,20 @@ export async function registerCredential2( |
|
|
|
|
|
|
|
|
console.log("credential", credential); |
|
|
console.log("credential", credential); |
|
|
console.log(credential?.id, " is the new ID base64-url-encoded"); |
|
|
console.log(credential?.id, " is the new ID base64-url-encoded"); |
|
|
console.log(toBase64Url(credential?.rawId), " is the base64 rawId"); |
|
|
console.log(arrayToBase64Url(credential?.rawId), " is the base64 rawId"); |
|
|
const attestationResponse = credential?.response; |
|
|
const attestationResponse = credential?.response; |
|
|
const verfInput: VerifyRegistrationResponseOpts = { |
|
|
const verfInput: VerifyRegistrationResponseOpts = { |
|
|
response: { |
|
|
response: { |
|
|
id: credential?.id as string, |
|
|
id: credential?.id as string, |
|
|
rawId: credential?.id as string, //Buffer.from(credential?.rawId).toString("base64"),
|
|
|
rawId: credential?.id as string, //Buffer.from(credential?.rawId).toString("base64"),
|
|
|
response: { |
|
|
response: { |
|
|
attestationObject: toBase64Url(attestationResponse?.attestationObject), |
|
|
attestationObject: arrayToBase64Url(attestationResponse?.attestationObject), |
|
|
clientDataJSON: toBase64Url(attestationResponse?.clientDataJSON), |
|
|
clientDataJSON: arrayToBase64Url(attestationResponse?.clientDataJSON), |
|
|
}, |
|
|
}, |
|
|
clientExtensionResults: {}, |
|
|
clientExtensionResults: {}, |
|
|
type: "public-key", |
|
|
type: "public-key", |
|
|
}, |
|
|
}, |
|
|
expectedChallenge: toBase64Url(challenge), |
|
|
expectedChallenge: arrayToBase64Url(challenge), |
|
|
expectedOrigin: window.location.origin, |
|
|
expectedOrigin: window.location.origin, |
|
|
}; |
|
|
}; |
|
|
console.log("verfInput", verfInput); |
|
|
console.log("verfInput", verfInput); |
|
@ -169,8 +170,8 @@ export async function registerCredential2( |
|
|
alg: "ES256", |
|
|
alg: "ES256", |
|
|
crv: "P-256", |
|
|
crv: "P-256", |
|
|
kty: "EC", |
|
|
kty: "EC", |
|
|
x: toBase64Url(jwkObj[-2]), |
|
|
x: arrayToBase64Url(jwkObj[-2]), |
|
|
y: toBase64Url(jwkObj[-3]), |
|
|
y: arrayToBase64Url(jwkObj[-3]), |
|
|
}; |
|
|
}; |
|
|
const publicKeyBytes = Buffer.concat([ |
|
|
const publicKeyBytes = Buffer.concat([ |
|
|
Buffer.from(jwkObj[-2]), |
|
|
Buffer.from(jwkObj[-2]), |
|
@ -207,12 +208,65 @@ export function createPeerDid(publicKeyBytes: Uint8Array) { |
|
|
|
|
|
|
|
|
export class PeerSetup { |
|
|
export class PeerSetup { |
|
|
public authenticatorData?: ArrayBuffer; |
|
|
public authenticatorData?: ArrayBuffer; |
|
|
|
|
|
public challenge?: Uint8Array; |
|
|
public clientDataJsonDecoded?: object; |
|
|
public clientDataJsonDecoded?: object; |
|
|
|
|
|
public signature?: Base64URLString; |
|
|
|
|
|
|
|
|
public async createJwt( |
|
|
public async createJwt( |
|
|
payload: object, |
|
|
payload: object, |
|
|
issuerDid: string, |
|
|
issuerDid: string, |
|
|
credentialId: string, |
|
|
credentialId: string, |
|
|
|
|
|
) { |
|
|
|
|
|
const header: JWTPayload = { typ: "JWT", alg: "ES256" }; |
|
|
|
|
|
|
|
|
|
|
|
// from createJWT in did-jwt/src/JWT.ts
|
|
|
|
|
|
const timestamps: Partial<JWTPayload> = { |
|
|
|
|
|
iat: Math.floor(Date.now() / 1000), |
|
|
|
|
|
exp: undefined, |
|
|
|
|
|
}; |
|
|
|
|
|
const fullPayload = { ...timestamps, ...payload, iss: issuerDid }; |
|
|
|
|
|
|
|
|
|
|
|
const payloadHash: Uint8Array = sha256( |
|
|
|
|
|
Buffer.from(JSON.stringify(fullPayload)), |
|
|
|
|
|
); |
|
|
|
|
|
const options: PublicKeyCredentialRequestOptionsJSON = |
|
|
|
|
|
await generateAuthenticationOptions({ |
|
|
|
|
|
challenge: payloadHash, |
|
|
|
|
|
rpID: window.location.hostname, |
|
|
|
|
|
// Require users to use a previously-registered authenticator
|
|
|
|
|
|
// allowCredentials: userPasskeys.map(passkey => ({
|
|
|
|
|
|
// id: passkey.id,
|
|
|
|
|
|
// transports: passkey.transports,
|
|
|
|
|
|
// })),
|
|
|
|
|
|
}); |
|
|
|
|
|
console.log("custom authentication options", options); |
|
|
|
|
|
|
|
|
|
|
|
const clientAuth = await startAuthentication(options); |
|
|
|
|
|
console.log("custom clientAuth", clientAuth); |
|
|
|
|
|
|
|
|
|
|
|
this.authenticatorData = Buffer.from( |
|
|
|
|
|
clientAuth.response.authenticatorData, |
|
|
|
|
|
"base64", |
|
|
|
|
|
).buffer; |
|
|
|
|
|
this.challenge = payloadHash; |
|
|
|
|
|
this.clientDataJsonDecoded = JSON.parse( |
|
|
|
|
|
Buffer.from(clientAuth.response.clientDataJSON, "base64").toString( |
|
|
|
|
|
"utf-8", |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
this.signature = clientAuth.response.signature; |
|
|
|
|
|
|
|
|
|
|
|
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64"); |
|
|
|
|
|
const payloadBase64 = clientAuth.response.clientDataJSON; |
|
|
|
|
|
const signature = clientAuth.response.signature; |
|
|
|
|
|
|
|
|
|
|
|
return headerBase64 + "." + payloadBase64 + "." + signature; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public async createJwt2( |
|
|
|
|
|
payload: object, |
|
|
|
|
|
issuerDid: string, |
|
|
|
|
|
credentialId: string, |
|
|
) { |
|
|
) { |
|
|
const signer = await this.webAuthnES256KSigner(credentialId); |
|
|
const signer = await this.webAuthnES256KSigner(credentialId); |
|
|
|
|
|
|
|
@ -319,58 +373,63 @@ export async function verifyJwt( |
|
|
credId: Base64URLString, |
|
|
credId: Base64URLString, |
|
|
rawId: Uint8Array, |
|
|
rawId: Uint8Array, |
|
|
authenticatorData: ArrayBuffer, |
|
|
authenticatorData: ArrayBuffer, |
|
|
|
|
|
challenge: Uint8Array, |
|
|
clientDataJSON: object, |
|
|
clientDataJSON: object, |
|
|
publicKey: Uint8Array, |
|
|
publicKey: Uint8Array, |
|
|
signature: Base64URLString, |
|
|
signature: Base64URLString, |
|
|
) { |
|
|
) { |
|
|
const options: PublicKeyCredentialRequestOptionsJSON = |
|
|
// Here's a combined auth & verify process, based on some of the inputs.
|
|
|
await generateAuthenticationOptions({ |
|
|
//
|
|
|
rpID: window.location.hostname, |
|
|
// const options: PublicKeyCredentialRequestOptionsJSON =
|
|
|
// Require users to use a previously-registered authenticator
|
|
|
// await generateAuthenticationOptions({
|
|
|
// allowCredentials: userPasskeys.map(passkey => ({
|
|
|
// rpID: window.location.hostname,
|
|
|
// id: passkey.id,
|
|
|
// // Require users to use a previously-registered authenticator
|
|
|
// transports: passkey.transports,
|
|
|
// // allowCredentials: userPasskeys.map(passkey => ({
|
|
|
// })),
|
|
|
// // id: passkey.id,
|
|
|
}); |
|
|
// // transports: passkey.transports,
|
|
|
console.log("authentication options", options); |
|
|
// // })),
|
|
|
|
|
|
// });
|
|
|
const clientAuth = await startAuthentication(options); |
|
|
// console.log("authentication options", options);
|
|
|
console.log("clientAuth", clientAuth); |
|
|
//
|
|
|
|
|
|
// const clientAuth = await startAuthentication(options);
|
|
|
const verfOpts: VerifyAuthenticationResponseOpts = { |
|
|
// console.log("clientAuth", clientAuth);
|
|
|
authenticator: { |
|
|
//
|
|
|
credentialID: credId, |
|
|
// const verfOpts: VerifyAuthenticationResponseOpts = {
|
|
|
credentialPublicKey: publicKey, |
|
|
// authenticator: {
|
|
|
counter: 0, |
|
|
// credentialID: credId,
|
|
|
}, |
|
|
// credentialPublicKey: publicKey,
|
|
|
expectedChallenge: options.challenge, |
|
|
// counter: 0,
|
|
|
expectedOrigin: window.location.origin, |
|
|
// },
|
|
|
expectedRPID: window.location.hostname, |
|
|
// expectedChallenge: options.challenge,
|
|
|
response: clientAuth, |
|
|
// expectedOrigin: window.location.origin,
|
|
|
}; |
|
|
// expectedRPID: window.location.hostname,
|
|
|
console.log("verfOpts", verfOpts); |
|
|
// response: clientAuth,
|
|
|
const verificationFromClient = await verifyAuthenticationResponse(verfOpts); |
|
|
// };
|
|
|
console.log("client auth verification", verificationFromClient); |
|
|
// console.log("verfOpts", verfOpts);
|
|
|
|
|
|
// const verificationFromClient = await verifyAuthenticationResponse(verfOpts);
|
|
|
const authData = toBase64Url(Buffer.from(authenticatorData)); |
|
|
// console.log("client auth verification", verificationFromClient);
|
|
|
|
|
|
|
|
|
|
|
|
const authData = arrayToBase64Url(Buffer.from(authenticatorData)); |
|
|
const authOpts: VerifyAuthenticationResponseOpts = { |
|
|
const authOpts: VerifyAuthenticationResponseOpts = { |
|
|
authenticator: { |
|
|
authenticator: { |
|
|
credentialID: credId, |
|
|
credentialID: credId, |
|
|
credentialPublicKey: publicKey, |
|
|
credentialPublicKey: publicKey, |
|
|
counter: 0, |
|
|
counter: 0, |
|
|
}, |
|
|
}, |
|
|
expectedChallenge: options.challenge, |
|
|
expectedChallenge: arrayToBase64Url(challenge), |
|
|
expectedOrigin: window.location.origin, |
|
|
expectedOrigin: window.location.origin, |
|
|
expectedRPID: window.location.hostname, |
|
|
expectedRPID: window.location.hostname, |
|
|
response: { |
|
|
response: { |
|
|
authenticatorAttachment: "platform", |
|
|
authenticatorAttachment: "platform", |
|
|
clientExtensionResults: {}, |
|
|
clientExtensionResults: {}, |
|
|
id: credId, |
|
|
id: credId, |
|
|
rawId: toBase64Url(rawId), |
|
|
rawId: arrayToBase64Url(rawId), |
|
|
response: { |
|
|
response: { |
|
|
authenticatorData: authData, |
|
|
authenticatorData: authData, |
|
|
clientDataJSON: clientAuth.response.clientDataJSON, |
|
|
clientDataJSON: arrayToBase64Url( |
|
|
signature: clientAuth.response.signature, |
|
|
Buffer.from(JSON.stringify(clientDataJSON)), |
|
|
|
|
|
), |
|
|
|
|
|
signature: signature, |
|
|
}, |
|
|
}, |
|
|
type: "public-key", |
|
|
type: "public-key", |
|
|
}, |
|
|
}, |
|
|