Skip to content

Commit

Permalink
feat(x509): new package for generating self-signed certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
justmoon committed Sep 10, 2024
1 parent 8177e63 commit 4b37ec1
Show file tree
Hide file tree
Showing 34 changed files with 1,365 additions and 137 deletions.
1 change: 1 addition & 0 deletions packages/app-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@dassie/lib-rpc-react": "workspace:^",
"@dassie/lib-sqlite": "workspace:^",
"@dassie/lib-type-utils": "workspace:*",
"@dassie/lib-x509": "workspace:*",
"@dassie/meta-unocss-config": "workspace:^",
"@noble/hashes": "^1.3.2",
"@react-hook/size": "^2.1.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/app-dev/src/backend/utils/prefill-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { readFile, unlink } from "node:fs/promises"
import type { BtpToken } from "@dassie/app-node/src/backend/api-keys/types/btp-token"
import { DASSIE_DATABASE_SCHEMA } from "@dassie/app-node/src/backend/database/schema"
import type { SettlementSchemeId } from "@dassie/app-node/src/backend/peer-protocol/types/settlement-scheme-id"
import { serializeEd25519Key } from "@dassie/app-node/src/backend/utils/pem"
import { createDatabase } from "@dassie/lib-sqlite"
import { isErrorWithCode } from "@dassie/lib-type-utils"
import { serializeEd25519Key } from "@dassie/lib-x509"

import { DEBUG_UI_PORT } from "../constants/ports"
import { setup as logger } from "../logger/instances"
Expand Down
3 changes: 3 additions & 0 deletions packages/app-dev/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
{
"path": "../lib-type-utils"
},
{
"path": "../lib-x509"
},
{
"path": "../meta-unocss-config"
}
Expand Down
1 change: 1 addition & 0 deletions packages/app-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@dassie/lib-sqlite": "workspace:^",
"@dassie/lib-terminal-graphics": "workspace:^",
"@dassie/lib-type-utils": "workspace:^",
"@dassie/lib-x509": "workspace:^",
"@dassie/meta-unocss-config": "workspace:^",
"@hookform/resolvers": "^3.9.0",
"@noble/curves": "^1.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
setCookie,
} from "@dassie/lib-http-server"
import { createActor } from "@dassie/lib-reactive"
import { serializeEd25519Key } from "@dassie/lib-x509"

import { SESSION_COOKIE_NAME } from "../../../common/constants/cookie-name"
import { SEED_PATH_NODE_LOGIN } from "../../../common/constants/seed-paths"
Expand All @@ -18,7 +19,6 @@ import {
} from "../../config/database-config"
import { getPrivateSeedAtPath } from "../../crypto/utils/seed-paths"
import { HttpsRouter } from "../../http-server/values/https-router"
import { serializeEd25519Key } from "../../utils/pem"
import { COOKIE_MAX_AGE_SECONDS } from "../constants/cookie-lifetime"
import { SessionsStore } from "../database-stores/sessions"
import { SetupAuthorizationTokenSignal } from "../signals/setup-authorization-token"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { assert } from "@dassie/lib-logger"
import { type Reactor, createComputed } from "@dassie/lib-reactive"
import { parseEd25519Key } from "@dassie/lib-x509"

import {
DatabaseConfigStore,
hasNodeIdentity,
} from "../../config/database-config"
import { crypto as logger } from "../../logger/instances"
import { parseEd25519Key } from "../../utils/pem"

