// Copyright 2015 Joyent, Inc.

module.exports = {

read: read,
readPkcs8: readPkcs8,
write: write,
writePkcs8: writePkcs8,

readECDSACurve: readECDSACurve,
writeECDSACurve: writeECDSACurve

};

var assert = require('assert-plus'); var asn1 = require('asn1'); var algs = require('../algs'); var utils = require('../utils'); var Key = require('../key'); var PrivateKey = require('../private-key'); var pem = require('./pem');

function read(buf, options) {

return (pem.read(buf, options, 'pkcs8'));

}

function write(key, options) {

return (pem.write(key, options, 'pkcs8'));

}

/* Helper to read in a single mpint */ function readMPInt(der, nm) {

assert.strictEqual(der.peek(), asn1.Ber.Integer,
    nm + ' is not an Integer');
return (utils.mpNormalize(der.readString(asn1.Ber.Integer, true)));

}

function readPkcs8(alg, type, der) {

/* Private keys in pkcs#8 format have a weird extra int */
if (der.peek() === asn1.Ber.Integer) {
        assert.strictEqual(type, 'private',
            'unexpected Integer at start of public key');
        der.readString(asn1.Ber.Integer, true);
}

der.readSequence();
var next = der.offset + der.length;

var oid = der.readOID();
switch (oid) {
case '1.2.840.113549.1.1.1':
        der._offset = next;
        if (type === 'public')
                return (readPkcs8RSAPublic(der));
        else
                return (readPkcs8RSAPrivate(der));
case '1.2.840.10040.4.1':
        if (type === 'public')
                return (readPkcs8DSAPublic(der));
        else
                return (readPkcs8DSAPrivate(der));
case '1.2.840.10045.2.1':
        if (type === 'public')
                return (readPkcs8ECDSAPublic(der));
        else
                return (readPkcs8ECDSAPrivate(der));
default:
        throw (new Error('Unknown key type OID ' + oid));
}

}

function readPkcs8RSAPublic(der) {

// bit string sequence
der.readSequence(asn1.Ber.BitString);
der.readByte();
der.readSequence();

// modulus
var n = readMPInt(der, 'modulus');
var e = readMPInt(der, 'exponent');

// now, make the key
var key = {
        type: 'rsa',
        source: der.originalInput,
        parts: [
                { name: 'e', data: e },
                { name: 'n', data: n }
        ]
};

return (new Key(key));

}

function readPkcs8RSAPrivate(der) {

der.readSequence(asn1.Ber.OctetString);
der.readSequence();

var ver = readMPInt(der, 'version');
assert.equal(ver[0], 0x0, 'unknown RSA private key version');

// modulus then public exponent
var n = readMPInt(der, 'modulus');
var e = readMPInt(der, 'public exponent');
var d = readMPInt(der, 'private exponent');
var p = readMPInt(der, 'prime1');
var q = readMPInt(der, 'prime2');
var dmodp = readMPInt(der, 'exponent1');
var dmodq = readMPInt(der, 'exponent2');
var iqmp = readMPInt(der, 'iqmp');

// now, make the key
var key = {
        type: 'rsa',
        parts: [
                { name: 'n', data: n },
                { name: 'e', data: e },
                { name: 'd', data: d },
                { name: 'iqmp', data: iqmp },
                { name: 'p', data: p },
                { name: 'q', data: q },
                { name: 'dmodp', data: dmodp },
                { name: 'dmodq', data: dmodq }
        ]
};

return (new PrivateKey(key));

}

function readPkcs8DSAPublic(der) {

der.readSequence();

var p = readMPInt(der, 'p');
var q = readMPInt(der, 'q');
var g = readMPInt(der, 'g');

// bit string sequence
der.readSequence(asn1.Ber.BitString);
der.readByte();

var y = readMPInt(der, 'y');

// now, make the key
var key = {
        type: 'dsa',
        parts: [
                { name: 'p', data: p },
                { name: 'q', data: q },
                { name: 'g', data: g },
                { name: 'y', data: y }
        ]
};

return (new Key(key));

}

function readPkcs8DSAPrivate(der) {

der.readSequence();

var p = readMPInt(der, 'p');
var q = readMPInt(der, 'q');
var g = readMPInt(der, 'g');

der.readSequence(asn1.Ber.OctetString);
var x = readMPInt(der, 'x');

/* The pkcs#8 format does not include the public key */
var y = utils.calculateDSAPublic(g, p, x);

var key = {
        type: 'dsa',
        parts: [
                { name: 'p', data: p },
                { name: 'q', data: q },
                { name: 'g', data: g },
                { name: 'y', data: y },
                { name: 'x', data: x }
        ]
};

return (new PrivateKey(key));

}

