@andrewheberle/serverless-ssh-ca 0.22.0 → 0.23.0
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/README.md +55 -1
- package/dist/api/operations.d.ts +49 -0
- package/dist/api/operations.d.ts.map +1 -0
- package/dist/api/operations.js +175 -0
- package/dist/api/operations.js.map +1 -0
- package/dist/api/schema.d.ts +71 -0
- package/dist/api/schema.d.ts.map +1 -0
- package/dist/api/schema.js +107 -0
- package/dist/api/schema.js.map +1 -0
- package/dist/api/v3/index.d.ts.map +1 -1
- package/dist/api/v3/index.js +13 -202
- package/dist/api/v3/index.js.map +1 -1
- package/dist/api/v3/schema.d.ts +23 -68
- package/dist/api/v3/schema.d.ts.map +1 -1
- package/dist/api/v3/schema.js +10 -100
- package/dist/api/v3/schema.js.map +1 -1
- package/dist/api/v4/index.d.ts +8 -0
- package/dist/api/v4/index.d.ts.map +1 -0
- package/dist/api/v4/index.js +155 -0
- package/dist/api/v4/index.js.map +1 -0
- package/dist/api/v4/schema.d.ts +268 -0
- package/dist/api/v4/schema.d.ts.map +1 -0
- package/dist/api/v4/schema.js +182 -0
- package/dist/api/v4/schema.js.map +1 -0
- package/dist/certificate.d.ts +9 -6
- package/dist/certificate.d.ts.map +1 -1
- package/dist/certificate.js +55 -99
- package/dist/certificate.js.map +1 -1
- package/dist/db/index.d.ts +17 -5
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +39 -23
- package/dist/db/index.js.map +1 -1
- package/dist/krl.d.ts +1 -1
- package/dist/krl.d.ts.map +1 -1
- package/dist/krl.js +9 -163
- package/dist/krl.js.map +1 -1
- package/dist/proof.d.ts +36 -7
- package/dist/proof.d.ts.map +1 -1
- package/dist/proof.js +53 -21
- package/dist/proof.js.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +3 -1
- package/dist/router.js.map +1 -1
- package/dist/ssh/certificate.d.ts +93 -0
- package/dist/ssh/certificate.d.ts.map +1 -0
- package/dist/ssh/certificate.js +178 -0
- package/dist/ssh/certificate.js.map +1 -0
- package/dist/ssh/encoding.d.ts +41 -0
- package/dist/ssh/encoding.d.ts.map +1 -0
- package/dist/ssh/encoding.js +70 -0
- package/dist/ssh/encoding.js.map +1 -0
- package/dist/ssh/keys.d.ts +80 -0
- package/dist/ssh/keys.d.ts.map +1 -0
- package/dist/ssh/keys.js +249 -0
- package/dist/ssh/keys.js.map +1 -0
- package/dist/ssh/signer.d.ts +35 -0
- package/dist/ssh/signer.d.ts.map +1 -0
- package/dist/ssh/signer.js +99 -0
- package/dist/ssh/signer.js.map +1 -0
- package/dist/types.d.ts +12 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +40 -21
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +87 -54
- package/dist/utils.js.map +1 -1
- package/openapi.json +1542 -1
- package/package.json +4 -3
- package/src/api/operations.ts +211 -0
- package/src/api/schema.ts +137 -0
- package/src/api/v3/index.ts +20 -248
- package/src/api/v3/schema.ts +26 -125
- package/src/api/v4/index.ts +265 -0
- package/src/api/v4/schema.ts +235 -0
- package/src/certificate.ts +62 -123
- package/src/db/index.ts +41 -26
- package/src/krl.ts +10 -195
- package/src/proof.ts +65 -31
- package/src/router.ts +3 -1
- package/src/ssh/certificate.ts +236 -0
- package/src/ssh/encoding.ts +81 -0
- package/src/ssh/keys.ts +294 -0
- package/src/ssh/signer.ts +129 -0
- package/src/types.ts +12 -18
- package/src/utils.ts +99 -69
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { refineRenewableCertificate, split, transformCertificate, transformProofOfPossession, } from "../../utils.js";
|
|
3
|
+
import { ConflictException, contentJson, InputValidationException, InternalServerErrorException, NotFoundException, UnauthorizedException, ForbiddenException, UnprocessableEntityException, } from "chanfana";
|
|
4
|
+
import { seconds } from "itty-time";
|
|
5
|
+
import { isRevoked } from "../../db/index.js";
|
|
6
|
+
import { CertificateType } from "../../db/types.js";
|
|
7
|
+
import { CaPublicKeyEndpointSchema, certificate, certificateTypeName, certificateTypeTag, createAccessTokenSchema, createIdentityTokenSchema, createRevocationListEndpointSchema, openapiStringByte, publicKey, } from "../schema.js";
|
|
8
|
+
/**
|
|
9
|
+
* The name of the header carrying the proof of possession of the private key
|
|
10
|
+
*/
|
|
11
|
+
export const ProofHeader = "Proof-Of-Possession";
|
|
12
|
+
export const createProofSchema = (env) => (z.string()
|
|
13
|
+
.meta({
|
|
14
|
+
description: "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
|
|
15
|
+
example: "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0K...",
|
|
16
|
+
})
|
|
17
|
+
.transform((val, ctx) => transformProofOfPossession(env, val, ctx)));
|
|
18
|
+
/**
|
|
19
|
+
* Headers for a request authorised by a proof of possession alone
|
|
20
|
+
*/
|
|
21
|
+
export const createProofHeaderSchema = (env) => (z.object({
|
|
22
|
+
[ProofHeader]: createProofSchema(env),
|
|
23
|
+
}));
|
|
24
|
+
/**
|
|
25
|
+
* Headers for a request authenticated with an access token and authorised by
|
|
26
|
+
* a proof of possession
|
|
27
|
+
*/
|
|
28
|
+
export const createAuthenticatedHeaderSchema = (env) => (z.object({
|
|
29
|
+
"Authorization": createAccessTokenSchema(env),
|
|
30
|
+
[ProofHeader]: createProofSchema(env),
|
|
31
|
+
}));
|
|
32
|
+
export const serialParamSchema = z.object({
|
|
33
|
+
// a string as JSON cannot carry a bigint and numbers lose precision
|
|
34
|
+
// above 2^53, and without leading zeros so the path a proof is made for
|
|
35
|
+
// has only one form
|
|
36
|
+
serial: z.string()
|
|
37
|
+
.regex(/^(0|[1-9]\d*)$/)
|
|
38
|
+
.transform(BigInt)
|
|
39
|
+
.refine((v) => v <= 0xffffffffffffffffn, { message: "serial must fit in an unsigned 64-bit integer" })
|
|
40
|
+
.meta({ description: "Serial number of the certificate", example: "12345678901234567890" }),
|
|
41
|
+
});
|
|
42
|
+
const certificateResponse = {
|
|
43
|
+
...contentJson(z.object({
|
|
44
|
+
certificate: certificate,
|
|
45
|
+
})
|
|
46
|
+
.openapi("Certificate Response"))
|
|
47
|
+
};
|
|
48
|
+
export const createCaPublicKeyEndpointSchema = () => ({
|
|
49
|
+
...CaPublicKeyEndpointSchema,
|
|
50
|
+
operationId: "get_ca_public_key",
|
|
51
|
+
});
|
|
52
|
+
export const createGetRevocationListEndpointSchema = (certificateType) => ({
|
|
53
|
+
...createRevocationListEndpointSchema(certificateType),
|
|
54
|
+
operationId: `get_${certificateTypeName(certificateType)}_revocation_list`,
|
|
55
|
+
});
|
|
56
|
+
export const issueUserCertificateEndpointBodySchema = (env) => (z.object({
|
|
57
|
+
public_key: publicKey,
|
|
58
|
+
identity: createIdentityTokenSchema(env),
|
|
59
|
+
extensions: z.array(z.string())
|
|
60
|
+
.default(split(env.SSH_CERTIFICATE_EXTENSIONS))
|
|
61
|
+
.meta({ description: "Extensions to include in the issued SSH certificate" }),
|
|
62
|
+
lifetime: z.int()
|
|
63
|
+
.min(seconds("5 minutes"))
|
|
64
|
+
.max(seconds(env.SSH_CERTIFICATE_LIFETIME))
|
|
65
|
+
.default(seconds(env.SSH_CERTIFICATE_LIFETIME))
|
|
66
|
+
.meta({ description: "Lifetime of issued SSH certificate" }),
|
|
67
|
+
})
|
|
68
|
+
.openapi("User Certificate Issue Request"));
|
|
69
|
+
export const createIssueUserCertificateEndpointSchema = (env) => ({
|
|
70
|
+
tags: [certificateTypeTag(CertificateType.User)],
|
|
71
|
+
operationId: "issue_user_certificate",
|
|
72
|
+
summary: "Request a user certificate",
|
|
73
|
+
description: `Issues a signed SSH user certificate for the provided public key. The request must be authenticated with an access token and include an identity token from the OIDC IdP for the same subject, along with a proof of possession of the private key for \`public_key\` in the \`${ProofHeader}\` header made for this request. Principals on the issued certificate are derived from the identity token.`,
|
|
74
|
+
security: [{ oidcAuth: [] }],
|
|
75
|
+
request: {
|
|
76
|
+
headers: createAuthenticatedHeaderSchema(env),
|
|
77
|
+
body: contentJson(issueUserCertificateEndpointBodySchema(env)),
|
|
78
|
+
},
|
|
79
|
+
responses: {
|
|
80
|
+
"200": {
|
|
81
|
+
description: "SSH User Certificate issued successfully",
|
|
82
|
+
...certificateResponse,
|
|
83
|
+
},
|
|
84
|
+
...UnauthorizedException.schema(),
|
|
85
|
+
...ForbiddenException.schema(),
|
|
86
|
+
...InputValidationException.schema(),
|
|
87
|
+
...UnprocessableEntityException.schema(),
|
|
88
|
+
...InternalServerErrorException.schema(),
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
export const issueHostCertificateEndpointBodySchema = (env) => (z.object({
|
|
92
|
+
public_key: publicKey,
|
|
93
|
+
identity: createIdentityTokenSchema(env),
|
|
94
|
+
principals: z.array(z.string()).min(1)
|
|
95
|
+
.meta({ description: "List of principals to include on the issued certificate" }),
|
|
96
|
+
lifetime: z.int()
|
|
97
|
+
.min(seconds("24 hours"))
|
|
98
|
+
.max(seconds(env.SSH_HOST_CERTIFICATE_LIFETIME))
|
|
99
|
+
.default(seconds(env.SSH_HOST_CERTIFICATE_LIFETIME))
|
|
100
|
+
.meta({ description: "Lifetime of issued Host SSH certificate" }),
|
|
101
|
+
})
|
|
102
|
+
.openapi("Host Certificate Issue Request"));
|
|
103
|
+
export const createIssueHostCertificateEndpointSchema = (env) => ({
|
|
104
|
+
tags: [certificateTypeTag(CertificateType.Host)],
|
|
105
|
+
operationId: "issue_host_certificate",
|
|
106
|
+
summary: "Request a host certificate",
|
|
107
|
+
description: `Issues a signed SSH host certificate for the provided public key and principals. The request must be authenticated with an access token and include an identity token from the OIDC IdP for the same subject, along with a proof of possession of the private key for \`public_key\` in the \`${ProofHeader}\` header made for this request. The user must be permitted to request host certificates by email address or role.`,
|
|
108
|
+
security: [{ oidcAuth: [] }],
|
|
109
|
+
request: {
|
|
110
|
+
headers: createAuthenticatedHeaderSchema(env),
|
|
111
|
+
body: contentJson(issueHostCertificateEndpointBodySchema(env)),
|
|
112
|
+
},
|
|
113
|
+
responses: {
|
|
114
|
+
"200": {
|
|
115
|
+
description: "SSH Host Certificate issued successfully",
|
|
116
|
+
...certificateResponse,
|
|
117
|
+
},
|
|
118
|
+
...UnauthorizedException.schema(),
|
|
119
|
+
...ForbiddenException.schema(),
|
|
120
|
+
...InputValidationException.schema(),
|
|
121
|
+
...UnprocessableEntityException.schema(),
|
|
122
|
+
...InternalServerErrorException.schema(),
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
export const renewHostCertificateEndpointBodySchema = (env) => (z.object({
|
|
126
|
+
certificate: openapiStringByte
|
|
127
|
+
.transform(transformCertificate)
|
|
128
|
+
.meta({ description: "SSH certificate to renew, which must have the serial number in the path" }),
|
|
129
|
+
lifetime: z.int()
|
|
130
|
+
.min(seconds("24 hours"))
|
|
131
|
+
.max(seconds(env.SSH_HOST_CERTIFICATE_LIFETIME))
|
|
132
|
+
.default(seconds(env.SSH_HOST_CERTIFICATE_LIFETIME))
|
|
133
|
+
.meta({ description: "Lifetime of renewed Host SSH certificate" }),
|
|
134
|
+
})
|
|
135
|
+
.superRefine((val, ctx) => refineRenewableCertificate(env, isRevoked, val, ctx))
|
|
136
|
+
.openapi("Host Certificate Renewal Request"));
|
|
137
|
+
export const createRenewHostCertificateEndpointSchema = (env) => ({
|
|
138
|
+
tags: [certificateTypeTag(CertificateType.Host)],
|
|
139
|
+
operationId: "renew_host_certificate",
|
|
140
|
+
summary: "Renew a host certificate",
|
|
141
|
+
description: `Issues a new SSH host certificate for the same public key and principals as an existing, unexpired and unrevoked host certificate issued by this CA. No OIDC authentication is required; instead the \`${ProofHeader}\` header must contain a proof of possession of the private key for the existing certificate made for this request. The lifetime of the renewed certificate is no longer than that of the existing certificate.`,
|
|
142
|
+
request: {
|
|
143
|
+
params: serialParamSchema,
|
|
144
|
+
headers: createProofHeaderSchema(env),
|
|
145
|
+
body: contentJson(renewHostCertificateEndpointBodySchema(env)),
|
|
146
|
+
},
|
|
147
|
+
responses: {
|
|
148
|
+
"200": {
|
|
149
|
+
description: "SSH Host Certificate renewed successfully",
|
|
150
|
+
...certificateResponse,
|
|
151
|
+
},
|
|
152
|
+
...UnauthorizedException.schema(),
|
|
153
|
+
...ForbiddenException.schema(),
|
|
154
|
+
...InputValidationException.schema(),
|
|
155
|
+
...UnprocessableEntityException.schema(),
|
|
156
|
+
...InternalServerErrorException.schema(),
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
export const createRevokeCertificateEndpointSchema = (env, certificateType) => ({
|
|
160
|
+
tags: [certificateTypeTag(certificateType)],
|
|
161
|
+
operationId: `revoke_${certificateTypeName(certificateType)}_certificate`,
|
|
162
|
+
summary: `Revoke a ${certificateTypeName(certificateType)} certificate`,
|
|
163
|
+
description: `Revokes a ${certificateTypeName(certificateType)} certificate issued by this CA and adds it to the ${certificateTypeName(certificateType)} Key Revocation List. The \`${ProofHeader}\` header must contain a proof of possession of the private key for the public key that the certificate was issued for, made for this request. A certificate issued for a different public key is reported as not found.`,
|
|
164
|
+
request: {
|
|
165
|
+
params: serialParamSchema,
|
|
166
|
+
headers: createProofHeaderSchema(env),
|
|
167
|
+
},
|
|
168
|
+
responses: {
|
|
169
|
+
"200": {
|
|
170
|
+
description: `SSH ${certificateTypeName(certificateType)} certificate revoked successfully`,
|
|
171
|
+
...contentJson(z.object({
|
|
172
|
+
revoked_at: z.iso.datetime(),
|
|
173
|
+
})
|
|
174
|
+
.openapi("Revocation Response")),
|
|
175
|
+
},
|
|
176
|
+
...InputValidationException.schema(),
|
|
177
|
+
...NotFoundException.schema(),
|
|
178
|
+
...ConflictException.schema(),
|
|
179
|
+
...InternalServerErrorException.schema(),
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/api/v4/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/certificate.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PrivateKey, PublicKey } from "./ssh/keys.js";
|
|
2
2
|
import type { SshCaBindings } from "./types.js";
|
|
3
|
+
import { type SshCertificate } from "./ssh/certificate.js";
|
|
3
4
|
export type CreateCertificateOptions = {
|
|
4
5
|
lifetime?: number;
|
|
5
6
|
principals?: string[];
|
|
@@ -16,16 +17,18 @@ export type GenerateCertificateOptions = {
|
|
|
16
17
|
*/
|
|
17
18
|
serial?: bigint;
|
|
18
19
|
} & CreateCertificateOptions;
|
|
19
|
-
export declare const generateCertificate: (env: SshCaBindings, email: string, key: PrivateKey, public_key:
|
|
20
|
-
export declare function createSignedCertificate(env: SshCaBindings, email: string, public_key:
|
|
20
|
+
export declare const generateCertificate: (env: SshCaBindings, email: string, key: PrivateKey, public_key: PublicKey, options?: GenerateCertificateOptions) => Promise<SshCertificate>;
|
|
21
|
+
export declare function createSignedCertificate(env: SshCaBindings, email: string, public_key: PublicKey, options?: CreateCertificateOptions): Promise<SshCertificate>;
|
|
21
22
|
export type CreateHostCertificateOptions = {
|
|
22
23
|
lifetime?: number;
|
|
23
24
|
principals?: string[];
|
|
24
|
-
subjects?: Identity[];
|
|
25
25
|
};
|
|
26
26
|
export declare class BadIssuerError extends Error {
|
|
27
27
|
constructor(message: string);
|
|
28
28
|
}
|
|
29
|
-
export declare function createSignedHostCertificate(env: SshCaBindings, public_key:
|
|
30
|
-
|
|
29
|
+
export declare function createSignedHostCertificate(env: SshCaBindings, public_key: PublicKey, options?: CreateHostCertificateOptions): Promise<SshCertificate>;
|
|
30
|
+
/**
|
|
31
|
+
* Returns a random certificate serial number
|
|
32
|
+
*/
|
|
33
|
+
export declare const generateSerial: () => bigint;
|
|
31
34
|
//# sourceMappingURL=certificate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certificate.d.ts","sourceRoot":"","sources":["../src/certificate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"certificate.d.ts","sourceRoot":"","sources":["../src/certificate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAA2C,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGnG,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB,CAAA;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,YAAY,OAAO,EAAE,MAAM,EAM1B;CACD;AAED,MAAM,MAAM,0BAA0B,GAAG;IACxC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CACf,GAAG,wBAAwB,CAAA;AAqB5B,eAAO,MAAM,mBAAmB,QAAe,aAAa,SAAS,MAAM,OAAO,UAAU,cAAc,SAAS,YAAY,0BAA0B,KAAG,OAAO,CAAC,cAAc,CAsDjL,CAAA;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CAanK;AAED,MAAM,MAAM,4BAA4B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB,CAAA;AAED,qBAAa,cAAe,SAAQ,KAAK;IACxC,YAAY,OAAO,EAAE,MAAM,EAK1B;CACD;AAED,wBAAsB,2BAA2B,CAAC,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,4BAA4B,GAAG,OAAO,CAAC,cAAc,CAAC,CAmB5J;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,QAAO,MAEjC,CAAA"}
|
package/dist/certificate.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { seconds } from "itty-time";
|
|
2
|
-
import sshpk from "sshpk";
|
|
3
2
|
import { getPrivateKey, split } from "./utils.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/* const DefaultCreateCertificateOptions: CreateCertificateOptions = {
|
|
7
|
-
lifetime: seconds(env.SSH_CERTIFICATE_LIFETIME),
|
|
8
|
-
principals: [],
|
|
9
|
-
extensions: sshCertificateExtensions
|
|
10
|
-
} */
|
|
3
|
+
import { signCertificate } from "./ssh/certificate.js";
|
|
4
|
+
import { createSigner } from "./ssh/signer.js";
|
|
11
5
|
export class CertificateError extends Error {
|
|
12
6
|
constructor(message) {
|
|
13
7
|
super(message);
|
|
@@ -16,17 +10,32 @@ export class CertificateError extends Error {
|
|
|
16
10
|
Object.setPrototypeOf(this, CertificateError.prototype);
|
|
17
11
|
}
|
|
18
12
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Signs a certificate with the CA key
|
|
15
|
+
*
|
|
16
|
+
* @throws {@link CertificateError} when the certificate has no principals,
|
|
17
|
+
* as OpenSSH accepts a certificate without principals for any user or host
|
|
18
|
+
*/
|
|
19
|
+
const sign = async (key, fields, lifetime) => {
|
|
20
|
+
if (fields.principals.length === 0) {
|
|
21
|
+
throw new CertificateError("a certificate must have at least one principal");
|
|
22
|
+
}
|
|
23
|
+
// the validity is to the second, so starts at the current second rather
|
|
24
|
+
// than in the future
|
|
25
|
+
const validAfter = new Date(Math.floor(Date.now() / 1000) * 1000);
|
|
26
|
+
const validBefore = new Date(validAfter.getTime() + lifetime * 1000);
|
|
27
|
+
return await signCertificate({ ...fields, validAfter, validBefore }, await createSigner(key));
|
|
28
|
+
};
|
|
29
|
+
export const generateCertificate = async (env, email, key, public_key, options) => {
|
|
30
|
+
const { lifetime, principals, extensions, serial } = options ?? {};
|
|
31
|
+
// add principals
|
|
23
32
|
const includeSelf = env.SSH_CERTIFICATE_INCLUDE_SELF === undefined
|
|
24
33
|
? false
|
|
25
34
|
: typeof env.SSH_CERTIFICATE_INCLUDE_SELF === "string"
|
|
26
35
|
? env.SSH_CERTIFICATE_INCLUDE_SELF === "true"
|
|
27
36
|
: env.SSH_CERTIFICATE_INCLUDE_SELF;
|
|
28
|
-
const
|
|
29
|
-
? [
|
|
37
|
+
const certificatePrincipals = includeSelf
|
|
38
|
+
? [email.split("@")[0]]
|
|
30
39
|
: [];
|
|
31
40
|
// add email?
|
|
32
41
|
const includeSelfEmail = env.SSH_CERTIFICATE_INCLUDE_SELF_EMAIL === undefined
|
|
@@ -35,69 +44,34 @@ export const generateCertificate = (env, email, key, public_key, options) => {
|
|
|
35
44
|
? env.SSH_CERTIFICATE_INCLUDE_SELF_EMAIL === "true"
|
|
36
45
|
: env.SSH_CERTIFICATE_INCLUDE_SELF_EMAIL;
|
|
37
46
|
if (includeSelfEmail) {
|
|
38
|
-
|
|
47
|
+
certificatePrincipals.push(email);
|
|
39
48
|
}
|
|
40
49
|
if (principals !== undefined) {
|
|
41
|
-
|
|
42
|
-
identity.push(identityForUser(p));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const p of split(env.SSH_CERTIFICATE_PRINCIPALS)) {
|
|
46
|
-
identity.push(identityForUser(p));
|
|
47
|
-
}
|
|
48
|
-
// lifetime is the smaller of what was provided in the options or the default
|
|
49
|
-
lifetime = lifetime !== undefined
|
|
50
|
-
? Math.min(lifetime, seconds(env.SSH_CERTIFICATE_LIFETIME))
|
|
51
|
-
: seconds(env.SSH_CERTIFICATE_LIFETIME);
|
|
52
|
-
// generate value for serial of certificate
|
|
53
|
-
const s = generateSerial(serial);
|
|
54
|
-
// set issuer of certificate based on ISSUER_DN
|
|
55
|
-
const issuer = identityFromDN(env.ISSUER_DN);
|
|
56
|
-
// create certificate
|
|
57
|
-
const certificate = createCertificate(identity, public_key, issuer, key, { lifetime: lifetime, serial: s });
|
|
58
|
-
// ensure openssh info is included in certificate (should not occur)
|
|
59
|
-
if (certificate.signatures.openssh === undefined) {
|
|
60
|
-
throw new CertificateError("missing openssh information in certificate");
|
|
50
|
+
certificatePrincipals.push(...principals);
|
|
61
51
|
}
|
|
52
|
+
certificatePrincipals.push(...split(env.SSH_CERTIFICATE_PRINCIPALS));
|
|
62
53
|
const sshCertificateExtensions = split(env.SSH_CERTIFICATE_EXTENSIONS);
|
|
63
|
-
//
|
|
64
|
-
const sshextensions = [];
|
|
54
|
+
// if extensions are provided in request, ensure they do not include extra extensions beyond the defaults
|
|
65
55
|
if (extensions !== undefined) {
|
|
66
|
-
// if extensions are provided in request, ensure they do not include extra extensions beyond the defaults
|
|
67
56
|
for (const ext of extensions) {
|
|
68
57
|
if (!sshCertificateExtensions.includes(ext)) {
|
|
69
58
|
throw new CertificateError(`${ext} is not allowed`);
|
|
70
59
|
}
|
|
71
|
-
// add to list of allowed extensions
|
|
72
|
-
sshextensions.push({
|
|
73
|
-
critical: false,
|
|
74
|
-
name: ext,
|
|
75
|
-
data: Buffer.alloc(0)
|
|
76
|
-
});
|
|
77
60
|
}
|
|
78
61
|
}
|
|
79
|
-
|
|
62
|
+
// lifetime is the smaller of what was provided in the options or the default
|
|
63
|
+
const certificateLifetime = lifetime !== undefined
|
|
64
|
+
? Math.min(lifetime, seconds(env.SSH_CERTIFICATE_LIFETIME))
|
|
65
|
+
: seconds(env.SSH_CERTIFICATE_LIFETIME);
|
|
66
|
+
return await sign(key, {
|
|
67
|
+
kind: "user",
|
|
68
|
+
publicKey: public_key,
|
|
69
|
+
serial: serial ?? generateSerial(),
|
|
70
|
+
keyId: email,
|
|
71
|
+
principals: certificatePrincipals,
|
|
80
72
|
// use defaults if not provided
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
critical: false,
|
|
84
|
-
name: ext,
|
|
85
|
-
data: Buffer.alloc(0)
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
// add info to certificate
|
|
90
|
-
certificate.signatures = {
|
|
91
|
-
openssh: {
|
|
92
|
-
nonce: certificate.signatures.openssh.nonce,
|
|
93
|
-
keyId: email,
|
|
94
|
-
signature: certificate.signatures.openssh.signature,
|
|
95
|
-
exts: sshextensions
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
// re-sign after changes
|
|
99
|
-
certificate.signWith(key);
|
|
100
|
-
return certificate;
|
|
73
|
+
extensions: extensions ?? sshCertificateExtensions,
|
|
74
|
+
}, certificateLifetime);
|
|
101
75
|
};
|
|
102
76
|
export async function createSignedCertificate(env, email, public_key, options) {
|
|
103
77
|
// grab private key from secret store
|
|
@@ -109,7 +83,7 @@ export async function createSignedCertificate(env, email, public_key, options) {
|
|
|
109
83
|
extensions: split(env.SSH_CERTIFICATE_EXTENSIONS)
|
|
110
84
|
};
|
|
111
85
|
}
|
|
112
|
-
return generateCertificate(env, email, key, public_key, options);
|
|
86
|
+
return await generateCertificate(env, email, key, public_key, options);
|
|
113
87
|
}
|
|
114
88
|
export class BadIssuerError extends Error {
|
|
115
89
|
constructor(message) {
|
|
@@ -119,44 +93,26 @@ export class BadIssuerError extends Error {
|
|
|
119
93
|
}
|
|
120
94
|
}
|
|
121
95
|
export async function createSignedHostCertificate(env, public_key, options) {
|
|
122
|
-
|
|
123
|
-
options = {
|
|
124
|
-
lifetime: seconds(env.SSH_HOST_CERTIFICATE_LIFETIME),
|
|
125
|
-
principals: []
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
// generate list of identities for host key
|
|
129
|
-
const identity = [];
|
|
130
|
-
if (options.principals !== undefined) {
|
|
131
|
-
for (const p of options.principals) {
|
|
132
|
-
identity.push(identityForHost(p));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// add any already provided subjects (for renewals only)
|
|
136
|
-
if (options.subjects !== undefined) {
|
|
137
|
-
identity.push(...options.subjects);
|
|
138
|
-
}
|
|
96
|
+
const principals = options?.principals ?? [];
|
|
139
97
|
// grab private key from secret store
|
|
140
98
|
const key = await getPrivateKey(env);
|
|
141
99
|
// lifetime is the smaller of what was provided in the options or the default
|
|
142
|
-
const lifetime = options
|
|
100
|
+
const lifetime = options?.lifetime !== undefined
|
|
143
101
|
? Math.min(options.lifetime, seconds(env.SSH_HOST_CERTIFICATE_LIFETIME))
|
|
144
102
|
: seconds(env.SSH_HOST_CERTIFICATE_LIFETIME);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
103
|
+
return await sign(key, {
|
|
104
|
+
kind: "host",
|
|
105
|
+
publicKey: public_key,
|
|
106
|
+
serial: generateSerial(),
|
|
107
|
+
keyId: `host_${principals[0]}`,
|
|
108
|
+
principals,
|
|
109
|
+
extensions: [],
|
|
110
|
+
}, lifetime);
|
|
152
111
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
const randomBytes = crypto.getRandomValues(new Uint8Array(8));
|
|
160
|
-
return Buffer.from(randomBytes);
|
|
112
|
+
/**
|
|
113
|
+
* Returns a random certificate serial number
|
|
114
|
+
*/
|
|
115
|
+
export const generateSerial = () => {
|
|
116
|
+
return new DataView(crypto.getRandomValues(new Uint8Array(8)).buffer).getBigUint64(0);
|
|
161
117
|
};
|
|
162
118
|
//# sourceMappingURL=certificate.js.map
|
package/dist/certificate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certificate.js","sourceRoot":"","sources":["../src/certificate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"certificate.js","sourceRoot":"","sources":["../src/certificate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAEjD,OAAO,EAAE,eAAe,EAA+C,MAAM,sBAAsB,CAAA;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAQ9C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;QAE9B,yDAAyD;QACzD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAA;IACxD,CAAC;CACD;AAWD;;;;;GAKG;AACH,MAAM,IAAI,GAAG,KAAK,EAAE,GAAe,EAAE,MAA6D,EAAE,QAAgB,EAA2B,EAAE;IAChJ,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,gBAAgB,CAAC,gDAAgD,CAAC,CAAA;IAC7E,CAAC;IAED,wEAAwE;IACxE,qBAAqB;IACrB,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IACjE,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,QAAQ,GAAG,IAAI,CAAC,CAAA;IAEpE,OAAO,MAAM,eAAe,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,CAAA;AAC9F,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,EAAE,GAAkB,EAAE,KAAa,EAAE,GAAe,EAAE,UAAqB,EAAE,OAAoC,EAA2B,EAAE;IACrL,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,IAAI,EAAE,CAAA;IAElE,iBAAiB;IACjB,MAAM,WAAW,GAAG,GAAG,CAAC,4BAA4B,KAAK,SAAS;QACjE,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,OAAO,GAAG,CAAC,4BAA4B,KAAK,QAAQ;YACrD,CAAC,CAAC,GAAG,CAAC,4BAA4B,KAAK,MAAM;YAC7C,CAAC,CAAC,GAAG,CAAC,4BAA4B,CAAA;IACpC,MAAM,qBAAqB,GAAG,WAAW;QACxC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACxB,CAAC,CAAC,EAAE,CAAA;IAEL,aAAa;IACb,MAAM,gBAAgB,GAAG,GAAG,CAAC,kCAAkC,KAAK,SAAS;QAC5E,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,OAAO,GAAG,CAAC,kCAAkC,KAAK,QAAQ;YAC3D,CAAC,CAAC,GAAG,CAAC,kCAAkC,KAAK,MAAM;YACnD,CAAC,CAAC,GAAG,CAAC,kCAAkC,CAAA;IAC1C,IAAI,gBAAgB,EAAE,CAAC;QACtB,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC9B,qBAAqB,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAA;IAC1C,CAAC;IAED,qBAAqB,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAA;IAEpE,MAAM,wBAAwB,GAAG,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;IAEtE,yGAAyG;IACzG,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,wBAAwB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7C,MAAM,IAAI,gBAAgB,CAAC,GAAG,GAAG,iBAAiB,CAAC,CAAA;YACpD,CAAC;QACF,CAAC;IACF,CAAC;IAED,6EAA6E;IAC7E,MAAM,mBAAmB,GAAG,QAAQ,KAAK,SAAS;QACjD,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QAC3D,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAA;IAExC,OAAO,MAAM,IAAI,CAAC,GAAG,EAAE;QACtB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,UAAU;QACrB,MAAM,EAAE,MAAM,IAAI,cAAc,EAAE;QAClC,KAAK,EAAE,KAAK;QACZ,UAAU,EAAE,qBAAqB;QACjC,+BAA+B;QAC/B,UAAU,EAAE,UAAU,IAAI,wBAAwB;KAClD,EAAE,mBAAmB,CAAC,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,GAAkB,EAAE,KAAa,EAAE,UAAqB,EAAE,OAAkC;IACzI,qCAAqC;IACrC,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAA;IAEpC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,GAAG;YACT,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;YAC/C,UAAU,EAAE,EAAE;YACd,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC;SACjD,CAAA;IACF,CAAC;IAED,OAAO,MAAM,mBAAmB,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;AACvE,CAAC;AAOD,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;QAE5B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,CAAA;IACtD,CAAC;CACD;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,GAAkB,EAAE,UAAqB,EAAE,OAAsC;IAClI,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,EAAE,CAAA;IAE5C,qCAAqC;IACrC,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAA;IAEpC,6EAA6E;IAC7E,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,KAAK,SAAS;QAC/C,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QACxE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAA;IAE7C,OAAO,MAAM,IAAI,CAAC,GAAG,EAAE;QACtB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,UAAU;QACrB,MAAM,EAAE,cAAc,EAAE;QACxB,KAAK,EAAE,QAAQ,UAAU,CAAC,CAAC,CAAC,EAAE;QAC9B,UAAU;QACV,UAAU,EAAE,EAAE;KACd,EAAE,QAAQ,CAAC,CAAA;AACb,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,GAAW,EAAE;IAC1C,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AACtF,CAAC,CAAA"}
|
package/dist/db/index.d.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { PublicKey } from "../ssh/keys.js";
|
|
2
|
+
import type { SshCertificate } from "../ssh/certificate.js";
|
|
2
3
|
import type { SshCaBindings } from "../types.js";
|
|
3
4
|
import { CertificateType, type CertificateRecord } from "./types.js";
|
|
4
5
|
export declare const dbCleanup: (env: SshCaBindings) => Promise<void>;
|
|
5
|
-
export declare const recordCertificate: (env: SshCaBindings, certificate:
|
|
6
|
+
export declare const recordCertificate: (env: SshCaBindings, certificate: SshCertificate, keyid: string, certificateType?: CertificateType) => Promise<void>;
|
|
6
7
|
export declare const isRevoked: (env: SshCaBindings, serial: bigint) => Promise<boolean>;
|
|
7
8
|
export declare enum RevocationStatus {
|
|
8
9
|
NotFound = 0,
|
|
9
10
|
Revoked = 1,
|
|
10
|
-
Revokable = 2
|
|
11
|
-
Unrevokable = 3
|
|
11
|
+
Revokable = 2
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Returns whether a certificate can be revoked by the holder of a public key.
|
|
15
|
+
*
|
|
16
|
+
* The public key is compared by fingerprint, so a key with a different
|
|
17
|
+
* comment to the one the certificate was issued for still matches.
|
|
18
|
+
*
|
|
19
|
+
* @param env The CA bindings
|
|
20
|
+
* @param serial The serial of the certificate
|
|
21
|
+
* @param publicKey The public key the requester has proven possession of
|
|
22
|
+
* @param certificateType The type of certificate
|
|
23
|
+
* @returns The revocation status of the certificate
|
|
24
|
+
*/
|
|
25
|
+
export declare const revocationStatus: (env: SshCaBindings, serial: bigint, publicKey: PublicKey, certificateType: CertificateType) => Promise<RevocationStatus>;
|
|
14
26
|
export declare const getRevocationList: (env: SshCaBindings, certificateType: CertificateType) => Promise<string[]>;
|
|
15
27
|
type RevokedCertificate = Pick<CertificateRecord, "revoked_at">;
|
|
16
28
|
export declare const revokeCertificate: (env: SshCaBindings, serial: bigint) => Promise<RevokedCertificate[]>;
|
package/dist/db/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKhD,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAuC,MAAM,YAAY,CAAA;AAmBzG,eAAO,MAAM,SAAS,QAAe,aAAa,kBAsBjD,CAAA;AAED,eAAO,MAAM,iBAAiB,QAAe,aAAa,eAAe,cAAc,SAAS,MAAM,oBAAmB,eAAe,kBAsBvI,CAAA;AAED,eAAO,MAAM,SAAS,QAAe,aAAa,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CAYnF,CAAA;AAED,oBAAY,gBAAgB;IAC3B,QAAQ,IAAA;IACR,OAAO,IAAA;IACP,SAAS,IAAA;CACT;AAYD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,QAAe,aAAa,UAAU,MAAM,aAAa,SAAS,mBAAmB,eAAe,KAAG,OAAO,CAAC,gBAAgB,CAsB3J,CAAA;AAED,eAAO,MAAM,iBAAiB,QAAe,aAAa,mBAAmB,eAAe,KAAG,OAAO,CAAC,MAAM,EAAE,CAsB9G,CAAA;AAED,KAAK,kBAAkB,GAAG,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAA;AAE/D,eAAO,MAAM,iBAAiB,QAAe,aAAa,UAAU,MAAM,KAAG,OAAO,CAAC,kBAAkB,EAAE,CAuBxG,CAAA;AAUD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,QAAe,aAAa,mBAAmB,eAAe,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CA4CxH,CAAA"}
|
package/dist/db/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PublicKey } from "../ssh/keys.js";
|
|
1
2
|
import { logger } from "../logger.js";
|
|
2
3
|
import { D1QB } from "workers-qb";
|
|
3
4
|
import { z } from "zod";
|
|
@@ -40,26 +41,21 @@ export const dbCleanup = async (env) => {
|
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
43
|
export const recordCertificate = async (env, certificate, keyid, certificateType = CertificateType.User) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}).join(",");
|
|
47
|
-
const extensions = certificate.getExtensions().map((v) => {
|
|
48
|
-
// @ts-expect-error: the name property does exist
|
|
49
|
-
return v.name;
|
|
50
|
-
}).join(",");
|
|
44
|
+
// principals are recorded as distinguished names, as they were when
|
|
45
|
+
// certificates were created with sshpk
|
|
46
|
+
const principals = certificate.principals.map((p) => `${certificate.kind === "host" ? "CN" : "UID"}=${p}`).join(",");
|
|
51
47
|
const qb = await connect(env);
|
|
52
48
|
const res = await qb.insert({
|
|
53
49
|
tableName: "certificates",
|
|
54
50
|
data: {
|
|
55
|
-
serial: `${serial}`,
|
|
51
|
+
serial: `${certificate.serial}`,
|
|
56
52
|
key_id: keyid,
|
|
57
|
-
principals:
|
|
58
|
-
extensions: extensions,
|
|
59
|
-
valid_after: certificate.
|
|
60
|
-
valid_before: certificate.
|
|
53
|
+
principals: principals,
|
|
54
|
+
extensions: certificate.extensions.join(","),
|
|
55
|
+
valid_after: certificate.validAfter.toISOString(),
|
|
56
|
+
valid_before: certificate.validBefore.toISOString(),
|
|
61
57
|
certificate_type: certificateType,
|
|
62
|
-
public_key: certificate.
|
|
58
|
+
public_key: certificate.publicKey.toString()
|
|
63
59
|
},
|
|
64
60
|
}).execute();
|
|
65
61
|
if (!res.success) {
|
|
@@ -83,28 +79,48 @@ export var RevocationStatus;
|
|
|
83
79
|
RevocationStatus[RevocationStatus["NotFound"] = 0] = "NotFound";
|
|
84
80
|
RevocationStatus[RevocationStatus["Revoked"] = 1] = "Revoked";
|
|
85
81
|
RevocationStatus[RevocationStatus["Revokable"] = 2] = "Revokable";
|
|
86
|
-
RevocationStatus[RevocationStatus["Unrevokable"] = 3] = "Unrevokable";
|
|
87
82
|
})(RevocationStatus || (RevocationStatus = {}));
|
|
88
|
-
|
|
83
|
+
// whether a public key recorded in OpenSSH format is the same as another key,
|
|
84
|
+
// treating a recorded key that cannot be parsed as a different key
|
|
85
|
+
const isKey = (recorded, publicKey) => {
|
|
86
|
+
try {
|
|
87
|
+
return PublicKey.parse(recorded).equals(publicKey);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Returns whether a certificate can be revoked by the holder of a public key.
|
|
95
|
+
*
|
|
96
|
+
* The public key is compared by fingerprint, so a key with a different
|
|
97
|
+
* comment to the one the certificate was issued for still matches.
|
|
98
|
+
*
|
|
99
|
+
* @param env The CA bindings
|
|
100
|
+
* @param serial The serial of the certificate
|
|
101
|
+
* @param publicKey The public key the requester has proven possession of
|
|
102
|
+
* @param certificateType The type of certificate
|
|
103
|
+
* @returns The revocation status of the certificate
|
|
104
|
+
*/
|
|
105
|
+
export const revocationStatus = async (env, serial, publicKey, certificateType) => {
|
|
89
106
|
const qb = await connect(env);
|
|
90
107
|
const res = await qb.fetchOne({
|
|
91
108
|
tableName: "certificates",
|
|
92
109
|
fields: ["public_key", "revoked_at"],
|
|
93
110
|
where: {
|
|
94
|
-
conditions: "serial = ? AND
|
|
95
|
-
params: [`${serial}`,
|
|
111
|
+
conditions: "serial = ? AND certificate_type = ?",
|
|
112
|
+
params: [`${serial}`, certificateType]
|
|
96
113
|
}
|
|
97
114
|
}).execute();
|
|
98
|
-
|
|
115
|
+
// a certificate issued for another key is reported as not found, so
|
|
116
|
+
// requests cannot be used to discover which serials exist
|
|
117
|
+
if (res.results?.public_key == null || !isKey(res.results.public_key, publicKey)) {
|
|
99
118
|
return RevocationStatus.NotFound;
|
|
100
119
|
}
|
|
101
120
|
if (res.results.revoked_at !== null) {
|
|
102
121
|
return RevocationStatus.Revoked;
|
|
103
122
|
}
|
|
104
|
-
|
|
105
|
-
return RevocationStatus.Revokable;
|
|
106
|
-
}
|
|
107
|
-
return RevocationStatus.Unrevokable;
|
|
123
|
+
return RevocationStatus.Revokable;
|
|
108
124
|
};
|
|
109
125
|
export const getRevocationList = async (env, certificateType) => {
|
|
110
126
|
const qb = await connect(env);
|