/*! * algorithms/ec-util.js - Elliptic Curve Utility Functions * * Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file. */ "use strict"; var clone = require("lodash/clone"), ecc = require("../deps/ecc"), forge = require("../deps/forge.js"), util = require("../util"); var EC_KEYSIZES = { "P-256": 256, "P-384": 384, "P-521": 521 }; function convertToForge(key, isPublic) { var parts = isPublic ? ["x", "y"] : ["d"]; parts = parts.map(function(f) { return new forge.jsbn.BigInteger(key[f].toString("hex"), 16); }); // prefix with curve parts = [key.crv].concat(parts); var fn = isPublic ? ecc.asPublicKey : ecc.asPrivateKey; return fn.apply(ecc, parts); } function convertToJWK(key, isPublic) { var result = clone(key); var parts = isPublic ? ["x", "y"] : ["x", "y", "d"]; parts.forEach(function(f) { result[f] = util.base64url.encode(result[f]); }); // remove potentially troublesome properties delete result.key_ops; delete result.use; delete result.alg; if (isPublic) { delete result.d; } return result; } function convertToObj(key, isPublic) { var result = clone(key); var parts = isPublic ? ["x", "y"] : ["d"]; parts.forEach(function(f) { // assume string if base64url-encoded result[f] = util.asBuffer(result[f], "base64url"); }); return result; } var UNCOMPRESSED = Buffer.from([0x04]); function convertToBuffer(key, isPublic) { key = convertToObj(key, isPublic); var result = isPublic ? Buffer.concat([UNCOMPRESSED, key.x, key.y]) : key.d; return result; } function curveSize(crv) { return EC_KEYSIZES[crv || ""] || NaN; } function curveNameToOid(crv) { switch (crv) { case "P-256": return "1.2.840.10045.3.1.7"; case "P-384": return "1.3.132.0.34"; case "P-521": return "1.3.132.0.35"; default: return null; } } var EC_OID = "1.2.840.10045.2.1"; function convertToPEM(key, isPrivate) { // curveName to OID var oid = key.crv; oid = curveNameToOid(oid); oid = forge.asn1.oidToDer(oid); // key as bytes var type, pub, asn1; if (isPrivate) { type = "EC PRIVATE KEY"; pub = Buffer.concat([ Buffer.from([0x00, 0x04]), key.x, key.y ]).toString("binary"); key = key.d.toString("binary"); asn1 = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [ forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.INTEGER, false, "\u0001"), forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OCTETSTRING, false, key), forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 0, true, [ forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, oid.bytes()) ]), forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 1, true, [ forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.BITSTRING, false, pub) ]) ]); } else { type = "PUBLIC KEY"; key = Buffer.concat([ Buffer.from([0x00, 0x04]), key.x, key.y ]).toString("binary"); asn1 = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [ forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [ forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, forge.asn1.oidToDer(EC_OID).bytes()), forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, oid.bytes()) ]), forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.BITSTRING, false, key) ]); } asn1 = forge.asn1.toDer(asn1).bytes(); var pem = forge.pem.encode({ type: type, body: asn1 }); return pem; } // Inspired by teifip/node-webtokens/blob/master/lib/ecdsa.js var ERR_MSG = "Could not extract parameters from DER signature"; function derToConcat(signature, size) { var offset = 0; if (signature[offset++] !== 0x30) { throw new Error(ERR_MSG); } var seqLength = signature[offset++]; if (seqLength === 0x81) { seqLength = signature[offset++]; } if (seqLength > signature.length - offset) { throw new Error(ERR_MSG); } if (signature[offset++] !== 0x02) { throw new Error(ERR_MSG); } var rLength = signature[offset++]; if (rLength > signature.length - offset - 2) { throw new Error(ERR_MSG); } if (rLength > size + 1) { throw new Error(ERR_MSG); } var rOffset = offset; offset += rLength; if (signature[offset++] !== 0x02) { throw new Error(ERR_MSG); } var sLength = signature[offset++]; if (sLength !== signature.length - offset) { throw new Error(ERR_MSG); } if (sLength > size + 1) { throw new Error(ERR_MSG); } var sOffset = offset; offset += sLength; if (offset !== signature.length) { throw new Error(ERR_MSG); } var rPadding = size - rLength; var sPadding = size - sLength; var dst = Buffer.alloc(rPadding + rLength + sPadding + sLength); for (offset = 0; offset < rPadding; ++offset) { dst[offset] = 0; } var rPad = Math.max(-rPadding, 0); signature.copy(dst, offset, rOffset + rPad, rOffset + rLength); offset = size; for (var o = offset; offset < o + sPadding; ++offset) { dst[offset] = 0; } var sPad = Math.max(-sPadding, 0); signature.copy(dst, offset, sOffset + sPad, sOffset + sLength); return dst; } function countPadding(buf, start, stop) { var padding = 0; while (start + padding < stop && buf[start + padding] === 0) { ++padding; } var needsSign = buf[start + padding] >= 0x80; if (needsSign) { --padding; } return padding; } function concatToDer(signature, size) { var rPadding = countPadding(signature, 0, size); var sPadding = countPadding(signature, size, signature.length); var rLength = size - rPadding; var sLength = size - sPadding; var rsBytes = rLength + sLength + 4; var shortLength = rsBytes < 0x80; var dst = Buffer.alloc((shortLength ? 2 : 3) + rsBytes); var offset = 0; dst[offset++] = 0x30; if (shortLength) { dst[offset++] = rsBytes; } else { dst[offset++] = 0x81; dst[offset++] = rsBytes & 0xFF; } dst[offset++] = 0x02; dst[offset++] = rLength; if (rPadding < 0) { dst[offset++] = 0; offset += signature.copy(dst, offset, 0, size); } else { offset += signature.copy(dst, offset, rPadding, size); } dst[offset++] = 0x02; dst[offset++] = sLength; if (sPadding < 0) { dst[offset++] = 0; signature.copy(dst, offset, size); } else { signature.copy(dst, offset, size + sPadding); } return dst; } module.exports = { convertToForge: convertToForge, convertToJWK: convertToJWK, convertToObj: convertToObj, convertToBuffer: convertToBuffer, curveSize: curveSize, derToConcat: derToConcat, concatToDer: concatToDer, convertToPEM: convertToPEM, EC_OID: EC_OID };