function readECDSACurve(der) {

var curveName, curveNames;
var j, c, cd;

if (der.peek() === asn1.Ber.OID) {
        var oid = der.readOID();

        curveNames = Object.keys(algs.curves);
        for (j = 0; j < curveNames.length; ++j) {
                c = curveNames[j];
                cd = algs.curves[c];
                if (cd.pkcs8oid === oid) {
                        curveName = c;
                        break;
                }
        }

} else {
        // ECParameters sequence
        der.readSequence();
        var version = der.readString(asn1.Ber.Integer, true);
        assert.strictEqual(version[0], 1, 'ECDSA key not version 1');

        var curve = {};

        // FieldID sequence
        der.readSequence();
        var fieldTypeOid = der.readOID();
        assert.strictEqual(fieldTypeOid, '1.2.840.10045.1.1',
            'ECDSA key is not from a prime-field');
        var p = curve.p = utils.mpNormalize(
            der.readString(asn1.Ber.Integer, true));
        /*
         * p always starts with a 1 bit, so count the zeros to get its
         * real size.
         */
        curve.size = p.length * 8 - utils.countZeros(p);

        // Curve sequence
        der.readSequence();
        curve.a = utils.mpNormalize(
            der.readString(asn1.Ber.OctetString, true));
        curve.b = utils.mpNormalize(
            der.readString(asn1.Ber.OctetString, true));
        if (der.peek() === asn1.Ber.BitString)
                curve.s = der.readString(asn1.Ber.BitString, true);

        // Combined Gx and Gy
        curve.G = der.readString(asn1.Ber.OctetString, true);
        assert.strictEqual(curve.G[0], 0x4,
            'uncompressed G is required');

        curve.n = utils.mpNormalize(
            der.readString(asn1.Ber.Integer, true));
        curve.h = utils.mpNormalize(
            der.readString(asn1.Ber.Integer, true));
        assert.strictEqual(curve.h[0], 0x1, 'a cofactor=1 curve is ' +
            'required');

        curveNames = Object.keys(algs.curves);
        var ks = Object.keys(curve);
        for (j = 0; j < curveNames.length; ++j) {
                c = curveNames[j];
                cd = algs.curves[c];
                var equal = true;
                for (var i = 0; i < ks.length; ++i) {
                        var k = ks[i];
                        if (cd[k] === undefined)
                                continue;
                        if (typeof (cd[k]) === 'object' &&
                            cd[k].equals !== undefined) {
                                if (!cd[k].equals(curve[k])) {
                                        equal = false;
                                        break;
                                }
                        } else if (Buffer.isBuffer(cd[k])) {
                                if (cd[k].toString('binary')
                                    !== curve[k].toString('binary')) {
                                        equal = false;
                                        break;
                                }
                        } else {
                                if (cd[k] !== curve[k]) {
                                        equal = false;
                                        break;
                                }
                        }
                }
                if (equal) {
                        curveName = c;
                        break;
                }
        }
}
return (curveName);

}

function readPkcs8ECDSAPrivate(der) {

var curveName = readECDSACurve(der);
assert.string(curveName, 'a known elliptic curve');

der.readSequence(asn1.Ber.OctetString);
der.readSequence();

var version = readMPInt(der, 'version');
assert.equal(version[0], 1, 'unknown version of ECDSA key');

var d = der.readString(asn1.Ber.OctetString, true);
der.readSequence(0xa1);

var Q = der.readString(asn1.Ber.BitString, true);
Q = utils.ecNormalize(Q);

var key = {
        type: 'ecdsa',
        parts: [
                { name: 'curve', data: new Buffer(curveName) },
                { name: 'Q', data: Q },
                { name: 'd', data: d }
        ]
};

return (new PrivateKey(key));

}

function readPkcs8ECDSAPublic(der) {

var curveName = readECDSACurve(der);
assert.string(curveName, 'a known elliptic curve');

var Q = der.readString(asn1.Ber.BitString, true);
Q = utils.ecNormalize(Q);

var key = {
        type: 'ecdsa',
        parts: [
                { name: 'curve', data: new Buffer(curveName) },
                { name: 'Q', data: Q }
        ]
};

return (new Key(key));

}

function writePkcs8(der, key) {

der.startSequence();

if (PrivateKey.isPrivateKey(key)) {
        var sillyInt = new Buffer(1);
        sillyInt[0] = 0x0;
        der.writeBuffer(sillyInt, asn1.Ber.Integer);
}

der.startSequence();
switch (key.type) {
case 'rsa':
        der.writeOID('1.2.840.113549.1.1.1');
        if (PrivateKey.isPrivateKey(key))
                writePkcs8RSAPrivate(key, der);
        else
                writePkcs8RSAPublic(key, der);
        break;
case 'dsa':
        der.writeOID('1.2.840.10040.4.1');
        if (PrivateKey.isPrivateKey(key))
                writePkcs8DSAPrivate(key, der);
        else
                writePkcs8DSAPublic(key, der);
        break;
case 'ecdsa':
        der.writeOID('1.2.840.10045.2.1');
        if (PrivateKey.isPrivateKey(key))
                writePkcs8ECDSAPrivate(key, der);
        else
                writePkcs8ECDSAPublic(key, der);
        break;
default:
        throw (new Error('Unsupported key type: ' + key.type));
}

der.endSequence();

}

