acme.js/lib/node/rsa.js

155 lines
3.6 KiB
JavaScript

'use strict';
var native = module.exports;
// XXX provided by caller: export
var RSA = native;
var PEM = require('../pem.js');
var x509 = require('../x509.js');
var ASN1 = require('../asn1/parser.js');
native.generate = function(opts) {
opts.kty = 'RSA';
return native._generate(opts).then(function(pair) {
var format = opts.format;
var encoding = opts.encoding;
// The easy way
if ('json' === format && !encoding) {
format = 'jwk';
encoding = 'json';
}
if (
('jwk' === format || !format) &&
('json' === encoding || !encoding)
) {
return pair;
}
if ('jwk' === format || 'json' === encoding) {
throw new Error(
"format '" +
format +
"' is incompatible with encoding '" +
encoding +
"'"
);
}
// The... less easy way
/*
var priv;
var pub;
if ('spki' === format || 'pkcs8' === format) {
format = 'pkcs8';
pub = 'spki';
}
if ('pem' === format) {
format = 'pkcs1';
encoding = 'pem';
} else if ('der' === format) {
format = 'pkcs1';
encoding = 'der';
}
priv = format;
pub = pub || format;
if (!encoding) {
encoding = 'pem';
}
if (priv) {
priv = { type: priv, format: encoding };
pub = { type: pub, format: encoding };
} else {
// jwk
priv = { type: 'pkcs1', format: 'pem' };
pub = { type: 'pkcs1', format: 'pem' };
}
*/
if (('pem' === format || 'der' === format) && !encoding) {
encoding = format;
format = 'pkcs1';
}
var exOpts = { jwk: pair.private, format: format, encoding: encoding };
return RSA.export(exOpts).then(function(priv) {
exOpts.public = true;
if ('pkcs8' === exOpts.format) {
exOpts.format = 'spki';
}
return RSA.export(exOpts).then(function(pub) {
return { private: priv, public: pub };
});
});
});
};
native._generate = function(opts) {
if (!opts) {
opts = {};
}
return new Promise(function(resolve, reject) {
try {
var modlen = opts.modulusLength || 2048;
var exp = opts.publicExponent || 0x10001;
var pair = require('./generate-privkey.js')(modlen, exp);
if (pair.private) {
resolve(pair);
return;
}
pair = toJwks(pair);
resolve({ private: pair.private, public: pair.public });
} catch (e) {
reject(e);
}
});
};
// PKCS1 to JWK only
function toJwks(oldpair) {
var block = PEM.parseBlock(oldpair.privateKeyPem);
var asn1 = ASN1.parse(block.bytes);
var jwk = { kty: 'RSA', n: null, e: null };
jwk = x509.parsePkcs1(block.bytes, asn1, jwk);
return { private: jwk, public: RSA.neuter({ jwk: jwk }) };
}
// TODO
var Enc = require('@root/encoding/base64');
x509.parsePkcs1 = function parseRsaPkcs1(buf, asn1, jwk) {
if (
!asn1.children.every(function(el) {
return 0x02 === el.type;
})
) {
throw new Error(
'not an RSA PKCS#1 public or private key (not all ints)'
);
}
if (2 === asn1.children.length) {
jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
return jwk;
} else if (asn1.children.length >= 9) {
// the standard allows for "otherPrimeInfos", hence at least 9
jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
return jwk;
} else {
throw new Error(
'not an RSA PKCS#1 public or private key (wrong number of ints)'
);
}
};