diff --git a/src/libs/crypto/vc/didPeer.ts b/src/libs/crypto/vc/didPeer.ts new file mode 100644 index 0000000..9c0fa73 --- /dev/null +++ b/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} + */ +export async function verifyPeerSignature( + payloadBytes: Buffer, + issuerDid: string, + signatureBytes: Uint8Array, +): Promise { + 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; +} \ No newline at end of file diff --git a/src/libs/crypto/vc/index.ts b/src/libs/crypto/vc/index.ts index 94355ef..7736708 100644 --- a/src/libs/crypto/vc/index.ts +++ b/src/libs/crypto/vc/index.ts @@ -1,13 +1,17 @@ /** * 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 { JWTDecoded } from "did-jwt/lib/JWT"; import { IIdentifier } from "@veramo/core"; import * as u8a from "uint8arrays"; import { createDidPeerJwt } from "@/libs/crypto/vc/passkeyDidPeer"; -import {JWTDecoded} from "did-jwt/lib/JWT"; /** * Meta info about a key @@ -93,7 +97,7 @@ function fromJose(signature: string): { const s = bytesToHex(signatureBytes.slice(32, 64)); const recoveryParam = signatureBytes.length === 65 ? signatureBytes[64] : undefined; - return {r, s, recoveryParam}; + return { r, s, recoveryParam }; } // from did-jwt/util; see SimpleSigner above diff --git a/src/libs/crypto/vc/passkeyDidPeer.ts b/src/libs/crypto/vc/passkeyDidPeer.ts index 6431123..920d751 100644 --- a/src/libs/crypto/vc/passkeyDidPeer.ts +++ b/src/libs/crypto/vc/passkeyDidPeer.ts @@ -1,6 +1,5 @@ import { Buffer } from "buffer/"; -import { decode as cborDecode } from "cbor-x"; -import { bytesToMultibase, JWTPayload, multibaseToBytes } from "did-jwt"; +import { JWTPayload } from "did-jwt"; import { DIDResolutionResult } from "did-resolver"; import { sha256 } from "ethereum-cryptography/sha256.js"; import { @@ -21,13 +20,14 @@ import { } from "@simplewebauthn/types"; import { AppString } from "@/constants/app"; +import { unwrapEC2Signature } from "@/libs/crypto/vc/passkeyHelpers"; import { - getWebCrypto, - unwrapEC2Signature, -} from "@/libs/crypto/vc/passkeyHelpers"; + arrayToBase64Url, + cborToKeys, + 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 { kty: string; crv: string; @@ -35,14 +35,6 @@ export interface JWK { 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) { const options: PublicKeyCredentialCreationOptionsJSON = 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 { public authenticatorData?: ArrayBuffer; public challenge?: Uint8Array; @@ -422,33 +399,7 @@ export async function verifyJwtWebCrypto( // Construct the preimage const preimage = Buffer.concat([authDataFromBase, hash]); - - 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; + return verifyPeerSignature(preimage, issuerDid, finalSigBuffer); } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -554,31 +505,6 @@ function base64URLStringToArrayBuffer(base64URLString: string) { 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 async function pemToCryptoKey(pem: string) { const binaryDerString = atob( diff --git a/src/libs/util.ts b/src/libs/util.ts index f7de997..c5f3b39 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -11,10 +11,11 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto"; import { GenericCredWrapper, containsHiddenDid } 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 {KeyMeta} from "@/libs/crypto/vc"; +import {createPeerDid} from "@/libs/crypto/vc/didPeer"; 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.";