Skip to content

Commit 8df0385

Browse files
authored
Add support for ed25519 private keys in pkcs8 (#92)
Reviewed by: Brian Bennett
1 parent 3e83664 commit 8df0385

File tree

4 files changed

+42
-21
lines changed

4 files changed

+42
-21
lines changed

bin/sshpk-conv

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ if (require.main === module) {
9191
console.error('sshpk-conv: converts between SSH key formats\n');
9292
console.error(help);
9393
console.error('\navailable key formats:');
94-
console.error(' - pem, pkcs1 eg id_rsa');
95-
console.error(' - ssh eg id_rsa.pub');
96-
console.error(' - pkcs8 format you want for openssl');
97-
console.error(' - openssh like output of ssh-keygen -o');
98-
console.error(' - rfc4253 raw OpenSSH wire format');
99-
console.error(' - dnssec dnssec-keygen format');
94+
console.error(' - pem, pkcs1 eg id_rsa');
95+
console.error(' - ssh eg id_rsa.pub');
96+
console.error(' - pkcs8 format you want for openssl');
97+
console.error(' - openssh like output of ssh-keygen -o');
98+
console.error(' - rfc4253 raw OpenSSH wire format');
99+
console.error(' - dnssec dnssec-keygen format');
100100
console.error(' - putty PuTTY ppk format');
101101
console.error('\navailable fingerprint formats:');
102102
console.error(' - hex colon-separated hex for SSH');

lib/formats/pkcs8.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -385,13 +385,17 @@ function readPkcs8EdDSAPrivate(der) {
385385
var k = der.readString(asn1.Ber.OctetString, true);
386386
k = utils.zeroPadToLength(k, 32);
387387

388-
var A;
389-
if (der.peek() === asn1.Ber.BitString) {
390-
A = utils.readBitString(der);
391-
A = utils.zeroPadToLength(A, 32);
392-
} else {
393-
A = utils.calculateED25519Public(k);
388+
var A, tag;
389+
while ((tag = der.peek()) !== null) {
390+
if (tag === (asn1.Ber.Context | 1)) {
391+
A = utils.readBitString(der, tag);
392+
} else {
393+
der.readSequence(tag);
394+
der._offset += der.length;
395+
}
394396
}
397+
if (A === undefined)
398+
A = utils.calculateED25519Public(k);
395399

396400
var key = {
397401
type: 'ed25519',
@@ -435,8 +439,11 @@ function writePkcs8(der, key) {
435439
der.startSequence();
436440

437441
if (PrivateKey.isPrivateKey(key)) {
438-
var sillyInt = Buffer.from([0]);
439-
der.writeBuffer(sillyInt, asn1.Ber.Integer);
442+
var version = 0;
443+
if (key.type === 'ed25519')
444+
version = 1;
445+
var vbuf = Buffer.from([version]);
446+
der.writeBuffer(vbuf, asn1.Ber.Integer);
440447
}
441448

442449
der.startSequence();
@@ -465,9 +472,9 @@ function writePkcs8(der, key) {
465472
case 'ed25519':
466473
der.writeOID('1.3.101.112');
467474
if (PrivateKey.isPrivateKey(key))
468-
throw (new Error('Ed25519 private keys in pkcs8 ' +
469-
'format are not supported'));
470-
writePkcs8EdDSAPublic(key, der);
475+
writePkcs8EdDSAPrivate(key, der);
476+
else
477+
writePkcs8EdDSAPublic(key, der);
471478
break;
472479
default:
473480
throw (new Error('Unsupported key type: ' + key.type));
@@ -624,8 +631,13 @@ function writePkcs8EdDSAPublic(key, der) {
624631
function writePkcs8EdDSAPrivate(key, der) {
625632
der.endSequence();
626633

627-
var k = utils.mpNormalize(key.part.k.data, true);
628634
der.startSequence(asn1.Ber.OctetString);
635+
var k = utils.mpNormalize(key.part.k.data);
636+
/* RFCs call for storing exactly 32 bytes, so strip any leading zeros */
637+
while (k.length > 32 && k[0] === 0x00)
638+
k = k.slice(1);
629639
der.writeBuffer(k, asn1.Ber.OctetString);
630640
der.endSequence();
641+
642+
utils.writeBitString(der, key.part.A.data, asn1.Ber.Context | 1);
631643
}

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sshpk",
3-
"version": "1.17.0",
3+
"version": "1.18.0",
44
"description": "A library for finding and using SSH public keys",
55
"main": "lib/index.js",
66
"scripts": {
@@ -49,8 +49,7 @@
4949
"ecc-jsbn": "~0.1.1",
5050
"bcrypt-pbkdf": "^1.0.0"
5151
},
52-
"optionalDependencies": {
53-
},
52+
"optionalDependencies": {},
5453
"devDependencies": {
5554
"tape": "^3.5.0",
5655
"benchmark": "^1.0.0",

test/private-key.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ test('PrivateKey load ed25519 key (w/ public curdle-pkix-05)', function (t) {
127127
t.end();
128128
});
129129

130+
test('PrivateKey convert ed25519 key from pkcs1 to pkcs8', function (t) {
131+
var keyPem = fs.readFileSync(path.join(testDir, 'id_ed25519.pem'));
132+
var key = sshpk.parsePrivateKey(keyPem, 'pem');
133+
var newPem = key.toString('pkcs8');
134+
var key2 = sshpk.parsePrivateKey(newPem, 'pem');
135+
t.strictEqual(key.type, 'ed25519');
136+
t.strictEqual(key.size, 256);
137+
t.end();
138+
});
139+
130140
test('PrivateKey invalid ed25519 key (not DER)', function (t) {
131141
var keyPem = fs.readFileSync(path.join(testDir,
132142
'ed25519-invalid-ber.pem'));

0 commit comments

Comments
 (0)