Browse Source

refine "vc" functions further, separating passkey-specific items from did-peer file

pull/120/head
Trent Larson 2 months ago
parent
commit
4bb311195b
  1. 96
      src/libs/crypto/vc/didPeer.ts
  2. 8
      src/libs/crypto/vc/index.ts
  3. 90
      src/libs/crypto/vc/passkeyDidPeer.ts
  4. 3
      src/libs/util.ts

96
src/libs/crypto/vc/didPeer.ts

@ -0,0 +1,96 @@
import {Buffer} from "buffer/";
import {decode as cborDecode} from "cbor-x";
import {bytesToMultibase, multibaseToBytes} from "did-jwt";
import {getWebCrypto} from "@/libs/crypto/vc/passkeyHelpers";
const PEER_DID_PREFIX = "did:peer:";
const PEER_DID_MULTIBASE_PREFIX = PEER_DID_PREFIX + "0";
/**
*
*
* similar code is in crowd-funder-for-time-pwa libs/crypto/vc/passkeyDidPeer.ts verifyJwtWebCrypto
*
* @returns {Promise<boolean>}
*/
export async function verifyPeerSignature(
payloadBytes: Buffer,
issuerDid: string,
signatureBytes: Uint8Array,
): Promise<boolean> {
const publicKeyBytes = peerDidToPublicKeyBytes(issuerDid);
const WebCrypto = await getWebCrypto();
const verifyAlgorithm = {
name: "ECDSA",
hash: { name: "SHA-256" },
};
const publicKeyJwk = cborToKeys(publicKeyBytes).publicKeyJwk;
const keyAlgorithm = {
name: "ECDSA",
namedCurve: publicKeyJwk.crv,
};
const publicKeyCryptoKey = await WebCrypto.subtle.importKey(
"jwk",
publicKeyJwk,
keyAlgorithm,
false,
["verify"],
);
const verified = await WebCrypto.subtle.verify(
verifyAlgorithm,
publicKeyCryptoKey,
signatureBytes,
payloadBytes,
);
return verified;
}
export function cborToKeys(publicKeyBytes: Uint8Array) {
const jwkObj = cborDecode(publicKeyBytes);
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: arrayToBase64Url(jwkObj[-2]),
y: arrayToBase64Url(jwkObj[-3]),
};
const publicKeyBuffer = Buffer.concat([
Buffer.from(jwkObj[-2]),
Buffer.from(jwkObj[-3]),
]);
return { publicKeyJwk, publicKeyBuffer };
}
export function toBase64Url(anythingB64: string) {
return anythingB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
export function arrayToBase64Url(anything: Uint8Array) {
return toBase64Url(Buffer.from(anything).toString("base64"));
}
export function peerDidToPublicKeyBytes(did: string) {
return multibaseToBytes(did.substring(PEER_DID_MULTIBASE_PREFIX.length));
}
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(
publicKeyBytes,
"base58btc",
"p256-pub",
);
return PEER_DID_MULTIBASE_PREFIX + methodSpecificId;
}

8
src/libs/crypto/vc/index.ts

