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