Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
openapi.json
.esm-check-*
25 changes: 13 additions & 12 deletions packages/core/src/api/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,45 @@ import {
UnprocessableEntityException,
} from "chanfana"
import { Hono } from "hono"
import { AppContext } from "../../router"
import { CertificateSignerResponse } from "../../types"
import { AppContext } from "../../router.js"
import { CertificateSignerResponse } from "../../types.js"
import {
BadIssuerError,
CreateCertificateOptions,
CreateHostCertificateOptions,
createSignedCertificate,
createSignedHostCertificate,

} from "../../certificate"
} from "../../certificate.js"
import {
getPrivateKey,
getPublic,
split,
UnsupportedKeyError,
} from "../../utils"
import {
KeyParseError,
} from "sshpk"
} from "../../utils.js"
import sshpk from "sshpk"
import {
CertificateType,
getRevocationList,
recordCertificate,
RevocationStatus,
revocationStatus,
revokeCertificate,
} from "../../db"
import { KRLBuilder } from "../../krl"
} from "../../db/index.js"
import { KRLBuilder } from "../../krl.js"
import {
CaPublicKeyEndpointSchema,
createUserCertificateRequestEndpointSchema,
createRevocationListEndpointSchema,
createHostCertificateRequestEndpointSchema,
createHostCertificateRenewEndpointSchema,
createRevokeCertificateEndpointSchema,
} from "./schema"
import { logger } from "../../logger"
import type { SshCaBindings } from "../../types"
} from "./schema.js"
import { logger } from "../../logger.js"
import type { SshCaBindings } from "../../types.js"

// sshpk is CommonJS and Node's ESM loader cannot detect most of its named exports, so values are taken from the default import
const { KeyParseError } = sshpk

