// LICENSE_CODE ZON
'use strict'; /*zlint node*/
require('./config.js');
var zutil = require('./util.js');
var array = require('./array.js');
var fs = require('fs');
var asn1js_common = require('asn1js/org/pkijs/common');
var asn1js = require('asn1js');
var pkijs = require('pkijs');
var x509schema = require('pkijs/org/pkijs/x509_schema');
var crypto = require('crypto');
var E = exports;
E.pkijs = pkijs.org.pkijs;
function uint32_to_buffer(i){
    let res = new Buffer(4);
    res.writeUInt32BE(i);
    return res;
}

function x509_find_extension(cert, id){
    for (var i=0; i<cert.extensions.length; i++)
    {
        if (cert.extensions[i].extnID==id)
            return cert.extensions[i].parsedValue;
    }
}

function x509_parse_id(id){
    var typemap = {
        '2.5.4.6': 'C',
        '2.5.4.10': 'OU',
        '2.5.4.11': 'O',
        '2.5.4.3': 'CN',
        '2.5.4.7': 'L',
        '2.5.4.8': 'S',
        '2.5.4.12': 'T',
        '2.5.4.42': 'GN',
        '2.5.4.43': 'I',
        '2.5.4.4': 'SN',
    };
    var res = {};
    for (var i=0, parts = id.types_and_values; i<parts.length; i++)
    {
        var key = typemap[parts[i].type];
        res[key] = parts[i].value.value_block.value;
    }
    return res;
}

E.x509_subject = function(cert){ return x509_parse_id(cert.subject); };
E.x509_issuer = function(cert){ return x509_parse_id(cert.issuer); };
E.x509_alt_names = function(cert){
    var res = [], ext_value;
    // 'Subject Alternative Name'
    if (!(ext_value = x509_find_extension(cert, '2.5.29.17')))
        return res;
    ext_value.altNames.forEach(function(alt_name){
        // type is 'dNSName'
        if (alt_name.NameType==2)
            res.push(alt_name.Name);
    });
    return res;
};

E.x509_all_names = function(cert){
    var res = E.x509_alt_names(cert), cn;
    if (cn = E.x509_subject(cert).CN)
        array.add_elm(res, cn);
    return res;
};

E.x509_der_parse = function(node_buffer){
    var asn1 = E.pkijs.fromBER(new Uint8Array(node_buffer).buffer);
    var cert = new E.pkijs.simpl.CERT({schema: asn1.result});
    return cert;
};

E.x509_pem_parse = function(str){
    var base64 = str.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, '');
    return E.x509_der_parse(new Buffer(base64, 'base64'));
};

E.x509_pubkey = function(x509){
    let asn1_pub = new E.pkijs.fromBER(
        x509.subjectPublicKeyInfo.subjectPublicKey.value_block.value_hex);
    let rsa_pub = new E.pkijs.simpl.x509.RSAPublicKey({
        schema: asn1_pub.result});
    let mod = Buffer.from(rsa_pub.modulus.value_block.value_hex);
    let exp = Buffer.from(rsa_pub.publicExponent.value_block.value_hex);
    return {mod, exp};
};

function ssh_next(opt){
    let len = opt.buf.readUInt32BE(opt.pos);
    opt.pos += 4;
    let res = opt.buf.slice(opt.pos, opt.pos+len);
    opt.pos += len;
    return res;
}

E.ssh_pubkey = function(pub_str){
    let data = {pos: 0, buf: new Buffer(pub_str.split(' ')[1], 'base64')};
    let type = ssh_next(data).toString();
    if (type!='ssh-rsa')
        throw new Error('Non-RSA keys are not supported: '+type);
    let exp = ssh_next(data);
    let mod = ssh_next(data);
    return {mod, exp};
};

function encode_ssh_pubkey(pub){
    return Buffer.concat([uint32_to_buffer(7), new Buffer('ssh-rsa'),
        uint32_to_buffer(pub.exp.length), pub.exp,
        uint32_to_buffer(pub.mod.length), pub.mod]);
}

function pub_fp(pub, opt){
    opt = opt||{};
    let hash = crypto.createHash(opt.ssh_compat ? 'md5' : 'sha1');
    if (opt.ssh_compat)
        hash.update(encode_ssh_pubkey(pub));
    else
        hash.update(pub.mod).update(pub.exp);
    return hash.digest('hex');
}

E.cert_pub_fingerprint = function(fname, opt){
    let src = fs.readFileSync(fname).toString();
    let pub = E.x509_pubkey(E.x509_pem_parse(src));
    return pub_fp(pub, opt);
};

E.ssh_pub_fingerprint = function(fname, opt){
    let src = fs.readFileSync(fname).toString();
    let pub = E.ssh_pubkey(src);
    return pub_fp(pub, opt);
};

zutil.extend_deep(asn1js, asn1js_common);
zutil.extend_deep(x509schema, asn1js);
zutil.extend_deep(pkijs, x509schema, asn1js);
