use code from @simplewebauthn/server in hopes of more accurate parsing
This commit is contained in:
2120
package-lock.json
generated
2120
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.6",
|
"@fortawesome/vue-fontawesome": "^3.0.6",
|
||||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||||
|
"@simplewebauthn/server": "^10.0.0",
|
||||||
"@tweenjs/tween.js": "^21.1.1",
|
"@tweenjs/tween.js": "^21.1.1",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ 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 { bytesToMultibase } from "@veramo/utils";
|
import { bytesToMultibase } from "@veramo/utils";
|
||||||
|
import {
|
||||||
|
verifyRegistrationResponse,
|
||||||
|
VerifyRegistrationResponseOpts,
|
||||||
|
} from "@simplewebauthn/server";
|
||||||
|
|
||||||
export interface JWK {
|
export interface JWK {
|
||||||
kty: string;
|
kty: string;
|
||||||
@@ -16,6 +20,14 @@ export interface PublicKeyCredential {
|
|||||||
jwt: JWK;
|
jwt: JWK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toBase64Url(anything: Uint8Array) {
|
||||||
|
return Buffer.from(anything)
|
||||||
|
.toString("base64")
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
.replace(/=+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
export async function registerCredential(
|
export async function registerCredential(
|
||||||
userId: Uint8Array,
|
userId: Uint8Array,
|
||||||
challenge: Uint8Array,
|
challenge: Uint8Array,
|
||||||
@@ -49,7 +61,30 @@ export async function registerCredential(
|
|||||||
publicKey: publicKeyOptions,
|
publicKey: publicKeyOptions,
|
||||||
});
|
});
|
||||||
console.log("credential", credential);
|
console.log("credential", credential);
|
||||||
|
console.log(credential?.id, " is the new Id");
|
||||||
|
console.log(
|
||||||
|
Buffer.from(credential?.rawId).toString("base64"),
|
||||||
|
" is the base64 rawId",
|
||||||
|
);
|
||||||
const attestationResponse = credential?.response;
|
const attestationResponse = credential?.response;
|
||||||
|
const verfInput: VerifyRegistrationResponseOpts = {
|
||||||
|
response: {
|
||||||
|
id: credential?.id as string,
|
||||||
|
rawId: credential?.id as string, //Buffer.from(credential?.rawId).toString("base64"),
|
||||||
|
response: {
|
||||||
|
attestationObject: toBase64Url(attestationResponse?.attestationObject),
|
||||||
|
clientDataJSON: toBase64Url(attestationResponse?.clientDataJSON),
|
||||||
|
},
|
||||||
|
clientExtensionResults: {},
|
||||||
|
type: "public-key",
|
||||||
|
},
|
||||||
|
//expectedChallenge: Buffer.from(challenge).toString("base64"),
|
||||||
|
expectedChallenge: toBase64Url(challenge),
|
||||||
|
expectedOrigin: window.location.origin,
|
||||||
|
};
|
||||||
|
console.log("verfInput", verfInput);
|
||||||
|
const verification = await verifyRegistrationResponse(verfInput);
|
||||||
|
console.log("verification", verification);
|
||||||
|
|
||||||
// Parse the attestation response to get the public key
|
// Parse the attestation response to get the public key
|
||||||
const clientDataJSON = attestationResponse.clientDataJSON;
|
const clientDataJSON = attestationResponse.clientDataJSON;
|
||||||
@@ -63,43 +98,50 @@ export async function registerCredential(
|
|||||||
);
|
);
|
||||||
console.log("attestationObject", attestationObject);
|
console.log("attestationObject", attestationObject);
|
||||||
|
|
||||||
const publicKeyCose = extractPublicKeyCose(attestationObject.authData);
|
const credData = parseAuthData(attestationObject.authData);
|
||||||
const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
|
console.log("new attempt at publicKey", credData);
|
||||||
|
|
||||||
return { rawId: credential?.rawId, publicKeyJwk, publicKeyCose };
|
const jwkObj = cborDecode(
|
||||||
}
|
verification.registrationInfo?.credentialPublicKey as Uint8Array,
|
||||||
|
);
|
||||||
function extractPublicKeyJwk(authData: Uint8Array) {
|
console.log("jwkObj from verification", jwkObj);
|
||||||
const publicKeyCose = extractPublicKeyCose(authData); // Example position
|
if (
|
||||||
const publicKeyJwk = coseToJwk(publicKeyCose);
|
jwkObj[1] != 2 || // kty "EC"
|
||||||
return publicKeyJwk;
|
jwkObj[3] != -7 || // alg "ES256"
|
||||||
}
|
jwkObj[-1] != 1 || // crv "P-256"
|
||||||
|
jwkObj[-2].length != 32 || // x
|
||||||
function extractPublicKeyCose(authData: Uint8Array) {
|
jwkObj[-3].length != 32 // y
|
||||||
// Extract the public key from authData using appropriate parsing.
|
) {
|
||||||
// This involves extracting the COSE key format.
|
throw new Error("Unable to extract key.");
|
||||||
// For simplicity, we'll assume the public key is at a certain position in authData.
|
}
|
||||||
// Alternatively, see last answer here: https://chatgpt.com/share/78a5c91d-099d-46dc-aa6d-fc0c916509fa
|
const publicKeyJwk = {
|
||||||
const publicKeyCose = authData.slice(authData.length - 77);
|
alg: "ES256",
|
||||||
return publicKeyCose;
|
|
||||||
}
|
|
||||||
|
|
||||||
function coseToJwk(coseKey: Uint8Array) {
|
|
||||||
// Convert COSE key format to JWK
|
|
||||||
// This is simplified and needs appropriate parsing and conversion logic
|
|
||||||
return {
|
|
||||||
kty: "EC",
|
|
||||||
crv: "P-256",
|
crv: "P-256",
|
||||||
x: btoa(coseKey.slice(2, 34)),
|
kty: "EC",
|
||||||
y: btoa(coseKey.slice(34, 66)),
|
x: toBase64Url(jwkObj[-2]),
|
||||||
|
y: toBase64Url(jwkObj[-3]),
|
||||||
};
|
};
|
||||||
|
const publicKeyBytes = Buffer.concat([
|
||||||
|
Buffer.from(jwkObj[-2]),
|
||||||
|
Buffer.from(jwkObj[-3]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
//const publicKeyBytes = extractPublicKeyCose(attestationObject.authData);
|
||||||
|
//const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
|
||||||
|
|
||||||
|
return { rawId: credential?.rawId, publicKeyJwk, publicKeyBytes };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPeerDid(publicKeyCose: Uint8Array) {
|
// parse authData
|
||||||
|
// here's one: https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/parseAuthenticatorData.ts#L11
|
||||||
|
// from https://chatgpt.com/c/0ce72fda-bc5d-42ff-a748-6022f6e39fa0
|
||||||
|
// from https://chatgpt.com/share/78a5c91d-099d-46dc-aa6d-fc0c916509fa
|
||||||
|
|
||||||
|
export function createPeerDid(publicKeyBytes: Uint8Array) {
|
||||||
// https://github.com/decentralized-identity/veramo/blob/next/packages/did-provider-peer/src/peer-did-provider.ts#L67
|
// https://github.com/decentralized-identity/veramo/blob/next/packages/did-provider-peer/src/peer-did-provider.ts#L67
|
||||||
//const provider = new PeerDIDProvider({ defaultKms: LOCAL_KMS_NAME });
|
//const provider = new PeerDIDProvider({ defaultKms: LOCAL_KMS_NAME });
|
||||||
const methodSpecificId = bytesToMultibase(
|
const methodSpecificId = bytesToMultibase(
|
||||||
publicKeyCose,
|
publicKeyBytes,
|
||||||
"base58btc",
|
"base58btc",
|
||||||
"secp256k1-pub",
|
"secp256k1-pub",
|
||||||
);
|
);
|
||||||
@@ -216,21 +258,15 @@ export async function verifyJwt(
|
|||||||
publicKey: JWK, // eslint-disable-line @typescript-eslint/no-unused-vars
|
publicKey: JWK, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
) {
|
) {
|
||||||
const decoded = verifyJWT(jwt, {
|
const decoded = verifyJWT(jwt, {
|
||||||
// didAuthenticator: {
|
|
||||||
// authenticators: [{ publicKeyJwk: publicKey }],
|
|
||||||
// issuer: issuerDid,
|
|
||||||
// },
|
|
||||||
//resolver: new Resolver({ ...getResolver() }),
|
|
||||||
resolver: new Resolver({ peer: peerDidToDidDocument }),
|
resolver: new Resolver({ peer: peerDidToDidDocument }),
|
||||||
});
|
});
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
|
async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
|
||||||
if (!did.startsWith("did:peer:0z")) {
|
if (!did.startsWith("did:peer:0z")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"This only verifies a peer DID method 0 base58btc encoded.",
|
"This only verifies a peer DID method 0 encoded base58btc.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// this is basically hard-coded based on the results from the @aviarytech/did-peer resolver
|
// this is basically hard-coded based on the results from the @aviarytech/did-peer resolver
|
||||||
@@ -242,19 +278,19 @@ async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
|
|||||||
"https://www.w3.org/ns/did/v1",
|
"https://www.w3.org/ns/did/v1",
|
||||||
"https://w3id.org/security/suites/jws-2020/v1",
|
"https://w3id.org/security/suites/jws-2020/v1",
|
||||||
],
|
],
|
||||||
assertionMethod: ["did:peer:" + id + "#" + encnumbasis],
|
assertionMethod: [did + "#" + encnumbasis],
|
||||||
authentication: ["did:peer:" + id + "#" + encnumbasis],
|
authentication: [did + "#" + encnumbasis],
|
||||||
capabilityDelegation: ["did:peer:" + id + "#" + encnumbasis],
|
capabilityDelegation: [did + "#" + encnumbasis],
|
||||||
capabilityInvocation: ["did:peer:" + id + "#" + encnumbasis],
|
capabilityInvocation: [did + "#" + encnumbasis],
|
||||||
id: did,
|
id: did,
|
||||||
keyAgreement: undefined,
|
keyAgreement: undefined,
|
||||||
service: undefined,
|
service: undefined,
|
||||||
verificationMethod: [
|
verificationMethod: [
|
||||||
{
|
{
|
||||||
id: "did:peer:" + id + "#" + encnumbasis,
|
|
||||||
type: "EcdsaSecp256k1VerificationKey2019",
|
|
||||||
controller: did,
|
controller: did,
|
||||||
|
id: did + "#" + encnumbasis,
|
||||||
publicKeyMultibase: multibase,
|
publicKeyMultibase: multibase,
|
||||||
|
type: "EcdsaSecp256k1VerificationKey2019",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ import {
|
|||||||
export default class StartView extends Vue {
|
export default class StartView extends Vue {
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
publicKeyJwk?: JWK;
|
publicKeyJwk?: JWK;
|
||||||
publicKeyCose?: Uint8Array;
|
publicKeyBytes?: Uint8Array;
|
||||||
userId?: Uint8Array;
|
userId?: Uint8Array;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -96,14 +96,14 @@ export default class StartView extends Vue {
|
|||||||
const cred = await registerCredential(this.userId, generateRandomBytes(32));
|
const cred = await registerCredential(this.userId, generateRandomBytes(32));
|
||||||
console.log("public key", cred);
|
console.log("public key", cred);
|
||||||
this.publicKeyJwk = cred.publicKeyJwk;
|
this.publicKeyJwk = cred.publicKeyJwk;
|
||||||
this.publicKeyCose = cred.publicKeyCose;
|
this.publicKeyBytes = cred.publicKeyBytes;
|
||||||
this.userId = cred.rawId as Uint8Array;
|
this.userId = cred.rawId as Uint8Array;
|
||||||
//this.$router.push({ name: "new-identifier" });
|
//this.$router.push({ name: "new-identifier" });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onClickNo() {
|
public async onClickNo() {
|
||||||
const credArrBuff = this.userId;
|
const credArrBuff = this.userId;
|
||||||
const did = createPeerDid(this.publicKeyCose as Uint8Array);
|
const did = createPeerDid(this.publicKeyBytes as Uint8Array);
|
||||||
console.log("did", did);
|
console.log("did", did);
|
||||||
const jwt = await createJwt({ a: 1 }, did, credArrBuff as Uint8Array);
|
const jwt = await createJwt({ a: 1 }, did, credArrBuff as Uint8Array);
|
||||||
console.log("jwt", jwt);
|
console.log("jwt", jwt);
|
||||||
|
|||||||
Reference in New Issue
Block a user