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. 2292
      package-lock.json
  2. 1
      package.json
  3. 118
      src/libs/didPeer.ts
  4. 6
      src/views/StartView.vue

2292
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/vue-fontawesome": "^3.0.6",
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@simplewebauthn/server": "^10.0.0",
"@tweenjs/tween.js": "^21.1.1",
"@types/js-yaml": "^4.0.9",
"@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 { DIDResolutionResult, Resolver } from "did-resolver";
import { bytesToMultibase } from "@veramo/utils";
import {
verifyRegistrationResponse,
VerifyRegistrationResponseOpts,
} from "@simplewebauthn/server";
export interface JWK {
kty: string;
@ -16,6 +20,14 @@ export interface PublicKeyCredential {
jwt: JWK;
}
function toBase64Url(anything: Uint8Array) {
return Buffer.from(anything)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
export async function registerCredential(
userId: Uint8Array,
challenge: Uint8Array,
@ -49,7 +61,30 @@ export async function registerCredential(
publicKey: publicKeyOptions,
});
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 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
const clientDataJSON = attestationResponse.clientDataJSON;
@ -63,43 +98,50 @@ export async function registerCredential(
);
console.log("attestationObject", attestationObject);
const publicKeyCose = extractPublicKeyCose(attestationObject.authData);
const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
const credData = parseAuthData(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 publicKeyCose = extractPublicKeyCose(authData); // Example position
const publicKeyJwk = coseToJwk(publicKeyCose);
return publicKeyJwk;
}
//const publicKeyBytes = extractPublicKeyCose(attestationObject.authData);
//const publicKeyJwk = extractPublicKeyJwk(attestationObject.authData);
function extractPublicKeyCose(authData: Uint8Array) {
// 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;
return { rawId: credential?.rawId, publicKeyJwk, publicKeyBytes };
}
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",
x: btoa(coseKey.slice(2, 34)),
y: btoa(coseKey.slice(34, 66)),
};
}
// 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(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
//const provider = new PeerDIDProvider({ defaultKms: LOCAL_KMS_NAME });
const methodSpecificId = bytesToMultibase(
publicKeyCose,
publicKeyBytes,
"base58btc",
"secp256k1-pub",
);
@ -216,21 +258,15 @@ export async function verifyJwt(
publicKey: JWK, // eslint-disable-line @typescript-eslint/no-unused-vars
) {
const decoded = verifyJWT(jwt, {
// didAuthenticator: {
// authenticators: [{ publicKeyJwk: publicKey }],
// issuer: issuerDid,
// },
//resolver: new Resolver({ ...getResolver() }),
resolver: new Resolver({ peer: peerDidToDidDocument }),
});
return decoded;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
if (!did.startsWith("did:peer:0z")) {
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
@ -242,19 +278,19 @@ async function peerDidToDidDocument(did: string): Promise<DIDResolutionResult> {
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1",
],
assertionMethod: ["did:peer:" + id + "#" + encnumbasis],
authentication: ["did:peer:" + id + "#" + encnumbasis],
capabilityDelegation: ["did:peer:" + id + "#" + encnumbasis],
capabilityInvocation: ["did:peer:" + id + "#" + encnumbasis],
assertionMethod: [did + "#" + encnumbasis],
authentication: [did + "#" + encnumbasis],
capabilityDelegation: [did + "#" + encnumbasis],
capabilityInvocation: [did + "#" + encnumbasis],
id: did,
keyAgreement: undefined,
service: undefined,
verificationMethod: [
{
id: "did:peer:" + id + "#" + encnumbasis,
type: "EcdsaSecp256k1VerificationKey2019",
controller: did,
id: did + "#" + encnumbasis,
publicKeyMultibase: multibase,
type: "EcdsaSecp256k1VerificationKey2019",
},
],
};

6
src/views/StartView.vue

@ -80,7 +80,7 @@ import {
export default class StartView extends Vue {
numAccounts = 0;
publicKeyJwk?: JWK;
publicKeyCose?: Uint8Array;
publicKeyBytes?: Uint8Array;
userId?: Uint8Array;
async mounted() {
@ -96,14 +96,14 @@ export default class StartView extends Vue {
const cred = await registerCredential(this.userId, generateRandomBytes(32));
console.log("public key", cred);
this.publicKeyJwk = cred.publicKeyJwk;
this.publicKeyCose = cred.publicKeyCose;
this.publicKeyBytes = cred.publicKeyBytes;
this.userId = cred.rawId as Uint8Array;
//this.$router.push({ name: "new-identifier" });
}
public async onClickNo() {
const credArrBuff = this.userId;
const did = createPeerDid(this.publicKeyCose as Uint8Array);
const did = createPeerDid(this.publicKeyBytes as Uint8Array);
console.log("did", did);
const jwt = await createJwt({ a: 1 }, did, credArrBuff as Uint8Array);
console.log("jwt", jwt);

Loading…
Cancel
Save