From 1b7ffcc9c0de08b05928c13b9dc9e97844097c1f Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Wed, 19 Feb 2025 12:34:50 +0100 Subject: [PATCH 01/11] fix: refactor JWK implementation into a pollux task Signed-off-by: Francisco Javier Ribo Labrador --- src/apollo/Apollo.ts | 2 - src/apollo/utils/Secp256k1PublicKey.ts | 2 +- src/domain/models/errors/Apollo.ts | 16 +- src/domain/models/keyManagement/KeyTypes.ts | 1 + src/pollux/utils/jwt/FromJWK.ts | 148 ++++++++++++++++++ src/pollux/utils/jwt/PKInstance.ts | 27 ++-- tests/pollux/FromJWK.test.ts | 165 ++++++++++++++++++++ 7 files changed, 336 insertions(+), 25 deletions(-) create mode 100644 src/pollux/utils/jwt/FromJWK.ts create mode 100644 tests/pollux/FromJWK.test.ts diff --git a/src/apollo/Apollo.ts b/src/apollo/Apollo.ts index 933afca64..fbb42e57e 100644 --- a/src/apollo/Apollo.ts +++ b/src/apollo/Apollo.ts @@ -29,7 +29,6 @@ import { X25519PublicKey } from "./utils/X25519PublicKey"; import { isEmpty, notEmptyString } from "../utils"; import ApolloPKG from "@hyperledger/identus-apollo"; import { PrismDerivationPath } from "../domain/models/derivation/schemas/PrismDerivation"; - const ApolloSDK = ApolloPKG.org.hyperledger.identus.apollo; const Mnemonic = ApolloSDK.derivation.Mnemonic.Companion; const HDKey = ApolloSDK.derivation.HDKey; @@ -115,7 +114,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { static Ed25519PrivateKey = Ed25519PrivateKey; static X25519PrivateKey = X25519PrivateKey; - /** * Creates a random set of mnemonic phrases that can be used as a seed for generating a private key. * diff --git a/src/apollo/utils/Secp256k1PublicKey.ts b/src/apollo/utils/Secp256k1PublicKey.ts index b010a1c40..25ea537a7 100644 --- a/src/apollo/utils/Secp256k1PublicKey.ts +++ b/src/apollo/utils/Secp256k1PublicKey.ts @@ -36,7 +36,7 @@ export class Secp256k1PublicKey extends PublicKey implements StorableKey, Export ); } - private get native() { + get native() { return ApolloSDK.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromBytes( Int8Array.from(this.raw) ); diff --git a/src/domain/models/errors/Apollo.ts b/src/domain/models/errors/Apollo.ts index 71c4b4f5e..8f5322d5d 100644 --- a/src/domain/models/errors/Apollo.ts +++ b/src/domain/models/errors/Apollo.ts @@ -53,8 +53,7 @@ export class KeyRestoratonFailed extends SDKError { * thrown when given Key Curve is not supported */ export class InvalidKeyCurve extends SDKError { - constructor(keyCurve?: string) { - const options = Object.values(Curve); + constructor(keyCurve?: string, options = Object.values(Curve)) { const msg = `Invalid key curve: ${keyCurve ?? "undefined"}. Valid options are: ${options.join(", ")}`; super(16, msg); } @@ -64,8 +63,7 @@ export class InvalidKeyCurve extends SDKError { * thrown when give Key Type is not supported */ export class InvalidKeyType extends SDKError { - constructor(keyType?: string) { - const options = Object.values(KeyTypes); + constructor(keyType?: string, options = Object.values(KeyTypes)) { const msg = `Invalid key type: ${keyType ?? "undefined"}. Valid options are: ${options.join(", ")}`; super(17, msg); } @@ -80,6 +78,16 @@ export class MissingKeyParameters extends SDKError { } } +export class InvalidECParameters extends SDKError { + constructor(parameters: string | string[]) { + if (typeof parameters === "string") { + super(19, `Missing or invalid JWK parameter: ${parameters}`); + } else { + super(19, `Missing or invalid JWK parameters: ${parameters.join(", ")}`); + } + } +} + /** * thrown when failing to create a key */ diff --git a/src/domain/models/keyManagement/KeyTypes.ts b/src/domain/models/keyManagement/KeyTypes.ts index 068625641..62831edcc 100644 --- a/src/domain/models/keyManagement/KeyTypes.ts +++ b/src/domain/models/keyManagement/KeyTypes.ts @@ -1,5 +1,6 @@ export enum KeyTypes { "EC" = "EC", "Curve25519" = "Curve25519", + "OKP" = "OKP", "unknown" = "unknown" } diff --git a/src/pollux/utils/jwt/FromJWK.ts b/src/pollux/utils/jwt/FromJWK.ts new file mode 100644 index 000000000..3492c3f8c --- /dev/null +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -0,0 +1,148 @@ +import { Domain } from "../../.."; +import { expect, Task } from "../../../utils"; + +import { base64url } from "multiformats/bases/base64"; +import { ApolloError, Curve, JWK, KeyPair, KeyProperties, KeyTypes, PrivateKey, PublicKey } from "../../../domain"; +import { InvalidECParameters } from "../../../domain/models/errors/Apollo"; + + +export function isECJWK(jwk: JWK): jwk is JWK.EC { + if (jwk.kty !== "EC") { + return false; + } + return true; +} + +export function isOKPJWK(jwk: JWK): jwk is JWK.OKP { + if (jwk.kty !== "OKP") { + return false; + } + return true; +} + + +export function decodeJWKECParameter(coordinate: 'x' | 'y' | 'd', jwk: JWK.EC): Uint8Array { + if (!jwk[coordinate]) { + throw new InvalidECParameters(coordinate); + } + const coordinateValue = jwk[coordinate]!; + try { + const decoded = base64url.baseDecode(coordinateValue); + return new Uint8Array(decoded); + } catch (err) { + throw new InvalidECParameters(coordinate); + } +} + + + +export interface Args { + jwk: Domain.JWK; +} + + +export class FromJWK extends Task { + + private fromJWKEC(apollo: Domain.Apollo, jwk: JWK.EC): KeyPair | PrivateKey | PublicKey { + const crv = expect(jwk.crv); + const withCoordinates = + jwk.x !== undefined || + jwk.y !== undefined; + + if (withCoordinates) { + const decodedX = decodeJWKECParameter('x', jwk); + const decodedY = decodeJWKECParameter('y', jwk); + + if (crv === Curve.SECP256K1) { + let pk: PublicKey; + let sk: PrivateKey; + + pk = apollo.createPublicKey({ + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curvePointX]: decodedX, + [KeyProperties.curvePointY]: decodedY + }); + + if (jwk.d !== undefined) { + const decodedD = decodeJWKECParameter('d', jwk); + sk = apollo.createPrivateKey({ + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.rawKey]: decodedD + }); + + const keypair: Domain.KeyPair = { + privateKey: sk, + publicKey: pk, + curve: Curve.SECP256K1 + } + return keypair; + } + return pk; + } + + throw new ApolloError.InvalidKeyCurve(crv, [Curve.SECP256K1]); + } + + if (jwk.d !== undefined) { + if (crv !== Curve.SECP256K1 && crv !== Curve.ED25519 && crv !== Curve.X25519) { + throw new ApolloError.InvalidKeyCurve(); + } + const decodedD = decodeJWKECParameter('d', jwk); + return apollo.createPrivateKey({ + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.rawKey]: decodedD + }) + } + + throw new ApolloError.InvalidECParameters(['d', 'x and y']); + } + + private fromJWKOKP(apollo: Domain.Apollo, jwk: JWK.OKP): KeyPair | PublicKey { + const crv = expect(jwk.crv); + const x = expect(jwk.d); + + if (crv !== Curve.SECP256K1 && crv !== Curve.ED25519 && crv !== Curve.X25519) { + throw new ApolloError.InvalidKeyCurve(); + } + + const pk = apollo.createPublicKey({ + [KeyProperties.curve]: crv, + [KeyProperties.type]: KeyTypes.OKP, + [KeyProperties.rawKey]: base64url.baseDecode(x) + }); + + if (jwk.d !== undefined) { + const sk = apollo.createPrivateKey({ + [KeyProperties.curve]: crv, + [KeyProperties.type]: KeyTypes.OKP, + [KeyProperties.rawKey]: base64url.baseDecode(jwk.d) + }); + const keypair: Domain.KeyPair = { + privateKey: sk, + publicKey: pk, + curve: crv + } + return keypair; + } + return pk; + } + + async run(ctx: Task.Context): Promise { + const jwk = this.args.jwk; + const kty = expect(jwk.kty); + const isEC = isECJWK(jwk); + const isOKP = isOKPJWK(jwk); + + if (isEC) { + return this.fromJWKEC(ctx.Apollo, jwk); + } + if (isOKP) { + return this.fromJWKOKP(ctx.Apollo, jwk); + } + throw new ApolloError.InvalidKeyType(kty, [KeyTypes.EC, KeyTypes.OKP]); + } + +} \ No newline at end of file diff --git a/src/pollux/utils/jwt/PKInstance.ts b/src/pollux/utils/jwt/PKInstance.ts index bbf33542c..dbac309af 100644 --- a/src/pollux/utils/jwt/PKInstance.ts +++ b/src/pollux/utils/jwt/PKInstance.ts @@ -1,11 +1,10 @@ import type * as DIDResolver from "did-resolver"; import { base58btc } from 'multiformats/bases/base58'; -import { base64url } from "multiformats/bases/base64"; import * as Domain from "../../../domain"; -import { Task, expect } from "../../../utils"; +import { Task } from "../../../utils"; // TODO importing from Castor import { VerificationKeyType } from "../../../castor/types"; - +import { FromJWK } from "./FromJWK"; export interface Args { verificationMethod: DIDResolver.VerificationMethod; } @@ -36,21 +35,13 @@ export class PKInstance extends Task { } if (verificationMethod.publicKeyJwk) { - const crv = expect(verificationMethod.publicKeyJwk.crv); - const x = expect(verificationMethod.publicKeyJwk.x); - - if (crv === Domain.Curve.ED25519) { - pk = ctx.Apollo.createPublicKey({ - [Domain.KeyProperties.curve]: Domain.Curve.ED25519, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, - [Domain.KeyProperties.rawKey]: base64url.baseDecode(x) - }); - } else if (crv === Domain.Curve.SECP256K1) { - pk = ctx.Apollo.createPublicKey({ - [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, - [Domain.KeyProperties.rawKey]: base64url.baseDecode(x) - }); + const keyPair = await ctx.run( + new FromJWK({ jwk: verificationMethod.publicKeyJwk as Domain.JWK }) + ); + if (keyPair instanceof Domain.KeyPair) { + pk = keyPair.publicKey; + } else { + pk = keyPair; } return pk; } diff --git a/tests/pollux/FromJWK.test.ts b/tests/pollux/FromJWK.test.ts new file mode 100644 index 000000000..f9d4e0b32 --- /dev/null +++ b/tests/pollux/FromJWK.test.ts @@ -0,0 +1,165 @@ +import { describe, expect, beforeEach, vi, it } from 'vitest'; +import { Apollo, Castor, Domain, Secp256k1KeyPair, Secp256k1PrivateKey, Secp256k1PublicKey } from "../../src"; +import { Task } from '../../src/utils'; +import { FromJWK } from '../../src/pollux/utils/jwt/FromJWK'; +import { ApolloError, Curve } from '../../src/domain'; + +describe("Pollux - JWT FromJWK", async () => { + let ctx: Task.Context<{ + Apollo: Domain.Apollo; + }>; + let apollo: Domain.Apollo; + let castor: Domain.Castor; + let plutoMock: Domain.Pluto; + + beforeEach(() => { + apollo = new Apollo(); + castor = new Castor(apollo); + plutoMock = { getDIDPrivateKeysByDID: vi.fn() } as any; + ctx = Task.Context.make({ + Apollo: apollo, + }); + }); + + + describe("fromJWK", () => { + + it("Should throw an error if the key type is not EC or OKP", async () => { + const sut = new FromJWK({ + jwk: { + kty: "oct", + k: "1234567890" + } + }).run(ctx); + await expect(sut).rejects.toThrow(ApolloError.InvalidKeyType); + }); + + describe("EC Key type with secp256k1 curve", async () => { + + it("Should throw an error if both coordinates and d are missing", async () => { + const sut = new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + } + }).run(ctx); + await expect(sut).rejects.toThrow("19: Missing or invalid JWK parameters: d, x and y"); + }); + + it("Should throw an error if only one coordinate coordinate is present", async () => { + const sut = new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + x: "poDxfZtoOpBDtFqJmJ03_tei3ooCXrGXkJM_WUErZPM", + } + }).run(ctx); + await expect(sut).rejects.toThrow("19: Missing or invalid JWK parameter: y"); + + const sut2 = new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + y: "M6WTO1raVf2TNHO7t0IpiurajRo6k12HbJvNa2L-8sA", + } + }).run(ctx); + await expect(sut2).rejects.toThrow("19: Missing or invalid JWK parameter: x"); + }); + + it("Should throw an error if d is present but invalid", async () => { + const sut = new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + d: "adsasdasdadsadsasd", + } + }).run(ctx); + await expect(sut).rejects.toThrow("19: Missing or invalid JWK parameter: d"); + }); + + it("Should throw an error if d is present but curve is unsupported", async () => { + const sut = new FromJWK({ + jwk: { + kty: "EC", + crv: "wrong curve", + d: "adsasdasdadsadsasd", + } + }).run(ctx); + await expect(sut).rejects.toThrow(ApolloError.InvalidKeyCurve) + }); + + it("Should throw an error if coordinate encoding is not valid", async () => { + const sut = new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + //Should be base64url encoded + x: "1234567890", + y: "1234567890" + } + }).run(ctx); + await expect(sut).rejects.toThrow(ApolloError.InvalidECParameters); + }); + + it("Should not allow restoring from coordinates for non secp256k1 curves", async () => { + const sut = new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.ED25519, + x: "poDxfZtoOpBDtFqJmJ03_tei3ooCXrGXkJM_WUErZPM", + y: "M6WTO1raVf2TNHO7t0IpiurajRo6k12HbJvNa2L-8sA", + } + }).run(ctx); + await expect(sut).rejects.toThrow("16: Invalid key curve: Ed25519. Valid options are: secp256k1") + }); + + it("Should allow to recover a public key from x and y coordinates", async () => { + const sut = await new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + x: "poDxfZtoOpBDtFqJmJ03_tei3ooCXrGXkJM_WUErZPM", + y: "M6WTO1raVf2TNHO7t0IpiurajRo6k12HbJvNa2L-8sA", + } + }).run(ctx); + expect(sut).to.be.an.instanceOf(Secp256k1PublicKey); + }); + + it("Should allow to recover a keyPair key from x and y coordinates and d private key", async () => { + const sut = await new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + d: "ZLc6z-hpXb7RjPEve9rf-v44ElZJ-NQ5r0lqY-NpCuA", + x: "wYMtiI5wJqztx6ywMtm9ns8Pnlsq7_ZMj_gJh9DXiL0", + y: "g2p7eYiPxPMlZHQE95uV91gnwCMnOozpctwOPWzTCsM", + } + }).run(ctx); + + expect(sut).toHaveProperty("privateKey"); + expect(sut).toHaveProperty("publicKey"); + + expect((sut as Domain.KeyPair).privateKey).to.be.an.instanceOf(Secp256k1PrivateKey); + expect((sut as Domain.KeyPair).publicKey).to.be.an.instanceOf(Secp256k1PublicKey); + }); + + }); + + + describe("OKP Key type", async () => { + it("Should throw an error if the curve is not supported"); + + const supportedCurves = [Curve.ED25519, Curve.X25519, Curve.SECP256K1]; + + supportedCurves.forEach(curve => { + describe(`${curve} curve`, () => { + it("Should throw an error if x is not encoded with base64url") + it("Should throw an error if d property is not encoded with base64url") + it("Should return a keyPair if x and d are present") + it("Should return a public key if x is present") + }); + }); + }); + }); + +}) From c865fe09117d5908d09d1edb697efeb2021a93ec Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Wed, 19 Feb 2025 14:20:31 +0100 Subject: [PATCH 02/11] fix: error handling and OKP testing for JWK Signed-off-by: Francisco Javier Ribo Labrador --- src/domain/models/errors/Apollo.ts | 10 -- src/domain/models/errors/Pollux.ts | 21 +++- src/domain/models/keyManagement/KeyTypes.ts | 1 - src/pollux/utils/jwt/FromJWK.ts | 80 ++++++++------- tests/pollux/FromJWK.test.ts | 102 +++++++++++++++++--- 5 files changed, 156 insertions(+), 58 deletions(-) diff --git a/src/domain/models/errors/Apollo.ts b/src/domain/models/errors/Apollo.ts index 8f5322d5d..f3da55f5f 100644 --- a/src/domain/models/errors/Apollo.ts +++ b/src/domain/models/errors/Apollo.ts @@ -78,16 +78,6 @@ export class MissingKeyParameters extends SDKError { } } -export class InvalidECParameters extends SDKError { - constructor(parameters: string | string[]) { - if (typeof parameters === "string") { - super(19, `Missing or invalid JWK parameter: ${parameters}`); - } else { - super(19, `Missing or invalid JWK parameters: ${parameters.join(", ")}`); - } - } -} - /** * thrown when failing to create a key */ diff --git a/src/domain/models/errors/Pollux.ts b/src/domain/models/errors/Pollux.ts index 17ad0509c..e3a68dd4b 100644 --- a/src/domain/models/errors/Pollux.ts +++ b/src/domain/models/errors/Pollux.ts @@ -1,3 +1,5 @@ +import { SDKError } from "./Common"; + export class InvalidCredentialError extends Error { constructor(message?: string) { super(message || "Invalid credential error"); @@ -16,6 +18,23 @@ export class InvalidPresentationProofArgs extends Error { } } + +export class InvalidJWKParameters extends SDKError { + constructor(parameters: string | string[], message?: string) { + const isSingle = typeof parameters === "string" || parameters.length === 1; + const error = `${message || "Invalid JWK parameter"}${isSingle ? ":" : "s:"}`; + super(19, `${error} ${typeof parameters === "string" ? parameters : parameters.join(", ")}`); + } +} + +export class InvalidJWK extends SDKError { + constructor(message: string) { + super(20, message || `Missing or invalid JWK`); + + } +} + + export class CredentialRevocationTypeInvalid extends Error { constructor(message?: string) { super(message || "CredentialStatus revocation type not supported"); @@ -85,4 +104,4 @@ export class InvalidDescriptorFormatError extends Error { /** * general Revocation error, message should contain details */ -export class RevocationError extends Error {} +export class RevocationError extends Error { } diff --git a/src/domain/models/keyManagement/KeyTypes.ts b/src/domain/models/keyManagement/KeyTypes.ts index 62831edcc..068625641 100644 --- a/src/domain/models/keyManagement/KeyTypes.ts +++ b/src/domain/models/keyManagement/KeyTypes.ts @@ -1,6 +1,5 @@ export enum KeyTypes { "EC" = "EC", "Curve25519" = "Curve25519", - "OKP" = "OKP", "unknown" = "unknown" } diff --git a/src/pollux/utils/jwt/FromJWK.ts b/src/pollux/utils/jwt/FromJWK.ts index 3492c3f8c..556ed6f9e 100644 --- a/src/pollux/utils/jwt/FromJWK.ts +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -2,8 +2,7 @@ import { Domain } from "../../.."; import { expect, Task } from "../../../utils"; import { base64url } from "multiformats/bases/base64"; -import { ApolloError, Curve, JWK, KeyPair, KeyProperties, KeyTypes, PrivateKey, PublicKey } from "../../../domain"; -import { InvalidECParameters } from "../../../domain/models/errors/Apollo"; +import { ApolloError, Curve, JWK, KeyPair, KeyProperties, KeyTypes, PolluxError, PrivateKey, PublicKey } from "../../../domain"; export function isECJWK(jwk: JWK): jwk is JWK.EC { @@ -21,16 +20,22 @@ export function isOKPJWK(jwk: JWK): jwk is JWK.OKP { } -export function decodeJWKECParameter(coordinate: 'x' | 'y' | 'd', jwk: JWK.EC): Uint8Array { +export function decodeJWKParameter( + coordinate: 'x' | 'y' | 'd', + jwk: JWK.EC | JWK.OKP +): Uint8Array { if (!jwk[coordinate]) { - throw new InvalidECParameters(coordinate); + throw new PolluxError.InvalidJWKParameters(coordinate, `Missing JWK Parameter`); } - const coordinateValue = jwk[coordinate]!; + const coordinateValue = jwk[coordinate]; try { + if (typeof coordinateValue !== 'string') { + throw new PolluxError.InvalidJWKParameters(coordinate, `Invalid JWK Parameter, not string`); + } const decoded = base64url.baseDecode(coordinateValue); return new Uint8Array(decoded); } catch (err) { - throw new InvalidECParameters(coordinate); + throw new PolluxError.InvalidJWKParameters(coordinate, `Invalid JWK Parameter, not base64url encoded`); } } @@ -43,39 +48,49 @@ export interface Args { export class FromJWK extends Task { + private isSupportedCurve(crv: string): void { + const keys = Object.values(Curve); + if (!keys.includes(crv as Curve)) { + throw new ApolloError.InvalidKeyCurve(crv); + } + } + private fromJWKEC(apollo: Domain.Apollo, jwk: JWK.EC): KeyPair | PrivateKey | PublicKey { - const crv = expect(jwk.crv); + const crv = expect(jwk.crv, new PolluxError.InvalidJWKParameters(['crv'], 'Missing JWK Parameter')); + this.isSupportedCurve(crv); + const withCoordinates = jwk.x !== undefined || jwk.y !== undefined; + const keyType = crv === Curve.X25519 ? KeyTypes.Curve25519 : KeyTypes.EC; if (withCoordinates) { - const decodedX = decodeJWKECParameter('x', jwk); - const decodedY = decodeJWKECParameter('y', jwk); + const decodedX = decodeJWKParameter('x', jwk); + const decodedY = decodeJWKParameter('y', jwk); if (crv === Curve.SECP256K1) { let pk: PublicKey; let sk: PrivateKey; pk = apollo.createPublicKey({ - [KeyProperties.curve]: Curve.SECP256K1, - [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: crv, + [KeyProperties.type]: keyType, [KeyProperties.curvePointX]: decodedX, [KeyProperties.curvePointY]: decodedY }); if (jwk.d !== undefined) { - const decodedD = decodeJWKECParameter('d', jwk); + const decodedD = decodeJWKParameter('d', jwk); sk = apollo.createPrivateKey({ - [KeyProperties.curve]: Curve.SECP256K1, - [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: crv, + [KeyProperties.type]: keyType, [KeyProperties.rawKey]: decodedD }); const keypair: Domain.KeyPair = { privateKey: sk, publicKey: pk, - curve: Curve.SECP256K1 + curve: crv } return keypair; } @@ -86,39 +101,36 @@ export class FromJWK extends Task { } if (jwk.d !== undefined) { - if (crv !== Curve.SECP256K1 && crv !== Curve.ED25519 && crv !== Curve.X25519) { - throw new ApolloError.InvalidKeyCurve(); - } - const decodedD = decodeJWKECParameter('d', jwk); + const decodedD = decodeJWKParameter('d', jwk); return apollo.createPrivateKey({ - [KeyProperties.curve]: Curve.SECP256K1, - [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: crv, + [KeyProperties.type]: keyType, [KeyProperties.rawKey]: decodedD }) } - throw new ApolloError.InvalidECParameters(['d', 'x and y']); + throw new PolluxError.InvalidJWK('Required property x+y or d is missing in EC JWK'); } private fromJWKOKP(apollo: Domain.Apollo, jwk: JWK.OKP): KeyPair | PublicKey { - const crv = expect(jwk.crv); - const x = expect(jwk.d); + const crv = expect(jwk.crv, new PolluxError.InvalidJWKParameters(['crv'])); + this.isSupportedCurve(crv); - if (crv !== Curve.SECP256K1 && crv !== Curve.ED25519 && crv !== Curve.X25519) { - throw new ApolloError.InvalidKeyCurve(); - } + expect(jwk.x, new PolluxError.InvalidJWKParameters(['x'], 'Missing JWK Parameter x')); + + const keyType = crv === Curve.X25519 ? KeyTypes.Curve25519 : KeyTypes.EC; const pk = apollo.createPublicKey({ [KeyProperties.curve]: crv, - [KeyProperties.type]: KeyTypes.OKP, - [KeyProperties.rawKey]: base64url.baseDecode(x) + [KeyProperties.type]: keyType, + [KeyProperties.rawKey]: decodeJWKParameter('x', jwk) }); if (jwk.d !== undefined) { const sk = apollo.createPrivateKey({ [KeyProperties.curve]: crv, - [KeyProperties.type]: KeyTypes.OKP, - [KeyProperties.rawKey]: base64url.baseDecode(jwk.d) + [KeyProperties.type]: keyType, + [KeyProperties.rawKey]: decodeJWKParameter('d', jwk) }); const keypair: Domain.KeyPair = { privateKey: sk, @@ -127,22 +139,22 @@ export class FromJWK extends Task { } return keypair; } + return pk; } async run(ctx: Task.Context): Promise { const jwk = this.args.jwk; - const kty = expect(jwk.kty); + const kty = expect(jwk.kty, new PolluxError.InvalidJWKParameters(['kty'], 'Missing JWK Parameter kty')); const isEC = isECJWK(jwk); const isOKP = isOKPJWK(jwk); - if (isEC) { return this.fromJWKEC(ctx.Apollo, jwk); } if (isOKP) { return this.fromJWKOKP(ctx.Apollo, jwk); } - throw new ApolloError.InvalidKeyType(kty, [KeyTypes.EC, KeyTypes.OKP]); + throw new ApolloError.InvalidKeyType(kty, [KeyTypes.EC]); } } \ No newline at end of file diff --git a/tests/pollux/FromJWK.test.ts b/tests/pollux/FromJWK.test.ts index f9d4e0b32..f37a01a4c 100644 --- a/tests/pollux/FromJWK.test.ts +++ b/tests/pollux/FromJWK.test.ts @@ -2,7 +2,7 @@ import { describe, expect, beforeEach, vi, it } from 'vitest'; import { Apollo, Castor, Domain, Secp256k1KeyPair, Secp256k1PrivateKey, Secp256k1PublicKey } from "../../src"; import { Task } from '../../src/utils'; import { FromJWK } from '../../src/pollux/utils/jwt/FromJWK'; -import { ApolloError, Curve } from '../../src/domain'; +import { ApolloError, Curve, JWK, PolluxError, PrivateKey, PublicKey } from '../../src/domain'; describe("Pollux - JWT FromJWK", async () => { let ctx: Task.Context<{ @@ -43,7 +43,7 @@ describe("Pollux - JWT FromJWK", async () => { crv: Curve.SECP256K1, } }).run(ctx); - await expect(sut).rejects.toThrow("19: Missing or invalid JWK parameters: d, x and y"); + await expect(sut).rejects.toThrow("20: Required property x+y or d is missing in EC JWK"); }); it("Should throw an error if only one coordinate coordinate is present", async () => { @@ -54,7 +54,7 @@ describe("Pollux - JWT FromJWK", async () => { x: "poDxfZtoOpBDtFqJmJ03_tei3ooCXrGXkJM_WUErZPM", } }).run(ctx); - await expect(sut).rejects.toThrow("19: Missing or invalid JWK parameter: y"); + await expect(sut).rejects.toThrow("19: Missing JWK Parameter: y"); const sut2 = new FromJWK({ jwk: { @@ -63,7 +63,7 @@ describe("Pollux - JWT FromJWK", async () => { y: "M6WTO1raVf2TNHO7t0IpiurajRo6k12HbJvNa2L-8sA", } }).run(ctx); - await expect(sut2).rejects.toThrow("19: Missing or invalid JWK parameter: x"); + await expect(sut2).rejects.toThrow("19: Missing JWK Parameter: x"); }); it("Should throw an error if d is present but invalid", async () => { @@ -74,7 +74,7 @@ describe("Pollux - JWT FromJWK", async () => { d: "adsasdasdadsadsasd", } }).run(ctx); - await expect(sut).rejects.toThrow("19: Missing or invalid JWK parameter: d"); + await expect(sut).rejects.toThrow("19: Invalid JWK Parameter, not base64url encoded: d"); }); it("Should throw an error if d is present but curve is unsupported", async () => { @@ -98,7 +98,7 @@ describe("Pollux - JWT FromJWK", async () => { y: "1234567890" } }).run(ctx); - await expect(sut).rejects.toThrow(ApolloError.InvalidECParameters); + await expect(sut).rejects.toThrow(PolluxError.InvalidJWKParameters); }); it("Should not allow restoring from coordinates for non secp256k1 curves", async () => { @@ -146,17 +146,95 @@ describe("Pollux - JWT FromJWK", async () => { }); + + describe("OKP Key type", async () => { it("Should throw an error if the curve is not supported"); - const supportedCurves = [Curve.ED25519, Curve.X25519, Curve.SECP256K1]; - + const supportedCurves = [Curve.ED25519, Curve.X25519]; + const fixTures: Record = { + [Curve.ED25519]: { + private: { + "kty": "OKP", + "d": "c3sPtGX-Jy4Ayi1RUz27UIS4ztWSEfJdQ3etgsjBl5M", + "crv": "Ed25519", + "x": "j7J_Y6I6Qg0RRFmBw5kJhxj9IsYrLw57c0NTeh5WNiI", + "alg": "EdDSA" + }, + public: { + "kty": "OKP", + "crv": "Ed25519", + "x": "j7J_Y6I6Qg0RRFmBw5kJhxj9IsYrLw57c0NTeh5WNiI", + "alg": "EdDSA" + } + }, + [Curve.X25519]: { + private: { + "kty": "OKP", + "d": "jZ52YidJvBz6GUroCIvasKSIAUrfp5Nk59zaM8biVIw", + "crv": "X25519", + "x": "eKj9zIA32kZc9jwSGnix2i8aiJo-ovrB8FeJkFwpMBE", + "alg": "EdDSA" + }, + public: { + "kty": "OKP", + "crv": "X25519", + "x": "eKj9zIA32kZc9jwSGnix2i8aiJo-ovrB8FeJkFwpMBE", + "alg": "EdDSA" + } + } + } supportedCurves.forEach(curve => { describe(`${curve} curve`, () => { - it("Should throw an error if x is not encoded with base64url") - it("Should throw an error if d property is not encoded with base64url") - it("Should return a keyPair if x and d are present") - it("Should return a public key if x is present") + it(`${curve} Should throw an error if x is missing`, async () => { + const sut = new FromJWK({ + jwk: { + ...fixTures[curve].public, + x: undefined + } as any + }).run(ctx); + await expect(sut).rejects.toThrow("19: Missing JWK Parameter x") + }) + + it(`${curve} Should throw an error if x is not encoded with base64url`, async () => { + const sut = new FromJWK({ + jwk: { + ...fixTures[curve].public, + x: 'no' + } as any + }).run(ctx); + await expect(sut).rejects.toThrow("19: Invalid JWK Parameter, not base64url encoded: x") + }) + it(`${curve} Should throw an error if d property is not encoded with base64url`, async () => { + const sut = new FromJWK({ + jwk: { + ...fixTures[curve].private, + d: 'no' + } as any + }).run(ctx); + await expect(sut).rejects.toThrow("19: Invalid JWK Parameter, not base64url encoded: d") + }) + it(`${curve} Should return a keyPair if x and d are present`, async () => { + const sut = await new FromJWK({ + jwk: fixTures[curve].private + }).run(ctx); + + expect(sut).toHaveProperty("privateKey"); + expect(sut).toHaveProperty("publicKey"); + expect(sut).toHaveProperty("curve"); + expect((sut as Domain.KeyPair).privateKey).to.be.an.instanceOf(PrivateKey); + expect((sut as Domain.KeyPair).publicKey).to.be.an.instanceOf(PublicKey); + expect(sut.curve).to.eq(curve) + }) + it(`${curve} Should return a public key if x is present`, async () => { + const sut = await new FromJWK({ + jwk: fixTures[curve].public + }).run(ctx); + + expect(sut).to.be.an.instanceOf(PublicKey); + expect(sut).toHaveProperty("curve"); + expect(sut.curve).to.eq(curve) + }) }); }); }); From 8c5e1785f97a68b2ea5ed8503f5896c3d1e72692 Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Thu, 20 Feb 2025 10:39:02 +0100 Subject: [PATCH 03/11] fix: remove exposure of private getter Signed-off-by: Francisco Javier Ribo Labrador --- src/apollo/utils/Secp256k1PublicKey.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apollo/utils/Secp256k1PublicKey.ts b/src/apollo/utils/Secp256k1PublicKey.ts index 25ea537a7..b010a1c40 100644 --- a/src/apollo/utils/Secp256k1PublicKey.ts +++ b/src/apollo/utils/Secp256k1PublicKey.ts @@ -36,7 +36,7 @@ export class Secp256k1PublicKey extends PublicKey implements StorableKey, Export ); } - get native() { + private get native() { return ApolloSDK.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromBytes( Int8Array.from(this.raw) ); From 769c5b9419c7fa4e37e79b0f9e71887e5cba739d Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Tue, 25 Feb 2025 10:04:05 +0100 Subject: [PATCH 04/11] fix: make functions private in FromJWK task Signed-off-by: Francisco Javier Ribo Labrador --- src/pollux/utils/jwt/FromJWK.ts | 81 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/src/pollux/utils/jwt/FromJWK.ts b/src/pollux/utils/jwt/FromJWK.ts index 556ed6f9e..3df85ff00 100644 --- a/src/pollux/utils/jwt/FromJWK.ts +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -4,49 +4,46 @@ import { expect, Task } from "../../../utils"; import { base64url } from "multiformats/bases/base64"; import { ApolloError, Curve, JWK, KeyPair, KeyProperties, KeyTypes, PolluxError, PrivateKey, PublicKey } from "../../../domain"; - -export function isECJWK(jwk: JWK): jwk is JWK.EC { - if (jwk.kty !== "EC") { - return false; - } - return true; -} - -export function isOKPJWK(jwk: JWK): jwk is JWK.OKP { - if (jwk.kty !== "OKP") { - return false; - } - return true; +export interface Args { + jwk: Domain.JWK; } +export class FromJWK extends Task { -export function decodeJWKParameter( - coordinate: 'x' | 'y' | 'd', - jwk: JWK.EC | JWK.OKP -): Uint8Array { - if (!jwk[coordinate]) { - throw new PolluxError.InvalidJWKParameters(coordinate, `Missing JWK Parameter`); - } - const coordinateValue = jwk[coordinate]; - try { - if (typeof coordinateValue !== 'string') { - throw new PolluxError.InvalidJWKParameters(coordinate, `Invalid JWK Parameter, not string`); + private isECJWK(jwk: JWK): jwk is JWK.EC { + if (jwk.kty !== "EC") { + return false; } - const decoded = base64url.baseDecode(coordinateValue); - return new Uint8Array(decoded); - } catch (err) { - throw new PolluxError.InvalidJWKParameters(coordinate, `Invalid JWK Parameter, not base64url encoded`); + return true; } -} - + private isOKPJWK(jwk: JWK): jwk is JWK.OKP { + if (jwk.kty !== "OKP") { + return false; + } + return true; + } -export interface Args { - jwk: Domain.JWK; -} + private decodeJWKParameter( + coordinate: 'x' | 'y' | 'd', + jwk: JWK.EC | JWK.OKP + ): Uint8Array { + if (!jwk[coordinate]) { + throw new PolluxError.InvalidJWKParameters(coordinate, `Missing JWK Parameter`); + } + const coordinateValue = jwk[coordinate]; + try { + if (typeof coordinateValue !== 'string') { + throw new PolluxError.InvalidJWKParameters(coordinate, `Invalid JWK Parameter, not string`); + } + const decoded = base64url.baseDecode(coordinateValue); + return new Uint8Array(decoded); + } catch (err) { + throw new PolluxError.InvalidJWKParameters(coordinate, `Invalid JWK Parameter, not base64url encoded`); + } + } -export class FromJWK extends Task { private isSupportedCurve(crv: string): void { const keys = Object.values(Curve); @@ -65,8 +62,8 @@ export class FromJWK extends Task { const keyType = crv === Curve.X25519 ? KeyTypes.Curve25519 : KeyTypes.EC; if (withCoordinates) { - const decodedX = decodeJWKParameter('x', jwk); - const decodedY = decodeJWKParameter('y', jwk); + const decodedX = this.decodeJWKParameter('x', jwk); + const decodedY = this.decodeJWKParameter('y', jwk); if (crv === Curve.SECP256K1) { let pk: PublicKey; @@ -80,7 +77,7 @@ export class FromJWK extends Task { }); if (jwk.d !== undefined) { - const decodedD = decodeJWKParameter('d', jwk); + const decodedD = this.decodeJWKParameter('d', jwk); sk = apollo.createPrivateKey({ [KeyProperties.curve]: crv, [KeyProperties.type]: keyType, @@ -101,7 +98,7 @@ export class FromJWK extends Task { } if (jwk.d !== undefined) { - const decodedD = decodeJWKParameter('d', jwk); + const decodedD = this.decodeJWKParameter('d', jwk); return apollo.createPrivateKey({ [KeyProperties.curve]: crv, [KeyProperties.type]: keyType, @@ -123,14 +120,14 @@ export class FromJWK extends Task { const pk = apollo.createPublicKey({ [KeyProperties.curve]: crv, [KeyProperties.type]: keyType, - [KeyProperties.rawKey]: decodeJWKParameter('x', jwk) + [KeyProperties.rawKey]: this.decodeJWKParameter('x', jwk) }); if (jwk.d !== undefined) { const sk = apollo.createPrivateKey({ [KeyProperties.curve]: crv, [KeyProperties.type]: keyType, - [KeyProperties.rawKey]: decodeJWKParameter('d', jwk) + [KeyProperties.rawKey]: this.decodeJWKParameter('d', jwk) }); const keypair: Domain.KeyPair = { privateKey: sk, @@ -146,8 +143,8 @@ export class FromJWK extends Task { async run(ctx: Task.Context): Promise { const jwk = this.args.jwk; const kty = expect(jwk.kty, new PolluxError.InvalidJWKParameters(['kty'], 'Missing JWK Parameter kty')); - const isEC = isECJWK(jwk); - const isOKP = isOKPJWK(jwk); + const isEC = this.isECJWK(jwk); + const isOKP = this.isOKPJWK(jwk); if (isEC) { return this.fromJWKEC(ctx.Apollo, jwk); } From f94a5857194ca45816ac9fb7e86fb8392eca8471 Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Tue, 25 Feb 2025 10:26:06 +0100 Subject: [PATCH 05/11] fix: remove JWK casting and improve type validation at runtime Signed-off-by: Francisco Javier Ribo Labrador --- src/pollux/utils/jwt/PKInstance.ts | 72 ++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/src/pollux/utils/jwt/PKInstance.ts b/src/pollux/utils/jwt/PKInstance.ts index dbac309af..f89b14468 100644 --- a/src/pollux/utils/jwt/PKInstance.ts +++ b/src/pollux/utils/jwt/PKInstance.ts @@ -9,14 +9,78 @@ export interface Args { verificationMethod: DIDResolver.VerificationMethod; } +type VerificationMethodKeys = "id" | "type" | "controller"; +type MultibaseVerificationMethod = Pick< + DIDResolver.VerificationMethod, + VerificationMethodKeys +> & { + publicKeyMultibase: string; +}; + +type JWKVerificationMethod = Pick< + DIDResolver.VerificationMethod, + VerificationMethodKeys +> & { + publicKeyJwk: Domain.JWK; +}; + export class PKInstance extends Task { + + private isMultibaseVerificationMethod(verificationMethod: DIDResolver.VerificationMethod): verificationMethod is MultibaseVerificationMethod { + return verificationMethod.publicKeyMultibase !== undefined && + typeof verificationMethod.publicKeyMultibase === 'string'; + } + + private isJWKVerificationMethod(verificationMethod: DIDResolver.VerificationMethod): verificationMethod is JWKVerificationMethod { + const validStructure = verificationMethod.publicKeyJwk !== undefined && + typeof verificationMethod.publicKeyJwk === 'object'; + + if (!validStructure) { + return false; + } + + const kty = verificationMethod.publicKeyJwk?.kty; + const crv = verificationMethod.publicKeyJwk?.crv; + const x = verificationMethod.publicKeyJwk?.x; + const y = verificationMethod.publicKeyJwk?.y; + const d = verificationMethod.publicKeyJwk?.d; + + if (crv === undefined) { + return false; + } + + if (kty === 'EC') { + if (x === undefined && y === undefined && d === undefined) { + return false; + } + if ((typeof x !== 'string' && typeof y !== 'string') || typeof d !== 'string') { + return false; + } + return true; + } + + if (kty === 'OKP') { + if (x === undefined) { + return false; + } + if (typeof x !== 'string') { + return false; + } + if (d !== undefined && typeof d !== 'string') { + return false; + } + return true; + } + + return false; + } + async run(ctx: Task.Context) { const verificationMethod = this.args.verificationMethod; let pk: Domain.PublicKey | undefined = undefined; - if (verificationMethod.publicKeyMultibase) { + if (this.isMultibaseVerificationMethod(verificationMethod)) { const decoded = base58btc.decode(verificationMethod.publicKeyMultibase); - if (verificationMethod.type === VerificationKeyType.EcdsaSecp256k1VerificationKey2019) { pk = ctx.Apollo.createPublicKey({ [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, @@ -34,9 +98,9 @@ export class PKInstance extends Task { return pk; } - if (verificationMethod.publicKeyJwk) { + if (this.isJWKVerificationMethod(verificationMethod)) { const keyPair = await ctx.run( - new FromJWK({ jwk: verificationMethod.publicKeyJwk as Domain.JWK }) + new FromJWK({ jwk: verificationMethod.publicKeyJwk }) ); if (keyPair instanceof Domain.KeyPair) { pk = keyPair.publicKey; From 97e143615836315255bcc90375c841ad261ecf7b Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Wed, 5 Mar 2025 13:23:30 +0100 Subject: [PATCH 06/11] fix: pr suggestions, removing keyType and simplifying validation Signed-off-by: Francisco Javier Ribo Labrador --- src/apollo/Apollo.ts | 218 ++++++++---------- src/castor/did/prismDID/PrismDIDPublicKey.ts | 4 - src/domain/models/KeyProperties.ts | 5 - src/edge-agent/Agent.Backup.ts | 3 +- src/edge-agent/didFunctions/CreatePrismDID.ts | 2 - .../internal/dif/IsCredentialRevoked.ts | 1 - .../internal/oea/jwt/CredentialOffer.ts | 2 - .../internal/oea/sdjwt/CredentialOffer.ts | 2 - src/pollux/utils/jwt/FromJWK.ts | 18 +- src/pollux/utils/jwt/PKInstance.ts | 73 +----- tests/pollux/FromJWK.test.ts | 14 +- 11 files changed, 118 insertions(+), 224 deletions(-) diff --git a/src/apollo/Apollo.ts b/src/apollo/Apollo.ts index fbb42e57e..7d679fb00 100644 --- a/src/apollo/Apollo.ts +++ b/src/apollo/Apollo.ts @@ -6,7 +6,6 @@ import { getKeyCurveByNameAndIndex, ApolloError, Curve, - KeyTypes, KeyProperties, MnemonicWordList, PrivateKey, @@ -43,7 +42,6 @@ const BigIntegerWrapper = ApolloSDK.derivation.BigIntegerWrapper; * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.EC, * curve: Curve.ED25519, * }); * ``` @@ -195,7 +193,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { * * ```ts * const privateKey = apollo.createPublicKey({ - * type: KeyTypes.EC, * curve: Curve.SECP256K1, * raw: Buffer.from(new Arra(64).fill(1)), * }); @@ -207,13 +204,8 @@ export default class Apollo implements ApolloInterface, KeyRestoration { createPublicKey(parameters: { [name: KeyProperties | string]: any; }): PublicKey { - const keyType = parameters[KeyProperties.type]; const keyCurve = parameters[KeyProperties.curve]; - if (isEmpty(keyType)) { - throw new ApolloError.InvalidKeyType(); - } - if (isEmpty(keyCurve)) { throw new ApolloError.InvalidKeyCurve(); } @@ -221,42 +213,39 @@ export default class Apollo implements ApolloInterface, KeyRestoration { const { curve } = getKeyCurveByNameAndIndex(keyCurve); const keyData = parameters[KeyProperties.rawKey]; - if (keyType === KeyTypes.EC) { - if (curve === Curve.ED25519) { - if (keyData) { - return new Ed25519PublicKey(keyData); - } - - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); + if (curve === Curve.ED25519) { + if (keyData) { + return new Ed25519PublicKey(keyData); } - if (curve === Curve.SECP256K1) { - if (keyData) { - return new Secp256k1PublicKey(keyData); - } else { - const xData = parameters[KeyProperties.curvePointX]; - const yData = parameters[KeyProperties.curvePointY]; + throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); + } - if (xData && yData) { - return Secp256k1PublicKey.secp256k1FromByteCoordinates(xData, yData); - } - } + if (curve === Curve.SECP256K1) { + if (keyData) { + return new Secp256k1PublicKey(keyData); + } else { + const xData = parameters[KeyProperties.curvePointX]; + const yData = parameters[KeyProperties.curvePointY]; - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey, KeyProperties.curvePointX, KeyProperties.curvePointY); + if (xData && yData) { + return Secp256k1PublicKey.secp256k1FromByteCoordinates(xData, yData); + } } + + throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey, KeyProperties.curvePointX, KeyProperties.curvePointY); } - if (keyType === KeyTypes.Curve25519) { - if (curve === Curve.X25519) { - if (keyData) { - return new X25519PublicKey(keyData); - } - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); + if (curve === Curve.X25519) { + if (keyData) { + return new X25519PublicKey(keyData); } + + throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); } - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); + throw new ApolloError.InvalidKeyCurve(); } /** @@ -268,7 +257,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.EC, * curve: Curve.SECP256K1, * seed: Buffer.from(seed.value).toString("hex"), * }); @@ -280,7 +268,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.EC, * curve: Curve.SECP256K1, * seed: Buffer.from(seed.value).toString("hex"), * derivationPath: "m/0'/0'/0'" @@ -295,7 +282,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.EC, * curve: Curve.ED25519, * }); * ``` @@ -308,7 +294,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.Curve25519, * curve: Curve.X25519, * }); * ``` @@ -319,13 +304,8 @@ export default class Apollo implements ApolloInterface, KeyRestoration { createPrivateKey(parameters: { [name: KeyProperties | string]: any; }): PrivateKey { - const keyType = parameters[KeyProperties.type]; const keyCurve = parameters[KeyProperties.curve]; - if (isEmpty(keyType)) { - throw new ApolloError.InvalidKeyType(); - } - if (isEmpty(keyCurve)) { throw new ApolloError.InvalidKeyCurve(); } @@ -333,69 +313,20 @@ export default class Apollo implements ApolloInterface, KeyRestoration { const { curve } = getKeyCurveByNameAndIndex(parameters[KeyProperties.curve]); const keyData = parameters[KeyProperties.rawKey]; - if (keyType === KeyTypes.EC) { - if (curve === Curve.ED25519) { - if (keyData) { - return new Ed25519PrivateKey(keyData); - } - - const seedHex = parameters[KeyProperties.seed]; - if (notEmptyString(seedHex)) { - const derivationIndex = parameters[KeyProperties.index] ?? "0"; - const derivationParam = parameters[KeyProperties.derivationPath]; - const defaultPath: string = derivationParam ?? PrismDerivationPath.init(derivationIndex).toString(); - const seed = Int8Array.from(Buffer.from(seedHex, "hex")); - const hdKey = ApolloSDK.derivation.EdHDKey.Companion.initFromSeed(seed); - const baseKey = new Ed25519PrivateKey(Uint8Array.from(hdKey.privateKey)); - - baseKey.keySpecification.set(KeyProperties.chainCode, Buffer.from(Uint8Array.from(hdKey.chainCode)).toString("hex")); - baseKey.keySpecification.set(KeyProperties.derivationPath, Buffer.from(defaultPath).toString("hex")); - baseKey.keySpecification.set(KeyProperties.index, derivationIndex); - - if (derivationParam) { - const privateKey = baseKey.derive(defaultPath); - return privateKey; - } - - return baseKey; - } - - const keyPair = Ed25519KeyPair.generateKeyPair(); - return keyPair.privateKey; + if (curve === Curve.ED25519) { + if (keyData) { + return new Ed25519PrivateKey(keyData); } - if (curve === Curve.SECP256K1) { - if (keyData) { - return new Secp256k1PrivateKey(keyData); - } - - const seedHex = parameters[KeyProperties.seed]; - if (isEmpty(seedHex)) { - throw new ApolloError.MissingKeyParameters(KeyProperties.seed); - } - - const seed = Buffer.from(seedHex, "hex"); + const seedHex = parameters[KeyProperties.seed]; + if (notEmptyString(seedHex)) { const derivationIndex = parameters[KeyProperties.index] ?? "0"; const derivationParam = parameters[KeyProperties.derivationPath]; - const defaultPath: string = derivationParam ?? PrismDerivationPath.init( - derivationIndex - ).toString(); - - const hdKey = HDKey.InitFromSeed( - Int8Array.from(seed), - defaultPath.split("/").slice(1).length, - BigIntegerWrapper.initFromInt(0) - ); - - if (hdKey.privateKey == null) { - throw new ApolloError.ApolloLibError("Key generated incorrectly: missing privateKey"); - } + const defaultPath: string = derivationParam ?? PrismDerivationPath.init(derivationIndex).toString(); + const seed = Int8Array.from(Buffer.from(seedHex, "hex")); + const hdKey = ApolloSDK.derivation.EdHDKey.Companion.initFromSeed(seed); + const baseKey = new Ed25519PrivateKey(Uint8Array.from(hdKey.privateKey)); - if (hdKey.chainCode == null) { - throw new ApolloError.ApolloLibError("Key generated incorrectly: missing chainCode"); - } - - const baseKey = new Secp256k1PrivateKey(Uint8Array.from(hdKey.privateKey)); baseKey.keySpecification.set(KeyProperties.chainCode, Buffer.from(Uint8Array.from(hdKey.chainCode)).toString("hex")); baseKey.keySpecification.set(KeyProperties.derivationPath, Buffer.from(defaultPath).toString("hex")); baseKey.keySpecification.set(KeyProperties.index, derivationIndex); @@ -407,37 +338,82 @@ export default class Apollo implements ApolloInterface, KeyRestoration { return baseKey; } + + const keyPair = Ed25519KeyPair.generateKeyPair(); + return keyPair.privateKey; } - if (keyType === KeyTypes.Curve25519) { - if (curve === Curve.X25519) { - if (keyData) { - return new X25519PrivateKey(keyData); - } + if (curve === Curve.SECP256K1) { + if (keyData) { + return new Secp256k1PrivateKey(keyData); + } - const seedHex = parameters[KeyProperties.seed]; - if (notEmptyString(seedHex)) { - const derivationIndex = parameters[KeyProperties.index] ?? "0"; - const derivationParam: string = parameters[KeyProperties.derivationPath] ?? PrismDerivationPath.init(derivationIndex).toString(); + const seedHex = parameters[KeyProperties.seed]; + if (isEmpty(seedHex)) { + throw new ApolloError.MissingKeyParameters(KeyProperties.seed); + } - const seed = Int8Array.from(Buffer.from(seedHex, "hex")); - const hdKey = ApolloSDK.derivation.EdHDKey.Companion.initFromSeed(seed).derive(derivationParam); - const edKey = Ed25519PrivateKey.from.Buffer(Buffer.from(hdKey.privateKey)); - const xKey = edKey.x25519(); + const seed = Buffer.from(seedHex, "hex"); + const derivationIndex = parameters[KeyProperties.index] ?? "0"; + const derivationParam = parameters[KeyProperties.derivationPath]; + const defaultPath: string = derivationParam ?? PrismDerivationPath.init( + derivationIndex + ).toString(); + + const hdKey = HDKey.InitFromSeed( + Int8Array.from(seed), + defaultPath.split("/").slice(1).length, + BigIntegerWrapper.initFromInt(0) + ); + + if (hdKey.privateKey == null) { + throw new ApolloError.ApolloLibError("Key generated incorrectly: missing privateKey"); + } - xKey.keySpecification.set(KeyProperties.chainCode, Buffer.from(hdKey.chainCode).toString("hex")); - xKey.keySpecification.set(KeyProperties.derivationPath, Buffer.from(derivationParam).toString("hex")); - xKey.keySpecification.set(KeyProperties.index, derivationIndex); + if (hdKey.chainCode == null) { + throw new ApolloError.ApolloLibError("Key generated incorrectly: missing chainCode"); + } - return xKey; - } + const baseKey = new Secp256k1PrivateKey(Uint8Array.from(hdKey.privateKey)); + baseKey.keySpecification.set(KeyProperties.chainCode, Buffer.from(Uint8Array.from(hdKey.chainCode)).toString("hex")); + baseKey.keySpecification.set(KeyProperties.derivationPath, Buffer.from(defaultPath).toString("hex")); + baseKey.keySpecification.set(KeyProperties.index, derivationIndex); + + if (derivationParam) { + const privateKey = baseKey.derive(defaultPath); + return privateKey; + } + + return baseKey; + } + + if (curve === Curve.X25519) { + if (keyData) { + return new X25519PrivateKey(keyData); + } - const keyPair = X25519KeyPair.generateKeyPair(); - return keyPair.privateKey; + const seedHex = parameters[KeyProperties.seed]; + if (notEmptyString(seedHex)) { + const derivationIndex = parameters[KeyProperties.index] ?? "0"; + const derivationParam: string = parameters[KeyProperties.derivationPath] ?? PrismDerivationPath.init(derivationIndex).toString(); + + const seed = Int8Array.from(Buffer.from(seedHex, "hex")); + const hdKey = ApolloSDK.derivation.EdHDKey.Companion.initFromSeed(seed).derive(derivationParam); + const edKey = Ed25519PrivateKey.from.Buffer(Buffer.from(hdKey.privateKey)); + const xKey = edKey.x25519(); + + xKey.keySpecification.set(KeyProperties.chainCode, Buffer.from(hdKey.chainCode).toString("hex")); + xKey.keySpecification.set(KeyProperties.derivationPath, Buffer.from(derivationParam).toString("hex")); + xKey.keySpecification.set(KeyProperties.index, derivationIndex); + + return xKey; } + + const keyPair = X25519KeyPair.generateKeyPair(); + return keyPair.privateKey; } - throw new ApolloError.InvalidKeyType(keyType); + throw new ApolloError.InvalidKeyCurve(); } restorePrivateKey(key: StorableKey): PrivateKey { diff --git a/src/castor/did/prismDID/PrismDIDPublicKey.ts b/src/castor/did/prismDID/PrismDIDPublicKey.ts index 64f58e523..380f6222d 100644 --- a/src/castor/did/prismDID/PrismDIDPublicKey.ts +++ b/src/castor/did/prismDID/PrismDIDPublicKey.ts @@ -28,13 +28,11 @@ export class PrismDIDPublicKey { switch (proto.key_data) { case "compressed_ec_key_data": return apollo.createPublicKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.rawKey]: proto.compressed_ec_key_data.data }) case "ec_key_data": return apollo.createPublicKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.curvePointX]: proto.ec_key_data.x, [KeyProperties.curvePointY]: proto.ec_key_data.y, @@ -52,14 +50,12 @@ export class PrismDIDPublicKey { if (proto.has_compressed_ec_key_data) { if (curve === Curve.ED25519) { return apollo.createPublicKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.ED25519, [KeyProperties.rawKey]: proto.compressed_ec_key_data.data }) } if (curve === Curve.X25519) { return apollo.createPublicKey({ - [KeyProperties.type]: KeyTypes.Curve25519, [KeyProperties.curve]: Curve.X25519, [KeyProperties.rawKey]: proto.compressed_ec_key_data.data }) diff --git a/src/domain/models/KeyProperties.ts b/src/domain/models/KeyProperties.ts index 2f63f5d41..bb0096f7b 100644 --- a/src/domain/models/KeyProperties.ts +++ b/src/domain/models/KeyProperties.ts @@ -40,11 +40,6 @@ export enum KeyProperties { derivationPath = "derivationPath", index = "index", - /** - * The 'type' denotes the type of the key. - */ - type = "type", - /** * The 'curvePointX' represents the x-coordinate of a curve point for an elliptic-curve key. */ diff --git a/src/edge-agent/Agent.Backup.ts b/src/edge-agent/Agent.Backup.ts index 8d9869f2d..28bfc8796 100644 --- a/src/edge-agent/Agent.Backup.ts +++ b/src/edge-agent/Agent.Backup.ts @@ -25,7 +25,7 @@ export class AgentBackup { constructor( public readonly Agent: BackupAgent - ) {} + ) { } /** * Creates a JWE (JSON Web Encryption) containing the backup data stored in Pluto. @@ -152,7 +152,6 @@ export class AgentBackup { } const masterKey = this.Agent.apollo.createPrivateKey({ - [Domain.KeyProperties.type]: Domain.KeyTypes.Curve25519, [Domain.KeyProperties.curve]: Domain.Curve.X25519, [Domain.KeyProperties.seed]: Buffer.from(this.Agent.seed.value).toString("hex"), [Domain.KeyProperties.derivationPath]: "m/0'/0'/0'" diff --git a/src/edge-agent/didFunctions/CreatePrismDID.ts b/src/edge-agent/didFunctions/CreatePrismDID.ts index 5d3f69702..bfe898e69 100644 --- a/src/edge-agent/didFunctions/CreatePrismDID.ts +++ b/src/edge-agent/didFunctions/CreatePrismDID.ts @@ -48,7 +48,6 @@ export class CreatePrismDID extends Task { const seedHex = Buffer.from(ctx.Seed.value).toString("hex"); const masterSK = ctx.Apollo.createPrivateKey({ - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, [Domain.KeyProperties.seed]: seedHex, [Domain.KeyProperties.derivationPath]: masterKeyDerivation.toString(), @@ -56,7 +55,6 @@ export class CreatePrismDID extends Task { }); const edSk = ctx.Apollo.createPrivateKey({ - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.curve]: Domain.Curve.ED25519, [Domain.KeyProperties.seed]: seedHex, [Domain.KeyProperties.derivationPath]: issuingDerivation.toString(), diff --git a/src/plugins/internal/dif/IsCredentialRevoked.ts b/src/plugins/internal/dif/IsCredentialRevoked.ts index 1e44cf5ad..9e07106e9 100644 --- a/src/plugins/internal/dif/IsCredentialRevoked.ts +++ b/src/plugins/internal/dif/IsCredentialRevoked.ts @@ -151,7 +151,6 @@ export class IsCredentialRevoked extends Plugins.Task { } const { x, y } = decodedVerificationMethod.publicKeyJwk; const pk = ctx.Apollo.createPublicKey({ - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, [Domain.KeyProperties.curvePointX]: Buffer.from(base64url.baseDecode(x)), [Domain.KeyProperties.curvePointY]: Buffer.from(base64url.baseDecode(y)) diff --git a/src/plugins/internal/oea/jwt/CredentialOffer.ts b/src/plugins/internal/oea/jwt/CredentialOffer.ts index 14089327b..d283e3055 100644 --- a/src/plugins/internal/oea/jwt/CredentialOffer.ts +++ b/src/plugins/internal/oea/jwt/CredentialOffer.ts @@ -20,14 +20,12 @@ export class CredentialOffer extends Plugins.Task { const masterSk = await ctx.Apollo.createPrivateKey({ [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, [Domain.KeyProperties.index]: index, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.seed]: Buffer.from(ctx.Seed.value).toString("hex"), }); const authSk = await ctx.Apollo.createPrivateKey({ [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, [Domain.KeyProperties.index]: index + 1, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.seed]: Buffer.from(ctx.Seed.value).toString("hex"), }); diff --git a/src/plugins/internal/oea/sdjwt/CredentialOffer.ts b/src/plugins/internal/oea/sdjwt/CredentialOffer.ts index 19e41fc9e..d630387b3 100644 --- a/src/plugins/internal/oea/sdjwt/CredentialOffer.ts +++ b/src/plugins/internal/oea/sdjwt/CredentialOffer.ts @@ -22,14 +22,12 @@ export class CredentialOffer extends Plugins.Task { const masterSk = await ctx.Apollo.createPrivateKey({ [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, [Domain.KeyProperties.index]: index, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.seed]: Buffer.from(ctx.Seed.value).toString("hex"), }); const authSk = await ctx.Apollo.createPrivateKey({ [Domain.KeyProperties.curve]: Domain.Curve.ED25519, [Domain.KeyProperties.index]: index + 1, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.seed]: Buffer.from(ctx.Seed.value).toString("hex"), }); diff --git a/src/pollux/utils/jwt/FromJWK.ts b/src/pollux/utils/jwt/FromJWK.ts index 3df85ff00..57a893259 100644 --- a/src/pollux/utils/jwt/FromJWK.ts +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -45,7 +45,7 @@ export class FromJWK extends Task { } - private isSupportedCurve(crv: string): void { + private isSupportedCurve(crv: string): asserts crv is Curve { const keys = Object.values(Curve); if (!keys.includes(crv as Curve)) { throw new ApolloError.InvalidKeyCurve(crv); @@ -60,18 +60,17 @@ export class FromJWK extends Task { jwk.x !== undefined || jwk.y !== undefined; - const keyType = crv === Curve.X25519 ? KeyTypes.Curve25519 : KeyTypes.EC; if (withCoordinates) { - const decodedX = this.decodeJWKParameter('x', jwk); - const decodedY = this.decodeJWKParameter('y', jwk); if (crv === Curve.SECP256K1) { + const decodedX = this.decodeJWKParameter('x', jwk); + const decodedY = this.decodeJWKParameter('y', jwk); + let pk: PublicKey; let sk: PrivateKey; pk = apollo.createPublicKey({ [KeyProperties.curve]: crv, - [KeyProperties.type]: keyType, [KeyProperties.curvePointX]: decodedX, [KeyProperties.curvePointY]: decodedY }); @@ -80,7 +79,6 @@ export class FromJWK extends Task { const decodedD = this.decodeJWKParameter('d', jwk); sk = apollo.createPrivateKey({ [KeyProperties.curve]: crv, - [KeyProperties.type]: keyType, [KeyProperties.rawKey]: decodedD }); @@ -101,7 +99,6 @@ export class FromJWK extends Task { const decodedD = this.decodeJWKParameter('d', jwk); return apollo.createPrivateKey({ [KeyProperties.curve]: crv, - [KeyProperties.type]: keyType, [KeyProperties.rawKey]: decodedD }) } @@ -115,18 +112,14 @@ export class FromJWK extends Task { expect(jwk.x, new PolluxError.InvalidJWKParameters(['x'], 'Missing JWK Parameter x')); - const keyType = crv === Curve.X25519 ? KeyTypes.Curve25519 : KeyTypes.EC; - const pk = apollo.createPublicKey({ [KeyProperties.curve]: crv, - [KeyProperties.type]: keyType, [KeyProperties.rawKey]: this.decodeJWKParameter('x', jwk) }); if (jwk.d !== undefined) { const sk = apollo.createPrivateKey({ [KeyProperties.curve]: crv, - [KeyProperties.type]: keyType, [KeyProperties.rawKey]: this.decodeJWKParameter('d', jwk) }); const keypair: Domain.KeyPair = { @@ -142,7 +135,6 @@ export class FromJWK extends Task { async run(ctx: Task.Context): Promise { const jwk = this.args.jwk; - const kty = expect(jwk.kty, new PolluxError.InvalidJWKParameters(['kty'], 'Missing JWK Parameter kty')); const isEC = this.isECJWK(jwk); const isOKP = this.isOKPJWK(jwk); if (isEC) { @@ -151,7 +143,7 @@ export class FromJWK extends Task { if (isOKP) { return this.fromJWKOKP(ctx.Apollo, jwk); } - throw new ApolloError.InvalidKeyType(kty, [KeyTypes.EC]); + throw new PolluxError.InvalidJWKParameters("kty", "The kty field must be EC or OKP"); } } \ No newline at end of file diff --git a/src/pollux/utils/jwt/PKInstance.ts b/src/pollux/utils/jwt/PKInstance.ts index f89b14468..3ab3c2058 100644 --- a/src/pollux/utils/jwt/PKInstance.ts +++ b/src/pollux/utils/jwt/PKInstance.ts @@ -1,7 +1,7 @@ import type * as DIDResolver from "did-resolver"; import { base58btc } from 'multiformats/bases/base58'; import * as Domain from "../../../domain"; -import { Task } from "../../../utils"; +import { isObject, notEmptyString, Task } from "../../../utils"; // TODO importing from Castor import { VerificationKeyType } from "../../../castor/types"; import { FromJWK } from "./FromJWK"; @@ -9,98 +9,33 @@ export interface Args { verificationMethod: DIDResolver.VerificationMethod; } -type VerificationMethodKeys = "id" | "type" | "controller"; -type MultibaseVerificationMethod = Pick< - DIDResolver.VerificationMethod, - VerificationMethodKeys -> & { - publicKeyMultibase: string; -}; - -type JWKVerificationMethod = Pick< - DIDResolver.VerificationMethod, - VerificationMethodKeys -> & { - publicKeyJwk: Domain.JWK; -}; export class PKInstance extends Task { - private isMultibaseVerificationMethod(verificationMethod: DIDResolver.VerificationMethod): verificationMethod is MultibaseVerificationMethod { - return verificationMethod.publicKeyMultibase !== undefined && - typeof verificationMethod.publicKeyMultibase === 'string'; - } - - private isJWKVerificationMethod(verificationMethod: DIDResolver.VerificationMethod): verificationMethod is JWKVerificationMethod { - const validStructure = verificationMethod.publicKeyJwk !== undefined && - typeof verificationMethod.publicKeyJwk === 'object'; - - if (!validStructure) { - return false; - } - - const kty = verificationMethod.publicKeyJwk?.kty; - const crv = verificationMethod.publicKeyJwk?.crv; - const x = verificationMethod.publicKeyJwk?.x; - const y = verificationMethod.publicKeyJwk?.y; - const d = verificationMethod.publicKeyJwk?.d; - - if (crv === undefined) { - return false; - } - - if (kty === 'EC') { - if (x === undefined && y === undefined && d === undefined) { - return false; - } - if ((typeof x !== 'string' && typeof y !== 'string') || typeof d !== 'string') { - return false; - } - return true; - } - - if (kty === 'OKP') { - if (x === undefined) { - return false; - } - if (typeof x !== 'string') { - return false; - } - if (d !== undefined && typeof d !== 'string') { - return false; - } - return true; - } - - return false; - } - async run(ctx: Task.Context) { const verificationMethod = this.args.verificationMethod; let pk: Domain.PublicKey | undefined = undefined; - if (this.isMultibaseVerificationMethod(verificationMethod)) { + if (notEmptyString(verificationMethod.publicKeyMultibase)) { const decoded = base58btc.decode(verificationMethod.publicKeyMultibase); if (verificationMethod.type === VerificationKeyType.EcdsaSecp256k1VerificationKey2019) { pk = ctx.Apollo.createPublicKey({ [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.rawKey]: decoded }); } else if (verificationMethod.type === VerificationKeyType.Ed25519VerificationKey2018 || verificationMethod.type === VerificationKeyType.Ed25519VerificationKey2020) { pk = ctx.Apollo.createPublicKey({ [Domain.KeyProperties.curve]: Domain.Curve.ED25519, - [Domain.KeyProperties.type]: Domain.KeyTypes.EC, [Domain.KeyProperties.rawKey]: decoded }); } return pk; } - if (this.isJWKVerificationMethod(verificationMethod)) { + if (isObject(verificationMethod.publicKeyJwk)) { const keyPair = await ctx.run( - new FromJWK({ jwk: verificationMethod.publicKeyJwk }) + new FromJWK({ jwk: verificationMethod.publicKeyJwk as Domain.JWK }) ); if (keyPair instanceof Domain.KeyPair) { pk = keyPair.publicKey; diff --git a/tests/pollux/FromJWK.test.ts b/tests/pollux/FromJWK.test.ts index f37a01a4c..55d2ff68e 100644 --- a/tests/pollux/FromJWK.test.ts +++ b/tests/pollux/FromJWK.test.ts @@ -10,12 +10,10 @@ describe("Pollux - JWT FromJWK", async () => { }>; let apollo: Domain.Apollo; let castor: Domain.Castor; - let plutoMock: Domain.Pluto; beforeEach(() => { apollo = new Apollo(); castor = new Castor(apollo); - plutoMock = { getDIDPrivateKeysByDID: vi.fn() } as any; ctx = Task.Context.make({ Apollo: apollo, }); @@ -149,7 +147,16 @@ describe("Pollux - JWT FromJWK", async () => { describe("OKP Key type", async () => { - it("Should throw an error if the curve is not supported"); + it("Should throw an error if the curve is not supported", async () => { + const sut = new FromJWK({ + jwk: { + ...fixTures[Curve.ED25519].public, + crv: "wrong curve", + x: undefined + } as any + }).run(ctx); + await expect(sut).rejects.toThrow("16: Invalid key curve: wrong curve. Valid options are: X25519, Ed25519, secp256k1") + }); const supportedCurves = [Curve.ED25519, Curve.X25519]; const fixTures: Record = { @@ -184,6 +191,7 @@ describe("Pollux - JWT FromJWK", async () => { } } } + supportedCurves.forEach(curve => { describe(`${curve} curve`, () => { it(`${curve} Should throw an error if x is missing`, async () => { From 144df182054b065cfaa9cc135aa2863b516aa0db Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Wed, 5 Mar 2025 13:24:06 +0100 Subject: [PATCH 07/11] fix: remove unused importa Signed-off-by: Francisco Javier Ribo Labrador --- src/castor/did/prismDID/PrismDIDPublicKey.ts | 2 +- src/pollux/utils/jwt/FromJWK.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/castor/did/prismDID/PrismDIDPublicKey.ts b/src/castor/did/prismDID/PrismDIDPublicKey.ts index 380f6222d..546568bc6 100644 --- a/src/castor/did/prismDID/PrismDIDPublicKey.ts +++ b/src/castor/did/prismDID/PrismDIDPublicKey.ts @@ -3,7 +3,7 @@ import { Curve, getProtosUsage, getUsage, PublicKey, Usage } from "../../../doma import { ApolloError, CastorError } from "../../../domain/models/Errors"; import * as Protos from "../../protos/node_models"; -import { Apollo, KeyProperties, KeyTypes } from "../../../domain"; +import { Apollo, KeyProperties } from "../../../domain"; export class PrismDIDPublicKey { diff --git a/src/pollux/utils/jwt/FromJWK.ts b/src/pollux/utils/jwt/FromJWK.ts index 57a893259..a92439157 100644 --- a/src/pollux/utils/jwt/FromJWK.ts +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -2,7 +2,7 @@ import { Domain } from "../../.."; import { expect, Task } from "../../../utils"; import { base64url } from "multiformats/bases/base64"; -import { ApolloError, Curve, JWK, KeyPair, KeyProperties, KeyTypes, PolluxError, PrivateKey, PublicKey } from "../../../domain"; +import { ApolloError, Curve, JWK, KeyPair, KeyProperties, PolluxError, PrivateKey, PublicKey } from "../../../domain"; export interface Args { jwk: Domain.JWK; From a0487210d14a0f47e453ef2335f71748697dd68e Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Wed, 5 Mar 2025 16:08:57 +0100 Subject: [PATCH 08/11] fix: error improvement in test Signed-off-by: Francisco Javier Ribo Labrador --- tests/apollo/Apollo.createPrivateKey.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/apollo/Apollo.createPrivateKey.test.ts b/tests/apollo/Apollo.createPrivateKey.test.ts index 74e74dcfb..4e8f9dae4 100644 --- a/tests/apollo/Apollo.createPrivateKey.test.ts +++ b/tests/apollo/Apollo.createPrivateKey.test.ts @@ -26,7 +26,7 @@ describe("Apollo", () => { [KeyProperties.seed]: seedHex, }); - expect(sut).to.throw(ApolloError.InvalidKeyType); + expect(sut).to.throw(ApolloError.InvalidKeyCurve); }); it("KeyProperties.curve - missing - throws", () => { From 0aa6452f4ec7eba749cfa2625653c10d7a8b3f2d Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Wed, 5 Mar 2025 16:32:47 +0100 Subject: [PATCH 09/11] fix: remove test testing for keyCurve Signed-off-by: Francisco Javier Ribo Labrador --- tests/apollo/Apollo.createPrivateKey.test.ts | 18 ------------------ tests/pollux/FromJWK.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/tests/apollo/Apollo.createPrivateKey.test.ts b/tests/apollo/Apollo.createPrivateKey.test.ts index 4e8f9dae4..9e5f9c680 100644 --- a/tests/apollo/Apollo.createPrivateKey.test.ts +++ b/tests/apollo/Apollo.createPrivateKey.test.ts @@ -20,18 +20,8 @@ describe("Apollo", () => { const seedHex = "947877896c61a5c64f266adbebbc69a2a01f1a2cfbf72c08a11c693d0429ccded34bdc0c28b5be910a5095b97e7bc6e3e209527ce8e75f9964d25cd6f6ad63e0"; describe("Secp256k1", () => { - it("KeyProperties.type - missing - throws", () => { - const sut = () => apollo.createPrivateKey({ - [KeyProperties.curve]: Curve.SECP256K1, - [KeyProperties.seed]: seedHex, - }); - - expect(sut).to.throw(ApolloError.InvalidKeyCurve); - }); - it("KeyProperties.curve - missing - throws", () => { const sut = () => apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.seed]: seedHex, }); @@ -40,7 +30,6 @@ describe("Apollo", () => { it("KeyProperties.seed - missing - throws", () => { const sut = () => apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, }); @@ -50,7 +39,6 @@ describe("Apollo", () => { it("KeyProperties.seed - invalid (non hex) - throws", () => { const sut = () => apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.seed]: "notAHexSeed", }); @@ -62,7 +50,6 @@ describe("Apollo", () => { it("KeyProperties.derivationPath - invalid - throws", () => { const sut = () => apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.seed]: seedHex, [KeyProperties.derivationPath]: "invalid" @@ -75,7 +62,6 @@ describe("Apollo", () => { Fixtures.Keys.Derivations.forEach(fixture => { test('key.derive is equal to apollo.createPrivateKey with derivationPath', function () { const master = apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.seed]: fixture.seed, }); @@ -89,7 +75,6 @@ describe("Apollo", () => { : null; const derived = apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.seed]: fixture.seed, [KeyProperties.derivationPath]: fixture.path @@ -103,7 +88,6 @@ describe("Apollo", () => { it("KeyProperties.derivationPath - `m/0'/0'/0'` - returns key", () => { const result = apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.seed]: seedHex, [KeyProperties.derivationPath]: `m/0'/0'/0'` @@ -121,7 +105,6 @@ describe("Apollo", () => { it("KeyProperties.derivationPath - `m/1'/0'/0'` - returns key", () => { const result = apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.seed]: seedHex, [KeyProperties.derivationPath]: `m/1'/0'/0'` @@ -141,7 +124,6 @@ describe("Apollo", () => { const derivationPath = DerivationPath.fromPath(`m/2'/0'/0'`, [DeprecatedDerivationPath, PrismDerivationPath]); const result = apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, [KeyProperties.curve]: Curve.SECP256K1, [KeyProperties.seed]: seedHex, [KeyProperties.derivationPath]: derivationPath.toString() diff --git a/tests/pollux/FromJWK.test.ts b/tests/pollux/FromJWK.test.ts index 55d2ff68e..2985d065e 100644 --- a/tests/pollux/FromJWK.test.ts +++ b/tests/pollux/FromJWK.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, beforeEach, vi, it } from 'vitest'; -import { Apollo, Castor, Domain, Secp256k1KeyPair, Secp256k1PrivateKey, Secp256k1PublicKey } from "../../src"; +import { describe, expect, beforeEach, it } from 'vitest'; +import { Apollo, Castor, Domain, Secp256k1PrivateKey, Secp256k1PublicKey } from "../../src"; import { Task } from '../../src/utils'; import { FromJWK } from '../../src/pollux/utils/jwt/FromJWK'; import { ApolloError, Curve, JWK, PolluxError, PrivateKey, PublicKey } from '../../src/domain'; From 32e5d56a0a87f75350b9b337eb91d0cd8a64e216 Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Wed, 5 Mar 2025 16:45:07 +0100 Subject: [PATCH 10/11] fix: tests fix Signed-off-by: Francisco Javier Ribo Labrador --- src/pollux/utils/jwt/FromJWK.ts | 2 +- tests/pollux/FromJWK.test.ts | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pollux/utils/jwt/FromJWK.ts b/src/pollux/utils/jwt/FromJWK.ts index a92439157..ac3585494 100644 --- a/src/pollux/utils/jwt/FromJWK.ts +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -143,7 +143,7 @@ export class FromJWK extends Task { if (isOKP) { return this.fromJWKOKP(ctx.Apollo, jwk); } - throw new PolluxError.InvalidJWKParameters("kty", "The kty field must be EC or OKP"); + throw new PolluxError.InvalidJWKParameters(["kty"], "The kty field must be EC or OKP"); } } \ No newline at end of file diff --git a/tests/pollux/FromJWK.test.ts b/tests/pollux/FromJWK.test.ts index 2985d065e..ec104257a 100644 --- a/tests/pollux/FromJWK.test.ts +++ b/tests/pollux/FromJWK.test.ts @@ -29,7 +29,7 @@ describe("Pollux - JWT FromJWK", async () => { k: "1234567890" } }).run(ctx); - await expect(sut).rejects.toThrow(ApolloError.InvalidKeyType); + await expect(sut).rejects.toThrow("19: The kty field must be EC or OKP"); }); describe("EC Key type with secp256k1 curve", async () => { @@ -147,16 +147,6 @@ describe("Pollux - JWT FromJWK", async () => { describe("OKP Key type", async () => { - it("Should throw an error if the curve is not supported", async () => { - const sut = new FromJWK({ - jwk: { - ...fixTures[Curve.ED25519].public, - crv: "wrong curve", - x: undefined - } as any - }).run(ctx); - await expect(sut).rejects.toThrow("16: Invalid key curve: wrong curve. Valid options are: X25519, Ed25519, secp256k1") - }); const supportedCurves = [Curve.ED25519, Curve.X25519]; const fixTures: Record = { @@ -192,6 +182,18 @@ describe("Pollux - JWT FromJWK", async () => { } } + it("Should throw an error if the curve is not supported", async () => { + const sut = new FromJWK({ + jwk: { + ...fixTures[Curve.ED25519].public, + crv: "wrong curve", + x: undefined + } as any + }).run(ctx); + await expect(sut).rejects.toThrow("16: Invalid key curve: wrong curve. Valid options are: X25519, Ed25519, secp256k1") + }); + + supportedCurves.forEach(curve => { describe(`${curve} curve`, () => { it(`${curve} Should throw an error if x is missing`, async () => { From e4be8f9d7b6feffcae088ff2744d514a5b600982 Mon Sep 17 00:00:00 2001 From: Francisco Javier Ribo Labrador Date: Mon, 10 Mar 2025 13:34:28 +0100 Subject: [PATCH 11/11] fix: simplify FromJWK logic, our Apollo implementation only supports pk creation from curvePoints with Secp256K1, but pollux shouldn't care about it and send whatever it has Signed-off-by: Francisco Javier Ribo Labrador --- src/apollo/Apollo.ts | 38 +++++++++++----------------- src/pollux/utils/jwt/FromJWK.ts | 44 +++++++++++++++------------------ 2 files changed, 34 insertions(+), 48 deletions(-) diff --git a/src/apollo/Apollo.ts b/src/apollo/Apollo.ts index 7d679fb00..ae7fc6746 100644 --- a/src/apollo/Apollo.ts +++ b/src/apollo/Apollo.ts @@ -212,40 +212,30 @@ export default class Apollo implements ApolloInterface, KeyRestoration { const { curve } = getKeyCurveByNameAndIndex(keyCurve); const keyData = parameters[KeyProperties.rawKey]; + const xData = parameters[KeyProperties.curvePointX]; + const yData = parameters[KeyProperties.curvePointY]; - if (curve === Curve.ED25519) { - if (keyData) { + if (keyData) { + if (curve === Curve.ED25519) { return new Ed25519PublicKey(keyData); } - - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); - } - - if (curve === Curve.SECP256K1) { - if (keyData) { + if (curve === Curve.SECP256K1) { return new Secp256k1PublicKey(keyData); - } else { - const xData = parameters[KeyProperties.curvePointX]; - const yData = parameters[KeyProperties.curvePointY]; - - if (xData && yData) { - return Secp256k1PublicKey.secp256k1FromByteCoordinates(xData, yData); - } } - - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey, KeyProperties.curvePointX, KeyProperties.curvePointY); - } - - - if (curve === Curve.X25519) { - if (keyData) { + if (curve === Curve.X25519) { return new X25519PublicKey(keyData); } + throw new ApolloError.InvalidKeyCurve(); + } - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); + if (xData && yData) { + if (curve === Curve.SECP256K1) { + return Secp256k1PublicKey.secp256k1FromByteCoordinates(xData, yData); + } + throw new ApolloError.InvalidKeyCurve(curve, [Curve.SECP256K1]); } - throw new ApolloError.InvalidKeyCurve(); + throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey, KeyProperties.curvePointX, KeyProperties.curvePointY); } /** diff --git a/src/pollux/utils/jwt/FromJWK.ts b/src/pollux/utils/jwt/FromJWK.ts index ac3585494..c91e7e9d0 100644 --- a/src/pollux/utils/jwt/FromJWK.ts +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -62,37 +62,33 @@ export class FromJWK extends Task { if (withCoordinates) { - if (crv === Curve.SECP256K1) { - const decodedX = this.decodeJWKParameter('x', jwk); - const decodedY = this.decodeJWKParameter('y', jwk); + const decodedX = this.decodeJWKParameter('x', jwk); + const decodedY = this.decodeJWKParameter('y', jwk); - let pk: PublicKey; - let sk: PrivateKey; + let pk: PublicKey; + let sk: PrivateKey; - pk = apollo.createPublicKey({ + pk = apollo.createPublicKey({ + [KeyProperties.curve]: crv, + [KeyProperties.curvePointX]: decodedX, + [KeyProperties.curvePointY]: decodedY + }); + + if (jwk.d !== undefined) { + const decodedD = this.decodeJWKParameter('d', jwk); + sk = apollo.createPrivateKey({ [KeyProperties.curve]: crv, - [KeyProperties.curvePointX]: decodedX, - [KeyProperties.curvePointY]: decodedY + [KeyProperties.rawKey]: decodedD }); - if (jwk.d !== undefined) { - const decodedD = this.decodeJWKParameter('d', jwk); - sk = apollo.createPrivateKey({ - [KeyProperties.curve]: crv, - [KeyProperties.rawKey]: decodedD - }); - - const keypair: Domain.KeyPair = { - privateKey: sk, - publicKey: pk, - curve: crv - } - return keypair; + const keypair: Domain.KeyPair = { + privateKey: sk, + publicKey: pk, + curve: crv } - return pk; + return keypair; } - - throw new ApolloError.InvalidKeyCurve(crv, [Curve.SECP256K1]); + return pk; } if (jwk.d !== undefined) {