@andrewheberle/serverless-ssh-ca 0.22.0 → 0.24.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 +174 -1
- package/dist/api/operations.d.ts +50 -0
- package/dist/api/operations.d.ts.map +1 -0
- package/dist/api/operations.js +264 -0
- package/dist/api/operations.js.map +1 -0
- package/dist/api/schema.d.ts +72 -0
- package/dist/api/schema.d.ts.map +1 -0
- package/dist/api/schema.js +114 -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 +18 -104
- 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 +186 -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 +68 -9
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +165 -49
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations/0003_krl_revision.d.ts +3 -0
- package/dist/db/migrations/0003_krl_revision.d.ts.map +1 -0
- package/dist/db/migrations/0003_krl_revision.js +14 -0
- package/dist/db/migrations/0003_krl_revision.js.map +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -0
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/types.d.ts +2 -0
- package/dist/db/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/krl.d.ts +3 -2
- package/dist/krl.d.ts.map +1 -1
- package/dist/krl.js +12 -165
- package/dist/krl.js.map +1 -1
- package/dist/node/cache.d.ts +21 -0
- package/dist/node/cache.d.ts.map +1 -0
- package/dist/node/cache.js +49 -0
- package/dist/node/cache.js.map +1 -0
- package/dist/node/env.d.ts +6 -3
- package/dist/node/env.d.ts.map +1 -1
- package/dist/node/env.js +4 -3
- package/dist/node/env.js.map +1 -1
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -0
- package/dist/node/index.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 +25 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +41 -2
- 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 +92 -0
- package/dist/ssh/keys.d.ts.map +1 -0
- package/dist/ssh/keys.js +277 -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 +64 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +107 -21
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +251 -65
- package/dist/utils.js.map +1 -1
- package/dist/verify.d.ts +17 -5
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +47 -9
- package/dist/verify.js.map +1 -1
- package/openapi.json +1942 -1
- package/package.json +4 -3
- package/src/api/operations.ts +314 -0
- package/src/api/schema.ts +146 -0
- package/src/api/v3/index.ts +21 -248
- package/src/api/v3/schema.ts +36 -128
- package/src/api/v4/index.ts +266 -0
- package/src/api/v4/schema.ts +241 -0
- package/src/certificate.ts +62 -123
- package/src/db/index.ts +196 -56
- package/src/db/migrations/0003_krl_revision.ts +15 -0
- package/src/db/migrations/index.ts +2 -0
- package/src/db/types.ts +2 -0
- package/src/index.ts +6 -4
- package/src/krl.ts +13 -197
- package/src/node/cache.ts +62 -0
- package/src/node/env.ts +6 -5
- package/src/node/index.ts +1 -0
- package/src/proof.ts +65 -31
- package/src/router.ts +67 -3
- package/src/ssh/certificate.ts +236 -0
- package/src/ssh/encoding.ts +81 -0
- package/src/ssh/keys.ts +327 -0
- package/src/ssh/signer.ts +129 -0
- package/src/types.ts +60 -20
- package/src/utils.ts +285 -81
- package/src/verify.ts +54 -14
package/dist/utils.d.ts
CHANGED
|
@@ -1,17 +1,73 @@
|
|
|
1
1
|
import type { SshCaBindings } from "./types.js";
|
|
2
|
-
import
|
|
2
|
+
import { SshCertificate } from "./ssh/certificate.js";
|
|
3
|
+
import { PublicKey, UnsupportedKeyError, type PrivateKey } from "./ssh/keys.js";
|
|
3
4
|
import z from "zod";
|
|
4
5
|
import { CertificateRequestJWTPayload } from "./types.js";
|
|
5
6
|
import { RenewalProofOfPossession, ProofOfPossession } from "./proof.js";
|
|
6
7
|
import type { isRevoked as IsRevokedFn } from "./db/index.js";
|
|
8
|
+
export { UnsupportedKeyError };
|
|
7
9
|
export declare const fatalIssue: (ctx: z.RefinementCtx, message: string, val: unknown) => never;
|
|
8
10
|
export declare const transformProofOfPossession: (env: SshCaBindings, val: string, ctx: z.core.$RefinementCtx<string>) => ProofOfPossession | never;
|
|
9
11
|
type AccessToken = {
|
|
10
12
|
email: string;
|
|
11
13
|
sub: string;
|
|
12
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when a claim of a verified token is missing or is not of the
|
|
17
|
+
* expected type.
|
|
18
|
+
*/
|
|
19
|
+
export declare class TokenClaimError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns the subject of a verified token, which is compared between the
|
|
24
|
+
* access and identity tokens so must always be present.
|
|
25
|
+
*
|
|
26
|
+
* @param payload The payload of a verified token
|
|
27
|
+
* @returns The `sub` claim of the token
|
|
28
|
+
* @throws {@link TokenClaimError} when the `sub` claim is missing, empty or not a string
|
|
29
|
+
*/
|
|
30
|
+
export declare const tokenSubject: (payload: {
|
|
31
|
+
sub?: unknown;
|
|
32
|
+
}) => string;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the email address of a verified access token, or why it cannot be
|
|
35
|
+
* used.
|
|
36
|
+
*
|
|
37
|
+
* @param payload The payload of a verified access token
|
|
38
|
+
* @returns The email address, or an error message if the claims are not acceptable
|
|
39
|
+
*/
|
|
40
|
+
export declare const tokenEmail: (payload: Record<string, unknown>) => {
|
|
41
|
+
ok: true;
|
|
42
|
+
email: string;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
error: string;
|
|
46
|
+
};
|
|
13
47
|
export declare const transformAuthorizationHeader: (env: SshCaBindings, val: string, ctx: z.RefinementCtx) => Promise<AccessToken | never>;
|
|
14
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Parses a public key in OpenSSH format, `${type} ${base64}[ ${comment}]`
|
|
50
|
+
*
|
|
51
|
+
* @param val The public key, either base64 encoded or as bytes
|
|
52
|
+
* @param ctx The Zod context for any errors
|
|
53
|
+
*/
|
|
54
|
+
export declare const transformPublicKey: (val: string | Buffer<ArrayBufferLike>, ctx: z.RefinementCtx) => PublicKey | never;
|
|
55
|
+
/**
|
|
56
|
+
* Returns why a public key cannot be issued a certificate, or undefined if it
|
|
57
|
+
* can be.
|
|
58
|
+
*
|
|
59
|
+
* RSA keys must be at least {@link MinimumRsaKeyBits} bits, as smaller keys
|
|
60
|
+
* can be factored and are refused by current OpenSSH releases.
|
|
61
|
+
*
|
|
62
|
+
* @param key The public key a certificate would be issued for
|
|
63
|
+
* @returns The reason the key cannot be used, or undefined if it can be
|
|
64
|
+
*/
|
|
65
|
+
export declare const subjectKeyIssue: (key: PublicKey) => string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Refines a public key so only one allowed by {@link subjectKeyIssue} is
|
|
68
|
+
* accepted for a certificate request.
|
|
69
|
+
*/
|
|
70
|
+
export declare const refineSubjectKey: (val: PublicKey, ctx: z.RefinementCtx) => void;
|
|
15
71
|
/**
|
|
16
72
|
* Thrown by {@link identityPrincipals} when the principals claim of an
|
|
17
73
|
* identity token is not a string or an array of strings.
|
|
@@ -32,10 +88,10 @@ export declare const transformIdentityToken: (env: SshCaBindings, val: string, c
|
|
|
32
88
|
* @param ctx The Zod context for any errors
|
|
33
89
|
* @returns
|
|
34
90
|
*/
|
|
35
|
-
export declare const transformCertificate: (val: string | Buffer<ArrayBufferLike>, ctx: z.RefinementCtx) =>
|
|
91
|
+
export declare const transformCertificate: (val: string | Buffer<ArrayBufferLike>, ctx: z.RefinementCtx) => SshCertificate | never;
|
|
36
92
|
type ParsedCertificateRequest = {
|
|
37
93
|
proof: ProofOfPossession;
|
|
38
|
-
public_key:
|
|
94
|
+
public_key: PublicKey;
|
|
39
95
|
lifetime: number;
|
|
40
96
|
identity: IdentityToken;
|
|
41
97
|
extensions: string[];
|
|
@@ -43,7 +99,7 @@ type ParsedCertificateRequest = {
|
|
|
43
99
|
export declare const refineCertificateRequest: (env: SshCaBindings, val: ParsedCertificateRequest, ctx: z.RefinementCtx) => Promise<never>;
|
|
44
100
|
export declare const transformRenewalProofOfPossession: (env: SshCaBindings, val: string, ctx: z.RefinementCtx) => RenewalProofOfPossession | never;
|
|
45
101
|
type HostCertificateRequest = {
|
|
46
|
-
public_key:
|
|
102
|
+
public_key: PublicKey;
|
|
47
103
|
lifetime: number;
|
|
48
104
|
};
|
|
49
105
|
type ParsedHostCertificateRequest = {
|
|
@@ -51,37 +107,67 @@ type ParsedHostCertificateRequest = {
|
|
|
51
107
|
proof: ProofOfPossession;
|
|
52
108
|
} & HostCertificateRequest;
|
|
53
109
|
export declare const refineHostCertificateRequest: (env: SshCaBindings, val: ParsedHostCertificateRequest, ctx: z.RefinementCtx) => Promise<never>;
|
|
110
|
+
/**
|
|
111
|
+
* Returns why a principal cannot be included on a host certificate, or
|
|
112
|
+
* undefined if it can be.
|
|
113
|
+
*
|
|
114
|
+
* OpenSSH matches the principals of a host certificate as patterns, so a
|
|
115
|
+
* principal such as `*` would make the certificate valid for every host that
|
|
116
|
+
* trusts the CA. Principals are otherwise not restricted, as hosts may be
|
|
117
|
+
* reached by IP address, bare host name or fully qualified domain name.
|
|
118
|
+
*
|
|
119
|
+
* @param principal The principal to check
|
|
120
|
+
* @returns The reason the principal cannot be used, or undefined if it can be
|
|
121
|
+
*/
|
|
122
|
+
export declare const hostPrincipalIssue: (principal: string) => string | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Refines a host certificate principal so only one allowed by
|
|
125
|
+
* {@link hostPrincipalIssue} is accepted.
|
|
126
|
+
*/
|
|
127
|
+
export declare const refineHostPrincipal: (val: string, ctx: z.RefinementCtx) => void;
|
|
54
128
|
type ParsedHostCertificateRenewal = {
|
|
55
|
-
certificate:
|
|
129
|
+
certificate: SshCertificate;
|
|
56
130
|
proof: RenewalProofOfPossession;
|
|
57
131
|
} & HostCertificateRequest;
|
|
132
|
+
/**
|
|
133
|
+
* Checks a certificate presented for renewal was issued by this CA, is a host
|
|
134
|
+
* certificate whose principals are allowed (see {@link hostPrincipalIssue}),
|
|
135
|
+
* is within its validity period and has not been revoked.
|
|
136
|
+
*
|
|
137
|
+
* @param env The CA bindings
|
|
138
|
+
* @param isRevoked Returns whether a serial has been revoked
|
|
139
|
+
* @param certificate The certificate presented for renewal
|
|
140
|
+
* @returns The reason the certificate cannot be renewed, or undefined if it can be
|
|
141
|
+
* @throws {@link UnsupportedKeyError} when the CA key is not supported
|
|
142
|
+
*/
|
|
143
|
+
export declare const checkRenewableCertificate: (env: SshCaBindings, isRevoked: typeof IsRevokedFn, certificate: SshCertificate) => Promise<string | undefined>;
|
|
144
|
+
/**
|
|
145
|
+
* Refines a host certificate renewal so only a certificate that can be
|
|
146
|
+
* renewed (see {@link checkRenewableCertificate}) is accepted.
|
|
147
|
+
*/
|
|
148
|
+
export declare const refineRenewableCertificate: (env: SshCaBindings, isRevoked: typeof IsRevokedFn, val: {
|
|
149
|
+
certificate: SshCertificate;
|
|
150
|
+
}, ctx: z.RefinementCtx) => Promise<never>;
|
|
58
151
|
export declare const refineHostCertificateRenewal: (env: SshCaBindings, isRevoked: typeof IsRevokedFn, val: ParsedHostCertificateRenewal, ctx: z.RefinementCtx) => Promise<never>;
|
|
59
152
|
type ParsedRevokeCertificate = {
|
|
60
153
|
serial: bigint;
|
|
61
|
-
public_key:
|
|
154
|
+
public_key: PublicKey;
|
|
62
155
|
proof: ProofOfPossession;
|
|
63
156
|
};
|
|
64
157
|
export declare const refineRevokeCertificate: (env: SshCaBindings, val: ParsedRevokeCertificate, ctx: z.RefinementCtx) => Promise<never>;
|
|
65
158
|
export declare const split: (v?: string | string[]) => string[];
|
|
66
|
-
/**
|
|
67
|
-
* Thrown by {@link getPrivateKey} when the CA private key is of a type that
|
|
68
|
-
* cannot be used.
|
|
69
|
-
*/
|
|
70
|
-
export declare class UnsupportedKeyError extends Error {
|
|
71
|
-
constructor(message: string);
|
|
72
|
-
}
|
|
73
159
|
/**
|
|
74
160
|
* Load and parse the CA private key from the `PRIVATE_KEY` secret.
|
|
75
161
|
*
|
|
76
|
-
* Only Ed25519 and ECDSA keys are supported
|
|
77
|
-
*
|
|
78
|
-
* later refuse by default, and because RSA keys in OpenSSH format are
|
|
79
|
-
* converted with invalid CRT parameters that the Workers runtime rejects.
|
|
162
|
+
* Only Ed25519 and ECDSA keys are supported, as these are the key types that
|
|
163
|
+
* certificates and KRLs are signed with (see `createSigner` in `ssh/signer.ts`).
|
|
80
164
|
*
|
|
81
|
-
* @throws KeyParseError when the secret cannot be parsed.
|
|
165
|
+
* @throws {@link KeyParseError} when the secret cannot be parsed.
|
|
82
166
|
* @throws {@link UnsupportedKeyError} when the key is not Ed25519 or ECDSA.
|
|
83
167
|
*/
|
|
84
168
|
export declare const getPrivateKey: (env: SshCaBindings) => Promise<PrivateKey>;
|
|
85
|
-
|
|
86
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Returns the public key of the CA, with `ISSUER_DN` as its comment
|
|
171
|
+
*/
|
|
172
|
+
export declare const getPublic: (env: SshCaBindings, key?: PrivateKey) => Promise<PublicKey>;
|
|
87
173
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { JWKInvalid, JWKSInvalid, JWSInvalid, JWSSignatureVerificationFailed, JWTClaimValidationFailed, JWTExpired, JWTInvalid } from "jose/errors";
|
|
2
|
-
import
|
|
2
|
+
import { CertificateParseError, SshCertificate } from "./ssh/certificate.js";
|
|
3
|
+
import { KeyParseError, MinimumRsaKeyBits, parsePrivateKey, PublicKey, rsaKeyBits, UnsupportedKeyError } from "./ssh/keys.js";
|
|
3
4
|
import z from "zod";
|
|
4
|
-
import { verifyJWT } from "./verify.js";
|
|
5
|
+
import { MissingAudienceError, verifyJWT } from "./verify.js";
|
|
5
6
|
import { RenewalProofOfPossession, ProofOfPossession, PossessionParseError } from "./proof.js";
|
|
6
7
|
import { logger } from "./logger.js";
|
|
7
8
|
import { ms } from "itty-time";
|
|
8
9
|
import { InternalServerErrorException } from "chanfana";
|
|
9
|
-
|
|
10
|
-
const { KeyParseError, CertificateParseError, parseCertificate, parseKey, parsePrivateKey } = sshpk;
|
|
10
|
+
export { UnsupportedKeyError };
|
|
11
11
|
export const fatalIssue = (ctx, message, val) => {
|
|
12
12
|
ctx.issues.push({
|
|
13
13
|
code: "custom",
|
|
@@ -42,6 +42,64 @@ export const transformProofOfPossession = (env, val, ctx) => {
|
|
|
42
42
|
return z.NEVER;
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Thrown when a claim of a verified token is missing or is not of the
|
|
47
|
+
* expected type.
|
|
48
|
+
*/
|
|
49
|
+
export class TokenClaimError extends Error {
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "TokenClaimError";
|
|
53
|
+
Object.setPrototypeOf(this, TokenClaimError.prototype);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const SubjectClaimSchema = z.string().min(1);
|
|
57
|
+
/**
|
|
58
|
+
* Returns the subject of a verified token, which is compared between the
|
|
59
|
+
* access and identity tokens so must always be present.
|
|
60
|
+
*
|
|
61
|
+
* @param payload The payload of a verified token
|
|
62
|
+
* @returns The `sub` claim of the token
|
|
63
|
+
* @throws {@link TokenClaimError} when the `sub` claim is missing, empty or not a string
|
|
64
|
+
*/
|
|
65
|
+
export const tokenSubject = (payload) => {
|
|
66
|
+
const parsed = SubjectClaimSchema.safeParse(payload.sub);
|
|
67
|
+
if (!parsed.success) {
|
|
68
|
+
throw new TokenClaimError("JWT was verified but was missing required sub claim");
|
|
69
|
+
}
|
|
70
|
+
return parsed.data;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The claims of an access token that identify the user's email address.
|
|
74
|
+
*
|
|
75
|
+
* `email_verified` is optional as not every IdP includes it, but when it is
|
|
76
|
+
* included it must be true, as an IdP may otherwise issue a token for an
|
|
77
|
+
* address the user has not shown they control.
|
|
78
|
+
*/
|
|
79
|
+
const EmailClaimsSchema = z.object({
|
|
80
|
+
email: z.email(),
|
|
81
|
+
email_verified: z.boolean().optional(),
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Returns the email address of a verified access token, or why it cannot be
|
|
85
|
+
* used.
|
|
86
|
+
*
|
|
87
|
+
* @param payload The payload of a verified access token
|
|
88
|
+
* @returns The email address, or an error message if the claims are not acceptable
|
|
89
|
+
*/
|
|
90
|
+
export const tokenEmail = (payload) => {
|
|
91
|
+
if (payload.email === undefined) {
|
|
92
|
+
return { ok: false, error: "JWT was verified but was missing required email claim" };
|
|
93
|
+
}
|
|
94
|
+
const parsed = EmailClaimsSchema.safeParse(payload);
|
|
95
|
+
if (!parsed.success) {
|
|
96
|
+
return { ok: false, error: "JWT was verified but the email or email_verified claim was not valid" };
|
|
97
|
+
}
|
|
98
|
+
if (parsed.data.email_verified === false) {
|
|
99
|
+
return { ok: false, error: "JWT was verified but the email address has not been verified by the IdP" };
|
|
100
|
+
}
|
|
101
|
+
return { ok: true, email: parsed.data.email };
|
|
102
|
+
};
|
|
45
103
|
export const transformAuthorizationHeader = async (env, val, ctx) => {
|
|
46
104
|
if (!val.startsWith("Bearer ") || val.length <= 7) {
|
|
47
105
|
return fatalIssue(ctx, "authorization header must be a Bearer token", val);
|
|
@@ -52,18 +110,27 @@ export const transformAuthorizationHeader = async (env, val, ctx) => {
|
|
|
52
110
|
return fatalIssue(ctx, "request did not contain a JWT", val);
|
|
53
111
|
}
|
|
54
112
|
try {
|
|
55
|
-
// verify jwt
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
113
|
+
// verify jwt, which must be issued for this CA
|
|
114
|
+
const aud = split(env.JWT_ACCESS_TOKEN_AUD).length > 0 ? env.JWT_ACCESS_TOKEN_AUD : env.JWT_AUD;
|
|
115
|
+
const { payload } = await verifyJWT(env, jwt, aud);
|
|
116
|
+
const email = tokenEmail(payload);
|
|
117
|
+
if (!email.ok) {
|
|
118
|
+
l.warn(email.error, "in", "transformAuthorizationHeader");
|
|
119
|
+
return fatalIssue(ctx, email.error, val);
|
|
59
120
|
}
|
|
60
121
|
return {
|
|
61
|
-
email:
|
|
62
|
-
sub: payload
|
|
122
|
+
email: email.email,
|
|
123
|
+
sub: tokenSubject(payload),
|
|
63
124
|
};
|
|
64
125
|
}
|
|
65
126
|
catch (err) {
|
|
66
127
|
switch (true) {
|
|
128
|
+
case (err instanceof MissingAudienceError):
|
|
129
|
+
// a misconfigured CA is a server error, not a validation failure
|
|
130
|
+
l.error(err.message, "in", "transformAuthorizationHeader");
|
|
131
|
+
throw new InternalServerErrorException(err.message);
|
|
132
|
+
case (err instanceof TokenClaimError):
|
|
133
|
+
return fatalIssue(ctx, err.message, val);
|
|
67
134
|
case (err instanceof JWSInvalid):
|
|
68
135
|
return fatalIssue(ctx, "the access token was invalid", val);
|
|
69
136
|
case (err instanceof JWTInvalid):
|
|
@@ -84,10 +151,19 @@ export const transformAuthorizationHeader = async (env, val, ctx) => {
|
|
|
84
151
|
}
|
|
85
152
|
}
|
|
86
153
|
};
|
|
154
|
+
// decodes a value sent as base64, which may already have been decoded by the schema
|
|
155
|
+
const fromBase64 = (val) => {
|
|
156
|
+
return (typeof val === "string" ? Buffer.from(val, "base64") : Buffer.from(val)).toString("utf8");
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Parses a public key in OpenSSH format, `${type} ${base64}[ ${comment}]`
|
|
160
|
+
*
|
|
161
|
+
* @param val The public key, either base64 encoded or as bytes
|
|
162
|
+
* @param ctx The Zod context for any errors
|
|
163
|
+
*/
|
|
87
164
|
export const transformPublicKey = (val, ctx) => {
|
|
88
165
|
try {
|
|
89
|
-
|
|
90
|
-
return key;
|
|
166
|
+
return PublicKey.parse(fromBase64(val));
|
|
91
167
|
}
|
|
92
168
|
catch (err) {
|
|
93
169
|
switch (true) {
|
|
@@ -115,6 +191,33 @@ export const transformPublicKey = (val, ctx) => {
|
|
|
115
191
|
return z.NEVER;
|
|
116
192
|
}
|
|
117
193
|
};
|
|
194
|
+
/**
|
|
195
|
+
* Returns why a public key cannot be issued a certificate, or undefined if it
|
|
196
|
+
* can be.
|
|
197
|
+
*
|
|
198
|
+
* RSA keys must be at least {@link MinimumRsaKeyBits} bits, as smaller keys
|
|
199
|
+
* can be factored and are refused by current OpenSSH releases.
|
|
200
|
+
*
|
|
201
|
+
* @param key The public key a certificate would be issued for
|
|
202
|
+
* @returns The reason the key cannot be used, or undefined if it can be
|
|
203
|
+
*/
|
|
204
|
+
export const subjectKeyIssue = (key) => {
|
|
205
|
+
const bits = rsaKeyBits(key);
|
|
206
|
+
if (bits !== undefined && bits < MinimumRsaKeyBits) {
|
|
207
|
+
return `RSA keys must be at least ${MinimumRsaKeyBits} bits, not ${bits}`;
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Refines a public key so only one allowed by {@link subjectKeyIssue} is
|
|
213
|
+
* accepted for a certificate request.
|
|
214
|
+
*/
|
|
215
|
+
export const refineSubjectKey = (val, ctx) => {
|
|
216
|
+
const issue = subjectKeyIssue(val);
|
|
217
|
+
if (issue !== undefined) {
|
|
218
|
+
fatalIssue(ctx, issue, val.toString());
|
|
219
|
+
}
|
|
220
|
+
};
|
|
118
221
|
/**
|
|
119
222
|
* Thrown by {@link identityPrincipals} when the principals claim of an
|
|
120
223
|
* identity token is not a string or an array of strings.
|
|
@@ -161,11 +264,11 @@ export const parseIdentity = async (env, jwt, claim) => {
|
|
|
161
264
|
if (jwt === undefined) {
|
|
162
265
|
throw new Error("missing identity token");
|
|
163
266
|
}
|
|
164
|
-
const
|
|
165
|
-
const
|
|
267
|
+
const { payload } = await verifyJWT(env, jwt, env.JWT_AUD);
|
|
268
|
+
const sub = tokenSubject(payload);
|
|
166
269
|
const principals = identityPrincipals(env, payload, claim);
|
|
167
270
|
return {
|
|
168
|
-
sub:
|
|
271
|
+
sub: sub,
|
|
169
272
|
principals: principals,
|
|
170
273
|
};
|
|
171
274
|
};
|
|
@@ -175,9 +278,14 @@ export const transformIdentityToken = async (env, val, ctx) => {
|
|
|
175
278
|
return identity;
|
|
176
279
|
}
|
|
177
280
|
catch (err) {
|
|
281
|
+
// a misconfigured CA is a server error, not a validation failure
|
|
282
|
+
if (err instanceof MissingAudienceError) {
|
|
283
|
+
logger(env).error(err.message, "in", "transformIdentityToken");
|
|
284
|
+
throw new InternalServerErrorException(err.message);
|
|
285
|
+
}
|
|
178
286
|
ctx.issues.push({
|
|
179
287
|
code: "custom",
|
|
180
|
-
message: err instanceof PrincipalsClaimError ? err.message : "problem parsing identity token",
|
|
288
|
+
message: err instanceof PrincipalsClaimError || err instanceof TokenClaimError ? err.message : "problem parsing identity token",
|
|
181
289
|
input: val
|
|
182
290
|
});
|
|
183
291
|
return z.NEVER;
|
|
@@ -191,8 +299,7 @@ export const transformIdentityToken = async (env, val, ctx) => {
|
|
|
191
299
|
*/
|
|
192
300
|
export const transformCertificate = (val, ctx) => {
|
|
193
301
|
try {
|
|
194
|
-
|
|
195
|
-
return cert;
|
|
302
|
+
return SshCertificate.parse(fromBase64(val));
|
|
196
303
|
}
|
|
197
304
|
catch (err) {
|
|
198
305
|
switch (true) {
|
|
@@ -257,7 +364,7 @@ export const refineHostCertificateRequest = async (env, val, ctx) => {
|
|
|
257
364
|
l.debug("started");
|
|
258
365
|
try {
|
|
259
366
|
// check proof of possession fingerprint matches public key
|
|
260
|
-
l.debug("checking val.proof.matches()", "public_key", val.public_key.toString(
|
|
367
|
+
l.debug("checking val.proof.matches()", "public_key", val.public_key.toString());
|
|
261
368
|
if (!val.proof.matches(val.public_key)) {
|
|
262
369
|
l.warn("proof of possession fingerprint did not match public_key");
|
|
263
370
|
return fatalIssue(ctx, "proof of possession fingerprint did not match public_key", val);
|
|
@@ -277,31 +384,129 @@ export const refineHostCertificateRequest = async (env, val, ctx) => {
|
|
|
277
384
|
return fatalIssue(ctx, "proof of possession verification unhandled error", val);
|
|
278
385
|
}
|
|
279
386
|
};
|
|
280
|
-
|
|
387
|
+
// characters that OpenSSH treats as pattern syntax when matching host
|
|
388
|
+
// certificate principals, or that separate entries in a pattern list
|
|
389
|
+
const HostPrincipalPatternChars = /[*?!,]/;
|
|
390
|
+
// whitespace and control characters, which no host name contains
|
|
391
|
+
const HostPrincipalInvalidChars = /[\s\p{Cc}]/u;
|
|
392
|
+
/**
|
|
393
|
+
* Returns why a principal cannot be included on a host certificate, or
|
|
394
|
+
* undefined if it can be.
|
|
395
|
+
*
|
|
396
|
+
* OpenSSH matches the principals of a host certificate as patterns, so a
|
|
397
|
+
* principal such as `*` would make the certificate valid for every host that
|
|
398
|
+
* trusts the CA. Principals are otherwise not restricted, as hosts may be
|
|
399
|
+
* reached by IP address, bare host name or fully qualified domain name.
|
|
400
|
+
*
|
|
401
|
+
* @param principal The principal to check
|
|
402
|
+
* @returns The reason the principal cannot be used, or undefined if it can be
|
|
403
|
+
*/
|
|
404
|
+
export const hostPrincipalIssue = (principal) => {
|
|
405
|
+
if (principal === "") {
|
|
406
|
+
return "host certificate principals must not be empty";
|
|
407
|
+
}
|
|
408
|
+
if (HostPrincipalPatternChars.test(principal)) {
|
|
409
|
+
return `host certificate principal ${JSON.stringify(principal)} must not contain wildcard or pattern characters (*, ?, ! or ,)`;
|
|
410
|
+
}
|
|
411
|
+
if (HostPrincipalInvalidChars.test(principal)) {
|
|
412
|
+
return `host certificate principal ${JSON.stringify(principal)} must not contain whitespace or control characters`;
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Refines a host certificate principal so only one allowed by
|
|
418
|
+
* {@link hostPrincipalIssue} is accepted.
|
|
419
|
+
*/
|
|
420
|
+
export const refineHostPrincipal = (val, ctx) => {
|
|
421
|
+
const issue = hostPrincipalIssue(val);
|
|
422
|
+
if (issue !== undefined) {
|
|
423
|
+
fatalIssue(ctx, issue, val);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* Checks a certificate presented for renewal was issued by this CA, is a host
|
|
428
|
+
* certificate whose principals are allowed (see {@link hostPrincipalIssue}),
|
|
429
|
+
* is within its validity period and has not been revoked.
|
|
430
|
+
*
|
|
431
|
+
* @param env The CA bindings
|
|
432
|
+
* @param isRevoked Returns whether a serial has been revoked
|
|
433
|
+
* @param certificate The certificate presented for renewal
|
|
434
|
+
* @returns The reason the certificate cannot be renewed, or undefined if it can be
|
|
435
|
+
* @throws {@link UnsupportedKeyError} when the CA key is not supported
|
|
436
|
+
*/
|
|
437
|
+
export const checkRenewableCertificate = async (env, isRevoked, certificate) => {
|
|
438
|
+
// ensure certificate is signed by CA
|
|
439
|
+
const issuer = await getPublic(env);
|
|
440
|
+
if (!await certificate.isSignedBy(issuer)) {
|
|
441
|
+
return "the provided certificate was not signed by this CA";
|
|
442
|
+
}
|
|
443
|
+
// only host certificates can be renewed, as renewal does not need the
|
|
444
|
+
// OIDC authentication a user certificate is issued with
|
|
445
|
+
if (certificate.kind !== "host") {
|
|
446
|
+
return "the provided certificate is not a host certificate";
|
|
447
|
+
}
|
|
448
|
+
// a certificate issued before small RSA keys were refused must not be
|
|
449
|
+
// renewed, as the renewal would keep its key
|
|
450
|
+
const keyIssue = subjectKeyIssue(certificate.publicKey);
|
|
451
|
+
if (keyIssue !== undefined) {
|
|
452
|
+
return `the provided certificate cannot be renewed: ${keyIssue}`;
|
|
453
|
+
}
|
|
454
|
+
// a certificate issued before wildcard principals were refused must not
|
|
455
|
+
// be renewed, as the renewal would keep its principals
|
|
456
|
+
for (const principal of certificate.principals) {
|
|
457
|
+
const issue = hostPrincipalIssue(principal);
|
|
458
|
+
if (issue !== undefined) {
|
|
459
|
+
return `the provided certificate cannot be renewed: ${issue}`;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// check certificate is within its validity period, allowing for clock
|
|
463
|
+
// skew at the start as a certificate issued moments ago may have a
|
|
464
|
+
// validity start up to a second in the future
|
|
465
|
+
const now = Date.now();
|
|
466
|
+
if (now >= certificate.validBefore.getTime()) {
|
|
467
|
+
return "the provided certificate is expired";
|
|
468
|
+
}
|
|
469
|
+
if (certificate.validAfter.getTime() > now + ms(env.CERTIFICATE_REQUEST_TIME_SKEW_MAX)) {
|
|
470
|
+
return "the provided certificate is not yet valid";
|
|
471
|
+
}
|
|
472
|
+
// ensure certificate presented for renewal is not revoked
|
|
473
|
+
if (await isRevoked(env, certificate.serial)) {
|
|
474
|
+
return "the provided certificate is revoked";
|
|
475
|
+
}
|
|
476
|
+
return undefined;
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* Refines a host certificate renewal so only a certificate that can be
|
|
480
|
+
* renewed (see {@link checkRenewableCertificate}) is accepted.
|
|
481
|
+
*/
|
|
482
|
+
export const refineRenewableCertificate = async (env, isRevoked, val, ctx) => {
|
|
281
483
|
const l = logger(env);
|
|
282
484
|
try {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return fatalIssue(ctx, "the provided certificate was not signed by this CA", val);
|
|
485
|
+
const reason = await checkRenewableCertificate(env, isRevoked, val.certificate);
|
|
486
|
+
if (reason !== undefined) {
|
|
487
|
+
return fatalIssue(ctx, reason, val);
|
|
287
488
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (val.certificate.validFrom.getTime() > now + ms(env.CERTIFICATE_REQUEST_TIME_SKEW_MAX)) {
|
|
296
|
-
return fatalIssue(ctx, "the provided certificate is not yet valid", val);
|
|
489
|
+
return z.NEVER;
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
// a misconfigured CA key is a server error, not a validation failure
|
|
493
|
+
if (err instanceof UnsupportedKeyError) {
|
|
494
|
+
l.error(err.message, "in", "refineRenewableCertificate");
|
|
495
|
+
throw new InternalServerErrorException(err.message);
|
|
297
496
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
497
|
+
l.error("certificate renewal check unhandled error", "in", "refineRenewableCertificate", "error", err);
|
|
498
|
+
return fatalIssue(ctx, "certificate renewal check unhandled error", val);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
export const refineHostCertificateRenewal = async (env, isRevoked, val, ctx) => {
|
|
502
|
+
const l = logger(env);
|
|
503
|
+
try {
|
|
504
|
+
const reason = await checkRenewableCertificate(env, isRevoked, val.certificate);
|
|
505
|
+
if (reason !== undefined) {
|
|
506
|
+
return fatalIssue(ctx, reason, val);
|
|
302
507
|
}
|
|
303
508
|
// check proof of possession fingerprint matches public key and certificate subject key
|
|
304
|
-
if (!val.proof.matches(val.public_key, val.certificate.
|
|
509
|
+
if (!val.proof.matches(val.public_key, val.certificate.publicKey)) {
|
|
305
510
|
return fatalIssue(ctx, "proof of possession fingerprints did not match public_key and/or certificate", val);
|
|
306
511
|
}
|
|
307
512
|
// verify proof of possession signature
|
|
@@ -349,46 +554,27 @@ export const split = (v) => {
|
|
|
349
554
|
: v.split(",")
|
|
350
555
|
: v;
|
|
351
556
|
};
|
|
352
|
-
/**
|
|
353
|
-
* Thrown by {@link getPrivateKey} when the CA private key is of a type that
|
|
354
|
-
* cannot be used.
|
|
355
|
-
*/
|
|
356
|
-
export class UnsupportedKeyError extends Error {
|
|
357
|
-
constructor(message) {
|
|
358
|
-
super(message);
|
|
359
|
-
this.name = "UnsupportedKeyError";
|
|
360
|
-
Object.setPrototypeOf(this, UnsupportedKeyError.prototype);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
557
|
/**
|
|
364
558
|
* Load and parse the CA private key from the `PRIVATE_KEY` secret.
|
|
365
559
|
*
|
|
366
|
-
* Only Ed25519 and ECDSA keys are supported
|
|
367
|
-
*
|
|
368
|
-
* later refuse by default, and because RSA keys in OpenSSH format are
|
|
369
|
-
* converted with invalid CRT parameters that the Workers runtime rejects.
|
|
560
|
+
* Only Ed25519 and ECDSA keys are supported, as these are the key types that
|
|
561
|
+
* certificates and KRLs are signed with (see `createSigner` in `ssh/signer.ts`).
|
|
370
562
|
*
|
|
371
|
-
* @throws KeyParseError when the secret cannot be parsed.
|
|
563
|
+
* @throws {@link KeyParseError} when the secret cannot be parsed.
|
|
372
564
|
* @throws {@link UnsupportedKeyError} when the key is not Ed25519 or ECDSA.
|
|
373
565
|
*/
|
|
374
566
|
export const getPrivateKey = async (env) => {
|
|
375
567
|
// grab private key from secret store
|
|
376
568
|
const secret = await env.PRIVATE_KEY.get();
|
|
377
|
-
|
|
378
|
-
const key = parsePrivateKey(secret);
|
|
379
|
-
if (key.type !== "ed25519" && key.type !== "ecdsa") {
|
|
380
|
-
throw new UnsupportedKeyError(`CA key type ${key.type} is not supported, the CA key must be Ed25519 or ECDSA`);
|
|
381
|
-
}
|
|
382
|
-
return key;
|
|
569
|
+
return parsePrivateKey(secret);
|
|
383
570
|
};
|
|
571
|
+
/**
|
|
572
|
+
* Returns the public key of the CA, with `ISSUER_DN` as its comment
|
|
573
|
+
*/
|
|
384
574
|
export const getPublic = async (env, key) => {
|
|
385
575
|
if (key === undefined) {
|
|
386
576
|
key = await getPrivateKey(env);
|
|
387
577
|
}
|
|
388
|
-
|
|
389
|
-
const pub = key.toPublic();
|
|
390
|
-
pub.comment = env.ISSUER_DN;
|
|
391
|
-
// return in ssh format trimmed of any whitespace
|
|
392
|
-
return pub;
|
|
578
|
+
return key.publicKey.withComment(env.ISSUER_DN);
|
|
393
579
|
};
|
|
394
580
|
//# sourceMappingURL=utils.js.map
|