Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node-sshpk#87 Implemented password encryption for pem(pkcs#1, pkcs#8) #88

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 147 additions & 4 deletions lib/formats/pem.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,141 @@ function write(key, options, type) {

var der = new asn1.BerWriter();

//Encryption
var cipher = 'none';
var kdf = 'none';
var kdfopts = Buffer.alloc(0);
var cinf;
var passphrase;
if (PrivateKey.isPrivateKey(key)) {
if (options !== undefined) {
passphrase = options.passphrase;
if (typeof (passphrase) === 'string')
passphrase = Buffer.from(passphrase, 'utf-8');
if (passphrase !== undefined) {
assert.buffer(passphrase, 'options.passphrase');
assert.optionalString(options.cipher, 'options.cipher');
cipher = options.cipher;
if (cipher === undefined)
cipher = 'aes128-cbc';
cinf = utils.opensshCipherInfo(cipher);
}
}
}

var privBuf;

if (PrivateKey.isPrivateKey(key)) {
if (type && type === 'pkcs8') {
header = 'PRIVATE KEY';
pkcs8.writePkcs8(der, key);
if(cinf) header = 'ENCRYPTED PRIVATE KEY';
else header = 'PRIVATE KEY';

if (cinf !== undefined) {
der.startSequence();
der.startSequence();

der.writeOID(OID_PBES2);

der.startSequence(); /* PBES2-params */

der.startSequence(); /* keyDerivationFunc */

der.writeOID(OID_PBKDF2);

der.startSequence();

var salt = crypto.randomBytes(16);

der.writeBuffer(salt, asn1.Ber.OctetString);

//"iterations" is fixed to 1000
var iterations = 1000;
der.writeInt(iterations);

//"hash" is fixed to sha1
var hashAlg = "sha1";

der.startSequence();
der.writeOID(HASH_TO_OID[hashAlg]);
der.endSequence();

der.endSequence();

der.endSequence(); /* keyDerivationFunc */


der.startSequence(); /* encryptionScheme */

//cipher
var cipherOid = CIPHER_TO_OID[cipher];
if(cipherOid === undefined) {
throw (new Error('Unsupported PBES2 cipher: ' + cipher));
}

der.writeOID(cipherOid);

//iv
var iv = crypto.randomBytes(cinf.blockSize);
der.writeBuffer(iv, asn1.Ber.OctetString);

der.endSequence(); /* encryptionScheme */

der.endSequence(); /* PBES2-params */

der.endSequence();

var ckey = utils.pbkdf2(hashAlg, salt, iterations, cinf.keySize,
passphrase);

var cipherStream = crypto.createCipheriv(cinf.opensslName, ckey, iv);
cipherStream.setAutoPadding(true);

var eder = new asn1.BerWriter();
pkcs8.writePkcs8(eder, key);

var chunk, chunks = [];
cipherStream.once('error', function (e) {
throw (e);
});

cipherStream.write(eder.buffer);
cipherStream.end();
while ((chunk = cipherStream.read()) !== null)
chunks.push(chunk);
var ebuf = Buffer.concat(chunks);

der.writeBuffer(ebuf, asn1.Ber.OctetString);

der.endSequence();

}else{
pkcs8.writePkcs8(der, key);
}

} else {
if (type)
assert.strictEqual(type, 'pkcs1');
header = alg + ' PRIVATE KEY';
pkcs1.writePkcs1(der, key);

if (cinf !== undefined) {
var iv = crypto.randomBytes(cinf.blockSize);
var ckey = utils.opensslKeyDeriv(cinf.opensslName, iv, passphrase, 1).key;

var cipherStream = crypto.createCipheriv(cinf.opensslName,
ckey, iv);
cipherStream.setAutoPadding(true);
var chunk, chunks = [];
cipherStream.once('error', function (e) {
throw (e);
});

cipherStream.write(der.buffer);
cipherStream.end();
while ((chunk = cipherStream.read()) !== null)
chunks.push(chunk);
privBuf = Buffer.concat(chunks);
}
}

} else if (Key.isKey(key)) {
Expand All @@ -270,12 +396,29 @@ function write(key, options, type) {
throw (new Error('key is not a Key or PrivateKey'));
}

var tmp = der.buffer.toString('base64');
if(privBuf === undefined) privBuf = der.buffer;

var top = "";
if (PrivateKey.isPrivateKey(key)) {
if (type === undefined || type === 'pkcs1') {
if(cinf){
top +=
'Proc-Type: 4,ENCRYPTED\n'
+ 'DEK-Info: ' + cinf.opensslName.toUpperCase() + "," + iv.toString("hex").toUpperCase()
+ '\n\n';
}
}
}

var tmp = privBuf.toString('base64');
var len = tmp.length + (tmp.length / 64) +
18 + 16 + header.length*2 + 10;
18 + 16 + header.length*2 + 10 + top.length;
var buf = Buffer.alloc(len);
var o = 0;
o += buf.write('-----BEGIN ' + header + '-----\n', o);

o += buf.write(top, o);

for (var i = 0; i < tmp.length; ) {
var limit = i + 64;
if (limit > tmp.length)
Expand Down
42 changes: 42 additions & 0 deletions test/assets/pkcs1-enc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,8C0D86A3F866B3F73591946566938382

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
-----END RSA PRIVATE KEY-----
42 changes: 42 additions & 0 deletions test/assets/pkcs8-enc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
1+45uQs68P7GdtYcNQxsjLpIQg==
-----END ENCRYPTED PRIVATE KEY-----
110 changes: 110 additions & 0 deletions test/private-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,58 @@ test('parse pkcs8 unencrypted private keys', function (t) {
t.end();
});

test('parse and produce pkcs#1 encrypted pem rsa', function (t) {
var keyPem = fs.readFileSync(path.join(testDir, 'pkcs1-enc'));
t.throws(function () {
sshpk.parsePrivateKey(keyPem, 'pkcs1');
});
t.throws(function () {
sshpk.parsePrivateKey(keyPem, 'pkcs1',
{ passphrase: 'incorrect' });
});
var key = sshpk.parsePrivateKey(keyPem, 'pkcs1',
{ passphrase: 'foobar' });
t.strictEqual(key.type, 'rsa');

var keySsh2 = key.toBuffer('pkcs1', { passphrase: 'foobar2' });
t.throws(function () {
sshpk.parsePrivateKey(keySsh2, 'pkcs1',
{ passphrase: 'foobar' });
});
var key2 = sshpk.parsePrivateKey(keySsh2, 'pkcs1',
{ passphrase: 'foobar2' });
t.strictEqual(key2.type, 'rsa');

//console.log();

t.end();
});

test('parse and produce pkcs#8 encrypted pem rsa', function (t) {
var keyPem = fs.readFileSync(path.join(testDir, 'pkcs8-enc'));
t.throws(function () {
sshpk.parsePrivateKey(keyPem, 'pkcs8');
});
t.throws(function () {
sshpk.parsePrivateKey(keyPem, 'pkcs8',
{ passphrase: 'incorrect' });
});
var key = sshpk.parsePrivateKey(keyPem, 'pkcs8',
{ passphrase: 'foobar' });
t.strictEqual(key.type, 'rsa');

var keySsh2 = key.toBuffer('pkcs8', { passphrase: 'foobar2' });
t.throws(function () {
sshpk.parsePrivateKey(keySsh2, 'pkcs8',
{ passphrase: 'foobar' });
});
var key2 = sshpk.parsePrivateKey(keySsh2, 'pkcs8',
{ passphrase: 'foobar2' });
t.strictEqual(key2.type, 'rsa');

t.end();
});

test('parse and produce encrypted ssh-private ecdsa', function (t) {
var keySsh = fs.readFileSync(path.join(testDir, 'id_ecdsa_enc'));
t.throws(function () {
Expand Down Expand Up @@ -339,6 +391,64 @@ test('PrivateKey#createSign on ECDSA 256 key', function (t) {
t.end();
});

test('PrivateKey#createSign on encrypted pkcs#1 key', function (t) {
var privateKey = sshpk.parsePrivateKey(fs.readFileSync(path.join(testDir, 'id_rsa')), 'pem');
var publicKey = privateKey.toPublic();

var encrypted = privateKey.toBuffer("pkcs1", {passphrase: "foobar"});

t.throws(function () {
sshpk.parsePrivateKey(encrypted, "pem");
});

t.throws(function () {
sshpk.parsePrivateKey(encrypted, "pem", {passphrase: "incorrect"});
});

var encPrivateKey = sshpk.parsePrivateKey(encrypted, "pem", {passphrase: "foobar"});

var s = encPrivateKey.createSign('sha256');
s.update('foobar');
var sig = s.sign();
t.ok(sig);
t.ok(sig instanceof sshpk.Signature);

var v = publicKey.createVerify('sha256');
v.update('foobar');
t.ok(v.verify(sig));

t.end();
});

test('PrivateKey#createSign on encrypted pkcs#8 key', function (t) {
var privateKey = sshpk.parsePrivateKey(fs.readFileSync(path.join(testDir, 'id_rsa')), 'pem');
var publicKey = privateKey.toPublic();

var encrypted = privateKey.toBuffer("pkcs8", {passphrase: "foobar"});

t.throws(function () {
sshpk.parsePrivateKey(encrypted, "pem");
});

t.throws(function () {
sshpk.parsePrivateKey(encrypted, "pem", {passphrase: "incorrect"});
});

var encPrivateKey = sshpk.parsePrivateKey(encrypted, "pem", {passphrase: "foobar"});

var s = encPrivateKey.createSign('sha256');
s.update('foobar');
var sig = s.sign();
t.ok(sig);
t.ok(sig instanceof sshpk.Signature);

var v = publicKey.createVerify('sha256');
v.update('foobar');
t.ok(v.verify(sig));

t.end();
});

test('PrivateKey.generate ecdsa default', function (t) {
var key = sshpk.generatePrivateKey('ecdsa');
t.ok(sshpk.PrivateKey.isPrivateKey(key));
Expand Down