|
@ -1,34 +1,87 @@ |
|
|
'use strict'; |
|
|
'use strict'; |
|
|
|
|
|
|
|
|
var Enc = require('./encoding.js'); |
|
|
var Enc = require('./encoding.js'); |
|
|
|
|
|
var PEM = require('./pem.js'); |
|
|
var SSH = module.exports; |
|
|
var SSH = module.exports; |
|
|
|
|
|
|
|
|
SSH.pack = function (opts) { |
|
|
SSH.pack = function (opts) { |
|
|
|
|
|
if (opts.jwk.d && !opts.public) { |
|
|
|
|
|
return SSH._packPrivate(opts); |
|
|
|
|
|
} else { |
|
|
|
|
|
delete opts.jwk.d; |
|
|
|
|
|
return SSH._packPublic(opts); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// https://tools.ietf.org/html/rfc4253#section-6.6
|
|
|
|
|
|
SSH._packPublic = function (opts) { |
|
|
|
|
|
var els = SSH._packKey(opts); |
|
|
|
|
|
var hex = SSH._packElements(els); |
|
|
|
|
|
var typ = Enc.hexToBin(els[0]); |
|
|
|
|
|
var parts = [ typ, Enc.hexToBase64(hex) ]; |
|
|
|
|
|
if (opts.comment) { parts.push(opts.comment); } |
|
|
|
|
|
return parts.join(' '); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
SSH._packPrivate = function (opts) { |
|
|
|
|
|
var pubjwk = JSON.parse(JSON.stringify(opts.jwk)); |
|
|
|
|
|
delete pubjwk.d; |
|
|
|
|
|
var pubels = SSH._packKey({ jwk: pubjwk }); |
|
|
|
|
|
var pubhex = SSH._packElements(pubels); |
|
|
|
|
|
var els = SSH._packKey(opts); |
|
|
|
|
|
var hex = SSH._packElements(els); |
|
|
|
|
|
var privlen = hex.length/2; |
|
|
|
|
|
var padlen = (privlen % 8) && (8 - (privlen % 8)) || 0; // blocksize is 8 (no cipher)
|
|
|
|
|
|
var bin = "openssh-key-v1" + String.fromCharCode(0) |
|
|
|
|
|
+ Enc.hexToBin( |
|
|
|
|
|
SSH._packElements([ |
|
|
|
|
|
Enc.binToHex("none") // ciphername
|
|
|
|
|
|
, Enc.binToHex("none") // kdfname
|
|
|
|
|
|
, "" // empty kdf
|
|
|
|
|
|
]) |
|
|
|
|
|
+ SSH._numToUint32Hex(1) // number of keys (always 1)
|
|
|
|
|
|
+ SSH._numToUint32Hex(pubhex.length/2) // pubkey length
|
|
|
|
|
|
+ pubhex |
|
|
|
|
|
+ SSH._numToUint32Hex(8 + privlen + padlen) // privkey length
|
|
|
|
|
|
+ '62636a7362636a73' // 64-bit dummy checksum ("bcjs", "bcjs")
|
|
|
|
|
|
+ hex // (only cihpered keys use real checksums)
|
|
|
|
|
|
); |
|
|
|
|
|
var pad = ''; |
|
|
|
|
|
var i; |
|
|
|
|
|
for (i = 1; i <= padlen; i += 1) { |
|
|
|
|
|
pad += '0' + i; |
|
|
|
|
|
} |
|
|
|
|
|
return PEM.packBlock({ |
|
|
|
|
|
type: "OPENSSH PRIVATE KEY" |
|
|
|
|
|
, bytes: Enc.binToBuf(bin + Enc.hexToBin(pad)) |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
SSH._packKey = function (opts) { |
|
|
var jwk = opts.jwk; |
|
|
var jwk = opts.jwk; |
|
|
var els = []; |
|
|
var els = []; |
|
|
var ssh = { |
|
|
|
|
|
type: '' |
|
|
|
|
|
, _elements: els |
|
|
|
|
|
, comment: opts.comment || '' |
|
|
|
|
|
}; |
|
|
|
|
|
var len; |
|
|
var len; |
|
|
|
|
|
|
|
|
if ("RSA" === jwk.kty) { |
|
|
if ("RSA" === jwk.kty) { |
|
|
ssh.type = 'ssh-rsa'; |
|
|
els.push(Enc.binToHex('ssh-rsa')); |
|
|
els.push(Enc.binToHex(ssh.type)); |
|
|
|
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.e))); |
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.e))); |
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.n))); |
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.n))); |
|
|
return SSH._packElements(ssh); |
|
|
if (jwk.d) { |
|
|
|
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.d))); |
|
|
|
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.qi))); |
|
|
|
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.p))); |
|
|
|
|
|
els.push(SSH._padRsa(Enc.base64ToHex(jwk.q))); |
|
|
|
|
|
els.push(Enc.binToHex(opts.comment || '')); |
|
|
|
|
|
} |
|
|
|
|
|
return SSH._packElements(els); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if ("P-256" === jwk.crv) { |
|
|
if ("P-256" === jwk.crv) { |
|
|
ssh.type = 'ecdsa-sha2-nistp256'; |
|
|
els.push(Enc.binToHex('ecdsa-sha2-nistp256')); |
|
|
els.push(Enc.binToHex(ssh.type)); |
|
|
|
|
|
els.push(Enc.binToHex('nistp256')); |
|
|
els.push(Enc.binToHex('nistp256')); |
|
|
len = 32; |
|
|
len = 32; |
|
|
} else if ("P-384" === jwk.crv) { |
|
|
} else if ("P-384" === jwk.crv) { |
|
|
ssh.type = 'ecdsa-sha2-nistp384'; |
|
|
els.push(Enc.binToHex('ecdsa-sha2-nistp384')); |
|
|
els.push(Enc.binToHex(ssh.type)); |
|
|
|
|
|
els.push(Enc.binToHex('nistp384')); |
|
|
els.push(Enc.binToHex('nistp384')); |
|
|
len = 48; |
|
|
len = 48; |
|
|
} else { |
|
|
} else { |
|
@ -39,14 +92,18 @@ SSH.pack = function (opts) { |
|
|
+ SSH._padEc(Enc.base64ToHex(jwk.x), len) |
|
|
+ SSH._padEc(Enc.base64ToHex(jwk.x), len) |
|
|
+ SSH._padEc(Enc.base64ToHex(jwk.y), len) |
|
|
+ SSH._padEc(Enc.base64ToHex(jwk.y), len) |
|
|
); |
|
|
); |
|
|
return SSH._packElements(ssh); |
|
|
if (jwk.d) { |
|
|
|
|
|
els.push(SSH._padEc(Enc.base64ToHex(jwk.d), len)); |
|
|
|
|
|
els.push(Enc.binToHex(opts.comment || '')); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return els; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
SSH._packElements = function (ssh) { |
|
|
SSH._packElements = function(els) { |
|
|
var hex = ssh._elements.map(function (hex) { |
|
|
return els.map(function (hex) { |
|
|
return SSH._numToUint32Hex(hex.length/2) + hex; |
|
|
return SSH._numToUint32Hex(hex.length/2) + hex; |
|
|
}).join(''); |
|
|
}).join(''); |
|
|
return [ ssh.type, Enc.hexToBase64(hex), ssh.comment ].join(' '); |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
SSH._numToUint32Hex = function (num) { |
|
|
SSH._numToUint32Hex = function (num) { |
|
|