@electerm/ssh2 1.16.2 → 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.
@@ -0,0 +1,191 @@
1
+ # This file is generated by gyp; do not edit.
2
+
3
+ TOOLSET := target
4
+ TARGET := sshcrypto
5
+ DEFS_Debug := \
6
+ '-DNODE_GYP_MODULE_NAME=sshcrypto' \
7
+ '-DUSING_UV_SHARED=1' \
8
+ '-DUSING_V8_SHARED=1' \
9
+ '-DV8_DEPRECATION_WARNINGS=1' \
10
+ '-D_GLIBCXX_USE_CXX11_ABI=1' \
11
+ '-D_FILE_OFFSET_BITS=64' \
12
+ '-D_DARWIN_USE_64_BIT_INODE=1' \
13
+ '-D_LARGEFILE_SOURCE' \
14
+ '-DOPENSSL_NO_PINSHARED' \
15
+ '-DOPENSSL_THREADS' \
16
+ '-DOPENSSL_API_COMPAT=0x10100000L' \
17
+ '-DREAL_OPENSSL_MAJOR=3' \
18
+ '-DBUILDING_NODE_EXTENSION' \
19
+ '-DDEBUG' \
20
+ '-D_DEBUG'
21
+
22
+ # Flags passed to all source files.
23
+ CFLAGS_Debug := \
24
+ -O0 \
25
+ -gdwarf-2 \
26
+ -fno-strict-aliasing \
27
+ -mmacosx-version-min=11.0 \
28
+ -arch \
29
+ x86_64 \
30
+ -Wall \
31
+ -Wendif-labels \
32
+ -W \
33
+ -Wno-unused-parameter
34
+
35
+ # Flags passed to only C files.
36
+ CFLAGS_C_Debug :=
37
+
38
+ # Flags passed to only C++ files.
39
+ CFLAGS_CC_Debug := \
40
+ -std=gnu++17 \
41
+ -stdlib=libc++ \
42
+ -fno-rtti \
43
+ -fno-exceptions
44
+
45
+ # Flags passed to only ObjC files.
46
+ CFLAGS_OBJC_Debug :=
47
+
48
+ # Flags passed to only ObjC++ files.
49
+ CFLAGS_OBJCC_Debug :=
50
+
51
+ INCS_Debug := \
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 \
59
+ -I$(srcdir)/../../../node_modules/nan
60
+
61
+ DEFS_Release := \
62
+ '-DNODE_GYP_MODULE_NAME=sshcrypto' \
63
+ '-DUSING_UV_SHARED=1' \
64
+ '-DUSING_V8_SHARED=1' \
65
+ '-DV8_DEPRECATION_WARNINGS=1' \
66
+ '-D_GLIBCXX_USE_CXX11_ABI=1' \
67
+ '-D_FILE_OFFSET_BITS=64' \
68
+ '-D_DARWIN_USE_64_BIT_INODE=1' \
69
+ '-D_LARGEFILE_SOURCE' \
70
+ '-DOPENSSL_NO_PINSHARED' \
71
+ '-DOPENSSL_THREADS' \
72
+ '-DOPENSSL_API_COMPAT=0x10100000L' \
73
+ '-DREAL_OPENSSL_MAJOR=3' \
74
+ '-DBUILDING_NODE_EXTENSION'
75
+
76
+ # Flags passed to all source files.
77
+ CFLAGS_Release := \
78
+ -O3 \
79
+ -gdwarf-2 \
80
+ -fno-strict-aliasing \
81
+ -mmacosx-version-min=11.0 \
82
+ -arch \
83
+ x86_64 \
84
+ -Wall \
85
+ -Wendif-labels \
86
+ -W \
87
+ -Wno-unused-parameter
88
+
89
+ # Flags passed to only C files.
90
+ CFLAGS_C_Release :=
91
+
92
+ # Flags passed to only C++ files.
93
+ CFLAGS_CC_Release := \
94
+ -std=gnu++17 \
95
+ -stdlib=libc++ \
96
+ -fno-rtti \
97
+ -fno-exceptions
98
+
99
+ # Flags passed to only ObjC files.
100
+ CFLAGS_OBJC_Release :=
101
+
102
+ # Flags passed to only ObjC++ files.
103
+ CFLAGS_OBJCC_Release :=
104
+
105
+ INCS_Release := \
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 \
113
+ -I$(srcdir)/../../../node_modules/nan
114
+
115
+ OBJS := \
116
+ $(obj).target/$(TARGET)/src/binding.o
117
+
118
+ # Add to the list of files we specially track dependencies for.
119
+ all_deps += $(OBJS)
120
+
121
+ # CFLAGS et al overrides must be target-local.
122
+ # See "Target-specific Variable Values" in the GNU Make manual.
123
+ $(OBJS): TOOLSET := $(TOOLSET)
124
+ $(OBJS): GYP_CFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE))
125
+ $(OBJS): GYP_CXXFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE))
126
+ $(OBJS): GYP_OBJCFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE)) $(CFLAGS_OBJC_$(BUILDTYPE))
127
+ $(OBJS): GYP_OBJCXXFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE)) $(CFLAGS_OBJCC_$(BUILDTYPE))
128
+
129
+ # Suffix rules, putting all outputs into $(obj).
130
+
131
+ $(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
132
+ @$(call do_cmd,cxx,1)
133
+
134
+ # Try building from generated source, too.
135
+
136
+ $(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
137
+ @$(call do_cmd,cxx,1)
138
+
139
+ $(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
140
+ @$(call do_cmd,cxx,1)
141
+
142
+ # End of this set of suffix rules
143
+ ### Rules for final target.
144
+ LDFLAGS_Debug := \
145
+ -undefined dynamic_lookup \
146
+ -Wl,-search_paths_first \
147
+ -mmacosx-version-min=11.0 \
148
+ -arch \
149
+ x86_64 \
150
+ -L$(builddir) \
151
+ -stdlib=libc++
152
+
153
+ LIBTOOLFLAGS_Debug := \
154
+ -undefined dynamic_lookup \
155
+ -Wl,-search_paths_first
156
+
157
+ LDFLAGS_Release := \
158
+ -undefined dynamic_lookup \
159
+ -Wl,-search_paths_first \
160
+ -mmacosx-version-min=11.0 \
161
+ -arch \
162
+ x86_64 \
163
+ -L$(builddir) \
164
+ -stdlib=libc++
165
+
166
+ LIBTOOLFLAGS_Release := \
167
+ -undefined dynamic_lookup \
168
+ -Wl,-search_paths_first
169
+
170
+ LIBS :=
171
+
172
+ $(builddir)/sshcrypto.node: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
173
+ $(builddir)/sshcrypto.node: LIBS := $(LIBS)
174
+ $(builddir)/sshcrypto.node: GYP_LIBTOOLFLAGS := $(LIBTOOLFLAGS_$(BUILDTYPE))
175
+ $(builddir)/sshcrypto.node: TOOLSET := $(TOOLSET)
176
+ $(builddir)/sshcrypto.node: $(OBJS) FORCE_DO_CMD
177
+ $(call do_cmd,solink_module)
178
+
179
+ all_deps += $(builddir)/sshcrypto.node
180
+ # Add target alias
181
+ .PHONY: sshcrypto
182
+ sshcrypto: $(builddir)/sshcrypto.node
183
+
184
+ # Short alias for building this executable.
185
+ .PHONY: sshcrypto.node
186
+ sshcrypto.node: $(builddir)/sshcrypto.node
187
+
188
+ # Add executable to "all" target.
189
+ .PHONY: all
190
+ all: $(builddir)/sshcrypto.node
191
+
@@ -84,15 +84,11 @@ class ChaChaPolyCipher : public ObjectWrap {
84
84
  SetPrototypeMethod(tpl, "encrypt", Encrypt);
85
85
  SetPrototypeMethod(tpl, "free", Free);
86
86
 
87
- Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
88
- Local<Context> context = Nan::GetCurrentContext();
89
- v8::Isolate* isolate = context->GetIsolate();
90
-
91
- constructor().Set(isolate, func);
87
+ constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
92
88
 
93
89
  Nan::Set(target,
94
90
  Nan::New("ChaChaPolyCipher").ToLocalChecked(),
95
- func);
91
+ Nan::GetFunction(tpl).ToLocalChecked());
96
92
  }
97
93
 
98
94
  private:
@@ -391,8 +387,8 @@ out:
391
387
  obj->clear();
392
388
  }
393
389
 
394
- static inline v8::Eternal<v8::Function> & constructor() {
395
- static v8::Eternal<v8::Function> my_constructor;
390
+ static inline Nan::Persistent<Function> & constructor() {
391
+ static Nan::Persistent<Function> my_constructor;
396
392
  return my_constructor;
397
393
  }
398
394
 
@@ -418,15 +414,11 @@ class AESGCMCipher : public ObjectWrap {
418
414
  SetPrototypeMethod(tpl, "encrypt", Encrypt);
419
415
  SetPrototypeMethod(tpl, "free", Free);
420
416
 
421
- Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
422
- Local<Context> context = Nan::GetCurrentContext();
423
- v8::Isolate* isolate = context->GetIsolate();
424
-
425
- constructor().Set(isolate, func);
417
+ constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
426
418
 
427
419
  Nan::Set(target,
428
420
  Nan::New("AESGCMCipher").ToLocalChecked(),
429
- func);
421
+ Nan::GetFunction(tpl).ToLocalChecked());
430
422
  }
431
423
 
432
424
  private:
@@ -641,8 +633,8 @@ out:
641
633
  obj->clear();
642
634
  }
