Trent Larson
6 months ago
4 changed files with 266 additions and 2 deletions
@ -0,0 +1,130 @@ |
|||
import { decode as cborDecode } from "cbor-x"; |
|||
import { createJWS, JWTPayload } from "did-jwt"; |
|||
|
|||
export async function registerCredential() { |
|||
const publicKeyOptions: PublicKeyCredentialCreationOptions = { |
|||
challenge: new Uint8Array(32), // Random challenge
|
|||
rp: { |
|||
name: "Time Safari", |
|||
id: window.location.hostname, |
|||
}, |
|||
user: { |
|||
id: new Uint8Array(16), // User ID
|
|||
name: "user@example.com", |
|||
displayName: "Example User", |
|||
}, |
|||
pubKeyCredParams: [ |
|||
{ |
|||
type: "public-key", |
|||
alg: -7, // ES256 algorithm
|
|||
}, |
|||
], |
|||
authenticatorSelection: { |
|||
authenticatorAttachment: "platform", |
|||
userVerification: "preferred", |
|||
}, |
|||
timeout: 60000, |
|||
attestation: "direct", |
|||
}; |
|||
|
|||
const credential = await navigator.credentials.create({ |
|||
publicKey: publicKeyOptions, |
|||
}); |
|||
console.log("credential", credential); |
|||
const attestationResponse = credential?.response; |
|||
|
|||
// Parse the attestation response to get the public key
|
|||
const clientDataJSON = attestationResponse.clientDataJSON; |
|||
console.log("clientDataJSON", clientDataJSON); |
|||
const attestationObject = cborDecode( |
|||
new Uint8Array(attestationResponse.attestationObject), |
|||
); |
|||
|
|||
const authData = new Uint8Array(attestationObject.authData); |
|||
const publicKey = extractPublicKey(authData); |
|||
|
|||
return publicKey; |
|||
} |
|||
|
|||
// @ts-expect-error just because it doesn't like the "any"
|
|||
function extractPublicKey(authData) { |
|||
// Extract the public key from authData using appropriate parsing
|
|||
// This involves extracting the COSE key format and converting it to JWK
|
|||
// For simplicity, we'll assume the public key is at a certain position in authData
|
|||
const publicKeyCose = authData.slice(authData.length - 77); // Example position
|
|||
const publicKeyJwk = coseToJwk(publicKeyCose); |
|||
return publicKeyJwk; |
|||
} |
|||
|
|||
// @ts-expect-error just because it doesn't like the "any"
|
|||
function coseToJwk(coseKey) { |
|||
// Convert COSE key format to JWK
|
|||
// This is simplified and needs appropriate parsing and conversion logic
|
|||
return { |
|||
kty: "EC", |
|||
crv: "P-256", |
|||
x: btoa(coseKey.slice(2, 34)), |
|||
y: btoa(coseKey.slice(34, 66)), |
|||
}; |
|||
} |
|||
|
|||
async function generateWebAuthnSignature( |
|||
dataToSign: string | Uint8Array, |
|||
credentialID: ArrayBuffer, |
|||
) { |
|||
if (!(dataToSign instanceof Uint8Array)) { |
|||
dataToSign = new TextEncoder().encode(dataToSign as string); |
|||
} |
|||
const challenge = dataToSign; |
|||
|
|||
const options = { |
|||
challenge: challenge, |
|||
allowCredentials: [{ id: credentialID, type: "public-key" }], |
|||
userVerification: "preferred", |
|||
}; |
|||
|
|||
const assertion = await navigator.credentials.get({ publicKey: options }); |
|||
|
|||
const authenticatorAssertionResponse = assertion?.response; |
|||
return { |
|||
signature: authenticatorAssertionResponse.signature, |
|||
clientDataJSON: authenticatorAssertionResponse.clientDataJSON, |
|||
authenticatorData: authenticatorAssertionResponse.authenticatorData, |
|||
}; |
|||
} |
|||
|
|||
async function webAuthnES256KSigner(credentialID: ArrayBuffer) { |
|||
return async (data: string | Uint8Array) => { |
|||
// also has clientDataJSON
|
|||
const { signature, authenticatorData } = await generateWebAuthnSignature( |
|||
data, |
|||
credentialID, |
|||
); |
|||
|
|||
// Combine the WebAuthn components into a single signature format as required by did-jwt
|
|||
const combinedSignature = Buffer.concat([ |
|||
Buffer.from(authenticatorData), |
|||
Buffer.from(signature), |
|||
]); |
|||
|
|||
return combinedSignature.toString("base64"); |
|||
}; |
|||
} |
|||
|
|||
export async function createJwt( |
|||
payload: object, |
|||
issuerDid: string, |
|||
credentialId: ArrayBuffer, |
|||
) { |
|||
const signer = await webAuthnES256KSigner(credentialId); |
|||
|
|||
// from createJWT in did-jwt/src/JWT.ts
|
|||
const header: JWTPayload = { typ: "JWT", alg: "ES256K" }; |
|||
const timestamps: Partial<JWTPayload> = { |
|||
iat: Math.floor(Date.now() / 1000), |
|||
exp: undefined, |
|||
}; |
|||
const fullPayload = { ...timestamps, ...payload, iss: issuerDid }; |
|||
|
|||
return createJWS(fullPayload, signer, header); |
|||
} |
Loading…
Reference in new issue