function writePkcs8RSAPrivate(key, der) {

der.writeNull();
der.endSequence();

der.startSequence(asn1.Ber.OctetString);
der.startSequence();

var version = new Buffer(1);
version[0] = 0;
der.writeBuffer(version, asn1.Ber.Integer);

der.writeBuffer(key.part.n.data, asn1.Ber.Integer);
der.writeBuffer(key.part.e.data, asn1.Ber.Integer);
der.writeBuffer(key.part.d.data, asn1.Ber.Integer);
der.writeBuffer(key.part.p.data, asn1.Ber.Integer);
der.writeBuffer(key.part.q.data, asn1.Ber.Integer);
if (!key.part.dmodp || !key.part.dmodq)
        utils.addRSAMissing(key);
der.writeBuffer(key.part.dmodp.data, asn1.Ber.Integer);
der.writeBuffer(key.part.dmodq.data, asn1.Ber.Integer);
der.writeBuffer(key.part.iqmp.data, asn1.Ber.Integer);

der.endSequence();
der.endSequence();

}

function writePkcs8RSAPublic(key, der) {

der.writeNull();
der.endSequence();

der.startSequence(asn1.Ber.BitString);
der.writeByte(0x00);

der.startSequence();
der.writeBuffer(key.part.n.data, asn1.Ber.Integer);
der.writeBuffer(key.part.e.data, asn1.Ber.Integer);
der.endSequence();

der.endSequence();

}

function writePkcs8DSAPrivate(key, der) {

der.startSequence();
der.writeBuffer(key.part.p.data, asn1.Ber.Integer);
der.writeBuffer(key.part.q.data, asn1.Ber.Integer);
der.writeBuffer(key.part.g.data, asn1.Ber.Integer);
der.endSequence();

der.endSequence();

der.startSequence(asn1.Ber.OctetString);
der.writeBuffer(key.part.x.data, asn1.Ber.Integer);
der.endSequence();

}

function writePkcs8DSAPublic(key, der) {

der.startSequence();
der.writeBuffer(key.part.p.data, asn1.Ber.Integer);
der.writeBuffer(key.part.q.data, asn1.Ber.Integer);
der.writeBuffer(key.part.g.data, asn1.Ber.Integer);
der.endSequence();
der.endSequence();

der.startSequence(asn1.Ber.BitString);
der.writeByte(0x00);
der.writeBuffer(key.part.y.data, asn1.Ber.Integer);
der.endSequence();

}

function writeECDSACurve(key, der) {

var curve = algs.curves[key.curve];
if (curve.pkcs8oid) {
        /* This one has a name in pkcs#8, so just write the oid */
        der.writeOID(curve.pkcs8oid);

} else {
        // ECParameters sequence
        der.startSequence();

        var version = new Buffer(1);
        version.writeUInt8(1, 0);
        der.writeBuffer(version, asn1.Ber.Integer);

        // FieldID sequence
        der.startSequence();
        der.writeOID('1.2.840.10045.1.1'); // prime-field
        der.writeBuffer(curve.p, asn1.Ber.Integer);
        der.endSequence();

        // Curve sequence
        der.startSequence();
        var a = curve.p;
        if (a[0] === 0x0)
                a = a.slice(1);
        der.writeBuffer(a, asn1.Ber.OctetString);
        der.writeBuffer(curve.b, asn1.Ber.OctetString);
        der.writeBuffer(curve.s, asn1.Ber.BitString);
        der.endSequence();

        der.writeBuffer(curve.G, asn1.Ber.OctetString);
        der.writeBuffer(curve.n, asn1.Ber.Integer);
        var h = curve.h;
        if (!h) {
                h = new Buffer(1);
                h[0] = 1;
        }
        der.writeBuffer(h, asn1.Ber.Integer);

        // ECParameters
        der.endSequence();
}

}

function writePkcs8ECDSAPublic(key, der) {

writeECDSACurve(key, der);
der.endSequence();

var Q = utils.ecNormalize(key.part.Q.data, true);
der.writeBuffer(Q, asn1.Ber.BitString);

}

function writePkcs8ECDSAPrivate(key, der) {

writeECDSACurve(key, der);
der.endSequence();

der.startSequence(asn1.Ber.OctetString);
der.startSequence();

var version = new Buffer(1);
version[0] = 1;
der.writeBuffer(version, asn1.Ber.Integer);

der.writeBuffer(key.part.d.data, asn1.Ber.OctetString);

der.startSequence(0xa1);
var Q = utils.ecNormalize(key.part.Q.data, true);
der.writeBuffer(Q, asn1.Ber.BitString);
der.endSequence();

der.endSequence();
der.endSequence();

}