643
635
 
644
- static inline v8::Eternal<v8::Function> & constructor() {
645
- static v8::Eternal<v8::Function> my_constructor;
636
+ static inline Nan::Persistent<Function> & constructor() {
637
+ static Nan::Persistent<Function> my_constructor;
646
638
  return my_constructor;
647
639
  }
648
640
 
@@ -659,15 +651,11 @@ class GenericCipher : public ObjectWrap {
659
651
  SetPrototypeMethod(tpl, "encrypt", Encrypt);
660
652
  SetPrototypeMethod(tpl, "free", Free);
661
653
 
662
- Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
663
- Local<Context> context = Nan::GetCurrentContext();
664
- v8::Isolate* isolate = context->GetIsolate();
665
-
666
- constructor().Set(isolate, func);
654
+ constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
667
655
 
668
656
  Nan::Set(target,
669
657
  Nan::New("GenericCipher").ToLocalChecked(),
670
- func);
658
+ Nan::GetFunction(tpl).ToLocalChecked());
671
659
  }
672
660
 
673
661
  private:
@@ -1026,8 +1014,8 @@ out:
1026
1014
  obj->clear();
1027
1015
  }
1028
1016
 
1029
- static inline v8::Eternal<v8::Function> & constructor() {
1030
- static v8::Eternal<v8::Function> my_constructor;
1017
+ static inline Nan::Persistent<Function> & constructor() {
1018
+ static Nan::Persistent<Function> my_constructor;
1031
1019
  return my_constructor;
1032
1020
  }
1033
1021
 
@@ -1056,15 +1044,11 @@ class ChaChaPolyDecipher : public ObjectWrap {
1056
1044
  SetPrototypeMethod(tpl, "decryptLen", DecryptLen);
1057
1045
  SetPrototypeMethod(tpl, "free", Free);
1058
1046
 
1059
- Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
1060
- Local<Context> context = Nan::GetCurrentContext();
1061
- v8::Isolate* isolate = context->GetIsolate();
1062
-
1063
- constructor().Set(isolate, func);
1047
+ constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
1064
1048
 
1065
1049
  Nan::Set(target,
1066
1050
  Nan::New("ChaChaPolyDecipher").ToLocalChecked(),
1067
- func);
1051
+ Nan::GetFunction(tpl).ToLocalChecked());
1068
1052
  }
