diff --git a/src/apollo/utils/Ed25519PrivateKey.ts b/src/apollo/utils/Ed25519PrivateKey.ts index dad6a4c8d..6269c442f 100644 --- a/src/apollo/utils/Ed25519PrivateKey.ts +++ b/src/apollo/utils/Ed25519PrivateKey.ts @@ -1,18 +1,28 @@ -import ApolloBaseAsymmetricEncryption from "@atala/apollo"; -import { Curve, KeyTypes, PrivateKey } from "../../domain"; -import { KeyProperties } from "../../domain/models/KeyProperties"; -import { SignableKey } from "../../domain/models/keyManagement/SignableKey"; +import ApolloPkg from "@atala/apollo"; import { Ed25519PublicKey } from "./Ed25519PublicKey"; +import { + Curve, + ExportableKey, + ImportableKey, + KeyProperties, + KeyTypes, + PrivateKey, + SignableKey +} from "../../domain"; + /** * @ignore */ -export class Ed25519PrivateKey extends PrivateKey implements SignableKey { +export class Ed25519PrivateKey extends PrivateKey implements ExportableKey, SignableKey { public keySpecification: Map = new Map(); public raw: Buffer; public size: number; public type: KeyTypes = KeyTypes.EC; + public readonly to = ExportableKey.factory(this, { pemLabel: "PRIVATE KEY" }); + static from = ImportableKey.factory(Ed25519PrivateKey, { pemLabel: "PRIVATE KEY" }); + constructor(bytes: Int8Array | Uint8Array) { super(); @@ -36,27 +46,14 @@ export class Ed25519PrivateKey extends PrivateKey implements SignableKey { private getInstance( value?: Int8Array | Uint8Array - ): ApolloBaseAsymmetricEncryption.io.iohk.atala.prism.apollo.utils.KMMEdPrivateKey { + ): ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMEdPrivateKey { // eslint-disable-next-line no-extra-boolean-cast const bytes = !!value ? Buffer.from(value) : this.raw; const instance = - new ApolloBaseAsymmetricEncryption.io.iohk.atala.prism.apollo.utils.KMMEdPrivateKey( + new ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMEdPrivateKey( Int8Array.from(bytes) ); return instance; } - - public readonly to = { - Buffer: () => Buffer.from(this.raw), - Hex: () => this.to.Buffer().toString("hex"), - }; - - static from = { - Buffer: (value: Buffer) => new Ed25519PrivateKey(Int8Array.from(value)), - Hex: (value: string) => - Ed25519PrivateKey.from.Buffer(Buffer.from(value, "hex")), - String: (value: string) => - Ed25519PrivateKey.from.Buffer(Buffer.from(value)), - }; } diff --git a/src/apollo/utils/Ed25519PublicKey.ts b/src/apollo/utils/Ed25519PublicKey.ts index be2f2b7ca..1b3385b5d 100644 --- a/src/apollo/utils/Ed25519PublicKey.ts +++ b/src/apollo/utils/Ed25519PublicKey.ts @@ -1,17 +1,26 @@ -import ApolloBaseAsymmetricEncryption from "@atala/apollo"; -import { Curve, KeyTypes, PublicKey } from "../../domain"; -import { KeyProperties } from "../../domain/models/KeyProperties"; -import { VerifiableKey } from "../../domain/models/keyManagement/VerifiableKey"; +import ApolloPkg from "@atala/apollo"; +import { + Curve, + ExportableKey, + ImportableKey, + KeyProperties, + KeyTypes, + PublicKey, + VerifiableKey +} from "../../domain"; /** * @ignore */ -export class Ed25519PublicKey extends PublicKey implements VerifiableKey { +export class Ed25519PublicKey extends PublicKey implements ExportableKey, VerifiableKey { public keySpecification: Map = new Map(); public raw: Buffer; public size: number; public type: KeyTypes = KeyTypes.EC; + public readonly to = ExportableKey.factory(this, { pemLabel: "PUBLIC KEY" }); + static from = ImportableKey.factory(Ed25519PublicKey, { pemLabel: "PUBLIC KEY" }); + constructor(bytes: Int8Array | Uint8Array) { super(); @@ -33,11 +42,11 @@ export class Ed25519PublicKey extends PublicKey implements VerifiableKey { private getInstance( value?: Int8Array | Uint8Array - ): ApolloBaseAsymmetricEncryption.io.iohk.atala.prism.apollo.utils.KMMEdPublicKey { + ): ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMEdPublicKey { // eslint-disable-next-line no-extra-boolean-cast const bytes = !!value ? Buffer.from(value) : this.raw; const instance = - new ApolloBaseAsymmetricEncryption.io.iohk.atala.prism.apollo.utils.KMMEdPublicKey( + new ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMEdPublicKey( Int8Array.from(bytes) ); diff --git a/src/apollo/utils/Secp256k1PrivateKey.ts b/src/apollo/utils/Secp256k1PrivateKey.ts index 90ff6ad21..ff452fe85 100644 --- a/src/apollo/utils/Secp256k1PrivateKey.ts +++ b/src/apollo/utils/Secp256k1PrivateKey.ts @@ -1,41 +1,35 @@ +import ApolloPkg from "@atala/apollo"; import BN from "bn.js"; + import * as ECConfig from "../../config/ECConfig"; import { Secp256k1PublicKey } from "./Secp256k1PublicKey"; import { ApolloError } from "../../domain/models/Errors"; import { SignableKey } from "../../domain/models/keyManagement/SignableKey"; import { KeyProperties } from "../../domain/models/KeyProperties"; -import { Curve, DerivableKey, KeyTypes, PrivateKey } from "../../domain"; - -import * as ApolloPKG from "@atala/apollo"; +import { Curve, DerivableKey, ExportableKey, ImportableKey, KeyTypes, PrivateKey } from "../../domain"; import { DerivationPath } from "./derivation/DerivationPath"; -const ApolloSDK = ApolloPKG.io.iohk.atala.prism.apollo; - -const HDKey = ApolloSDK.derivation.HDKey; -const BigIntegerWrapper = ApolloSDK.derivation.BigIntegerWrapper; -const { - io: { - iohk: { - atala: { - prism: { apollo }, - }, - }, - }, -} = ApolloPKG; +const Apollo = ApolloPkg.io.iohk.atala.prism.apollo; +const HDKey = Apollo.derivation.HDKey; +const BigIntegerWrapper = Apollo.derivation.BigIntegerWrapper; /** * @ignore */ export class Secp256k1PrivateKey extends PrivateKey - implements SignableKey, DerivableKey { - public type: KeyTypes = KeyTypes.EC; + implements DerivableKey, ExportableKey, SignableKey +{ public keySpecification: Map = new Map(); public raw: Uint8Array; public size: number; + public type: KeyTypes = KeyTypes.EC; + + public readonly to = ExportableKey.factory(this, { pemLabel: "EC PRIVATE KEY" }); + static from = ImportableKey.factory(Secp256k1PrivateKey, { pemLabel: "EC PRIVATE KEY" }); private get native() { - return apollo.utils.KMMECSecp256k1PrivateKey.Companion.secp256k1FromByteArray( + return Apollo.utils.KMMECSecp256k1PrivateKey.Companion.secp256k1FromByteArray( Int8Array.from(this.raw) ); } @@ -112,18 +106,4 @@ export class Secp256k1PrivateKey const bnprv = new BN(encoded); return new Secp256k1PrivateKey(Uint8Array.from(bnprv.toArray())); } - - public readonly to = { - Buffer: () => Buffer.from(this.getEncoded()), - Hex: () => this.to.Buffer().toString("hex"), - }; - - static from = { - Buffer: (value: Buffer) => - Secp256k1PrivateKey.secp256k1FromBytes(new Uint8Array(value)), - Hex: (value: string) => - Secp256k1PrivateKey.from.Buffer(Buffer.from(value, "hex")), - String: (value: string) => - Secp256k1PrivateKey.from.Buffer(Buffer.from(value)), - }; } diff --git a/src/apollo/utils/Secp256k1PublicKey.ts b/src/apollo/utils/Secp256k1PublicKey.ts index f1b3ffa49..3afbb732a 100644 --- a/src/apollo/utils/Secp256k1PublicKey.ts +++ b/src/apollo/utils/Secp256k1PublicKey.ts @@ -1,32 +1,32 @@ +import ApolloPkg from "@atala/apollo"; import BN from "bn.js"; import BigInteger from "bn.js"; -import * as ApolloPKG from "@atala/apollo"; import * as ECConfig from "../../config/ECConfig"; import { ECPoint } from "./ec/ECPoint"; -import { VerifiableKey } from "../../domain/models/keyManagement/VerifiableKey"; -import { KeyProperties } from "../../domain/models/KeyProperties"; import { ApolloError } from "../../domain/models/Errors"; -import { Curve, KeyTypes, PublicKey } from "../../domain"; - -const { - io: { - iohk: { - atala: { - prism: { apollo }, - }, - }, - }, -} = ApolloPKG; +import { + Curve, + ExportableKey, + ImportableKey, + KeyProperties, + KeyTypes, + PublicKey, + VerifiableKey +} from "../../domain"; /** * @ignore */ -export class Secp256k1PublicKey extends PublicKey implements VerifiableKey { - public type: KeyTypes = KeyTypes.EC; +export class Secp256k1PublicKey extends PublicKey implements ExportableKey, VerifiableKey { public keySpecification: Map = new Map(); - public size; + public size: number; public raw: Uint8Array; + public type: KeyTypes = KeyTypes.EC; + + public readonly to = ExportableKey.factory(this, { pemLabel: "EC PUBLIC KEY" }); + static from = ImportableKey.factory(Secp256k1PublicKey, { pemLabel: "EC PUBLIC KEY" }); + public get isCompressed() { return ( this.keySpecification.has("compressed") && @@ -35,10 +35,11 @@ export class Secp256k1PublicKey extends PublicKey implements VerifiableKey { } private get native() { - return apollo.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromBytes( + return ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromBytes( Int8Array.from(this.raw) ); } + constructor(nativeValue: Uint8Array) { super(); @@ -142,7 +143,7 @@ export class Secp256k1PublicKey extends PublicKey implements VerifiableKey { static secp256k1FromBytes(encoded: Uint8Array): Secp256k1PublicKey { return new Secp256k1PublicKey( Uint8Array.from( - ApolloPKG.io.iohk.atala.prism.apollo.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromBytes( + ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromBytes( Int8Array.from(encoded) ).raw ) @@ -177,7 +178,7 @@ export class Secp256k1PublicKey extends PublicKey implements VerifiableKey { const xCoord = Buffer.from(x.toArray()); const yCoord = Buffer.from(y.toArray()); const publicKey = - apollo.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromByteCoordinates( + ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMECSecp256k1PublicKey.Companion.secp256k1FromByteCoordinates( Int8Array.from(xCoord), Int8Array.from(yCoord) ); diff --git a/src/apollo/utils/X25519PrivateKey.ts b/src/apollo/utils/X25519PrivateKey.ts index c7f8d6558..46bf10073 100644 --- a/src/apollo/utils/X25519PrivateKey.ts +++ b/src/apollo/utils/X25519PrivateKey.ts @@ -1,17 +1,26 @@ -import ApolloBaseAsymmetricEncryption from "@atala/apollo"; -import { Curve, KeyTypes, PrivateKey } from "../../domain"; -import { KeyProperties } from "../../domain/models/KeyProperties"; +import ApolloPkg from "@atala/apollo"; import { X25519PublicKey } from "./X25519PublicKey"; +import { + Curve, + ExportableKey, + ImportableKey, + KeyProperties, + KeyTypes, + PrivateKey +} from "../../domain"; /** * @ignore */ -export class X25519PrivateKey extends PrivateKey { +export class X25519PrivateKey extends PrivateKey implements ExportableKey { public keySpecification: Map = new Map(); public raw: Buffer; public size: number; public type: KeyTypes = KeyTypes.EC; + public readonly to = ExportableKey.factory(this, { pemLabel: "PRIVATE KEY" }); + static from = ImportableKey.factory(X25519PrivateKey, { pemLabel: "PRIVATE KEY" }); + constructor(bytes: Int8Array | Uint8Array) { super(); @@ -32,22 +41,10 @@ export class X25519PrivateKey extends PrivateKey { // eslint-disable-next-line no-extra-boolean-cast const bytes = !!value ? Buffer.from(value) : this.raw; const instance = - new ApolloBaseAsymmetricEncryption.io.iohk.atala.prism.apollo.utils.KMMX25519PrivateKey( + new ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMX25519PrivateKey( Int8Array.from(bytes) ); return instance; } - - public readonly to = { - Buffer: () => Buffer.from(this.raw), - Hex: () => this.to.Buffer().toString("hex"), - }; - - static from = { - Buffer: (value: Buffer) => new X25519PrivateKey(new Uint8Array(value)), - Hex: (value: string) => - X25519PrivateKey.from.Buffer(Buffer.from(value, "hex")), - String: (value: string) => X25519PrivateKey.from.Buffer(Buffer.from(value)), - }; } diff --git a/src/apollo/utils/X25519PublicKey.ts b/src/apollo/utils/X25519PublicKey.ts index 637c9b7df..bb925b6ee 100644 --- a/src/apollo/utils/X25519PublicKey.ts +++ b/src/apollo/utils/X25519PublicKey.ts @@ -1,16 +1,25 @@ -import ApolloBaseAsymmetricEncryption from "@atala/apollo"; -import { Curve, KeyTypes, PublicKey } from "../../domain"; -import { KeyProperties } from "../../domain/models/KeyProperties"; +import ApolloPkg from "@atala/apollo"; +import { + Curve, + ExportableKey, + ImportableKey, + KeyProperties, + KeyTypes, + PublicKey +} from "../../domain"; /** * @ignore */ -export class X25519PublicKey extends PublicKey { +export class X25519PublicKey extends PublicKey implements ExportableKey { public keySpecification: Map = new Map(); public raw: Buffer; public size: number; public type: KeyTypes = KeyTypes.EC; + public readonly to = ExportableKey.factory(this, { pemLabel: "PUBLIC KEY" }); + static from = ImportableKey.factory(X25519PublicKey, { pemLabel: "PUBLIC KEY" }); + constructor(bytes: Int8Array | Uint8Array) { super(); @@ -27,7 +36,7 @@ export class X25519PublicKey extends PublicKey { // eslint-disable-next-line no-extra-boolean-cast const bytes = !!value ? Buffer.from(value) : this.raw; const instance = - new ApolloBaseAsymmetricEncryption.io.iohk.atala.prism.apollo.utils.KMMX25519PublicKey( + new ApolloPkg.io.iohk.atala.prism.apollo.utils.KMMX25519PublicKey( Int8Array.from(bytes) ); diff --git a/src/domain/models/keyManagement/Key.ts b/src/domain/models/keyManagement/Key.ts index 0b490030b..a15e41f6b 100644 --- a/src/domain/models/keyManagement/Key.ts +++ b/src/domain/models/keyManagement/Key.ts @@ -7,6 +7,7 @@ import { VerifiableKey } from "./VerifiableKey"; import { KeyCurve } from "../KeyCurve"; import { Curve } from "./Curve"; import { KeyTypes } from "./KeyTypes"; +import { ExportableKey } from "./exportable"; export function getKeyCurveByNameAndIndex( name: string, @@ -29,9 +30,15 @@ export abstract class Key { abstract keySpecification: Map; abstract size: number; abstract raw: Uint8Array; + abstract to: ExportableKey.Common["to"]; abstract getEncoded(): Uint8Array; + get curve() { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.getProperty(KeyProperties.curve)!; + } + isExportable(): this is StorableKey { return "export" in this; } diff --git a/src/domain/models/keyManagement/PrivateKey.ts b/src/domain/models/keyManagement/PrivateKey.ts index ed36f5111..453a61aaf 100644 --- a/src/domain/models/keyManagement/PrivateKey.ts +++ b/src/domain/models/keyManagement/PrivateKey.ts @@ -3,10 +3,6 @@ import { KeyProperties } from "../KeyProperties"; import { PublicKey } from "./PublicKey"; export abstract class PrivateKey extends Key { - get curve() { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.getProperty(KeyProperties.curve)!; - } get index() { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.getProperty(KeyProperties.index)!; @@ -15,9 +11,4 @@ export abstract class PrivateKey extends Key { return this.raw; } abstract publicKey(): PublicKey; - - abstract to: { - Buffer: () => Buffer; - Hex: () => string; - }; } diff --git a/src/domain/models/keyManagement/PublicKey.ts b/src/domain/models/keyManagement/PublicKey.ts index 556216371..61ba7c779 100644 --- a/src/domain/models/keyManagement/PublicKey.ts +++ b/src/domain/models/keyManagement/PublicKey.ts @@ -1,11 +1,6 @@ import { Key } from "./Key"; -import { KeyProperties } from "../KeyProperties"; export abstract class PublicKey extends Key { - get curve() { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.getProperty(KeyProperties.curve)!; - } get value() { return this.raw; } diff --git a/src/domain/models/keyManagement/exportable/ExportableKey.ts b/src/domain/models/keyManagement/exportable/ExportableKey.ts new file mode 100644 index 000000000..7bce87484 --- /dev/null +++ b/src/domain/models/keyManagement/exportable/ExportableKey.ts @@ -0,0 +1,83 @@ +import { Key } from "../Key"; +import { JWK as _JWK } from "./JWK"; +import { PEM as _PEM } from "./PEM"; +import { PrivateKey } from "../PrivateKey"; +import { PublicKey } from "../PublicKey"; + +/** + * ExportableKey defines the formats a crypographic key can be converted to + * Default is all + */ +export type ExportableKey = ExportableKey.All; + +/** + * Factory options to customise export / import functions + */ +interface Options { + pemLabel: string; +} + +/** + * Declaration merge to contain key conversion functions + */ +export namespace ExportableKey { + export type All = Common & JWK & PEM; + + export interface Common { + to: { + Buffer: () => Buffer; + String: (encoding?: BufferEncoding) => string; + }; + } + + export interface JWK { + to: { + JWK: (base?: _JWK.Base) => _JWK; + }; + } + + export interface PEM { + to: { + PEM: () => string; + }; + } + + /** + * factory to create Key property with desired functions + * which allow converting the Key raw into different formats + * + * @param key + * @param opts + * @returns object with exportable functions + */ + export const factory = (key: PublicKey | PrivateKey, opts: Options) => ({ + Buffer: () => toBuffer(key), + // deprecate Hex, use String instead + Hex: () => toString(key, "hex"), + JWK: (base?: _JWK.Base) => _JWK.fromKey(key, base), + PEM: () => _PEM.fromKey(key, opts.pemLabel), + String: (encoding?: BufferEncoding) => toString(key, encoding) + }); + + const toBuffer = (key: Key): Buffer => Buffer.from(key.raw); + + const toString = (key: Key, encoding?: BufferEncoding) => toBuffer(key).toString(encoding); +} + +export namespace ImportableKey { + /** + * factory to create Key property with desired functions + * allows creation of a given Key through different data types + * + * @param ctor - the Key Class + * @param opts + * @returns object with importable functions + */ + export const factory = (ctor: { new(bytes: any): T; }, opts: Options) => ({ + Buffer: (value: Buffer) => new ctor(value), + // deprecate Hex, use String instead + Hex: (value: string) => new ctor(Buffer.from(value, "hex")), + PEM: (value: string) => new ctor(_PEM.toRaw(value, opts.pemLabel)), + String: (value: string, encoding?: BufferEncoding) => new ctor(Buffer.from(value, encoding)), + }); +} diff --git a/src/domain/models/keyManagement/exportable/JWK.ts b/src/domain/models/keyManagement/exportable/JWK.ts new file mode 100644 index 000000000..82ad36d65 --- /dev/null +++ b/src/domain/models/keyManagement/exportable/JWK.ts @@ -0,0 +1,139 @@ +import { PrivateKey } from "../PrivateKey"; +import { PublicKey } from "../PublicKey"; + +/** + * JWK + */ +export type JWK = JWK.EC | JWK.OCT | JWK.OKP | JWK.RSA; + +/** + * JWK definitions + * based on https://www.iana.org/assignments/jose/jose.xhtml + */ +export namespace JWK { + export type key_ops = + | "sign" + | "verify" + | "encrypt" + | "decrypt" + | "wrapKey" + | "unwrapKey" + | "deriveKey" + | "deriveBits"; + + // Properties common across all JWK variances + export interface Base { + // Algorithm + alg?: string; + // Extractable + ext?: string; + // Key operations + key_ops?: key_ops[]; + // Key ID + kid?: string; + // Key Type + kty?: string; + // Public key use + use?: "sig" | "enc"; + // X.509 Certificate chain + x5c?: string; + // X.509 Certificate SHA-1 Thumbprint + x5t?: string; + // X.509 Certificate SHA-256 Thumbprint + 'x5t#S256'?: string; + // X.509 URL + x5u?: string; + } + + // Elliptic Curve (DSS) key type + export interface EC extends Base { + kty: "EC"; + // curve + crv: string; + // ECC private key + d: string; + // X coord + x: string; + // Y coord + y: string; + } + + // Octet sequence key type + export interface OCT extends Base { + kty: "oct"; + // key value + k: string; + } + + // Octet key pair + export interface OKP extends Base { + kty: "OKP"; + // subtype of key pair + crv: string; + // private key (base64url) + d?: string; + // public key (base64url) + x: string; + } + + // RSA + export interface RSA extends Base { + kty: "RSA"; + d: string; + dp: string; + dq: string; + e: string; + n: string; + oth: string; + p: string; + q: string; + qi: string; + } + + + /** + * create a JWK from a given Key + * + * @param {PublicKey | PrivateKey} key + * @param base - set of JWK properties to be added + * @returns {JWK} + */ + export const fromKey = (key: PublicKey | PrivateKey, base: Base = {}): JWK => { + const prototype = Object.getPrototypeOf(key); + + if (prototype instanceof PublicKey) { + return Object.assign(base, publicKeyToJWK(key as PublicKey)); + } + + if (prototype instanceof PrivateKey) { + return Object.assign(base, privateKeyToJWK(key as PrivateKey)); + } + + throw new Error("invalid Key given"); + }; + + /** + * create a JWK OKP from a PrivateKey + * + * @param key + * @returns + */ + const privateKeyToJWK = (key: PrivateKey): JWK => ({ + kty: "OKP", + crv: key.curve, + d: key.to.String("base64url"), + x: key.publicKey().to.String("base64url") + }); + + /** + * create a JWK OKP from a PublicKey + * + * @param key + * @returns + */ + const publicKeyToJWK = (key: PublicKey): JWK => ({ + kty: "OKP", + crv: key.curve, + x: key.to.String("base64url") + }); +} diff --git a/src/domain/models/keyManagement/exportable/PEM.ts b/src/domain/models/keyManagement/exportable/PEM.ts new file mode 100644 index 000000000..e9426510c --- /dev/null +++ b/src/domain/models/keyManagement/exportable/PEM.ts @@ -0,0 +1,47 @@ +import { Key } from "../Key"; + +export namespace PEM { + const syntax = { + dashes: "-----", + header: "BEGIN", + footer: "END", + }; + + /** + * create a Uint8Array from a PEM + * + * @param pem + * @param {string} label - expected header / footer label + * @returns + */ + export const toRaw = (pem: string, label: string): Buffer => { + const lines = pem.split("\n"); + const firstLine = lines.at(0); + const lastLine = lines.at(-1); + + const beginMarker = `${syntax.dashes}${syntax.header} ${label}${syntax.dashes}`; + const endMarker = `${syntax.dashes}${syntax.footer} ${label}${syntax.dashes}`; + + if (firstLine === beginMarker && lastLine === endMarker) { + const base64 = lines.slice(1, -1).join(); + + return Buffer.from(base64, "base64"); + } + + throw new Error("invalid pem"); + }; + + /** + * create a PEM from a Key + * + * @param {Key} key - Cryptographic key + * @param {string} label - PEM header / footer label + * @returns {string} + */ + export const fromKey = (key: Key, label: string): string => { + let base64Data = Buffer.from(key.raw).toString("base64"); + + return `-----BEGIN ${label}-----\n${base64Data}\n-----END ${label}-----`; + }; + +} diff --git a/src/domain/models/keyManagement/exportable/index.ts b/src/domain/models/keyManagement/exportable/index.ts new file mode 100644 index 000000000..bea94a4b4 --- /dev/null +++ b/src/domain/models/keyManagement/exportable/index.ts @@ -0,0 +1,3 @@ +export * from "./ExportableKey"; +export * from "./JWK"; +export * from "./PEM"; diff --git a/src/domain/models/keyManagement/index.ts b/src/domain/models/keyManagement/index.ts index 235286a27..5f63b8d66 100644 --- a/src/domain/models/keyManagement/index.ts +++ b/src/domain/models/keyManagement/index.ts @@ -1,4 +1,5 @@ export * from "./DerivableKey"; +export * from "./exportable"; export * from "./Key"; export * from "./PrivateKey"; export * from "./PublicKey"; diff --git a/tests/apollo/keys/Exportable.test.ts b/tests/apollo/keys/Exportable.test.ts new file mode 100644 index 000000000..cd0963018 --- /dev/null +++ b/tests/apollo/keys/Exportable.test.ts @@ -0,0 +1,234 @@ +import { expect } from "chai"; +import { Ed25519PrivateKey } from "../../../src/apollo/utils/Ed25519PrivateKey"; +import { Ed25519PublicKey } from "../../../src/apollo/utils/Ed25519PublicKey"; +import { X25519PrivateKey } from "../../../src/apollo/utils/X25519PrivateKey"; +import { X25519PublicKey } from "../../../src/apollo/utils/X25519PublicKey"; +import { Secp256k1PrivateKey } from "../../../src/apollo/utils/Secp256k1PrivateKey"; +import { Secp256k1PublicKey } from "../../../src/apollo/utils/Secp256k1PublicKey"; +import { JWK } from "../../../src/domain"; + +describe("Keys", () => { + describe("Ed25519PrivateKey", () => { + const raw = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + const pem = `-----BEGIN PRIVATE KEY-----\n${raw.toString("base64")}\n-----END PRIVATE KEY-----`; + const jwk: JWK.OKP = { + kty: 'OKP', + crv: 'Ed25519', + d: raw.toString("base64url"), + x: 'TLWr9q15-_WrvMr8wmnYXNJlHtS4hbWGnyQa7fCluik' + }; + + describe("Exportable", () => { + test("JWK", () => { + const key = new Ed25519PrivateKey(raw); + const result = key.to.JWK(); + + expect(result).to.eql(jwk); + }); + + test("PEM", () => { + const key = new Ed25519PrivateKey(raw); + const result = key.to.PEM(); + + expect(result).to.equal(pem); + }); + }); + + test("Importable - PEM", () => { + const result = Ed25519PrivateKey.from.PEM(pem); + + expect(result.raw).to.eql(raw); + }); + }); + + describe("Ed25519PublicKey", () => { + const raw = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + const pem = `-----BEGIN PUBLIC KEY-----\n${raw.toString("base64")}\n-----END PUBLIC KEY-----`; + const jwk: JWK.OKP = { + kty: 'OKP', + crv: 'Ed25519', + x: raw.toString("base64url") + }; + + describe("Exportable", () => { + test("JWK", () => { + const key = new Ed25519PublicKey(raw); + const result = key.to.JWK(); + + expect(result).to.eql(jwk); + }); + + test("PEM", () => { + const key = new Ed25519PublicKey(raw); + const result = key.to.PEM(); + + expect(result).to.equal(pem); + }); + }); + + test("Importable - PEM", () => { + const result = Ed25519PublicKey.from.PEM(pem); + + expect(result.raw).to.eql(raw); + }); + }); + + describe("X25519PrivateKey", () => { + const raw = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + const pem = `-----BEGIN PRIVATE KEY-----\n${raw.toString("base64")}\n-----END PRIVATE KEY-----`; + const jwk: JWK.OKP = { + kty: 'OKP', + crv: 'X25519', + d: raw.toString("base64url"), + x: '_TOE4TKtAqVsePRVR-5AA43HkAK5DSntkOCO7nYq5xU' + }; + + describe("Exportable", () => { + test("JWK", () => { + const key = new X25519PrivateKey(raw); + const result = key.to.JWK(); + + expect(result).to.eql(jwk); + }); + + test("PEM", () => { + const key = new X25519PrivateKey(raw); + const result = key.to.PEM(); + + expect(result).to.equal(pem); + }); + }); + + test("Importable - PEM", () => { + const result = X25519PrivateKey.from.PEM(pem); + + expect(result.raw).to.eql(raw); + }); + }); + + describe("X25519PublicKey", () => { + const raw = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + const pem = `-----BEGIN PUBLIC KEY-----\n${raw.toString("base64")}\n-----END PUBLIC KEY-----`; + const jwk: JWK.OKP = { + kty: 'OKP', + crv: 'X25519', + x: raw.toString("base64url"), + }; + + describe("Exportable", () => { + test("JWK", () => { + const key = new X25519PublicKey(raw); + const result = key.to.JWK(); + + expect(result).to.eql(jwk); + }); + + test("PEM", () => { + const key = new X25519PublicKey(raw); + const result = key.to.PEM(); + + expect(result).to.equal(pem); + }); + }); + + test("Importable - PEM", () => { + const result = X25519PublicKey.from.PEM(pem); + + expect(result.raw).to.eql(raw); + }); + }); + + describe("Secp256k1PrivateKey", () => { + const raw = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + const pem = `-----BEGIN EC PRIVATE KEY-----\n${raw.toString("base64")}\n-----END EC PRIVATE KEY-----`; + const jwk: JWK.OKP = { + kty: 'OKP', + crv: 'Secp256k1', + d: raw.toString("base64url"), + x: "BHm-Zn753LusVaBilc6HCwcCm_zbLc4o2VnygVsW-BeYSDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg", + }; + + describe("Exportable", () => { + test("JWK", () => { + const key = new Secp256k1PrivateKey(raw); + const result = key.to.JWK(); + + expect(result).to.eql(jwk); + }); + + test("JWK with kid", () => { + const key = new Secp256k1PrivateKey(raw); + const kid = "qweruty"; + const result = key.to.JWK({ kid }); + + expect(result).to.have.property("kid", kid); + expect(result).to.have.property("kty", jwk.kty); + expect(result).to.have.property("crv", jwk.crv); + expect(result).to.have.property("d", jwk.d); + expect(result).to.have.property("x", jwk.x); + }); + + test("PEM", () => { + const key = new Secp256k1PrivateKey(raw); + const result = key.to.PEM(); + + expect(result).to.equal(pem); + }); + }); + + test("Importable - PEM", () => { + const result = Secp256k1PrivateKey.from.PEM(pem); + + expect(result.raw).to.eql(raw); + }); + }); + + describe("Secp256k1PublicKey", () => { + const raw = Buffer.from([4, 49, 167, 173, 103, 15, 188, 85, 154, 102, 229, 108, 189, 122, 78, 227, 245, 99, 79, 55, 81, 220, 201, 4, 16, 89, 24, 121, 177, 48, 51, 1, 184, 41, 196, 54, 243, 176, 147, 60, 249, 136, 0, 13, 183, 1, 111, 60, 2, 85, 245, 209, 131, 187, 123, 221, 142, 111, 153, 145, 21, 106, 13, 19, 244]); + const pem = `-----BEGIN EC PUBLIC KEY-----\n${raw.toString("base64")}\n-----END EC PUBLIC KEY-----`; + const jwk: JWK.OKP = { + kty: 'OKP', + crv: 'Secp256k1', + x: raw.toString("base64url"), + }; + + describe("Exportable", () => { + test("JWK", () => { + const key = new Secp256k1PublicKey(raw); + const result = key.to.JWK(); + + expect(result).to.eql(jwk); + }); + + test("JWK with kid", () => { + const key = new Secp256k1PublicKey(raw); + const kid = "qweruty"; + const result = key.to.JWK({ kid }); + + expect(result).to.have.property("kid", kid); + expect(result).to.have.property("kty", jwk.kty); + expect(result).to.have.property("crv", jwk.crv); + expect(result).to.have.property("x", jwk.x); + }); + + test("PEM", () => { + const key = new Secp256k1PublicKey(raw); + const result = key.to.PEM(); + + expect(result).to.equal(pem); + }); + }); + + test("Importable - PEM", () => { + const result = Secp256k1PublicKey.from.PEM(pem); + + expect(result.raw).to.eql(raw); + }); + }); + + describe("JWK", () => { + test("non-Key given - throws", () => { + expect(() => JWK.fromKey({} as any)).to.throw; + }); + }); +});