@andrewheberle/serverless-ssh-ca 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/v3/index.d.ts +1 -1
- package/dist/api/v3/index.d.ts.map +1 -1
- package/dist/api/v3/index.js +9 -7
- package/dist/api/v3/index.js.map +1 -1
- package/dist/api/v3/schema.d.ts +13 -13
- package/dist/api/v3/schema.d.ts.map +1 -1
- package/dist/api/v3/schema.js +2 -2
- package/dist/api/v3/schema.js.map +1 -1
- package/dist/certificate.d.ts +2 -2
- package/dist/certificate.d.ts.map +1 -1
- package/dist/certificate.js +4 -2
- package/dist/certificate.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -2
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations/index.js +1 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/krl.d.ts.map +1 -1
- package/dist/krl.js +46 -16
- package/dist/krl.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/proof.d.ts +2 -2
- package/dist/proof.d.ts.map +1 -1
- package/dist/proof.js +5 -3
- package/dist/proof.js.map +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2 -2
- package/dist/router.js.map +1 -1
- package/dist/sshsig/formats.d.ts +1 -1
- package/dist/sshsig/formats.d.ts.map +1 -1
- package/dist/sshsig/index.d.ts +1 -1
- package/dist/sshsig/index.d.ts.map +1 -1
- package/dist/sshsig/index.js +2 -2
- package/dist/sshsig/index.js.map +1 -1
- package/dist/sshsig/sig.d.ts +1 -1
- package/dist/sshsig/sig.d.ts.map +1 -1
- package/dist/sshsig/sig_parser.d.ts +1 -1
- package/dist/sshsig/sig_parser.d.ts.map +1 -1
- package/dist/sshsig/sig_parser.js +3 -3
- package/dist/sshsig/sig_parser.js.map +1 -1
- package/dist/sshsig/verifier.d.ts +1 -1
- package/dist/sshsig/verifier.d.ts.map +1 -1
- package/dist/sshsig/verifier.js +2 -2
- package/dist/sshsig/verifier.js.map +1 -1
- package/dist/utils.d.ts +5 -5
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/dist/verify.d.ts +2 -2
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
- package/src/api/v3/index.ts +13 -12
- package/src/api/v3/schema.ts +3 -3
- package/src/certificate.ts +8 -12
- package/src/db/index.ts +4 -4
- package/src/db/migrations/index.ts +1 -1
- package/src/index.ts +4 -4
- package/src/krl.ts +50 -19
- package/src/logger.ts +1 -1
- package/src/proof.ts +8 -4
- package/src/router.ts +3 -3
- package/src/sshsig/formats.ts +1 -1
- package/src/sshsig/index.ts +3 -3
- package/src/sshsig/sig.ts +1 -1
- package/src/sshsig/sig_parser.ts +4 -4
- package/src/sshsig/verifier.ts +3 -3
- package/src/utils.ts +11 -7
- package/src/verify.ts +3 -3
package/src/krl.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { PrivateKey } from "sshpk"
|
|
1
|
+
import type { AlgorithmPart, PrivateKey } from "sshpk"
|
|
2
|
+
import { toFixedWidth } from "./sshsig/sig_parser.js"
|
|
2
3
|
|
|
3
4
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
4
5
|
|
|
@@ -94,20 +95,25 @@ const HASH_ALGORITHM = "sha512"
|
|
|
94
95
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
|
-
*
|
|
98
|
-
* Works for both PKCS8 ("-----BEGIN PRIVATE KEY-----") and any other PEM type.
|
|
98
|
+
* Get the raw bytes of a named part of an sshpk key.
|
|
99
99
|
*/
|
|
100
|
-
function
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
.filter((l) => !l.startsWith("-----"))
|
|
105
|
-
const binary = atob(lines.join(""))
|
|
106
|
-
const bytes = new Uint8Array(binary.length)
|
|
107
|
-
for (let i = 0; i < binary.length; i++) {
|
|
108
|
-
bytes[i] = binary.charCodeAt(i)
|
|
100
|
+
function keyPart(key: PrivateKey, name: AlgorithmPart): Uint8Array {
|
|
101
|
+
const part = key.parts.find((p) => p.name === name)
|
|
102
|
+
if (part === undefined) {
|
|
103
|
+
throw new Error(`Missing ${name} part in ${key.type} key`)
|
|
109
104
|
}
|
|
110
|
-
return
|
|
105
|
+
return part.data
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Base64url-encode bytes without padding, as used for JWK values.
|
|
110
|
+
*/
|
|
111
|
+
function base64url(bytes: Uint8Array): string {
|
|
112
|
+
let binary = ""
|
|
113
|
+
for (const byte of bytes) {
|
|
114
|
+
binary += String.fromCharCode(byte)
|
|
115
|
+
}
|
|
116
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
/**
|
|
@@ -119,15 +125,21 @@ async function importPrivateKey(caKey: PrivateKey): Promise<{
|
|
|
119
125
|
sigAlgo: string // SSH signature algorithm name
|
|
120
126
|
webCryptoAlgo: SubtleCryptoSignAlgorithm
|
|
121
127
|
}> {
|
|
122
|
-
const pkcs8Pem = caKey.toString("pkcs8")
|
|
123
|
-
const der = pemToDer(pkcs8Pem)
|
|
124
128
|
const type = caKey.type // "ed25519" | "ecdsa" | etc.
|
|
125
129
|
const curve = (caKey as unknown as { curve?: string }).curve
|
|
126
130
|
|
|
127
131
|
if (type === "ed25519") {
|
|
132
|
+
// import from the raw key parts as a JWK rather than via sshpk's PKCS#8
|
|
133
|
+
// output, which drops a leading zero byte from the seed (about 1 key in
|
|
134
|
+
// 512) and is then rejected by WebCrypto
|
|
135
|
+
const seed = keyPart(caKey, "k")
|
|
136
|
+
const publicKey = keyPart(caKey, "A")
|
|
137
|
+
if (seed.length !== 32 || publicKey.length !== 32) {
|
|
138
|
+
throw new Error(`Unexpected Ed25519 key part lengths: k=${seed.length} A=${publicKey.length}`)
|
|
139
|
+
}
|
|
128
140
|
const cryptoKey = await crypto.subtle.importKey(
|
|
129
|
-
"
|
|
130
|
-
|
|
141
|
+
"jwk",
|
|
142
|
+
{ kty: "OKP", crv: "Ed25519", d: base64url(seed), x: base64url(publicKey) },
|
|
131
143
|
{ name: "Ed25519" },
|
|
132
144
|
false,
|
|
133
145
|
["sign"],
|
|
@@ -141,26 +153,45 @@ async function importPrivateKey(caKey: PrivateKey): Promise<{
|
|
|
141
153
|
let namedCurve: string
|
|
142
154
|
let sigAlgo: string
|
|
143
155
|
let hash: string
|
|
156
|
+
let size: number // bytes in each coordinate and the private key
|
|
144
157
|
|
|
145
158
|
if (curve === "nistp256") {
|
|
146
159
|
namedCurve = "P-256"
|
|
147
160
|
sigAlgo = "ecdsa-sha2-nistp256"
|
|
148
161
|
hash = "SHA-256"
|
|
162
|
+
size = 32
|
|
149
163
|
} else if (curve === "nistp384") {
|
|
150
164
|
namedCurve = "P-384"
|
|
151
165
|
sigAlgo = "ecdsa-sha2-nistp384"
|
|
152
166
|
hash = "SHA-384"
|
|
167
|
+
size = 48
|
|
153
168
|
} else if (curve === "nistp521") {
|
|
154
169
|
namedCurve = "P-521"
|
|
155
170
|
sigAlgo = "ecdsa-sha2-nistp521"
|
|
156
171
|
hash = "SHA-512"
|
|
172
|
+
size = 66
|
|
157
173
|
} else {
|
|
158
174
|
throw new Error(`Unsupported ECDSA curve: ${curve}`)
|
|
159
175
|
}
|
|
160
176
|
|
|
177
|
+
// import from the raw key parts as a JWK rather than via sshpk's PKCS#8
|
|
178
|
+
// output, which drops leading zero bytes from the private key so it is
|
|
179
|
+
// shorter than RFC 5915 requires (about half of P-521 keys)
|
|
180
|
+
const point = keyPart(caKey, "Q")
|
|
181
|
+
if (point.length !== 1 + size * 2 || point[0] !== 0x04) {
|
|
182
|
+
throw new Error(`Unexpected ECDSA public key encoding for ${curve}`)
|
|
183
|
+
}
|
|
184
|
+
// d is an SSH mpint, so may be short or have a leading sign byte
|
|
185
|
+
const d = toFixedWidth(keyPart(caKey, "d"), size)
|
|
161
186
|
const cryptoKey = await crypto.subtle.importKey(
|
|
162
|
-
"
|
|
163
|
-
|
|
187
|
+
"jwk",
|
|
188
|
+
{
|
|
189
|
+
kty: "EC",
|
|
190
|
+
crv: namedCurve,
|
|
191
|
+
d: base64url(d),
|
|
192
|
+
x: base64url(point.subarray(1, 1 + size)),
|
|
193
|
+
y: base64url(point.subarray(1 + size)),
|
|
194
|
+
},
|
|
164
195
|
{ name: "ECDSA", namedCurve },
|
|
165
196
|
false,
|
|
166
197
|
["sign"],
|
package/src/logger.ts
CHANGED
package/src/proof.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { ms } from "itty-time"
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import sshpk from "sshpk"
|
|
3
|
+
import type { Fingerprint, Key } from "sshpk"
|
|
4
|
+
import { verify } from "./sshsig/index.js"
|
|
5
|
+
import { parse } from "./sshsig/sig_parser.js"
|
|
6
|
+
import { Sig } from "./sshsig/sig.js"
|
|
6
7
|
import { group, Logger, LogLevel } from "@andrewheberle/ts-slog"
|
|
7
8
|
|
|
9
|
+
// sshpk is CommonJS and Node's ESM loader cannot detect most of its named exports, so values are taken from the default import
|
|
10
|
+
const { FingerprintFormatError, parseFingerprint, parseKey } = sshpk
|
|
11
|
+
|
|
8
12
|
export const Namespace = "proof-of-possession@com.github.serverless-ssh-ca.andrewheberle"
|
|
9
13
|
|
|
10
14
|
export class PossessionParseError extends Error {
|
package/src/router.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fromHono } from "chanfana"
|
|
2
2
|
import { Hono, type Context } from "hono"
|
|
3
|
-
import { createApi as apiv3 } from "./api/v3"
|
|
3
|
+
import { createApi as apiv3 } from "./api/v3/index.js"
|
|
4
4
|
import { HTTPException } from "hono/http-exception"
|
|
5
|
-
import { logger } from "./logger"
|
|
6
|
-
import type { SshCaBindings } from "./types"
|
|
5
|
+
import { logger } from "./logger.js"
|
|
6
|
+
import type { SshCaBindings } from "./types.js"
|
|
7
7
|
|
|
8
8
|
export type CFArgs = [SshCaBindings, ExecutionContext]
|
|
9
9
|
export type AppContext = Context<{ Bindings: SshCaBindings }>
|
package/src/sshsig/formats.ts
CHANGED
package/src/sshsig/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Sig } from "./sig";
|
|
2
|
-
import { verify as rawVerify } from "./verifier";
|
|
3
|
-
import { parse } from "./sig_parser";
|
|
1
|
+
import { Sig } from "./sig.js";
|
|
2
|
+
import { verify as rawVerify } from "./verifier.js";
|
|
3
|
+
import { parse } from "./sig_parser.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Verifies SSH signature against provided data.
|
package/src/sshsig/sig.ts
CHANGED
package/src/sshsig/sig_parser.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Sig } from "./sig";
|
|
2
|
-
import { Reader } from "./reader";
|
|
3
|
-
import { parsePubkey } from "./formats";
|
|
4
|
-
import { dearmor } from "./armor";
|
|
1
|
+
import { Sig } from "./sig.js";
|
|
2
|
+
import { Reader } from "./reader.js";
|
|
3
|
+
import { parsePubkey } from "./formats.js";
|
|
4
|
+
import { dearmor } from "./armor.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* ECDSA signature algorithms whose signatures need converting from SSH
|
package/src/sshsig/verifier.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Sig } from "./sig";
|
|
2
|
-
import { convertAlgorithm, convertHash, convertPublicKey } from "./formats";
|
|
3
|
-
import { Writer } from "./writer";
|
|
1
|
+
import { Sig } from "./sig.js";
|
|
2
|
+
import { convertAlgorithm, convertHash, convertPublicKey } from "./formats.js";
|
|
3
|
+
import { Writer } from "./writer.js";
|
|
4
4
|
|
|
5
5
|
export async function verify(
|
|
6
6
|
subtle: SubtleCrypto,
|
package/src/utils.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import type { SshCaBindings } from "./types"
|
|
1
|
+
import type { SshCaBindings } from "./types.js"
|
|
2
2
|
import { JWKInvalid, JWKSInvalid, JWSInvalid, JWSSignatureVerificationFailed, JWTClaimValidationFailed, JWTExpired, JWTInvalid } from "jose/errors"
|
|
3
|
-
import
|
|
3
|
+
import sshpk from "sshpk"
|
|
4
|
+
import type { Certificate, Key, PrivateKey } from "sshpk"
|
|
4
5
|
import z from "zod"
|
|
5
|
-
import { verifyJWT } from "./verify"
|
|
6
|
-
import { CertificateRequestJWTPayload } from "./types"
|
|
7
|
-
import { RenewalProofOfPossession, ProofOfPossession, PossessionParseError } from "./proof"
|
|
8
|
-
import type { isRevoked as IsRevokedFn } from "./db"
|
|
9
|
-
import { logger } from "./logger"
|
|
6
|
+
import { verifyJWT } from "./verify.js"
|
|
7
|
+
import { CertificateRequestJWTPayload } from "./types.js"
|
|
8
|
+
import { RenewalProofOfPossession, ProofOfPossession, PossessionParseError } from "./proof.js"
|
|
9
|
+
import type { isRevoked as IsRevokedFn } from "./db/index.js"
|
|
10
|
+
import { logger } from "./logger.js"
|
|
10
11
|
import { ms } from "itty-time"
|
|
11
12
|
import { InternalServerErrorException } from "chanfana"
|
|
12
13
|
|
|
14
|
+
// sshpk is CommonJS and Node's ESM loader cannot detect most of its named exports, so values are taken from the default import
|
|
15
|
+
const { KeyParseError, CertificateParseError, parseCertificate, parseKey, parsePrivateKey } = sshpk
|
|
16
|
+
|
|
13
17
|
export const fatalIssue = (ctx: z.RefinementCtx, message: string, val: unknown) => {
|
|
14
18
|
ctx.issues.push({
|
|
15
19
|
code: "custom",
|
package/src/verify.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRemoteJWKSet, jwtVerify } from "jose"
|
|
2
|
-
import { CertificateRequestJWTPayload } from "./types"
|
|
3
|
-
import type { SshCaBindings } from "./types"
|
|
4
|
-
import { split } from "./utils"
|
|
2
|
+
import { CertificateRequestJWTPayload } from "./types.js"
|
|
3
|
+
import type { SshCaBindings } from "./types.js"
|
|
4
|
+
import { split } from "./utils.js"
|
|
5
5
|
|
|
6
6
|
type VerifyOptions = {
|
|
7
7
|
aud?: string | string[]
|