@electerm/ssh2 1.16.1 → 1.17.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/lib/client.js +41 -0
- package/lib/protocol/Protocol.js +76 -16
- package/lib/protocol/certificateAuth.js +104 -0
- package/lib/protocol/crypto/build/Makefile +5 -5
- package/lib/protocol/crypto/build/Release/.deps/Release/obj.target/sshcrypto/src/binding.o.d +219 -215
- package/lib/protocol/crypto/build/Release/.deps/Release/sshcrypto.node.d +1 -1
- package/lib/protocol/crypto/build/Release/obj.target/sshcrypto/src/binding.o +0 -0
- package/lib/protocol/crypto/build/Release/sshcrypto.node +0 -0
- package/lib/protocol/crypto/build/gyp-mac-tool +4 -8
- package/lib/protocol/crypto/build/sshcrypto.target.mk +35 -38
- package/lib/protocol/sshCertificate.js +243 -0
- package/package.json +1 -1
|
@@ -7,43 +7,40 @@ DEFS_Debug := \
|
|
|
7
7
|
'-DUSING_UV_SHARED=1' \
|
|
8
8
|
'-DUSING_V8_SHARED=1' \
|
|
9
9
|
'-DV8_DEPRECATION_WARNINGS=1' \
|
|
10
|
-
'-DV8_DEPRECATION_WARNINGS' \
|
|
11
|
-
'-DV8_IMMINENT_DEPRECATION_WARNINGS' \
|
|
12
10
|
'-D_GLIBCXX_USE_CXX11_ABI=1' \
|
|
11
|
+
'-D_FILE_OFFSET_BITS=64' \
|
|
13
12
|
'-D_DARWIN_USE_64_BIT_INODE=1' \
|
|
14
13
|
'-D_LARGEFILE_SOURCE' \
|
|
15
|
-
'-D_FILE_OFFSET_BITS=64' \
|
|
16
14
|
'-DOPENSSL_NO_PINSHARED' \
|
|
17
15
|
'-DOPENSSL_THREADS' \
|
|
18
16
|
'-DOPENSSL_API_COMPAT=0x10100000L' \
|
|
19
17
|
'-DREAL_OPENSSL_MAJOR=3' \
|
|
20
18
|
'-DBUILDING_NODE_EXTENSION' \
|
|
21
19
|
'-DDEBUG' \
|
|
22
|
-
'-D_DEBUG'
|
|
23
|
-
'-DV8_ENABLE_CHECKS'
|
|
20
|
+
'-D_DEBUG'
|
|
24
21
|
|
|
25
22
|
# Flags passed to all source files.
|
|
26
23
|
CFLAGS_Debug := \
|
|
27
24
|
-O0 \
|
|
28
25
|
-gdwarf-2 \
|
|
29
|
-
-
|
|
30
|
-
-
|
|
26
|
+
-fno-strict-aliasing \
|
|
27
|
+
-mmacosx-version-min=11.0 \
|
|
28
|
+
-arch \
|
|
29
|
+
x86_64 \
|
|
31
30
|
-Wall \
|
|
32
31
|
-Wendif-labels \
|
|
33
32
|
-W \
|
|
34
33
|
-Wno-unused-parameter
|
|
35
34
|
|
|
36
35
|
# Flags passed to only C files.
|
|
37
|
-
CFLAGS_C_Debug :=
|
|
38
|
-
-fno-strict-aliasing
|
|
36
|
+
CFLAGS_C_Debug :=
|
|
39
37
|
|
|
40
38
|
# Flags passed to only C++ files.
|
|
41
39
|
CFLAGS_CC_Debug := \
|
|
42
40
|
-std=gnu++17 \
|
|
43
41
|
-stdlib=libc++ \
|
|
44
42
|
-fno-rtti \
|
|
45
|
-
-fno-exceptions
|
|
46
|
-
-fno-strict-aliasing
|
|
43
|
+
-fno-exceptions
|
|
47
44
|
|
|
48
45
|
# Flags passed to only ObjC files.
|
|
49
46
|
CFLAGS_OBJC_Debug :=
|
|
@@ -52,13 +49,13 @@ CFLAGS_OBJC_Debug :=
|
|
|
52
49
|
CFLAGS_OBJCC_Debug :=
|
|
53
50
|
|
|
54
51
|
INCS_Debug := \
|
|
55
|
-
-I/Users/
|
|
56
|
-
-I/Users/
|
|
57
|
-
-I/Users/
|
|
58
|
-
-I/Users/
|
|
59
|
-
-I/Users/
|
|
60
|
-
-I/Users/
|
|
61
|
-
-I/Users/
|
|
52
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/include/node \
|
|
53
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/src \
|
|
54
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/openssl/config \
|
|
55
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/openssl/openssl/include \
|
|
56
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/uv/include \
|
|
57
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/zlib \
|
|
58
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/v8/include \
|
|
62
59
|
-I$(srcdir)/../../../node_modules/nan
|
|
63
60
|
|
|
64
61
|
DEFS_Release := \
|
|
@@ -66,12 +63,10 @@ DEFS_Release := \
|
|
|
66
63
|
'-DUSING_UV_SHARED=1' \
|
|
67
64
|
'-DUSING_V8_SHARED=1' \
|
|
68
65
|
'-DV8_DEPRECATION_WARNINGS=1' \
|
|
69
|
-
'-DV8_DEPRECATION_WARNINGS' \
|
|
70
|
-
'-DV8_IMMINENT_DEPRECATION_WARNINGS' \
|
|
71
66
|
'-D_GLIBCXX_USE_CXX11_ABI=1' \
|
|
67
|
+
'-D_FILE_OFFSET_BITS=64' \
|
|
72
68
|
'-D_DARWIN_USE_64_BIT_INODE=1' \
|
|
73
69
|
'-D_LARGEFILE_SOURCE' \
|
|
74
|
-
'-D_FILE_OFFSET_BITS=64' \
|
|
75
70
|
'-DOPENSSL_NO_PINSHARED' \
|
|
76
71
|
'-DOPENSSL_THREADS' \
|
|
77
72
|
'-DOPENSSL_API_COMPAT=0x10100000L' \
|
|
@@ -82,24 +77,24 @@ DEFS_Release := \
|
|
|
82
77
|
CFLAGS_Release := \
|
|
83
78
|
-O3 \
|
|
84
79
|
-gdwarf-2 \
|
|
85
|
-
-
|
|
86
|
-
-
|
|
80
|
+
-fno-strict-aliasing \
|
|
81
|
+
-mmacosx-version-min=11.0 \
|
|
82
|
+
-arch \
|
|
83
|
+
x86_64 \
|
|
87
84
|
-Wall \
|
|
88
85
|
-Wendif-labels \
|
|
89
86
|
-W \
|
|
90
87
|
-Wno-unused-parameter
|
|
91
88
|
|
|
92
89
|
# Flags passed to only C files.
|
|
93
|
-
CFLAGS_C_Release :=
|
|
94
|
-
-fno-strict-aliasing
|
|
90
|
+
CFLAGS_C_Release :=
|
|
95
91
|
|
|
96
92
|
# Flags passed to only C++ files.
|
|
97
93
|
CFLAGS_CC_Release := \
|
|
98
94
|
-std=gnu++17 \
|
|
99
95
|
-stdlib=libc++ \
|
|
100
96
|
-fno-rtti \
|
|
101
|
-
-fno-exceptions
|
|
102
|
-
-fno-strict-aliasing
|
|
97
|
+
-fno-exceptions
|
|
103
98
|
|
|
104
99
|
# Flags passed to only ObjC files.
|
|
105
100
|
CFLAGS_OBJC_Release :=
|
|
@@ -108,13 +103,13 @@ CFLAGS_OBJC_Release :=
|
|
|
108
103
|
CFLAGS_OBJCC_Release :=
|
|
109
104
|
|
|
110
105
|
INCS_Release := \
|
|
111
|
-
-I/Users/
|
|
112
|
-
-I/Users/
|
|
113
|
-
-I/Users/
|
|
114
|
-
-I/Users/
|
|
115
|
-
-I/Users/
|
|
116
|
-
-I/Users/
|
|
117
|
-
-I/Users/
|
|
106
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/include/node \
|
|
107
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/src \
|
|
108
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/openssl/config \
|
|
109
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/openssl/openssl/include \
|
|
110
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/uv/include \
|
|
111
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/zlib \
|
|
112
|
+
-I/Users/zxd/Library/Caches/node-gyp/22.19.0/deps/v8/include \
|
|
118
113
|
-I$(srcdir)/../../../node_modules/nan
|
|
119
114
|
|
|
120
115
|
OBJS := \
|
|
@@ -149,8 +144,9 @@ $(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
|
|
|
149
144
|
LDFLAGS_Debug := \
|
|
150
145
|
-undefined dynamic_lookup \
|
|
151
146
|
-Wl,-search_paths_first \
|
|
152
|
-
-mmacosx-version-min=
|
|
153
|
-
-arch
|
|
147
|
+
-mmacosx-version-min=11.0 \
|
|
148
|
+
-arch \
|
|
149
|
+
x86_64 \
|
|
154
150
|
-L$(builddir) \
|
|
155
151
|
-stdlib=libc++
|
|
156
152
|
|
|
@@ -161,8 +157,9 @@ LIBTOOLFLAGS_Debug := \
|
|
|
161
157
|
LDFLAGS_Release := \
|
|
162
158
|
-undefined dynamic_lookup \
|
|
163
159
|
-Wl,-search_paths_first \
|
|
164
|
-
-mmacosx-version-min=
|
|
165
|
-
-arch
|
|
160
|
+
-mmacosx-version-min=11.0 \
|
|
161
|
+
-arch \
|
|
162
|
+
x86_64 \
|
|
166
163
|
-L$(builddir) \
|
|
167
164
|
-stdlib=libc++
|
|
168
165
|
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { readUInt32BE } = require('./utils.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse SSH certificate format from complete certificate buffer
|
|
7
|
+
* RFC: https://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.17
|
|
8
|
+
*
|
|
9
|
+
* Certificate format (for ssh-rsa-cert-v01@openssh.com):
|
|
10
|
+
* string "ssh-rsa-cert-v01@openssh.com"
|
|
11
|
+
* string nonce
|
|
12
|
+
* mpint e (RSA exponent)
|
|
13
|
+
* mpint n (RSA modulus)
|
|
14
|
+
* uint64 serial
|
|
15
|
+
* uint32 type (1=user, 2=host)
|
|
16
|
+
* string key_id
|
|
17
|
+
* string valid_principals
|
|
18
|
+
* uint64 valid_after
|
|
19
|
+
* uint64 valid_before
|
|
20
|
+
* string critical_options
|
|
21
|
+
* string extensions
|
|
22
|
+
* string reserved
|
|
23
|
+
* string signature_key
|
|
24
|
+
* string signature
|
|
25
|
+
*/
|
|
26
|
+
function parseSSHCertificate(certBuffer) {
|
|
27
|
+
if (!Buffer.isBuffer(certBuffer) || certBuffer.length < 8) {
|
|
28
|
+
return new Error('Invalid certificate buffer');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let pos = 0;
|
|
32
|
+
|
|
33
|
+
function readString() {
|
|
34
|
+
if (pos + 4 > certBuffer.length) {
|
|
35
|
+
throw new Error('Unexpected end of certificate');
|
|
36
|
+
}
|
|
37
|
+
const len = readUInt32BE(certBuffer, pos);
|
|
38
|
+
pos += 4;
|
|
39
|
+
if (pos + len > certBuffer.length) {
|
|
40
|
+
throw new Error('Unexpected end of certificate');
|
|
41
|
+
}
|
|
42
|
+
const result = certBuffer.slice(pos, pos + len);
|
|
43
|
+
pos += len;
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readUInt64() {
|
|
48
|
+
if (pos + 8 > certBuffer.length) {
|
|
49
|
+
throw new Error('Unexpected end of certificate');
|
|
50
|
+
}
|
|
51
|
+
const result = certBuffer.readBigUInt64BE(pos);
|
|
52
|
+
pos += 8;
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readUInt32() {
|
|
57
|
+
if (pos + 4 > certBuffer.length) {
|
|
58
|
+
throw new Error('Unexpected end of certificate');
|
|
59
|
+
}
|
|
60
|
+
const result = readUInt32BE(certBuffer, pos);
|
|
61
|
+
pos += 4;
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const cert = {};
|
|
67
|
+
|
|
68
|
+
// 1. Type string
|
|
69
|
+
const typeStr = readString().toString('utf8');
|
|
70
|
+
cert.type = typeStr;
|
|
71
|
+
|
|
72
|
+
// 2. Nonce (random bytes for uniqueness)
|
|
73
|
+
cert.nonce = readString();
|
|
74
|
+
|
|
75
|
+
// 3-4. Key-specific fields - skip them based on key type
|
|
76
|
+
// For RSA: e (exponent), n (modulus)
|
|
77
|
+
// For ECDSA: curve identifier, Q (point)
|
|
78
|
+
// For ED25519: pk (public key)
|
|
79
|
+
if (typeStr.startsWith('ssh-rsa')) {
|
|
80
|
+
// RSA: e, n
|
|
81
|
+
readString(); // e
|
|
82
|
+
readString(); // n
|
|
83
|
+
} else if (typeStr.startsWith('ecdsa-sha2-nistp')) {
|
|
84
|
+
// ECDSA: curve, Q
|
|
85
|
+
readString(); // curve identifier
|
|
86
|
+
readString(); // Q
|
|
87
|
+
} else if (typeStr.startsWith('ssh-ed25519')) {
|
|
88
|
+
// ED25519: pk
|
|
89
|
+
readString(); // pk
|
|
90
|
+
} else if (typeStr.startsWith('ssh-dss')) {
|
|
91
|
+
// DSA: p, q, g, y
|
|
92
|
+
readString(); // p
|
|
93
|
+
readString(); // q
|
|
94
|
+
readString(); // g
|
|
95
|
+
readString(); // y
|
|
96
|
+
} else {
|
|
97
|
+
return new Error(`Unsupported certificate type: ${typeStr}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 5. Serial
|
|
101
|
+
cert.serial = readUInt64();
|
|
102
|
+
|
|
103
|
+
// 6. Type (1 = user cert, 2 = host cert)
|
|
104
|
+
const typeNum = readUInt32();
|
|
105
|
+
cert.certType = typeNum === 1 ? 'user' : typeNum === 2 ? 'host' : 'unknown';
|
|
106
|
+
|
|
107
|
+
// 7. Key ID (comment/identifier)
|
|
108
|
+
cert.keyId = readString().toString('utf8');
|
|
109
|
+
|
|
110
|
+
// 8. Valid principals (array of valid user/host names)
|
|
111
|
+
const principalsBuffer = readString();
|
|
112
|
+
cert.principals = [];
|
|
113
|
+
if (principalsBuffer.length > 0) {
|
|
114
|
+
let pPos = 0;
|
|
115
|
+
while (pPos < principalsBuffer.length) {
|
|
116
|
+
const len = readUInt32BE(principalsBuffer, pPos);
|
|
117
|
+
pPos += 4;
|
|
118
|
+
cert.principals.push(principalsBuffer.slice(pPos, pPos + len).toString('utf8'));
|
|
119
|
+
pPos += len;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 9. Valid after
|
|
124
|
+
cert.validAfter = readUInt64();
|
|
125
|
+
|
|
126
|
+
// 10. Valid before
|
|
127
|
+
cert.validBefore = readUInt64();
|
|
128
|
+
|
|
129
|
+
// 11. Critical options
|
|
130
|
+
const criticalBuffer = readString();
|
|
131
|
+
cert.criticalOptions = {};
|
|
132
|
+
if (criticalBuffer.length > 0) {
|
|
133
|
+
let cPos = 0;
|
|
134
|
+
while (cPos < criticalBuffer.length) {
|
|
135
|
+
const nameLen = readUInt32BE(criticalBuffer, cPos);
|
|
136
|
+
cPos += 4;
|
|
137
|
+
const name = criticalBuffer.slice(cPos, cPos + nameLen).toString('utf8');
|
|
138
|
+
cPos += nameLen;
|
|
139
|
+
const dataLen = readUInt32BE(criticalBuffer, cPos);
|
|
140
|
+
cPos += 4;
|
|
141
|
+
const data = criticalBuffer.slice(cPos, cPos + dataLen);
|
|
142
|
+
cPos += dataLen;
|
|
143
|
+
cert.criticalOptions[name] = data;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 12. Extensions
|
|
148
|
+
const extensionsBuffer = readString();
|
|
149
|
+
cert.extensions = {};
|
|
150
|
+
if (extensionsBuffer.length > 0) {
|
|
151
|
+
let ePos = 0;
|
|
152
|
+
while (ePos < extensionsBuffer.length) {
|
|
153
|
+
const nameLen = readUInt32BE(extensionsBuffer, ePos);
|
|
154
|
+
ePos += 4;
|
|
155
|
+
const name = extensionsBuffer.slice(ePos, ePos + nameLen).toString('utf8');
|
|
156
|
+
ePos += nameLen;
|
|
157
|
+
const dataLen = readUInt32BE(extensionsBuffer, ePos);
|
|
158
|
+
ePos += 4;
|
|
159
|
+
const data = extensionsBuffer.slice(ePos, ePos + dataLen);
|
|
160
|
+
ePos += dataLen;
|
|
161
|
+
cert.extensions[name] = data;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 13. Reserved (currently unused)
|
|
166
|
+
readString();
|
|
167
|
+
|
|
168
|
+
// 14. Signature key
|
|
169
|
+
cert.signatureKeyBlob = readString();
|
|
170
|
+
|
|
171
|
+
// 15. Signature
|
|
172
|
+
cert.signature = readString();
|
|
173
|
+
|
|
174
|
+
return cert;
|
|
175
|
+
} catch (err) {
|
|
176
|
+
return err;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check if a buffer represents an SSH certificate
|
|
182
|
+
*/
|
|
183
|
+
function isCertificate(data) {
|
|
184
|
+
if (!Buffer.isBuffer(data) || data.length < 16) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Check if the type string indicates a certificate (ends with -cert-v01@openssh.com or similar)
|
|
190
|
+
if (data.length < 4) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const len = readUInt32BE(data, 0);
|
|
195
|
+
if (len > 64 || data.length < 4 + len) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const type = data.slice(4, 4 + len).toString('utf8');
|
|
200
|
+
return type.includes('-cert-v');
|
|
201
|
+
} catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if a certificate is valid for the given username
|
|
208
|
+
*/
|
|
209
|
+
function validateCertificate(cert, username) {
|
|
210
|
+
if (cert instanceof Error) {
|
|
211
|
+
return { valid: false, reason: cert.message };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
215
|
+
|
|
216
|
+
// Check if currently expired
|
|
217
|
+
if (cert.validBefore && now >= cert.validBefore) {
|
|
218
|
+
return { valid: false, reason: 'Certificate has expired' };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check if not yet valid
|
|
222
|
+
if (cert.validAfter && now < cert.validAfter) {
|
|
223
|
+
return { valid: false, reason: 'Certificate is not yet valid' };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check if username is in principals (for user certificates)
|
|
227
|
+
if (cert.certType === 'user' && cert.principals && cert.principals.length > 0) {
|
|
228
|
+
if (!cert.principals.includes(username)) {
|
|
229
|
+
return {
|
|
230
|
+
valid: false,
|
|
231
|
+
reason: `Username "${username}" not in certificate principals: ${cert.principals.join(', ')}`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { valid: true };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
parseSSHCertificate,
|
|
241
|
+
isCertificate,
|
|
242
|
+
validateCertificate,
|
|
243
|
+
};
|
package/package.json
CHANGED