Browse Source

use code from @simplewebauthn/server in hopes of more accurate parsing

pull/116/head
Trent Larson 4 months ago
parent
commit
8ee0c4f25e
  1. 2296
      package-lock.json
  2. 1
      package.json
  3. 118
      src/libs/didPeer.ts
  4. 6
      src/views/StartView.vue

2296
package-lock.json

File diff suppressed because it is too large

1
package.json

@ -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",

118
src/libs/didPeer.ts

@ -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,
);
console.log("jwkObj from verification", jwkObj);
if (
jwkObj[1] != 2 || // kty "EC"
jwkObj[3] != -7 || // alg "ES256"
jwkObj[-1] != 1 || // crv "P-256"
jwkObj[-2].length != 32 || // x
jwkObj[-3].length != 32 // y
) {
throw new Error("Unable to extract key.");
}
const publicKeyJwk = {
alg: "ES256",
crv: "P-256",
kty: "EC",
x: toBase64Url(jwkObj[-2]),
y: toBase64Url(jwkObj[-3]),
};
const publicKeyBytes = Buffer.concat([
Buffer.from(jwkObj[-2]),
Buffer.from(jwkObj[-3]),
]);
function extractPublicKeyJwk(authData: Uint8Array) { //const publicKeyBytes = extractPublicKeyCose(attestationObject.authData);
const publicKeyCose = extractPublicKeyCose(authData); // Example position //const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
const publicKeyJwk = coseToJwk(publicKeyCose);
return publicKeyJwk;
}
function extractPublicKeyCose(authData: Uint8Array) { return { rawId: credential?.rawId, publicKeyJwk, publicKeyBytes };
// Extract the public key from authData using appropriate parsing.
// This involves extracting the COSE key format.
// 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 publicKeyCose = authData.slice(authData.length - 77);
return publicKeyCose;
} }
function coseToJwk(coseKey: Uint8Array) { // parse authData
// Convert COSE key format to JWK // here's one: https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/helpers/parseAuthenticatorData.ts#L11
// This is simplified and needs appropriate parsing and conversion logic // from https://chatgpt.com/c/0ce72fda-bc5d-42ff-a748-6022f6e39fa0
return { // from https://chatgpt.com/share/78a5c91d-099d-46dc-aa6d-fc0c916509fa
kty: "EC",
crv: "P-256",
x: btoa(coseKey.slice(2, 34)),
y: btoa(coseKey.slice(34, 66)),
};
}
export function createPeerDid(publicKeyCose: Uint8Array) { 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",
}, },
], ],
}; };

6
src/views/StartView.vue

@ -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);

Loading…
Cancel
Save