@ -1,13 +1,17 @@
/** /**
* Verifiable Credential & DID functions, specifically for EndorserSearch.org tools * Verifiable Credential & DID functions, specifically for EndorserSearch.org tools
*
* The goal is to make this folder similar across projects, then move it to a library.
* Other projects: endorser-ch, image-api
*
*/ */
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import { JWTDecoded } from "did-jwt/lib/JWT";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import * as u8a from "uint8arrays"; import * as u8a from "uint8arrays";
import { createDidPeerJwt } from "@/libs/crypto/vc/passkeyDidPeer"; import { createDidPeerJwt } from "@/libs/crypto/vc/passkeyDidPeer";
import {JWTDecoded} from "did-jwt/lib/JWT";
/** /**
* Meta info about a key * Meta info about a key
@ -93,7 +97,7 @@ function fromJose(signature: string): {
const s = bytesToHex(signatureBytes.slice(32, 64)); const s = bytesToHex(signatureBytes.slice(32, 64));
const recoveryParam = const recoveryParam =
signatureBytes.length === 65 ? signatureBytes[64] : undefined; signatureBytes.length === 65 ? signatureBytes[64] : undefined;
return {r, s, recoveryParam}; return { r, s, recoveryParam };
} }
// from did-jwt/util; see SimpleSigner above // from did-jwt/util; see SimpleSigner above

90
src/libs/crypto/vc/passkeyDidPeer.ts

@ -1,6 +1,5 @@
import { Buffer } from "buffer/"; import { Buffer } from "buffer/";
import { decode as cborDecode } from "cbor-x"; import { JWTPayload } from "did-jwt";
import { bytesToMultibase, JWTPayload, multibaseToBytes } from "did-jwt";
import { DIDResolutionResult } from "did-resolver"; import { DIDResolutionResult } from "did-resolver";
import { sha256 } from "ethereum-cryptography/sha256.js"; import { sha256 } from "ethereum-cryptography/sha256.js";
import { import {
@ -21,13 +20,14 @@ import {
} from "@simplewebauthn/types"; } from "@simplewebauthn/types";
import { AppString } from "@/constants/app"; import { AppString } from "@/constants/app";
import { unwrapEC2Signature } from "@/libs/crypto/vc/passkeyHelpers";
import { import {
getWebCrypto, arrayToBase64Url,
unwrapEC2Signature, cborToKeys,
} from "@/libs/crypto/vc/passkeyHelpers"; peerDidToPublicKeyBytes,
verifyPeerSignature,
} from "@/libs/crypto/vc/didPeer";
const PEER_DID_PREFIX = "did:peer:";
const PEER_DID_MULTIBASE_PREFIX = PEER_DID_PREFIX + "0";
export interface JWK { export interface JWK {
kty: string; kty: string;
crv: string; crv: string;
@ -35,14 +35,6 @@ export interface JWK {
y: string; y: string;
} }
function toBase64Url(anythingB64: string) {
return anythingB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function arrayToBase64Url(anything: Uint8Array) {
return toBase64Url(Buffer.from(anything).toString("base64"));
}
export async function registerCredential(passkeyName?: string) { export async function registerCredential(passkeyName?: string) {
const options: PublicKeyCredentialCreationOptionsJSON = const options: PublicKeyCredentialCreationOptionsJSON =
await generateRegistrationOptions({ await generateRegistrationOptions({
@ -95,21 +87,6 @@ export async function registerCredential(passkeyName?: string) {
}; };
} }
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(
publicKeyBytes,
"base58btc",
"p256-pub",
);
return PEER_DID_MULTIBASE_PREFIX + methodSpecificId;
}
function peerDidToPublicKeyBytes(did: string) {
return multibaseToBytes(did.substring(PEER_DID_MULTIBASE_PREFIX.length));
}
export class PeerSetup { export class PeerSetup {
public authenticatorData?: ArrayBuffer; public authenticatorData?: ArrayBuffer;
public challenge?: Uint8Array; public challenge?: Uint8Array;
@ -422,33 +399,7 @@ export async function verifyJwtWebCrypto(
// Construct the preimage // Construct the preimage
const preimage = Buffer.concat([authDataFromBase, hash]); const preimage = Buffer.concat([authDataFromBase, hash]);
return verifyPeerSignature(preimage, issuerDid, finalSigBuffer);
const publicKeyBytes = peerDidToPublicKeyBytes(issuerDid);
const WebCrypto = await getWebCrypto();
const verifyAlgorithm = {
name: "ECDSA",
hash: { name: "SHA-256" },
};
const publicKeyJwk = cborToKeys(publicKeyBytes).publicKeyJwk;
const keyAlgorithm = {
name: "ECDSA",
namedCurve: publicKeyJwk.crv,
};
const publicKeyCryptoKey = await WebCrypto.subtle.importKey(
"jwk",
publicKeyJwk,
keyAlgorithm,
false,
["verify"],
);
const verified = await WebCrypto.subtle.verify(
verifyAlgorithm,
publicKeyCryptoKey,
finalSigBuffer,
preimage,
);
return verified;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -554,31 +505,6 @@ function base64URLStringToArrayBuffer(base64URLString: string) {
return buffer; return buffer;
} }
function cborToKeys(publicKeyBytes: Uint8Array) {
const jwkObj = cborDecode(publicKeyBytes);
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: arrayToBase64Url(jwkObj[-2]),
y: arrayToBase64Url(jwkObj[-3]),
};
const publicKeyBuffer = Buffer.concat([
Buffer.from(jwkObj[-2]),
Buffer.from(jwkObj[-3]),
]);
return { publicKeyJwk, publicKeyBuffer };
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
async function pemToCryptoKey(pem: string) { async function pemToCryptoKey(pem: string) {
const binaryDerString = atob( const binaryDerString = atob(

3
src/libs/util.ts

@ -11,10 +11,11 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto"; import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import { GenericCredWrapper, containsHiddenDid } from "@/libs/endorserServer"; import { GenericCredWrapper, containsHiddenDid } from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { createPeerDid, registerCredential } from "@/libs/crypto/vc/passkeyDidPeer"; import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
import {KeyMeta} from "@/libs/crypto/vc"; import {KeyMeta} from "@/libs/crypto/vc";
import {createPeerDid} from "@/libs/crypto/vc/didPeer";
export const PRIVACY_MESSAGE = export const PRIVACY_MESSAGE =
"The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow."; "The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow.";

Loading…
Cancel
Save