@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.
- 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 +347 -0
- package/lib/protocol/crypto/build/Release/.deps/Release/obj.target/sshcrypto/src/binding.o.d +251 -0
- package/lib/protocol/crypto/build/Release/.deps/Release/sshcrypto.node.d +1 -0
- 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/binding.Makefile +6 -0
- package/lib/protocol/crypto/build/gyp-mac-tool +768 -0
- package/lib/protocol/crypto/build/sshcrypto.target.mk +191 -0
- package/lib/protocol/crypto/src/binding.cc +24 -48
- package/lib/protocol/sshCertificate.js +243 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
395
|
-
static
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
645
|
-
static
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1030
|
-
static
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1460
|
-
static
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1721
|
-
static
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2211
|
-
static
|
|
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