const CaPublicKeyEndpoint = (env: SshCaBindings) => {
return class extends OpenAPIRoute {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/api/v3/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import z from "zod"
import type { SshCaBindings } from "../../types"
import type { SshCaBindings } from "../../types.js"
import {
split,
refineCertificateRequest,
Expand All @@ -12,7 +12,7 @@ import {
refineHostCertificateRenewal,
refineRevokeCertificate,
transformIdentityToken,
} from "../../utils"
} from "../../utils.js"
import {
ConflictException,
contentJson,
Expand All @@ -23,7 +23,7 @@ import {
UnprocessableEntityException,
} from "chanfana"
import { seconds } from "itty-time"
import { isRevoked } from "../../db"
import { isRevoked } from "../../db/index.js"

const openapiStringByte = z.base64()
.transform((v) => {
Expand Down
20 changes: 8 additions & 12 deletions packages/core/src/certificate.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { seconds } from "itty-time"
import {
Certificate,
createCertificate,
Identity,
identityForHost,
identityForUser,
identityFromDN,
Key,
PrivateKey } from "sshpk"
import { SSHExtension } from "./types"
import { getPrivateKey, split } from "./utils"
import type { SshCaBindings } from "./types"
import sshpk from "sshpk"
import type { Certificate, Identity, Key, PrivateKey } from "sshpk"
import { SSHExtension } from "./types.js"
import { getPrivateKey, split } from "./utils.js"
import type { SshCaBindings } from "./types.js"

// sshpk is CommonJS and Node's ESM loader cannot detect most of its named exports, so values are taken from the default import
const { createCertificate, identityForHost, identityForUser, identityFromDN } = sshpk

// const sshCertificateExtensions = split(env.SSH_CERTIFICATE_EXTENSIONS)

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Certificate, Format, Identity } from "sshpk"
import { logger } from "../logger"
import type { SshCaBindings } from "../types"
import type { Certificate, Format, Identity } from "sshpk"
import { logger } from "../logger.js"
import type { SshCaBindings } from "../types.js"
import { D1QB } from "workers-qb"
import { migrations } from "./migrations"
import { migrations } from "./migrations/index.js"

export enum CertificateType {
User,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/db/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Migration } from "workers-qb"
import { migration as initialSchema0001 } from "./0001_initial_schema"
import { migration as initialSchema0001 } from "./0001_initial_schema.js"

export const migrations: Migration[] = [
initialSchema0001
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { dbCleanup } from "./db"
import { createApp } from "./router"
import type { SshCaBindings } from "./types"
export type { SshCaBindings } from "./types"
import { dbCleanup } from "./db/index.js"
import { createApp } from "./router.js"
import type { SshCaBindings } from "./types.js"
export type { SshCaBindings } from "./types.js"

let app: ReturnType<typeof createApp> | undefined

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/krl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AlgorithmPart, PrivateKey } from "sshpk"
import { toFixedWidth } from "./sshsig/sig_parser"
import { toFixedWidth } from "./sshsig/sig_parser.js"

// ── Constants ────────────────────────────────────────────────────────────────

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SshCaBindings } from "./types"
import type { SshCaBindings } from "./types.js"
import { Logger, LogLevel } from "@andrewheberle/ts-slog"

export const logger = (env: SshCaBindings): Logger => {
Expand Down
12 changes: 8 additions & 4 deletions packages/core/src/proof.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ms } from "itty-time"
import { Fingerprint, FingerprintFormatError, Key, parseFingerprint, parseKey } from "sshpk"
import { verify } from "./sshsig"
import { parse } from "./sshsig/sig_parser"
import { Sig } from "./sshsig/sig"
import sshpk from "sshpk"
import type { Fingerprint, Key } from "sshpk"
import { verify } from "./sshsig/index.js"
import { parse } from "./sshsig/sig_parser.js"
import { Sig } from "./sshsig/sig.js"
import { group, Logger, LogLevel } from "@andrewheberle/ts-slog"

// sshpk is CommonJS and Node's ESM loader cannot detect most of its named exports, so values are taken from the default import
const { FingerprintFormatError, parseFingerprint, parseKey } = sshpk

export const Namespace = "proof-of-possession@com.github.serverless-ssh-ca.andrewheberle"

export class PossessionParseError extends Error {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/router.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { fromHono } from "chanfana"
import { Hono, type Context } from "hono"
import { createApi as apiv3 } from "./api/v3"
import { createApi as apiv3 } from "./api/v3/index.js"
import { HTTPException } from "hono/http-exception"
import { logger } from "./logger"
import type { SshCaBindings } from "./types"
import { logger } from "./logger.js"
import type { SshCaBindings } from "./types.js"

export type CFArgs = [SshCaBindings, ExecutionContext]
export type AppContext = Context<{ Bindings: SshCaBindings }>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/sshsig/formats.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Reader } from "./reader";
import { Reader } from "./reader.js";

/**
* Represents an SSH public key.
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/sshsig/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Sig } from "./sig";
import { verify as rawVerify } from "./verifier";
import { parse } from "./sig_parser";
import { Sig } from "./sig.js";
import { verify as rawVerify } from "./verifier.js";
import { parse } from "./sig_parser.js";

/**
* Verifies SSH signature against provided data.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/sshsig/sig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Pubkey } from "./formats";
import { Pubkey } from "./formats.js";

/**
* Represents a parsed SSH signature.
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/sshsig/sig_parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Sig } from "./sig";
import { Reader } from "./reader";
import { parsePubkey } from "./formats";
import { dearmor } from "./armor";
import { Sig } from "./sig.js";
import { Reader } from "./reader.js";
import { parsePubkey } from "./formats.js";
import { dearmor } from "./armor.js";

/**
* ECDSA signature algorithms whose signatures need converting from SSH
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/sshsig/verifier.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Sig } from "./sig";
import { convertAlgorithm, convertHash, convertPublicKey } from "./formats";
import { Writer } from "./writer";
import { Sig } from "./sig.js";
import { convertAlgorithm, convertHash, convertPublicKey } from "./formats.js";
import { Writer } from "./writer.js";

export async function verify(
subtle: SubtleCrypto,
Expand Down
18 changes: 11 additions & 7 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import type { SshCaBindings } from "./types"
import type { SshCaBindings } from "./types.js"
import { JWKInvalid, JWKSInvalid, JWSInvalid, JWSSignatureVerificationFailed, JWTClaimValidationFailed, JWTExpired, JWTInvalid } from "jose/errors"
import { Certificate, Key, KeyParseError, CertificateParseError, parseCertificate, parseKey, parsePrivateKey, PrivateKey } from "sshpk"
import sshpk from "sshpk"
import type { Certificate, Key, PrivateKey } from "sshpk"
import z from "zod"
import { verifyJWT } from "./verify"
import { CertificateRequestJWTPayload } from "./types"
import { RenewalProofOfPossession, ProofOfPossession, PossessionParseError } from "./proof"
import type { isRevoked as IsRevokedFn } from "./db"
import { logger } from "./logger"
import { verifyJWT } from "./verify.js"
import { CertificateRequestJWTPayload } from "./types.js"
import { RenewalProofOfPossession, ProofOfPossession, PossessionParseError } from "./proof.js"
import type { isRevoked as IsRevokedFn } from "./db/index.js"
import { logger } from "./logger.js"
import { ms } from "itty-time"
import { InternalServerErrorException } from "chanfana"

// sshpk is CommonJS and Node's ESM loader cannot detect most of its named exports, so values are taken from the default import
const { KeyParseError, CertificateParseError, parseCertificate, parseKey, parsePrivateKey } = sshpk

export const fatalIssue = (ctx: z.RefinementCtx, message: string, val: unknown) => {
ctx.issues.push({
code: "custom",
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/verify.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRemoteJWKSet, jwtVerify } from "jose"
import { CertificateRequestJWTPayload } from "./types"
import type { SshCaBindings } from "./types"
import { split } from "./utils"
import { CertificateRequestJWTPayload } from "./types.js"
import type { SshCaBindings } from "./types.js"
import { split } from "./utils.js"

type VerifyOptions = {
aud?: string | string[]
Expand Down
16 changes: 8 additions & 8 deletions packages/core/tests/certificate.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { key as rsaKey } from "./keys/rsa"
import { key as ecdsaKey } from "./keys/ecdsa"
import { key as ed25519Key } from "./keys/ed25519"
import { key as rsaKey } from "./keys/rsa.js"
import { key as ecdsaKey } from "./keys/ecdsa.js"
import { key as ed25519Key } from "./keys/ed25519.js"
import { describe, expect, it } from "vitest"
import { createSignedCertificate, generateCertificate, generateSerial } from "../src/certificate"
import { createSignedCertificate, generateCertificate, generateSerial } from "../src/certificate.js"
import { seconds } from "itty-time"
import { split, UnsupportedKeyError } from "../src/utils"
import { makeEnv } from "./env"
import { MockSecretStore } from "./helpers/secret"
import { split, UnsupportedKeyError } from "../src/utils.js"
import { makeEnv } from "./env.js"
import { MockSecretStore } from "./helpers/secret.js"
import { Format, Identity, identityForUser, PrivateKey } from "sshpk"
import { SshCaBindings } from "../src/types"
import { SshCaBindings } from "../src/types.js"

const lifetimeString = "24 hours"

Expand Down
4 changes: 2 additions & 2 deletions packages/core/tests/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest"
import { dbCleanup } from "../src/db"
import { makeEnv } from "./env"
import { dbCleanup } from "../src/db/index.js"
import { makeEnv } from "./env.js"

// a D1 binding that records any use of it
const trackedDatabase = (): { db: D1Database, used: PropertyKey[] } => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SshCaBindings } from "../src/types"
import type { SshCaBindings } from "../src/types.js"

const port = parseInt(process.env.OIDC_PORT ?? "4567")

Expand Down
48 changes: 48 additions & 0 deletions packages/core/tests/esm-output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, it, expect } from "vitest"
import { spawnSync } from "node:child_process"
import { mkdtempSync, rmSync } from "node:fs"
import { createRequire } from "node:module"
import { dirname, join, resolve } from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"
import { z } from "zod"

const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..")

// Vitest resolves extensionless and directory imports itself, so the compiled
// output is loaded by a separate plain Node process to catch anything that only
// works under a bundler (missing extensions, CommonJS named exports, etc)
describe("compiled output", () => {
it("should be importable by Node without a bundler", () => {
// emitted inside the package so "type": "module" and node_modules resolution apply
const outDir = mkdtempSync(join(packageRoot, ".esm-check-"))

try {
// typescript's "exports" map hides bin/tsc, so locate it via the package's "bin" field
const require = createRequire(import.meta.url)
const pkgJson = require.resolve("typescript/package.json")
const { bin } = z.object({ bin: z.object({ tsc: z.string() }) }).parse(require(pkgJson))
const tsc = join(dirname(pkgJson), bin.tsc)
const build = spawnSync(process.execPath, [
tsc,
"-p", join(packageRoot, "tsconfig.json"),
"--outDir", outDir,
"--declaration", "false",
"--declarationMap", "false",
"--sourceMap", "false",
], { encoding: "utf-8" })
expect(build.status, build.stdout + build.stderr).toBe(0)

const entry = pathToFileURL(join(outDir, "index.js")).href
const script = `
const mod = await import(${JSON.stringify(entry)})
if (typeof mod.default?.fetch !== "function" || typeof mod.default?.scheduled !== "function") {
throw new Error("default export is missing fetch or scheduled handlers")
}
`
const run = spawnSync(process.execPath, ["--input-type=module", "-e", script], { encoding: "utf-8" })
expect(run.status, run.stderr).toBe(0)
} finally {
rmSync(outDir, { recursive: true, force: true })
}
}, 60_000)
})
2 changes: 1 addition & 1 deletion packages/core/tests/helpers/proof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { writeFileSync, readFileSync, unlinkSync, mkdtempSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { PrivateKey } from "sshpk"
import { Namespace } from "../../src/proof"
import { Namespace } from "../../src/proof.js"

export const generateProof = (key: PrivateKey): string => {
const timestamp = Date.now()
Expand Down
14 changes: 7 additions & 7 deletions packages/core/tests/host-certificate.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { key as rsaKey } from "./keys/rsa"
import { key as ecdsaKey } from "./keys/ecdsa"
import { key as ed25519Key } from "./keys/ed25519"
import { key as rsaKey } from "./keys/rsa.js"
import { key as ecdsaKey } from "./keys/ecdsa.js"
import { key as ed25519Key } from "./keys/ed25519.js"
import { describe, expect, it } from "vitest"
import { createSignedHostCertificate } from "../src/certificate"
import { createSignedHostCertificate } from "../src/certificate.js"
import { seconds } from "itty-time"
import { makeEnv } from "./env"
import { makeEnv } from "./env.js"
import { Format, Identity, type PrivateKey } from "sshpk"
import { MockSecretStore } from "./helpers/secret"
import { UnsupportedKeyError } from "../src/utils"
import { MockSecretStore } from "./helpers/secret.js"
import { UnsupportedKeyError } from "../src/utils.js"

type Test = {
name: string
Expand Down
Loading
Loading