Skip to content

Commit

Permalink
[feat] Normalize export to use JWK types
Browse files Browse the repository at this point in the history
  • Loading branch information
kshinn committed Mar 8, 2024
1 parent 76f3b95 commit 552581d
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 32 deletions.
21 changes: 16 additions & 5 deletions packages/default-plugins/src/ed25519/keypair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as ed25519 from "@stablelib/ed25519"
import * as crypto from "./crypto.js"

import { DidableKey, Encodings, ExportableKey } from "@ucans/core"
import { PrivateKeyJwk } from "../types.js"


export class EdKeypair implements DidableKey, ExportableKey {
Expand All @@ -28,7 +29,7 @@ export class EdKeypair implements DidableKey, ExportableKey {
}

static fromSecretKey(key: string, params?: {
format?: Encodings,
format?: Encodings
exportable?: boolean
}): EdKeypair {
const { format = "base64pad", exportable = false } = params || {}
Expand All @@ -45,17 +46,27 @@ export class EdKeypair implements DidableKey, ExportableKey {
return ed25519.sign(this.secretKey, msg)
}

async export(format: Encodings = "base64pad"): Promise<string> {
async export(): Promise<PrivateKeyJwk> {
if (!this.exportable) {
throw new Error("Key is not exportable")
}

return uint8arrays.toString(this.secretKey, format)
const jwk: PrivateKeyJwk = {
kty: "EC",
crv: "Ed25519",
d: uint8arrays.toString(this.secretKey, "base64pad"),
}
return jwk
}

static async import(secretKey: string, params?: { exportable: boolean }): Promise<EdKeypair> {
static async import(jwk: PrivateKeyJwk, params?: { exportable: boolean }): Promise<EdKeypair> {
const { exportable = false } = params || {}
return EdKeypair.fromSecretKey(secretKey, { exportable })

if (jwk.kty !== "EC" || jwk.crv !== "Ed25519") {
throw new Error("Cannot import key of type: ${jwk.kty} curve: ${jwk.crv} into ED25519 key")
}

return EdKeypair.fromSecretKey(jwk.d, { exportable })
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/default-plugins/src/p256/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ export const importKeypairJwk = async (

export const exportPrivateKeyJwk = async (
keyPair: AvailableCryptoKeyPair
): Promise<JsonWebKey> => {
return await webcrypto.subtle.exportKey("jwk", keyPair.privateKey)
): Promise<PrivateKeyJwk> => {
return await webcrypto.subtle.exportKey("jwk", keyPair.privateKey) as PrivateKeyJwk
}

export const exportKey = async (key: CryptoKey): Promise<Uint8Array> => {
Expand Down
8 changes: 3 additions & 5 deletions packages/default-plugins/src/p256/keypair.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { webcrypto } from "one-webcrypto"
import * as uint8arrays from "uint8arrays"
import { DidableKey, Encodings, ExportableKey } from "@ucans/core"
import { DidableKey, ExportableKey } from "@ucans/core"

import * as crypto from "./crypto.js"
import {
Expand Down Expand Up @@ -66,11 +64,11 @@ export class EcdsaKeypair implements DidableKey, ExportableKey {
return await crypto.sign(msg, this.keypair.privateKey)
}

async export(format: Encodings = "base64pad"): Promise<string> {
async export(): Promise<PrivateKeyJwk> {
if (!this.exportable) {
throw new Error("Key is not exportable")
}
return JSON.stringify(await crypto.exportPrivateKeyJwk(this.keypair))
return await crypto.exportPrivateKeyJwk(this.keypair)
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/default-plugins/src/rsa/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const importKey = async (key: Uint8Array): Promise<CryptoKey> => {
}

export const importKeypairJwk = async (
privKeyJwk: JsonWebKey,
privKeyJwk: PrivateKeyJwk,
exportable = false
): Promise<AvailableCryptoKeyPair> => {
const privateKey = await webcrypto.subtle.importKey(
Expand Down
13 changes: 4 additions & 9 deletions packages/default-plugins/src/rsa/keypair.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { webcrypto } from "one-webcrypto"
import * as uint8arrays from "uint8arrays"

import * as crypto from "./crypto.js"
import { AvailableCryptoKeyPair, PrivateKeyJwk, isAvailableCryptoKeyPair } from "../types.js"
import { DidableKey, Encodings, ExportableKey } from "@ucans/core"
import { PrivateKeyInput } from "crypto"
import { DidableKey, ExportableKey } from "@ucans/core"


export class RsaKeypair implements DidableKey, ExportableKey {
Expand Down Expand Up @@ -42,15 +38,14 @@ export class RsaKeypair implements DidableKey, ExportableKey {
return await crypto.sign(msg, this.keypair.privateKey)
}

async export(format: Encodings = "base64pad"): Promise<string> {
async export(): Promise<PrivateKeyJwk> {
if (!this.exportable) {
throw new Error("Key is not exportable")
}
const exported = await crypto.exportPrivateKeyJwk(this.keypair)
return JSON.stringify(exported)
return await crypto.exportPrivateKeyJwk(this.keypair) as PrivateKeyJwk
}

static async importFromJwk(jwk: JsonWebKey, params: { exportable: true }): Promise<RsaKeypair> {
static async importFromJwk(jwk: PrivateKeyJwk, params: { exportable: true }): Promise<RsaKeypair> {
const { exportable = false } = params || {}
const keypair = await crypto.importKeypairJwk(jwk, exportable)

Expand Down
11 changes: 9 additions & 2 deletions packages/default-plugins/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ export interface AvailableCryptoKeyPair {
export type PublicKeyJwk = {
kty: string
crv: string
x: string
y: string

// For P256 curves
x?: string
y?: string

// For RSA curves
n?: string
e?: string

}

export type PrivateKeyJwk = PublicKeyJwk & { d: string }
Expand Down
15 changes: 8 additions & 7 deletions packages/default-plugins/tests/ecdsa.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as uint8arrays from 'uint8arrays'
import * as uint8arrays from "uint8arrays"
import { p256Plugin } from "../src/p256/plugin.js"
import EcdsaKeypair from "../src/p256/keypair.js"

Expand Down Expand Up @@ -63,8 +63,8 @@ describe("ecdsa did:key", () => {
})

describe("import and exporting a key", () => {
let exportableKeypair: EcdsaKeypair;
let nonExportableKeypair: EcdsaKeypair;
let exportableKeypair: EcdsaKeypair
let nonExportableKeypair: EcdsaKeypair

beforeAll(async () => {
exportableKeypair = await EcdsaKeypair.create({ exportable: true })
Expand All @@ -73,19 +73,20 @@ describe("import and exporting a key", () => {

it("can export a key using jwk", async () => {
const exported = await exportableKeypair.export()
expect(exported.length).toBeGreaterThan(0)
expect(exported.kty).toBe("EC")
expect(exported.crv).toBe("P-256")
})

it("won't export a non exportable keypar", async () => {
await expect(nonExportableKeypair.export())
.rejects
.toThrow('Key is not exportable')
.toThrow("Key is not exportable")
})

it('Can export a key and re-import from it', async () => {
it("Can export a key and re-import from it", async () => {
const exported = await exportableKeypair.export()

const jwk = JSON.parse(exported)
const jwk = exported
const newKey = await EcdsaKeypair.import(jwk)

const msg = uint8arrays.fromString("test message", "utf-8")
Expand Down
2 changes: 1 addition & 1 deletion packages/default-plugins/tests/rsa.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe("ASN", () => {

it("can import an exported key", async () => {
const exported = await exportableKey.export()
const newKey = await RsaKeypair.import(JSON.parse(exported))
const newKey = await RsaKeypair.import(exported)

expect(newKey.did()).toEqual(exportableKey.did())

Expand Down

0 comments on commit 552581d

Please sign in to comment.