export const NodePrivateKeySignal = (reactor: Reactor) =>
createComputed(reactor, (sig) => {
Expand Down
96 changes: 0 additions & 96 deletions packages/app-node/src/backend/utils/pem.ts

This file was deleted.

9 changes: 4 additions & 5 deletions packages/app-node/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"extends": "@dassie/meta-tsconfig/vite-react.json",
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.js"
],
"include": ["**/*.ts", "**/*.tsx", "**/*.js"],
"references": [
{
"path": "../lib-format-utils"
Expand Down Expand Up @@ -51,6 +47,9 @@
{
"path": "../lib-type-utils"
},
{
"path": "../lib-x509"
},
{
"path": "../meta-unocss-config"
}
Expand Down
12 changes: 7 additions & 5 deletions packages/lib-protocol-stream/src/test/crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ import {
} from "uint8array-extras"
import { describe, test } from "vitest"

import { createMockDeterministicCrypto } from "@dassie/lib-reactive"
import { createCrypto } from "@dassie/lib-reactive-io"

import {
generateRandomCondition,
generateTokenNonce,
getPskEnvironment,
} from "../crypto/functions"
import { createMockCryptoContext } from "./mocks/crypto-context"

describe("Crypto", () => {
describe("generateTokenNonce", () => {
test("should generate a token nonce", ({ expect }) => {
const context = createMockCryptoContext()
const context = createMockDeterministicCrypto(createCrypto())
const nonce = generateTokenNonce(context)

expect(nonce.length).toBe(18)
Expand All @@ -28,7 +30,7 @@ describe("Crypto", () => {

describe("generateRandomCondition", () => {
test("should generate a random 32-byte condition", ({ expect }) => {
const context = createMockCryptoContext()
const context = createMockDeterministicCrypto(createCrypto())
const condition = generateRandomCondition(context)

expect(condition.length).toBe(32)
Expand All @@ -40,7 +42,7 @@ describe("Crypto", () => {

describe("hash", () => {
test("generates the expected condition", async ({ expect }) => {
const context = createMockCryptoContext()
const context = createMockDeterministicCrypto(createCrypto())
const fulfillment = new Uint8Array(32)
const wantCondition = base64ToUint8Array(
"Zmh6rfhivXdsj8GLjp+OIAiXFIVu4jOzkCpZHQ1fKSU=",
Expand All @@ -53,7 +55,7 @@ describe("Crypto", () => {

describe("getPskEnvironment", () => {
function getTestEnvironment() {
const context = createMockCryptoContext()
const context = createMockDeterministicCrypto(createCrypto())
const secret = stringToUint8Array("foo")
return getPskEnvironment(context, secret)
}
Expand Down
3 changes: 2 additions & 1 deletion packages/lib-reactive-io/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"license": "Apache-2.0",
"dependencies": {
"@dassie/lib-reactive": "workspace:^",
"@dassie/lib-type-utils": "workspace:^"
"@dassie/lib-type-utils": "workspace:^",
"uint8array-extras": "^1.4.0"
},
"devDependencies": {
"@dassie/eslint-config": "workspace:^",
Expand Down
96 changes: 82 additions & 14 deletions packages/lib-reactive-io/src/browser/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type EncryptionAlgorithm,
type HashAlgorithm,
type MacAlgorithm,
type RsaKeyPair,
} from "@dassie/lib-reactive"
import { isError } from "@dassie/lib-type-utils"

Expand Down Expand Up @@ -162,9 +163,7 @@ class BrowserCryptoImplementation implements Crypto {
}
}

async generateRsaKeyPair(
modulusLength: number,
): Promise<{ publicKey: string; privateKey: string }> {
async generateRsaKeyPair(modulusLength: number): Promise<RsaKeyPair> {
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
Expand All @@ -176,21 +175,90 @@ class BrowserCryptoImplementation implements Crypto {
["encrypt", "decrypt"],
)

return {
publicKey: uint8ArrayToString(
new Uint8Array(
await crypto.subtle.exportKey("spki", keyPair.publicKey),
),
),
privateKey: uint8ArrayToString(
new Uint8Array(
await crypto.subtle.exportKey("pkcs8", keyPair.privateKey),
),
),
return createRsaKeyPair(keyPair)
}

async importRsaKeyPair(
privateKeyData: string | Uint8Array,
): Promise<RsaKeyPair> {
if (typeof privateKeyData === "string") {
throw new TypeError("PEM format is not supported in this implementation")
}

const privateKey = await crypto.subtle.importKey(
"pkcs8",
privateKeyData,
{
name: "RSA-OAEP",
hash: "SHA-256",
},
true,
["sign", "verify"],
)

const publicKey = await derivePublicKeyFromPrivateKey(privateKey)

return createRsaKeyPair({ privateKey, publicKey })
}
}

export function createCrypto(): Crypto {
return new BrowserCryptoImplementation()
}

function createRsaKeyPair(keys: CryptoKeyPair): RsaKeyPair {
return {
async getPublicKeyPem() {
return uint8ArrayToString(
new Uint8Array(await crypto.subtle.exportKey("spki", keys.publicKey)),
)
},
async getPrivateKeyPem() {
return uint8ArrayToString(
new Uint8Array(await crypto.subtle.exportKey("pkcs8", keys.privateKey)),
)
},

async sign(message: Uint8Array) {
const signature = await crypto.subtle.sign(
"RSASSA-PKCS1-v1_5",
keys.privateKey,
message,
)
return new Uint8Array(signature)
},

async verify(message: Uint8Array, signature: Uint8Array) {
return crypto.subtle.verify(
"RSASSA-PKCS1-v1_5",
keys.publicKey,
signature,
message,
)
},
}
}

async function derivePublicKeyFromPrivateKey(
privateKey: CryptoKey,
): Promise<CryptoKey> {
const jwk = await crypto.subtle.exportKey("jwk", privateKey)

// remove private data from JWK
delete jwk.d
delete jwk.dp
delete jwk.dq
delete jwk.q
delete jwk.qi
jwk.key_ops = ["encrypt", "wrapKey"]

// import public key
const publicKey = await crypto.subtle.importKey(
"jwk",
jwk,
{ name: "RSA-OAEP", hash: "SHA-512" },
true,
["encrypt", "wrapKey"],
)
return publicKey
}
12 changes: 0 additions & 12 deletions packages/lib-reactive-io/src/browser/random.ts

This file was deleted.

Loading

0 comments on commit 4b37ec1

Please sign in to comment.