diff --git a/src/apollo/Apollo.ts b/src/apollo/Apollo.ts index 933afca64..ae7fc6746 100644 --- a/src/apollo/Apollo.ts +++ b/src/apollo/Apollo.ts @@ -6,7 +6,6 @@ import { getKeyCurveByNameAndIndex, ApolloError, Curve, - KeyTypes, KeyProperties, MnemonicWordList, PrivateKey, @@ -29,7 +28,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; @@ -44,7 +42,6 @@ const BigIntegerWrapper = ApolloSDK.derivation.BigIntegerWrapper; * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.EC, * curve: Curve.ED25519, * }); * ``` @@ -115,7 +112,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. * @@ -197,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)), * }); @@ -209,56 +204,38 @@ 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(); } const { curve } = getKeyCurveByNameAndIndex(keyCurve); const keyData = parameters[KeyProperties.rawKey]; + const xData = parameters[KeyProperties.curvePointX]; + const yData = parameters[KeyProperties.curvePointY]; - if (keyType === KeyTypes.EC) { + if (keyData) { if (curve === Curve.ED25519) { - if (keyData) { - return new Ed25519PublicKey(keyData); - } - - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey); + 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]; - - if (xData && yData) { - return Secp256k1PublicKey.secp256k1FromByteCoordinates(xData, yData); - } - } - - throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey, KeyProperties.curvePointX, KeyProperties.curvePointY); + return new Secp256k1PublicKey(keyData); } - } - - if (keyType === KeyTypes.Curve25519) { if (curve === Curve.X25519) { - if (keyData) { - return new X25519PublicKey(keyData); - } + 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.MissingKeyParameters(KeyProperties.rawKey); + throw new ApolloError.MissingKeyParameters(KeyProperties.rawKey, KeyProperties.curvePointX, KeyProperties.curvePointY); } /** @@ -270,7 +247,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"), * }); @@ -282,7 +258,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'" @@ -297,7 +272,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.EC, * curve: Curve.ED25519, * }); * ``` @@ -310,7 +284,6 @@ export default class Apollo implements ApolloInterface, KeyRestoration { * * ```ts * const privateKey = apollo.createPrivateKey({ - * type: KeyTypes.Curve25519, * curve: Curve.X25519, * }); * ``` @@ -321,13 +294,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(); } @@ -335,69 +303,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); @@ -409,37 +328,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 seedHex = parameters[KeyProperties.seed]; + if (notEmptyString(seedHex)) { + const derivationIndex = parameters[KeyProperties.index] ?? "0"; + const derivationParam: string = parameters[KeyProperties.derivationPath] ?? PrismDerivationPath.init(derivationIndex).toString(); - const keyPair = X25519KeyPair.generateKeyPair(); - return keyPair.privateKey; + 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..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 { @@ -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/domain/models/errors/Apollo.ts b/src/domain/models/errors/Apollo.ts index 71c4b4f5e..f3da55f5f 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); } 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/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 new file mode 100644 index 000000000..c91e7e9d0 --- /dev/null +++ b/src/pollux/utils/jwt/FromJWK.ts @@ -0,0 +1,145 @@ +import { Domain } from "../../.."; +import { expect, Task } from "../../../utils"; + +import { base64url } from "multiformats/bases/base64"; +import { ApolloError, Curve, JWK, KeyPair, KeyProperties, PolluxError, PrivateKey, PublicKey } from "../../../domain"; + +export interface Args { + jwk: Domain.JWK; +} + +export class FromJWK extends Task { + + private isECJWK(jwk: JWK): jwk is JWK.EC { + if (jwk.kty !== "EC") { + return false; + } + return true; + } + + private isOKPJWK(jwk: JWK): jwk is JWK.OKP { + if (jwk.kty !== "OKP") { + return false; + } + return true; + } + + + 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`); + } + } + + + private isSupportedCurve(crv: string): asserts crv is Curve { + 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, new PolluxError.InvalidJWKParameters(['crv'], 'Missing JWK Parameter')); + this.isSupportedCurve(crv); + + const withCoordinates = + jwk.x !== undefined || + jwk.y !== undefined; + + if (withCoordinates) { + + 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.curvePointX]: decodedX, + [KeyProperties.curvePointY]: decodedY + }); + + 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; + } + return pk; + } + + if (jwk.d !== undefined) { + const decodedD = this.decodeJWKParameter('d', jwk); + return apollo.createPrivateKey({ + [KeyProperties.curve]: crv, + [KeyProperties.rawKey]: decodedD + }) + } + + 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, new PolluxError.InvalidJWKParameters(['crv'])); + this.isSupportedCurve(crv); + + expect(jwk.x, new PolluxError.InvalidJWKParameters(['x'], 'Missing JWK Parameter x')); + + const pk = apollo.createPublicKey({ + [KeyProperties.curve]: crv, + [KeyProperties.rawKey]: this.decodeJWKParameter('x', jwk) + }); + + if (jwk.d !== undefined) { + const sk = apollo.createPrivateKey({ + [KeyProperties.curve]: crv, + [KeyProperties.rawKey]: this.decodeJWKParameter('d', jwk) + }); + 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 isEC = this.isECJWK(jwk); + const isOKP = this.isOKPJWK(jwk); + if (isEC) { + return this.fromJWKEC(ctx.Apollo, jwk); + } + if (isOKP) { + return this.fromJWKOKP(ctx.Apollo, jwk); + } + 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 bbf33542c..3ab3c2058 100644 --- a/src/pollux/utils/jwt/PKInstance.ts +++ b/src/pollux/utils/jwt/PKInstance.ts @@ -1,56 +1,46 @@ 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 { isObject, notEmptyString, Task } from "../../../utils"; // TODO importing from Castor import { VerificationKeyType } from "../../../castor/types"; - +import { FromJWK } from "./FromJWK"; export interface Args { verificationMethod: DIDResolver.VerificationMethod; } + export class PKInstance extends Task { + async run(ctx: Task.Context) { const verificationMethod = this.args.verificationMethod; let pk: Domain.PublicKey | undefined = undefined; - if (verificationMethod.publicKeyMultibase) { + 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 (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) - }); + if (isObject(verificationMethod.publicKeyJwk)) { + 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/apollo/Apollo.createPrivateKey.test.ts b/tests/apollo/Apollo.createPrivateKey.test.ts index 74e74dcfb..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.InvalidKeyType); - }); - 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 new file mode 100644 index 000000000..ec104257a --- /dev/null +++ b/tests/pollux/FromJWK.test.ts @@ -0,0 +1,253 @@ +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'; + +describe("Pollux - JWT FromJWK", async () => { + let ctx: Task.Context<{ + Apollo: Domain.Apollo; + }>; + let apollo: Domain.Apollo; + let castor: Domain.Castor; + + beforeEach(() => { + apollo = new Apollo(); + castor = new Castor(apollo); + 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("19: The kty field must be EC or OKP"); + }); + + 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("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 () => { + const sut = new FromJWK({ + jwk: { + kty: "EC", + crv: Curve.SECP256K1, + x: "poDxfZtoOpBDtFqJmJ03_tei3ooCXrGXkJM_WUErZPM", + } + }).run(ctx); + await expect(sut).rejects.toThrow("19: Missing 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 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: Invalid JWK Parameter, not base64url encoded: 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(PolluxError.InvalidJWKParameters); + }); + + 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 () => { + + 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" + } + } + } + + 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 () => { + 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) + }) + }); + }); + }); + }); + +})