@andrewheberle/serverless-ssh-ca 0.17.5 → 0.17.7
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/LICENSE +201 -0
- package/README.md +268 -0
- package/dist/api/v3/schema.js +2 -1
- package/dist/proof.js +1 -1
- package/dist/sshsig/formats.js +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +1 -2
- package/package.json +9 -13
- package/dist/src/api/v3/index.d.ts +0 -1
- package/dist/src/api/v3/index.js +0 -263
- package/dist/src/api/v3/schema.d.ts +0 -217
- package/dist/src/api/v3/schema.js +0 -216
- package/dist/src/certificate.d.ts +0 -30
- package/dist/src/certificate.js +0 -154
- package/dist/src/db.d.ts +0 -36
- package/dist/src/db.js +0 -111
- package/dist/src/index.d.ts +0 -6
- package/dist/src/index.js +0 -8
- package/dist/src/krl.d.ts +0 -39
- package/dist/src/krl.js +0 -317
- package/dist/src/logger.d.ts +0 -2
- package/dist/src/logger.js +0 -3
- package/dist/src/proof.d.ts +0 -51
- package/dist/src/proof.js +0 -154
- package/dist/src/router.d.ts +0 -14
- package/dist/src/router.js +0 -22
- package/dist/src/sshsig/armor.d.ts +0 -7
- package/dist/src/sshsig/armor.js +0 -18
- package/dist/src/sshsig/formats.d.ts +0 -52
- package/dist/src/sshsig/formats.js +0 -174
- package/dist/src/sshsig/index.d.ts +0 -15
- package/dist/src/sshsig/index.js +0 -31
- package/dist/src/sshsig/reader.d.ts +0 -20
- package/dist/src/sshsig/reader.js +0 -54
- package/dist/src/sshsig/sig.d.ts +0 -16
- package/dist/src/sshsig/sig.js +0 -1
- package/dist/src/sshsig/sig_parser.d.ts +0 -9
- package/dist/src/sshsig/sig_parser.js +0 -83
- package/dist/src/sshsig/verifier.d.ts +0 -2
- package/dist/src/sshsig/verifier.js +0 -37
- package/dist/src/sshsig/writer.d.ts +0 -17
- package/dist/src/sshsig/writer.js +0 -51
- package/dist/src/types.d.ts +0 -42
- package/dist/src/types.js +0 -20
- package/dist/src/utils.d.ts +0 -76
- package/dist/src/utils.js +0 -370
- package/dist/src/verify.d.ts +0 -6
- package/dist/src/verify.js +0 -17
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
export function parsePubkey(pk_algo, publickey, raw_publickey) {
|
|
2
|
-
let pubkey;
|
|
3
|
-
if (pk_algo === "ssh-rsa") {
|
|
4
|
-
pubkey = {
|
|
5
|
-
pk_algo,
|
|
6
|
-
e: new Uint8Array(publickey.readString().bytes()),
|
|
7
|
-
n: new Uint8Array(publickey.readString().bytes()),
|
|
8
|
-
toString() {
|
|
9
|
-
return `${pk_algo} ${base64Encode(new Uint8Array(raw_publickey))}`;
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
else if (pk_algo === "ssh-ed25519") {
|
|
14
|
-
pubkey = {
|
|
15
|
-
pk_algo,
|
|
16
|
-
key: new Uint8Array(publickey.readString().bytes()),
|
|
17
|
-
toString() {
|
|
18
|
-
return `${pk_algo} ${base64Encode(new Uint8Array(raw_publickey))}`;
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
else if (pk_algo === "sk-ssh-ed25519@openssh.com") {
|
|
23
|
-
const key = new Uint8Array(publickey.readString().bytes());
|
|
24
|
-
const application = publickey.readString().toString();
|
|
25
|
-
pubkey = {
|
|
26
|
-
pk_algo,
|
|
27
|
-
key,
|
|
28
|
-
application,
|
|
29
|
-
toString() {
|
|
30
|
-
return `${pk_algo} ${base64Encode(new Uint8Array(raw_publickey))}`;
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
else if (pk_algo === "ecdsa-sha2-nistp256" || pk_algo === "ecdsa-sha2-nistp384" ||
|
|
35
|
-
pk_algo === "ecdsa-sha2-nistp521") {
|
|
36
|
-
const curve = publickey.readString().toString();
|
|
37
|
-
const point = new Uint8Array(publickey.readString().bytes());
|
|
38
|
-
pubkey = {
|
|
39
|
-
pk_algo,
|
|
40
|
-
curve,
|
|
41
|
-
point,
|
|
42
|
-
toString() {
|
|
43
|
-
return `${pk_algo} ${base64Encode(new Uint8Array(raw_publickey))}`;
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
else if (pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
|
|
48
|
-
const curve = publickey.readString().toString();
|
|
49
|
-
const point = new Uint8Array(publickey.readString().bytes());
|
|
50
|
-
const application = publickey.readString().toString();
|
|
51
|
-
pubkey = {
|
|
52
|
-
pk_algo,
|
|
53
|
-
curve,
|
|
54
|
-
point,
|
|
55
|
-
application,
|
|
56
|
-
toString() {
|
|
57
|
-
return `${pk_algo} ${base64Encode(new Uint8Array(raw_publickey))}`;
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
throw new Error(`Unsupported pk_algo: ${pk_algo}`);
|
|
63
|
-
}
|
|
64
|
-
return pubkey;
|
|
65
|
-
}
|
|
66
|
-
function base64Encode(bytes) {
|
|
67
|
-
return btoa(String.fromCharCode.apply(null, bytes));
|
|
68
|
-
}
|
|
69
|
-
function base64UrlEncode(bytes) {
|
|
70
|
-
return base64Encode(bytes)
|
|
71
|
-
.replace(/\+/g, "-")
|
|
72
|
-
.replace(/\//g, "_")
|
|
73
|
-
.replace(/=/g, "");
|
|
74
|
-
}
|
|
75
|
-
export function convertPublicKey(publickey) {
|
|
76
|
-
const pk_algo = publickey.pk_algo;
|
|
77
|
-
if (pk_algo === "ssh-rsa") {
|
|
78
|
-
return {
|
|
79
|
-
keyData: {
|
|
80
|
-
kty: "RSA",
|
|
81
|
-
e: base64UrlEncode(publickey.e),
|
|
82
|
-
n: base64UrlEncode(publickey.n),
|
|
83
|
-
},
|
|
84
|
-
format: "jwk",
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
else if (pk_algo === "ssh-ed25519" || pk_algo === "sk-ssh-ed25519@openssh.com") {
|
|
88
|
-
return {
|
|
89
|
-
keyData: publickey.key.buffer,
|
|
90
|
-
format: "raw",
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
else if (pk_algo === "ecdsa-sha2-nistp256" || pk_algo === "ecdsa-sha2-nistp384" ||
|
|
94
|
-
pk_algo === "ecdsa-sha2-nistp521" ||
|
|
95
|
-
pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
|
|
96
|
-
if (publickey.point[0] !== 0x04) {
|
|
97
|
-
throw new Error("Only uncompressed (0x04) format is supported");
|
|
98
|
-
}
|
|
99
|
-
const point = publickey.point.slice(1);
|
|
100
|
-
let crv;
|
|
101
|
-
if (pk_algo === "ecdsa-sha2-nistp256" ||
|
|
102
|
-
pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
|
|
103
|
-
crv = "P-256";
|
|
104
|
-
}
|
|
105
|
-
else if (pk_algo === "ecdsa-sha2-nistp384") {
|
|
106
|
-
crv = "P-384";
|
|
107
|
-
}
|
|
108
|
-
{
|
|
109
|
-
crv = "P-521";
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
keyData: {
|
|
113
|
-
kty: "EC",
|
|
114
|
-
crv,
|
|
115
|
-
x: base64UrlEncode(point.slice(0, point.length / 2)),
|
|
116
|
-
y: base64UrlEncode(point.slice(point.length / 2)),
|
|
117
|
-
},
|
|
118
|
-
format: "jwk",
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
throw new Error(`Unsupported algo: ${pk_algo}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
export function convertAlgorithm(sig_algo) {
|
|
126
|
-
if (sig_algo === "rsa-sha2-512") {
|
|
127
|
-
return {
|
|
128
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
129
|
-
hash: { name: "SHA-512" },
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
else if (sig_algo === "ssh-ed25519" || sig_algo === "sk-ssh-ed25519@openssh.com") {
|
|
133
|
-
return {
|
|
134
|
-
name: "Ed25519",
|
|
135
|
-
hash: { name: "SHA-512" },
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
else if (sig_algo === "ecdsa-sha2-nistp256" ||
|
|
139
|
-
sig_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
|
|
140
|
-
return {
|
|
141
|
-
name: "ECDSA",
|
|
142
|
-
namedCurve: "P-256",
|
|
143
|
-
hash: { name: "SHA-256" },
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
else if (sig_algo === "ecdsa-sha2-nistp384") {
|
|
147
|
-
return {
|
|
148
|
-
name: "ECDSA",
|
|
149
|
-
namedCurve: "P-384",
|
|
150
|
-
hash: { name: "SHA-384" },
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
else if (sig_algo === "ecdsa-sha2-nistp521") {
|
|
154
|
-
return {
|
|
155
|
-
name: "ECDSA",
|
|
156
|
-
namedCurve: "P-521",
|
|
157
|
-
hash: { name: "SHA-512" },
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
throw new Error(`Unsupported algo: ${sig_algo}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
export function convertHash(hashName) {
|
|
165
|
-
if (hashName === "sha256") {
|
|
166
|
-
return "SHA-256";
|
|
167
|
-
}
|
|
168
|
-
else if (hashName === "sha512") {
|
|
169
|
-
return "SHA-512";
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
throw new Error(`Unknown hash: ${hashName}`);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Sig } from "./sig";
|
|
2
|
-
/**
|
|
3
|
-
* Verifies SSH signature against provided data.
|
|
4
|
-
*
|
|
5
|
-
* Returns `true` if the `signature` is a valid signature over `signed_data`, `false` otherwise.
|
|
6
|
-
*
|
|
7
|
-
* @param {Sig | string} signature SSH signature.
|
|
8
|
-
* @param {Uint8Array | string} signed_data Data that has been signed.
|
|
9
|
-
* @param {object} options Pass-in subtle crypto if required or a specific namespace.
|
|
10
|
-
* @returns {Promise<boolean>} Resolves to true if the signature is valid, to false otherwise.
|
|
11
|
-
*/
|
|
12
|
-
export declare function verify(signature: Sig | string, signed_data: Uint8Array | string, options?: {
|
|
13
|
-
subtle?: SubtleCrypto;
|
|
14
|
-
namespace?: string;
|
|
15
|
-
}): Promise<boolean>;
|
package/dist/src/sshsig/index.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { verify as rawVerify } from "./verifier";
|
|
2
|
-
import { parse } from "./sig_parser";
|
|
3
|
-
/**
|
|
4
|
-
* Verifies SSH signature against provided data.
|
|
5
|
-
*
|
|
6
|
-
* Returns `true` if the `signature` is a valid signature over `signed_data`, `false` otherwise.
|
|
7
|
-
*
|
|
8
|
-
* @param {Sig | string} signature SSH signature.
|
|
9
|
-
* @param {Uint8Array | string} signed_data Data that has been signed.
|
|
10
|
-
* @param {object} options Pass-in subtle crypto if required or a specific namespace.
|
|
11
|
-
* @returns {Promise<boolean>} Resolves to true if the signature is valid, to false otherwise.
|
|
12
|
-
*/
|
|
13
|
-
export async function verify(signature, signed_data, options) {
|
|
14
|
-
if (typeof options === "undefined") {
|
|
15
|
-
options = {};
|
|
16
|
-
}
|
|
17
|
-
const subtle = options.subtle || crypto.subtle;
|
|
18
|
-
if (typeof signature === "string") {
|
|
19
|
-
signature = parse(signature);
|
|
20
|
-
}
|
|
21
|
-
// check namespace matches if provided in options, otherwise default to
|
|
22
|
-
// signature's namespace. Reject if they don't match.
|
|
23
|
-
const namespace = options.namespace || signature.namespace;
|
|
24
|
-
if (namespace !== signature.namespace) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
if (typeof signed_data === "string") {
|
|
28
|
-
signed_data = new TextEncoder().encode(signed_data);
|
|
29
|
-
}
|
|
30
|
-
return await rawVerify(subtle, signature, signed_data);
|
|
31
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Byte stream reader helper.
|
|
3
|
-
*
|
|
4
|
-
* This class exposes several helper functions for reading SSH structured data.
|
|
5
|
-
*/
|
|
6
|
-
export declare class Reader {
|
|
7
|
-
private view;
|
|
8
|
-
private pos;
|
|
9
|
-
constructor(view: DataView);
|
|
10
|
-
readUint8(): number;
|
|
11
|
-
readUint32(): number;
|
|
12
|
-
peekUint32(): number;
|
|
13
|
-
peekBytes(pos: number, num: number): Reader;
|
|
14
|
-
readBytes(num: number): Reader;
|
|
15
|
-
readString(): Reader;
|
|
16
|
-
peekString(): Reader;
|
|
17
|
-
toString(): string;
|
|
18
|
-
bytes(): ArrayBuffer | SharedArrayBuffer;
|
|
19
|
-
get isAtEnd(): boolean;
|
|
20
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Byte stream reader helper.
|
|
3
|
-
*
|
|
4
|
-
* This class exposes several helper functions for reading SSH structured data.
|
|
5
|
-
*/
|
|
6
|
-
export class Reader {
|
|
7
|
-
view;
|
|
8
|
-
pos = 0;
|
|
9
|
-
constructor(view) {
|
|
10
|
-
this.view = view;
|
|
11
|
-
this.view = view;
|
|
12
|
-
}
|
|
13
|
-
readUint8() {
|
|
14
|
-
return this.view.getUint8(this.pos++);
|
|
15
|
-
}
|
|
16
|
-
readUint32() {
|
|
17
|
-
const v = this.peekUint32();
|
|
18
|
-
this.pos += 4;
|
|
19
|
-
return v;
|
|
20
|
-
}
|
|
21
|
-
peekUint32() {
|
|
22
|
-
return this.view.getUint32(this.pos);
|
|
23
|
-
}
|
|
24
|
-
peekBytes(pos, num) {
|
|
25
|
-
const dv = new DataView(this.view.buffer, pos + this.view.byteOffset, num);
|
|
26
|
-
return new Reader(dv);
|
|
27
|
-
}
|
|
28
|
-
readBytes(num) {
|
|
29
|
-
const reader = this.peekBytes(this.pos, num);
|
|
30
|
-
this.pos += num;
|
|
31
|
-
return reader;
|
|
32
|
-
}
|
|
33
|
-
readString() {
|
|
34
|
-
const len = this.readUint32();
|
|
35
|
-
return this.readBytes(len);
|
|
36
|
-
}
|
|
37
|
-
peekString() {
|
|
38
|
-
const len = this.peekUint32();
|
|
39
|
-
return this.peekBytes(this.pos + 4, len);
|
|
40
|
-
}
|
|
41
|
-
toString() {
|
|
42
|
-
let s = "";
|
|
43
|
-
for (let i = 0; i < this.view.byteLength; i++) {
|
|
44
|
-
s += String.fromCharCode(this.view.getUint8(i));
|
|
45
|
-
}
|
|
46
|
-
return s;
|
|
47
|
-
}
|
|
48
|
-
bytes() {
|
|
49
|
-
return this.view.buffer.slice(this.view.byteOffset + this.pos, this.view.byteOffset + this.view.byteLength);
|
|
50
|
-
}
|
|
51
|
-
get isAtEnd() {
|
|
52
|
-
return this.pos == this.view.byteLength;
|
|
53
|
-
}
|
|
54
|
-
}
|
package/dist/src/sshsig/sig.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Pubkey } from "./formats";
|
|
2
|
-
/**
|
|
3
|
-
* Represents a parsed SSH signature.
|
|
4
|
-
*/
|
|
5
|
-
export interface Sig {
|
|
6
|
-
publickey: Pubkey;
|
|
7
|
-
namespace: string;
|
|
8
|
-
reserved: ArrayBuffer;
|
|
9
|
-
hash_algorithm: string;
|
|
10
|
-
signature: {
|
|
11
|
-
sig_algo: string;
|
|
12
|
-
raw_signature: ArrayBuffer;
|
|
13
|
-
flags?: number;
|
|
14
|
-
counter?: number;
|
|
15
|
-
};
|
|
16
|
-
}
|
package/dist/src/sshsig/sig.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Sig } from "./sig";
|
|
2
|
-
/**
|
|
3
|
-
* Parse bytes into an SSH signature object.
|
|
4
|
-
*
|
|
5
|
-
* This function throws an Error when the signature is invalid or unsupported.
|
|
6
|
-
*
|
|
7
|
-
* @param {DataView | string} signature Raw bytes to parse.
|
|
8
|
-
*/
|
|
9
|
-
export declare function parse(signature: DataView | string): Sig;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Reader } from "./reader";
|
|
2
|
-
import { parsePubkey } from "./formats";
|
|
3
|
-
import { dearmor } from "./armor";
|
|
4
|
-
/**
|
|
5
|
-
* Parse bytes into an SSH signature object.
|
|
6
|
-
*
|
|
7
|
-
* This function throws an Error when the signature is invalid or unsupported.
|
|
8
|
-
*
|
|
9
|
-
* @param {DataView | string} signature Raw bytes to parse.
|
|
10
|
-
*/
|
|
11
|
-
export function parse(signature) {
|
|
12
|
-
let view;
|
|
13
|
-
if (typeof signature === "string") {
|
|
14
|
-
const bytes = dearmor(signature);
|
|
15
|
-
view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
view = signature;
|
|
19
|
-
}
|
|
20
|
-
const reader = new Reader(view);
|
|
21
|
-
const magic = reader.readBytes(6).toString();
|
|
22
|
-
if (magic !== "SSHSIG") {
|
|
23
|
-
throw new Error("Expected SSHSIG magic value but got unexpected bytes");
|
|
24
|
-
}
|
|
25
|
-
const version = reader.readUint32();
|
|
26
|
-
if (version !== 1) {
|
|
27
|
-
throw new Error("Expected version 1 but got unexpected version");
|
|
28
|
-
}
|
|
29
|
-
const raw_publickey = reader.peekString().bytes();
|
|
30
|
-
const publickey = reader.readString();
|
|
31
|
-
const pk_algo = publickey.readString().toString();
|
|
32
|
-
const pubkey = parsePubkey(pk_algo, publickey, raw_publickey);
|
|
33
|
-
const namespace = reader.readString().toString();
|
|
34
|
-
const reserved = reader.readString().bytes();
|
|
35
|
-
const hash_algorithm = reader.readString().toString();
|
|
36
|
-
const raw_signature = reader.readString();
|
|
37
|
-
const sig_algo = raw_signature.readString().toString();
|
|
38
|
-
const sig_bytes = raw_signature.readString();
|
|
39
|
-
let bytes;
|
|
40
|
-
if (sig_algo === "ecdsa-sha2-nistp256" || sig_algo === "ecdsa-sha2-nistp384" ||
|
|
41
|
-
sig_algo === "ecdsa-sha2-nistp512" ||
|
|
42
|
-
sig_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
|
|
43
|
-
let r = new Uint8Array(sig_bytes.readString().bytes());
|
|
44
|
-
if (r.length === 0) {
|
|
45
|
-
throw new Error("invalid ECDSA signature component");
|
|
46
|
-
}
|
|
47
|
-
if (r[0] === 0x00 && r.length % 2 == 1) {
|
|
48
|
-
r = r.slice(1);
|
|
49
|
-
}
|
|
50
|
-
let s = new Uint8Array(sig_bytes.readString().bytes());
|
|
51
|
-
if (s.length === 0) {
|
|
52
|
-
throw new Error("invalid ECDSA signature component");
|
|
53
|
-
}
|
|
54
|
-
if (s[0] === 0x00 && s.length % 2 == 1) {
|
|
55
|
-
s = s.slice(1);
|
|
56
|
-
}
|
|
57
|
-
bytes = new Uint8Array([...r, ...s]);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
bytes = sig_bytes.bytes();
|
|
61
|
-
}
|
|
62
|
-
let flags, counter;
|
|
63
|
-
if (sig_algo === "sk-ecdsa-sha2-nistp256@openssh.com" ||
|
|
64
|
-
sig_algo == "sk-ssh-ed25519@openssh.com") {
|
|
65
|
-
flags = new Uint8Array(raw_signature.readBytes(1).bytes())[0];
|
|
66
|
-
counter = raw_signature.readUint32();
|
|
67
|
-
}
|
|
68
|
-
if (!reader.isAtEnd) {
|
|
69
|
-
throw new Error("Signature was parsed but there were still bytes in the stream.");
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
publickey: pubkey,
|
|
73
|
-
namespace,
|
|
74
|
-
reserved,
|
|
75
|
-
hash_algorithm,
|
|
76
|
-
signature: {
|
|
77
|
-
sig_algo,
|
|
78
|
-
raw_signature: bytes,
|
|
79
|
-
flags,
|
|
80
|
-
counter,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { convertAlgorithm, convertHash, convertPublicKey } from "./formats";
|
|
2
|
-
import { Writer } from "./writer";
|
|
3
|
-
export async function verify(subtle, signature, signed_data) {
|
|
4
|
-
//https://nodejs.org/api/webcrypto.html#subtleimportkeyformat-keydata-algorithm-extractable-keyusages
|
|
5
|
-
// for 'RSASSA-PKCS1-v1_5' only spki, pkcs8 and jwk are supported
|
|
6
|
-
// https://stackoverflow.com/questions/46232571/webcrypto-importing-rsa-public-key-with-modulus-and-exponent-using-crypto-subtl
|
|
7
|
-
const { keyData, format } = convertPublicKey(signature.publickey);
|
|
8
|
-
const algorithm = convertAlgorithm(signature.signature.sig_algo);
|
|
9
|
-
const key = await subtle.importKey(format, keyData, algorithm, false, [
|
|
10
|
-
"verify",
|
|
11
|
-
]);
|
|
12
|
-
const writer = new Writer(96 + signature.namespace.length);
|
|
13
|
-
// https://github.com/openssh/openssh-portable/blob/d575cf44895104e0fcb0629920fb645207218129/PROTOCOL.sshsig
|
|
14
|
-
// MAGIC_PREAMBLE
|
|
15
|
-
writer.writeBytes("SSHSIG");
|
|
16
|
-
// namespace
|
|
17
|
-
writer.writeString(signature.namespace);
|
|
18
|
-
// reserved
|
|
19
|
-
writer.writeUint32(0);
|
|
20
|
-
// hash_algorithm
|
|
21
|
-
const hash = signature.hash_algorithm;
|
|
22
|
-
writer.writeString(hash);
|
|
23
|
-
const digest = new Uint8Array(await subtle.digest(convertHash(hash), signed_data));
|
|
24
|
-
writer.writeString(digest);
|
|
25
|
-
const data = writer.bytes();
|
|
26
|
-
if (signature.publickey.pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com" ||
|
|
27
|
-
signature.publickey.pk_algo === "sk-ssh-ed25519@openssh.com") {
|
|
28
|
-
// https://fuchsia.googlesource.com/third_party/openssh-portable/+/refs/heads/main/PROTOCOL.u2f#176
|
|
29
|
-
const u2f_data = new Writer(100);
|
|
30
|
-
u2f_data.writeBytes(await subtle.digest("SHA-256", Uint8Array.from(Array.prototype.map.call(signature.publickey.application, (x) => x.charCodeAt(0)))));
|
|
31
|
-
u2f_data.writeByte(signature.signature.flags || 0);
|
|
32
|
-
u2f_data.writeUint32(signature.signature.counter || 0);
|
|
33
|
-
u2f_data.writeBytes(await subtle.digest("SHA-256", data));
|
|
34
|
-
return await subtle.verify(algorithm, key, signature.signature.raw_signature, u2f_data.bytes());
|
|
35
|
-
}
|
|
36
|
-
return await subtle.verify(algorithm, key, signature.signature.raw_signature, data);
|
|
37
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Byte stream writer helper.
|
|
3
|
-
*
|
|
4
|
-
* This class exposes several helper functions for writing bytes into a buffer.
|
|
5
|
-
*/
|
|
6
|
-
export declare class Writer {
|
|
7
|
-
private pos;
|
|
8
|
-
private buffer;
|
|
9
|
-
private view;
|
|
10
|
-
constructor(maxByteLength: number);
|
|
11
|
-
private addResize;
|
|
12
|
-
writeUint32(num: number): void;
|
|
13
|
-
writeString(bytes: ArrayLike<number> | string): void;
|
|
14
|
-
writeBytes(bytes: ArrayLike<number> | string | ArrayBuffer): void;
|
|
15
|
-
writeByte(byte: number): void;
|
|
16
|
-
bytes(): Uint8Array<ArrayBuffer>;
|
|
17
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Byte stream writer helper.
|
|
3
|
-
*
|
|
4
|
-
* This class exposes several helper functions for writing bytes into a buffer.
|
|
5
|
-
*/
|
|
6
|
-
export class Writer {
|
|
7
|
-
pos = 0;
|
|
8
|
-
buffer;
|
|
9
|
-
view;
|
|
10
|
-
constructor(maxByteLength) {
|
|
11
|
-
this.buffer = new ArrayBuffer(0, { maxByteLength });
|
|
12
|
-
this.view = new DataView(this.buffer);
|
|
13
|
-
}
|
|
14
|
-
addResize(num) {
|
|
15
|
-
this.buffer.resize(this.buffer.byteLength + num);
|
|
16
|
-
}
|
|
17
|
-
writeUint32(num) {
|
|
18
|
-
this.addResize(4);
|
|
19
|
-
this.view.setUint32(this.pos, num);
|
|
20
|
-
this.pos += 4;
|
|
21
|
-
}
|
|
22
|
-
writeString(bytes) {
|
|
23
|
-
this.writeUint32(bytes.length);
|
|
24
|
-
this.writeBytes(bytes);
|
|
25
|
-
}
|
|
26
|
-
writeBytes(bytes) {
|
|
27
|
-
let uint8bytes;
|
|
28
|
-
if (typeof bytes === "string") {
|
|
29
|
-
uint8bytes = Array.prototype.map.call(bytes, (x) => x.charCodeAt(0));
|
|
30
|
-
}
|
|
31
|
-
else if (bytes instanceof ArrayBuffer) {
|
|
32
|
-
uint8bytes = new Uint8Array(bytes);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
uint8bytes = bytes;
|
|
36
|
-
}
|
|
37
|
-
// it's more efficient to resize once and write all bytes
|
|
38
|
-
// instead of calling `writeByte` in each iteration
|
|
39
|
-
this.addResize(uint8bytes.length);
|
|
40
|
-
for (let i = 0; i < uint8bytes.length; i++) {
|
|
41
|
-
this.view.setUint8(this.pos++, uint8bytes[i]);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
writeByte(byte) {
|
|
45
|
-
this.addResize(1);
|
|
46
|
-
this.view.setUint8(this.pos++, byte);
|
|
47
|
-
}
|
|
48
|
-
bytes() {
|
|
49
|
-
return new Uint8Array(this.buffer);
|
|
50
|
-
}
|
|
51
|
-
}
|
package/dist/src/types.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { JWTPayload } from "jose";
|
|
2
|
-
export type CertificateSignerPayload = {
|
|
3
|
-
public_key: string;
|
|
4
|
-
identity: string;
|
|
5
|
-
extensions?: string[];
|
|
6
|
-
lifetime?: number;
|
|
7
|
-
nonce?: string;
|
|
8
|
-
};
|
|
9
|
-
export type CertificateSignerResponse = {
|
|
10
|
-
certificate: string;
|
|
11
|
-
};
|
|
12
|
-
export type CertificateRequestJWTPayload = {
|
|
13
|
-
email: string;
|
|
14
|
-
sub: string;
|
|
15
|
-
[key: string]: string | string[];
|
|
16
|
-
} & JWTPayload;
|
|
17
|
-
export type SSHExtension = {
|
|
18
|
-
critical: boolean;
|
|
19
|
-
name: string;
|
|
20
|
-
data: Buffer<ArrayBuffer>;
|
|
21
|
-
};
|
|
22
|
-
export interface SshCaBindings {
|
|
23
|
-
DB: D1Database;
|
|
24
|
-
DB_CERTIFICATE_RETENTION: string;
|
|
25
|
-
PRIVATE_KEY: SecretsStoreSecret;
|
|
26
|
-
SSH_CERTIFICATE_EXTENSIONS: string;
|
|
27
|
-
SSH_CERTIFICATE_LIFETIME: string;
|
|
28
|
-
SSH_CERTIFICATE_INCLUDE_SELF: string;
|
|
29
|
-
SSH_CERTIFICATE_PRINCIPALS: string;
|
|
30
|
-
SSH_HOST_CERTIFICATE_LIFETIME: string;
|
|
31
|
-
SSH_HOST_CERTIFICATE_ALLOWED_EMAILS: string;
|
|
32
|
-
SSH_HOST_CERTIFICATE_ALLOWED_ROLES: string;
|
|
33
|
-
ISSUER_DN: string;
|
|
34
|
-
JWT_JWKS_URL: string;
|
|
35
|
-
JWT_AUD: string;
|
|
36
|
-
JWT_ISSUER: string;
|
|
37
|
-
JWT_ALGORITHMS: string;
|
|
38
|
-
JWT_SSH_CERTIFICATE_PRINCIPALS_CLAIM: string;
|
|
39
|
-
SSH_HOST_CERTIFICATE_ALLOWED_ROLES_CLAIM: string;
|
|
40
|
-
CERTIFICATE_REQUEST_TIME_SKEW_MAX: string;
|
|
41
|
-
LOG_LEVEL: string;
|
|
42
|
-
}
|
package/dist/src/types.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export {};
|
|
2
|
-
/*
|
|
3
|
-
ISSUER_DN: "CN=SSH CA,O=Internet Widgets Pty Ltd,C=US";
|
|
4
|
-
JWT_JWKS_URL: "https://idp.example.com/.well-known/jwks.json";
|
|
5
|
-
JWT_AUD: "audience";
|
|
6
|
-
JWT_ISSUER: "https://idp.example.com";
|
|
7
|
-
JWT_ALGORITHMS: "RS256";
|
|
8
|
-
JWT_SSH_CERTIFICATE_PRINCIPALS_CLAIM: "groups";
|
|
9
|
-
SSH_CERTIFICATE_LIFETIME: "24 hours";
|
|
10
|
-
SSH_CERTIFICATE_PRINCIPALS: "ssh-admin";
|
|
11
|
-
SSH_CERTIFICATE_INCLUDE_SELF: "false";
|
|
12
|
-
SSH_CERTIFICATE_EXTENSIONS: "permit-X11-forwarding,permit-agent-forwarding,permit-port-forwarding,permit-pty,permit-user-rc";
|
|
13
|
-
SSH_HOST_CERTIFICATE_ALLOWED_EMAILS: "";
|
|
14
|
-
SSH_HOST_CERTIFICATE_ALLOWED_ROLES: "";
|
|
15
|
-
SSH_HOST_CERTIFICATE_ALLOWED_ROLES_CLAIM: "groups";
|
|
16
|
-
SSH_HOST_CERTIFICATE_LIFETIME: "30 days";
|
|
17
|
-
CERTIFICATE_REQUEST_TIME_SKEW_MAX: "90 seconds";
|
|
18
|
-
DB_CERTIFICATE_RETENTION: "1 year";
|
|
19
|
-
LOG_LEVEL: "info"
|
|
20
|
-
*/
|
package/dist/src/utils.d.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { Certificate, Key, PrivateKey } from "sshpk";
|
|
2
|
-
import z from "zod";
|
|
3
|
-
import { CertificateRequestJWTPayload } from "./types";
|
|
4
|
-
import { RenewalProofOfPossession, ProofOfPossession } from "./proof";
|
|
5
|
-
export declare const fatalIssue: (ctx: z.RefinementCtx, message: string, val: unknown) => never;
|
|
6
|
-
export declare const transformProofOfPossession: (val: string, ctx: z.core.$RefinementCtx<string>) => ProofOfPossession | never;
|
|
7
|
-
type AccessToken = {
|
|
8
|
-
email: string;
|
|
9
|
-
sub: string;
|
|
10
|
-
};
|
|
11
|
-
export declare const transformAuthorizationHeader: (val: string, ctx: z.RefinementCtx) => Promise<AccessToken | never>;
|
|
12
|
-
export declare const transformPublicKey: (val: string | Buffer<ArrayBufferLike>, ctx: z.RefinementCtx) => Key | never;
|
|
13
|
-
export declare const identityPrincipals: (payload: CertificateRequestJWTPayload, claim?: string) => string[];
|
|
14
|
-
type IdentityToken = {
|
|
15
|
-
sub: string;
|
|
16
|
-
principals: string[];
|
|
17
|
-
};
|
|
18
|
-
export declare const parseIdentity: (jwt: string | undefined, claim?: string) => Promise<IdentityToken>;
|
|
19
|
-
export declare const transformIdentityToken: (val: string, ctx: z.RefinementCtx) => Promise<IdentityToken | never>;
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
* @param val A SSH certificate either a base64 encoded "string" or a "Buffer\<ArrayBufferLike\>"
|
|
23
|
-
* @param ctx The Zod context for any errors
|
|
24
|
-
* @returns
|
|
25
|
-
*/
|
|
26
|
-
export declare const transformCertificate: (val: string | Buffer<ArrayBufferLike>, ctx: z.RefinementCtx) => Certificate | never;
|
|
27
|
-
type ParsedCertificateRequest = {
|
|
28
|
-
proof: ProofOfPossession;
|
|
29
|
-
public_key: Key;
|
|
30
|
-
lifetime: number;
|
|
31
|
-
identity: IdentityToken;
|
|
32
|
-
extensions: string[];
|
|
33
|
-
};
|
|
34
|
-
export declare const refineCertificateRequest: (val: ParsedCertificateRequest, ctx: z.RefinementCtx) => Promise<never>;
|
|
35
|
-
type LegacyParsedCertificateRequest = {
|
|
36
|
-
nonce: ProofOfPossession;
|
|
37
|
-
public_key: Key;
|
|
38
|
-
lifetime: number;
|
|
39
|
-
identity: string;
|
|
40
|
-
extensions: string[];
|
|
41
|
-
};
|
|
42
|
-
export declare const refineLegacyCertificateRequest: (val: LegacyParsedCertificateRequest, ctx: z.RefinementCtx) => Promise<never>;
|
|
43
|
-
export declare const transformRenewalProofOfPossession: (val: string, ctx: z.RefinementCtx) => RenewalProofOfPossession | never;
|
|
44
|
-
type HostCertificateRequest = {
|
|
45
|
-
public_key: Key;
|
|
46
|
-
lifetime: number;
|
|
47
|
-
};
|
|
48
|
-
type ParsedHostCertificateRequest = {
|
|
49
|
-
principals: string[];
|
|
50
|
-
proof: ProofOfPossession;
|
|
51
|
-
} & HostCertificateRequest;
|
|
52
|
-
export declare const refineHostCertificateRequest: (val: ParsedHostCertificateRequest, ctx: z.RefinementCtx) => Promise<never>;
|
|
53
|
-
type LegacyParsedHostCertificateRequest = {
|
|
54
|
-
principals: string[];
|
|
55
|
-
nonce: ProofOfPossession;
|
|
56
|
-
} & HostCertificateRequest;
|
|
57
|
-
export declare const refineLegacyHostCertificateRequest: (val: LegacyParsedHostCertificateRequest, ctx: z.RefinementCtx) => Promise<never>;
|
|
58
|
-
type ParsedHostCertificateRenewal = {
|
|
59
|
-
certificate: Certificate;
|
|
60
|
-
proof: RenewalProofOfPossession;
|
|
61
|
-
} & HostCertificateRequest;
|
|
62
|
-
export declare const refineHostCertificateRenewal: (val: ParsedHostCertificateRenewal, ctx: z.RefinementCtx) => Promise<never>;
|
|
63
|
-
type ParsedRevokeCertificate = {
|
|
64
|
-
serial: bigint;
|
|
65
|
-
public_key: Key;
|
|
66
|
-
proof: ProofOfPossession;
|
|
67
|
-
};
|
|
68
|
-
export declare const refineRevokeCertificate: (val: ParsedRevokeCertificate, ctx: z.RefinementCtx) => Promise<never>;
|
|
69
|
-
type LegacyParsedHostCertificateRenewal = {
|
|
70
|
-
certificate: Certificate;
|
|
71
|
-
nonce: ProofOfPossession;
|
|
72
|
-
} & HostCertificateRequest;
|
|
73
|
-
export declare const refineLegacyHostCertificateRenewal: (val: LegacyParsedHostCertificateRenewal, ctx: z.RefinementCtx) => Promise<never>;
|
|
74
|
-
export declare const split: (v: string) => string[];
|
|
75
|
-
export declare const getPublic: (key?: PrivateKey) => Promise<Key>;
|
|
76
|
-
export {};
|