1069
1053
 
1070
1054
  private:
@@ -1456,8 +1440,8 @@ out:
1456
1440
  obj->clear();
1457
1441
  }
1458
1442
 
1459
- static inline v8::Eternal<v8::Function> & constructor() {
1460
- static v8::Eternal<v8::Function> my_constructor;
1443
+ static inline Nan::Persistent<Function> & constructor() {
1444
+ static Nan::Persistent<Function> my_constructor;
1461
1445
  return my_constructor;
1462
1446
  }
1463
1447
 
@@ -1484,15 +1468,11 @@ class AESGCMDecipher : public ObjectWrap {
1484
1468
  SetPrototypeMethod(tpl, "decrypt", Decrypt);
1485
1469
  SetPrototypeMethod(tpl, "free", Free);
1486
1470
 
1487
- Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
1488
- Local<Context> context = Nan::GetCurrentContext();
1489
- v8::Isolate* isolate = context->GetIsolate();
1490
-
1491
- constructor().Set(isolate, func);
1471
+ constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
1492
1472
 
1493
1473
  Nan::Set(target,
1494
1474
  Nan::New("AESGCMDecipher").ToLocalChecked(),
1495
- func);
1475
+ Nan::GetFunction(tpl).ToLocalChecked());
1496
1476
  }
1497
1477
 
1498
1478
  private:
@@ -1717,8 +1697,8 @@ out:
1717
1697
  obj->clear();
1718
1698
  }
1719
1699
 
1720
- static inline v8::Eternal<v8::Function> & constructor() {
1721
- static v8::Eternal<v8::Function> my_constructor;
1700
+ static inline Nan::Persistent<Function> & constructor() {
1701
+ static Nan::Persistent<Function> my_constructor;
1722
1702
  return my_constructor;
1723
1703
  }
1724
1704
 
@@ -1736,15 +1716,11 @@ class GenericDecipher : public ObjectWrap {
1736
1716
  SetPrototypeMethod(tpl, "decrypt", Decrypt);
1737
1717
  SetPrototypeMethod(tpl, "free", Free);
1738
1718
 
1739
- Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
1740
- Local<Context> context = Nan::GetCurrentContext();
1741
- v8::Isolate* isolate = context->GetIsolate();
1742
-
1743
- constructor().Set(isolate, func);
1719
+ constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
1744
1720
 
1745
1721
  Nan::Set(target,
1746
1722
  Nan::New("GenericDecipher").ToLocalChecked(),
1747
- func);
1723
+ Nan::GetFunction(tpl).ToLocalChecked());
1748
1724
  }
1749
1725
 
1750
1726
  private:
@@ -2207,8 +2183,8 @@ out:
2207
2183
  obj->clear();
2208
2184
  }
2209
2185
 
2210
- static inline v8::Eternal<v8::Function> & constructor() {
2211
- static v8::Eternal<v8::Function> my_constructor;
2186
+ static inline Nan::Persistent<Function> & constructor() {
2187
+ static Nan::Persistent<Function> my_constructor;
2212
2188
  return my_constructor;
2213
2189
  }
2214
2190
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/ssh2",
3
- "version": "1.16.2",
3
+ "version": "1.17.0",
4
4
  "author": "Brian White <mscdex@mscdex.net>",
5
5
  "description": "SSH2 client and server modules written in pure JavaScript for node.js",
6
6
  "main": "./lib/index.js",