@@ -9,20 +9,30 @@ var assert = require('assert-plus');
99var Buffer = require ( 'safer-buffer' ) . Buffer ;
1010var rfc4253 = require ( './rfc4253' ) ;
1111var Key = require ( '../key' ) ;
12+ var SSHBuffer = require ( '../ssh-buffer' ) ;
13+ var crypto = require ( 'crypto' ) ;
14+ var PrivateKey = require ( '../private-key' ) ;
1215
1316var errors = require ( '../errors' ) ;
1417
18+ // https://tartarus.org/~simon/putty-prerel-snapshots/htmldoc/AppendixC.html
1519function read ( buf , options ) {
1620 var lines = buf . toString ( 'ascii' ) . split ( / [ \r \n ] + / ) ;
1721 var found = false ;
1822 var parts ;
1923 var si = 0 ;
24+ var formatVersion ;
2025 while ( si < lines . length ) {
2126 parts = splitHeader ( lines [ si ++ ] ) ;
22- if ( parts &&
23- parts [ 0 ] . toLowerCase ( ) === 'putty-user-key-file-2' ) {
24- found = true ;
25- break ;
27+ if ( parts ) {
28+ formatVersion = {
29+ 'putty-user-key-file-2' : 2 ,
30+ 'putty-user-key-file-3' : 3 ,
31+ } [ parts [ 0 ] . toLowerCase ( ) ]
32+ if ( formatVersion ) {
33+ found = true ;
34+ break ;
35+ }
2636 }
2737 }
2838 if ( ! found ) {
@@ -32,6 +42,7 @@ function read(buf, options) {
3242
3343 parts = splitHeader ( lines [ si ++ ] ) ;
3444 assert . equal ( parts [ 0 ] . toLowerCase ( ) , 'encryption' ) ;
45+ var encryption = parts [ 1 ] ;
3546
3647 parts = splitHeader ( lines [ si ++ ] ) ;
3748 assert . equal ( parts [ 0 ] . toLowerCase ( ) , 'comment' ) ;
@@ -52,10 +63,93 @@ function read(buf, options) {
5263 if ( key . type !== keyType ) {
5364 throw ( new Error ( 'Outer key algorithm mismatch' ) ) ;
5465 }
66+
67+ si += publicLines ;
68+ if ( lines [ si ] ) {
69+ parts = splitHeader ( lines [ si ++ ] ) ;
70+ assert . equal ( parts [ 0 ] . toLowerCase ( ) , 'private-lines' ) ;
71+ var privateLines = parseInt ( parts [ 1 ] , 10 ) ;
72+ if ( ! isFinite ( privateLines ) || privateLines < 0 ||
73+ privateLines > lines . length ) {
74+ throw ( new Error ( 'Invalid private-lines count' ) ) ;
75+ }
76+
77+ var privateBuf = Buffer . from (
78+ lines . slice ( si , si + privateLines ) . join ( '' ) , 'base64' ) ;
79+
80+ if ( encryption !== 'none' && formatVersion === 3 ) {
81+ throw new Error ( 'Encrypted keys arenot supported for PuTTY format version 3' ) ;
82+ }
83+
84+ if ( encryption === 'aes256-cbc' ) {
85+ if ( ! options . passphrase ) {
86+ throw ( new errors . KeyEncryptedError (
87+ options . filename , 'PEM' ) ) ;
88+ }
89+
90+ var iv = Buffer . alloc ( 16 , 0 ) ;
91+ var decipher = crypto . createDecipheriv (
92+ 'aes-256-cbc' ,
93+ derivePPK2EncryptionKey ( options . passphrase ) ,
94+ iv ) ;
95+ decipher . setAutoPadding ( false ) ;
96+ privateBuf = Buffer . concat ( [
97+ decipher . update ( privateBuf ) , decipher . final ( ) ] ) ;
98+ }
99+
100+ key = new PrivateKey ( key ) ;
101+ if ( key . type !== keyType ) {
102+ throw ( new Error ( 'Outer key algorithm mismatch' ) ) ;
103+ }
104+
105+ var sshbuf = new SSHBuffer ( { buffer : privateBuf } ) ;
106+ var privateKeyParts ;
107+ if ( alg === 'ssh-dss' ) {
108+ privateKeyParts = [ {
109+ name : 'x' ,
110+ data : sshbuf . readBuffer ( )
111+ } ] ;
112+ } else if ( alg === 'ssh-rsa' ) {
113+ privateKeyParts = [
114+ { name : 'd' , data : sshbuf . readBuffer ( ) } ,
115+ { name : 'p' , data : sshbuf . readBuffer ( ) } ,
116+ { name : 'q' , data : sshbuf . readBuffer ( ) } ,
117+ { name : 'iqmp' , data : sshbuf . readBuffer ( ) }
118+ ] ;
119+ } else if ( alg . startsWith ( 'ecdsa-sha2-nistp' ) ) {
120+ privateKeyParts = [ {
121+ name : 'd' , data : sshbuf . readBuffer ( )
122+ } ] ;
123+ } else if ( alg === 'ssh-ed25519' ) {
124+ privateKeyParts = [ {
125+ name : 'k' , data : sshbuf . readBuffer ( )
126+ } ] ;
127+ } else {
128+ throw new Error ( 'Unsupported PPK key type: ' + alg ) ;
129+ }
130+
131+ key = new PrivateKey ( {
132+ type : key . type ,
133+ parts : key . parts . concat ( privateKeyParts )
134+ } ) ;
135+ }
136+
55137 key . comment = comment ;
56138 return ( key ) ;
57139}
58140
141+ function derivePPK2EncryptionKey ( passphrase ) {
142+ var hash1 = crypto . createHash ( 'sha1' ) . update ( Buffer . concat ( [
143+ Buffer . from ( [ 0 , 0 , 0 , 0 ] ) ,
144+ Buffer . from ( passphrase )
145+ ] ) ) . digest ( ) ;
146+ var hash2 = crypto . createHash ( 'sha1' ) . update ( Buffer . concat ( [
147+ Buffer . from ( [ 0 , 0 , 0 , 1 ] ) ,
148+ Buffer . from ( passphrase )
149+ ] ) ) . digest ( ) ;
150+ return ( Buffer . concat ( [ hash1 , hash2 ] ) . slice ( 0 , 32 ) ) ;
151+ }
152+
59153function splitHeader ( line ) {
60154 var idx = line . indexOf ( ':' ) ;
61155 if ( idx === - 1 )
0 commit comments