1
+ var _ = require ( '../utils' ) . _ ;
2
+ var utils = require ( '../utils' ) ;
3
+ var BigInteger = require ( '../libs/jsbn' ) ;
4
+
5
+ const PRIVATE_OPENING_BOUNDARY = '-----BEGIN OPENSSH PRIVATE KEY-----' ;
6
+ const PRIVATE_CLOSING_BOUNDARY = '-----END OPENSSH PRIVATE KEY-----' ;
7
+
8
+ module . exports = {
9
+ privateExport : function ( key , options ) {
10
+ const nbuf = key . n . toBuffer ( ) ;
11
+
12
+ let ebuf = Buffer . alloc ( 4 )
13
+ ebuf . writeUInt32BE ( key . e , 0 ) ;
14
+ //Slice leading zeroes
15
+ while ( ebuf [ 0 ] === 0 ) ebuf = ebuf . slice ( 1 ) ;
16
+
17
+ const dbuf = key . d . toBuffer ( ) ;
18
+ const coeffbuf = key . coeff . toBuffer ( ) ;
19
+ const pbuf = key . p . toBuffer ( ) ;
20
+ const qbuf = key . q . toBuffer ( ) ;
21
+ let commentbuf ;
22
+ if ( typeof key . sshcomment !== 'undefined' ) {
23
+ commentbuf = Buffer . from ( key . sshcomment ) ;
24
+ } else {
25
+ commentbuf = Buffer . from ( [ ] ) ;
26
+ }
27
+
28
+ const pubkeyLength =
29
+ 11 + // 32bit length, 'ssh-rsa'
30
+ 4 + ebuf . byteLength +
31
+ 4 + nbuf . byteLength ;
32
+
33
+ const privateKeyLength =
34
+ 8 + //64bit unused checksum
35
+ 11 + // 32bit length, 'ssh-rsa'
36
+ 4 + nbuf . byteLength +
37
+ 4 + ebuf . byteLength +
38
+ 4 + dbuf . byteLength +
39
+ 4 + coeffbuf . byteLength +
40
+ 4 + pbuf . byteLength +
41
+ 4 + qbuf . byteLength +
42
+ 4 + commentbuf . byteLength ;
43
+
44
+ let length =
45
+ 15 + //openssh-key-v1,0x00,
46
+ 16 + // 2*(32bit length, 'none')
47
+ 4 + // 32bit length, empty string
48
+ 4 + // 32bit number of keys
49
+ 4 + // 32bit pubkey length
50
+ pubkeyLength +
51
+ 4 + //32bit private+checksum+comment+padding length
52
+ privateKeyLength ;
53
+
54
+ const paddingLength = Math . ceil ( privateKeyLength / 8 ) * 8 - privateKeyLength ;
55
+ length += paddingLength ;
56
+
57
+ const buf = Buffer . alloc ( length ) ;
58
+ const writer = { buf :buf , off : 0 } ;
59
+ buf . write ( 'openssh-key-v1' , 'utf8' ) ;
60
+ buf . writeUInt8 ( 0 , 14 ) ;
61
+ writer . off += 15 ;
62
+
63
+ writeOpenSSHKeyString ( writer , Buffer . from ( 'none' ) ) ;
64
+ writeOpenSSHKeyString ( writer , Buffer . from ( 'none' ) ) ;
65
+ writeOpenSSHKeyString ( writer , Buffer . from ( '' ) ) ;
66
+
67
+ writer . off = writer . buf . writeUInt32BE ( 1 , writer . off ) ;
68
+ writer . off = writer . buf . writeUInt32BE ( pubkeyLength , writer . off ) ;
69
+
70
+ writeOpenSSHKeyString ( writer , Buffer . from ( 'ssh-rsa' ) ) ;
71
+ writeOpenSSHKeyString ( writer , ebuf ) ;
72
+ writeOpenSSHKeyString ( writer , nbuf ) ;
73
+
74
+ writer . off = writer . buf . writeUInt32BE (
75
+ length - 47 - pubkeyLength ,
76
+ writer . off
77
+ ) ;
78
+ writer . off += 8 ;
79
+
80
+ writeOpenSSHKeyString ( writer , Buffer . from ( 'ssh-rsa' ) ) ;
81
+ writeOpenSSHKeyString ( writer , nbuf ) ;
82
+ writeOpenSSHKeyString ( writer , ebuf ) ;
83
+ writeOpenSSHKeyString ( writer , dbuf ) ;
84
+ writeOpenSSHKeyString ( writer , coeffbuf ) ;
85
+ writeOpenSSHKeyString ( writer , pbuf ) ;
86
+ writeOpenSSHKeyString ( writer , qbuf ) ;
87
+ writeOpenSSHKeyString ( writer , commentbuf ) ;
88
+
89
+ let pad = 0x01 ;
90
+ while ( writer . off < length ) {
91
+ writer . off = writer . buf . writeUInt8 ( pad ++ , writer . off ) ;
92
+ }
93
+
94
+ if ( options . type === 'der' ) {
95
+ return writer . buf
96
+ } else {
97
+ return PRIVATE_OPENING_BOUNDARY + '\n' + utils . linebrk ( buf . toString ( 'base64' ) , 70 ) + '\n' + PRIVATE_CLOSING_BOUNDARY + '\n' ;
98
+ }
99
+ } ,
100
+
101
+ privateImport : function ( key , data , options ) {
102
+ options = options || { } ;
103
+ var buffer ;
104
+
105
+ if ( options . type !== 'der' ) {
106
+ if ( Buffer . isBuffer ( data ) ) {
107
+ data = data . toString ( 'utf8' ) ;
108
+ }
109
+
110
+ if ( _ . isString ( data ) ) {
111
+ var pem = utils . trimSurroundingText ( data , PRIVATE_OPENING_BOUNDARY , PRIVATE_CLOSING_BOUNDARY )
112
+ . replace ( / \s + | \n \r | \n | \r $ / gm, '' ) ;
113
+ buffer = Buffer . from ( pem , 'base64' ) ;
114
+ } else {
115
+ throw Error ( 'Unsupported key format' ) ;
116
+ }
117
+ } else if ( Buffer . isBuffer ( data ) ) {
118
+ buffer = data ;
119
+ } else {
120
+ throw Error ( 'Unsupported key format' ) ;
121
+ }
122
+
123
+ const reader = { buf :buffer , off :0 } ;
124
+
125
+ if ( buffer . slice ( 0 , 14 ) . toString ( 'ascii' ) !== 'openssh-key-v1' )
126
+ throw 'Invalid file format.' ;
127
+
128
+ reader . off += 15 ;
129
+
130
+ //ciphername
131
+ if ( readOpenSSHKeyString ( reader ) . toString ( 'ascii' ) !== 'none' )
132
+ throw Error ( 'Unsupported key type' ) ;
133
+ //kdfname
134
+ if ( readOpenSSHKeyString ( reader ) . toString ( 'ascii' ) !== 'none' )
135
+ throw Error ( 'Unsupported key type' ) ;
136
+ //kdf
137
+ if ( readOpenSSHKeyString ( reader ) . toString ( 'ascii' ) !== '' )
138
+ throw Error ( 'Unsupported key type' ) ;
139
+ //keynum
140
+ reader . off += 4 ;
141
+
142
+ //sshpublength
143
+ reader . off += 4 ;
144
+
145
+ //keytype
146
+ if ( readOpenSSHKeyString ( reader ) . toString ( 'ascii' ) !== 'ssh-rsa' )
147
+ throw Error ( 'Unsupported key type' ) ;
148
+ readOpenSSHKeyString ( reader ) ;
149
+ readOpenSSHKeyString ( reader ) ;
150
+
151
+ reader . off += 12 ;
152
+ if ( readOpenSSHKeyString ( reader ) . toString ( 'ascii' ) !== 'ssh-rsa' )
153
+ throw Error ( 'Unsupported key type' ) ;
154
+
155
+ const n = readOpenSSHKeyString ( reader ) ;
156
+ const e = readOpenSSHKeyString ( reader ) ;
157
+ const d = readOpenSSHKeyString ( reader ) ;
158
+ const coeff = readOpenSSHKeyString ( reader ) ;
159
+ const p = readOpenSSHKeyString ( reader ) ;
160
+ const q = readOpenSSHKeyString ( reader ) ;
161
+
162
+ //Calculate missing values
163
+ const dint = new BigInteger ( d ) ;
164
+ const qint = new BigInteger ( q ) ;
165
+ const pint = new BigInteger ( p ) ;
166
+ const dp = dint . mod ( pint . subtract ( BigInteger . ONE ) ) ;
167
+ const dq = dint . mod ( qint . subtract ( BigInteger . ONE ) ) ;
168
+
169
+ key . setPrivate (
170
+ n , // modulus
171
+ e , // publicExponent
172
+ d , // privateExponent
173
+ p , // prime1
174
+ q , // prime2
175
+ dp . toBuffer ( ) , // exponent1 -- d mod (p1)
176
+ dq . toBuffer ( ) , // exponent2 -- d mod (q-1)
177
+ coeff // coefficient -- (inverse of q) mod p
178
+ ) ;
179
+
180
+ key . sshcomment = readOpenSSHKeyString ( reader ) . toString ( 'ascii' ) ;
181
+ } ,
182
+
183
+ publicExport : function ( key , options ) {
184
+ let ebuf = Buffer . alloc ( 4 )
185
+ ebuf . writeUInt32BE ( key . e , 0 ) ;
186
+ //Slice leading zeroes
187
+ while ( ebuf [ 0 ] === 0 ) ebuf = ebuf . slice ( 1 ) ;
188
+ const nbuf = key . n . toBuffer ( ) ;
189
+ const buf = Buffer . alloc (
190
+ ebuf . byteLength + 4 +
191
+ nbuf . byteLength + 4 +
192
+ 'ssh-rsa' . length + 4
193
+ ) ;
194
+
195
+ const writer = { buf : buf , off : 0 } ;
196
+ writeOpenSSHKeyString ( writer , Buffer . from ( 'ssh-rsa' ) ) ;
197
+ writeOpenSSHKeyString ( writer , ebuf ) ;
198
+ writeOpenSSHKeyString ( writer , nbuf ) ;
199
+
200
+ let comment = key . sshcomment || '' ;
201
+
202
+ if ( options . type === 'der' ) {
203
+ return writer . buf
204
+ } else {
205
+ return 'ssh-rsa ' + buf . toString ( 'base64' ) + ' ' + comment + '\n' ;
206
+ }
207
+ } ,
208
+
209
+ publicImport : function ( key , data , options ) {
210
+ options = options || { } ;
211
+ var buffer ;
212
+
213
+ if ( options . type !== 'der' ) {
214
+ if ( Buffer . isBuffer ( data ) ) {
215
+ data = data . toString ( 'utf8' ) ;
216
+ }
217
+
218
+ if ( _ . isString ( data ) ) {
219
+ if ( data . substring ( 0 , 8 ) !== 'ssh-rsa ' )
220
+ throw Error ( 'Unsupported key format' ) ;
221
+ let pemEnd = data . indexOf ( ' ' , 8 ) ;
222
+
223
+ //Handle keys with no comment
224
+ if ( pemEnd === - 1 ) {
225
+ pemEnd = data . length ;
226
+ } else {
227
+ key . sshcomment = data . substring ( pemEnd + 1 )
228
+ . replace ( / \s + | \n \r | \n | \r $ / gm, '' ) ;
229
+ }
230
+
231
+ const pem = data . substring ( 8 , pemEnd )
232
+ . replace ( / \s + | \n \r | \n | \r $ / gm, '' ) ;
233
+ buffer = Buffer . from ( pem , 'base64' ) ;
234
+ } else {
235
+ throw Error ( 'Unsupported key format' ) ;
236
+ }
237
+ } else if ( Buffer . isBuffer ( data ) ) {
238
+ buffer = data ;
239
+ } else {
240
+ throw Error ( 'Unsupported key format' ) ;
241
+ }
242
+
243
+ const reader = { buf :buffer , off :0 } ;
244
+
245
+ const type = readOpenSSHKeyString ( reader ) . toString ( 'ascii' ) ;
246
+
247
+ if ( type !== 'ssh-rsa' )
248
+ throw Error ( 'Invalid key type: ' + type ) ;
249
+
250
+ const e = readOpenSSHKeyString ( reader ) ;
251
+ const n = readOpenSSHKeyString ( reader ) ;
252
+
253
+ key . setPublic (
254
+ n ,
255
+ e
256
+ ) ;
257
+ } ,
258
+
259
+ /**
260
+ * Trying autodetect and import key
261
+ * @param key
262
+ * @param data
263
+ */
264
+ autoImport : function ( key , data ) {
265
+ // [\S\s]* matches zero or more of any character
266
+ if ( / ^ [ \S \s ] * - - - - - B E G I N O P E N S S H P R I V A T E K E Y - - - - - \s * (? = ( ( [ A - Z a - z 0 - 9 + / = ] + \s * ) + ) ) \1- - - - - E N D O P E N S S H P R I V A T E K E Y - - - - - [ \S \s ] * $ / g. test ( data ) ) {
267
+ module . exports . privateImport ( key , data ) ;
268
+ return true ;
269
+ }
270
+
271
+ if ( / ^ [ \S \s ] * s s h - r s a \s * (? = ( ( [ A - Z a - z 0 - 9 + / = ] + \s * ) + ) ) \1[ \S \s ] * $ / g. test ( data ) ) {
272
+ module . exports . publicImport ( key , data ) ;
273
+ return true ;
274
+ }
275
+
276
+ return false ;
277
+ }
278
+ } ;
279
+
280
+ function readOpenSSHKeyString ( reader ) {
281
+ const len = reader . buf . readInt32BE ( reader . off ) ;
282
+ reader . off += 4 ;
283
+ const res = reader . buf . slice ( reader . off , reader . off + len ) ;
284
+ reader . off += len ;
285
+ return res ;
286
+ }
287
+
288
+ function writeOpenSSHKeyString ( writer , data ) {
289
+ writer . buf . writeInt32BE ( data . byteLength , writer . off ) ;
290
+ writer . off += 4 ;
291
+ writer . off += data . copy ( writer . buf , writer . off ) ;
292
+ }
0 commit comments