From 6bef1617b3a8a6de05fa1f6e4bb859e554f69616 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 1 Dec 2018 17:01:24 -0700 Subject: [PATCH] cleanup --- lib/asn1.js | 61 +++++++++++ lib/eckles.js | 292 +++----------------------------------------------- lib/ssh.js | 55 ++++++++++ lib/x509.js | 170 +++++++++++++++++++++++++++++ 4 files changed, 299 insertions(+), 279 deletions(-) create mode 100644 lib/asn1.js create mode 100644 lib/ssh.js create mode 100644 lib/x509.js diff --git a/lib/asn1.js b/lib/asn1.js new file mode 100644 index 0000000..224e7df --- /dev/null +++ b/lib/asn1.js @@ -0,0 +1,61 @@ +'use strict'; + +var Enc = require('./encoding.js'); + +// +// A dumbed-down, minimal ASN.1 packer +// + +// Almost every ASN.1 type that's important for CSR +// can be represented generically with only a few rules. +var ASN1 = module.exports = function ASN1(/*type, hexstrings...*/) { + var args = Array.prototype.slice.call(arguments); + var typ = args.shift(); + var str = args.join('').replace(/\s+/g, '').toLowerCase(); + var len = (str.length/2); + var lenlen = 0; + var hex = typ; + + // We can't have an odd number of hex chars + if (len !== Math.round(len)) { + throw new Error("invalid hex"); + } + + // The first byte of any ASN.1 sequence is the type (Sequence, Integer, etc) + // The second byte is either the size of the value, or the size of its size + + // 1. If the second byte is < 0x80 (128) it is considered the size + // 2. If it is > 0x80 then it describes the number of bytes of the size + // ex: 0x82 means the next 2 bytes describe the size of the value + // 3. The special case of exactly 0x80 is "indefinite" length (to end-of-file) + + if (len > 127) { + lenlen += 1; + while (len > 255) { + lenlen += 1; + len = len >> 8; + } + } + + if (lenlen) { hex += Enc.numToHex(0x80 + lenlen); } + return hex + Enc.numToHex(str.length/2) + str; +}; + +// The Integer type has some special rules +ASN1.UInt = function UINT() { + var str = Array.prototype.slice.call(arguments).join(''); + var first = parseInt(str.slice(0, 2), 16); + + // If the first byte is 0x80 or greater, the number is considered negative + // Therefore we add a '00' prefix if the 0x80 bit is set + if (0x80 & first) { str = '00' + str; } + + return ASN1('02', str); +}; + +// The Bit String type also has a special rule +ASN1.BitStr = function BITSTR() { + var str = Array.prototype.slice.call(arguments).join(''); + // '00' is a mask of how many bits of the next byte to ignore + return ASN1('03', '00' + str); +}; diff --git a/lib/eckles.js b/lib/eckles.js index cbcf989..1b48219 100644 --- a/lib/eckles.js +++ b/lib/eckles.js @@ -3,8 +3,9 @@ var EC = module.exports; var Enc = require('./encoding.js'); -var ASN1; var PEM = require('./pem.js'); +var SSH = require('./ssh.js'); +var x509 = require('./x509.js'); // 1.2.840.10045.3.1.7 // prime256v1 (ANSI X9.62 named elliptic curve) @@ -13,150 +14,9 @@ var OBJ_ID_EC = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase(); // secp384r1 (SECG (Certicom) named elliptic curve) var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase(); -// 1.2.840.10045.2.1 -// ecPublicKey (ANSI X9.62 public key type) -var OBJ_ID_EC_PUB = '06 07 2A8648CE3D0201'.replace(/\s+/g, '').toLowerCase(); - - // 19 e c d s a - s h a 2 - n i s t p 2 5 6 -var SSH_EC_P256 = '00000013 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36' - .replace(/\s+/g, '').toLowerCase(); - - // 19 e c d s a - s h a 2 - n i s t p 3 8 4 -var SSH_EC_P384 = '00000013 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34' - .replace(/\s+/g, '').toLowerCase(); - // The one good thing that came from the b***kchain hysteria: good EC documentation // https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/ -EC.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) { - var index = 7; - var len = 32; - var olen = OBJ_ID_EC.length/2; - - if ("P-384" === jwk.crv) { - olen = OBJ_ID_EC_384.length/2; - index = 8; - len = 48; - } - if (len !== u8[index - 1]) { - throw new Error("Unexpected bitlength " + len); - } - - // private part is d - var d = u8.slice(index, index + len); - // compression bit index - var ci = index + len + 2 + olen + 2 + 3; - var c = u8[ci]; - var x, y; - - if (0x04 === c) { - y = u8.slice(ci + 1 + len, ci + 1 + len + len); - } else if (0x02 !== c) { - throw new Error("not a supported EC private key"); - } - x = u8.slice(ci + 1, ci + 1 + len); - - return { - kty: jwk.kty - , crv: jwk.crv - , d: Enc.bufToUrlBase64(d) - //, dh: Enc.bufToHex(d) - , x: Enc.bufToUrlBase64(x) - //, xh: Enc.bufToHex(x) - , y: Enc.bufToUrlBase64(y) - //, yh: Enc.bufToHex(y) - }; -}; - -EC.parsePkcs8 = function parseEcPkcs8(u8, jwk) { - var index = 24 + (OBJ_ID_EC.length/2); - var len = 32; - if ("P-384" === jwk.crv) { - index = 24 + (OBJ_ID_EC_384.length/2) + 2; - len = 48; - } - - //console.log(index, u8.slice(index)); - if (0x04 !== u8[index]) { - //console.log(jwk); - throw new Error("privkey not found"); - } - var d = u8.slice(index+2, index+2+len); - var ci = index+2+len+5; - var xi = ci+1; - var x = u8.slice(xi, xi + len); - var yi = xi+len; - var y; - if (0x04 === u8[ci]) { - y = u8.slice(yi, yi + len); - } else if (0x02 !== u8[ci]) { - throw new Error("invalid compression bit (expected 0x04 or 0x02)"); - } - - return { - kty: jwk.kty - , crv: jwk.crv - , d: Enc.bufToUrlBase64(d) - //, dh: Enc.bufToHex(d) - , x: Enc.bufToUrlBase64(x) - //, xh: Enc.bufToHex(x) - , y: Enc.bufToUrlBase64(y) - //, yh: Enc.bufToHex(y) - }; -}; - -EC.parseSpki = function parsePem(u8, jwk) { - var ci = 16 + OBJ_ID_EC.length/2; - var len = 32; - - if ("P-384" === jwk.crv) { - ci = 16 + OBJ_ID_EC_384.length/2; - len = 48; - } - - var c = u8[ci]; - var xi = ci + 1; - var x = u8.slice(xi, xi + len); - var yi = xi + len; - var y; - if (0x04 === c) { - y = u8.slice(yi, yi + len); - } else if (0x02 !== c) { - throw new Error("not a supported EC private key"); - } - - return { - kty: jwk.kty - , crv: jwk.crv - , x: Enc.bufToUrlBase64(x) - //, xh: Enc.bufToHex(x) - , y: Enc.bufToUrlBase64(y) - //, yh: Enc.bufToHex(y) - }; -}; -EC.parsePkix = EC.parseSpki; - -EC.parseSsh = function (pem) { - var jwk = { kty: 'EC', crv: null, x: null, y: null }; - var b64 = pem.split(/\s+/g)[1]; - var buf = Buffer.from(b64, 'base64'); - var hex = Enc.bufToHex(buf); - var index = 40; - var len; - if (0 === hex.indexOf(SSH_EC_P256)) { - jwk.crv = 'P-256'; - len = 32; - } else if (0 === hex.indexOf(SSH_EC_P384)) { - jwk.crv = 'P-384'; - len = 48; - } - var x = buf.slice(index, index + len); - var y = buf.slice(index + len, index + len + len); - jwk.x = Enc.bufToUrlBase64(x); - jwk.y = Enc.bufToUrlBase64(y); - return jwk; -}; - /*global Promise*/ EC.generate = function (opts) { return Promise.resolve().then(function () { @@ -240,7 +100,7 @@ EC.importSync = function importEcSync(opts) { throw new Error("must pass { pem: pem } as a string"); } if (0 === opts.pem.indexOf('ecdsa-sha2-')) { - return EC.parseSsh(opts.pem); + return SSH.parseSsh(opts.pem); } var pem = opts.pem; var u8 = PEM.parseBlock(pem).bytes; @@ -254,15 +114,15 @@ EC.importSync = function importEcSync(opts) { // PKCS8 if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16)); - jwk = EC.parsePkcs8(u8, jwk); + jwk = x509.parsePkcs8(u8, jwk); // EC-only } else if (0x02 === u8[2] && 0x04 === u8[5] && 0xA0 === u8[39]) { //console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16)); - jwk = EC.parseSec1(u8, jwk); + jwk = x509.parseSec1(u8, jwk); // SPKI/PKIK (Public) } else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { //console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16)); - jwk = EC.parseSpki(u8, jwk); + jwk = x509.parseSpki(u8, jwk); // Error } else { //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16)); @@ -276,15 +136,15 @@ EC.importSync = function importEcSync(opts) { // PKCS8 if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16)); - jwk = EC.parsePkcs8(u8, jwk); + jwk = x509.parsePkcs8(u8, jwk); // EC-only } else if (0x02 === u8[3] && 0x04 === u8[6] && 0xA0 === u8[56]) { //console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16)); - jwk = EC.parseSec1(u8, jwk); + jwk = x509.parseSec1(u8, jwk); // SPKI/PKIK (Public) } else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { //console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16)); - jwk = EC.parseSpki(u8, jwk); + jwk = x509.parseSpki(u8, jwk); // Error } else { //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16)); @@ -347,13 +207,13 @@ EC.exportSync = function (opts) { } if ('sec1' === format) { - return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packSec1(jwk) }); + return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: x509.packSec1(jwk) }); } else if ('pkcs8' === format) { - return PEM.packBlock({ type: "PRIVATE KEY", bytes: EC.packPkcs8(jwk) }); + return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) }); } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { - return PEM.packBlock({ type: "PUBLIC KEY", bytes: EC.packSpki(jwk) }); + return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) }); } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { - return EC.packSsh(jwk); + return SSH.packSsh(jwk); } else { throw new Error("Sanity Error: reached unreachable code block with format: " + format); } @@ -364,130 +224,4 @@ EC.pack = function (opts) { }); }; -EC.packSec1 = function (jwk) { - var d = Enc.base64ToHex(jwk.d); - var x = Enc.base64ToHex(jwk.x); - var y = Enc.base64ToHex(jwk.y); - var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; - return Enc.hexToUint8( - ASN1('30' - , ASN1.UInt('01') - , ASN1('04', d) - , ASN1('A0', objId) - , ASN1('A1', ASN1.BitStr('04' + x + y))) - ); -}; -EC.packPkcs8 = function (jwk) { - var d = Enc.base64ToHex(jwk.d); - var x = Enc.base64ToHex(jwk.x); - var y = Enc.base64ToHex(jwk.y); - var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; - return Enc.hexToUint8( - ASN1('30' - , ASN1.UInt('00') - , ASN1('30' - , OBJ_ID_EC_PUB - , objId - ) - , ASN1('04' - , ASN1('30' - , ASN1.UInt('01') - , ASN1('04', d) - , ASN1('A1', ASN1.BitStr('04' + x + y))))) - ); -}; -EC.packSpki = function (jwk) { - var x = Enc.base64ToHex(jwk.x); - var y = Enc.base64ToHex(jwk.y); - var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; - return Enc.hexToUint8( - ASN1('30' - , ASN1('30' - , OBJ_ID_EC_PUB - , objId - ) - , ASN1.BitStr('04' + x + y)) - ); -}; -EC.packPkix = EC.packSpki; -EC.packSsh = function (jwk) { - // Custom SSH format - var typ = 'ecdsa-sha2-nistp256'; - var a = '32 35 36'; - var b = '41'; - var comment = jwk.crv + '@localhost'; - if ('P-256' !== jwk.crv) { - typ = 'ecdsa-sha2-nistp384'; - a = '33 38 34'; - b = '61'; - } - var x = Enc.base64ToHex(jwk.x); - var y = Enc.base64ToHex(jwk.y); - var ssh = Enc.hexToUint8( - ('00 00 00 13 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70' - + a + '00 00 00 08 6e 69 73 74 70' + a + '00 00 00' + b - + '04' + x + y).replace(/\s+/g, '').toLowerCase() - ); - - return typ + ' ' + Enc.bufToBase64(ssh) + ' ' + comment; -}; - -// -// A dumbed-down, minimal ASN.1 packer -// - -// Almost every ASN.1 type that's important for CSR -// can be represented generically with only a few rules. -ASN1 = function ASN1(/*type, hexstrings...*/) { - var args = Array.prototype.slice.call(arguments); - var typ = args.shift(); - var str = args.join('').replace(/\s+/g, '').toLowerCase(); - var len = (str.length/2); - var lenlen = 0; - var hex = typ; - - // We can't have an odd number of hex chars - if (len !== Math.round(len)) { - throw new Error("invalid hex"); - } - - // The first byte of any ASN.1 sequence is the type (Sequence, Integer, etc) - // The second byte is either the size of the value, or the size of its size - - // 1. If the second byte is < 0x80 (128) it is considered the size - // 2. If it is > 0x80 then it describes the number of bytes of the size - // ex: 0x82 means the next 2 bytes describe the size of the value - // 3. The special case of exactly 0x80 is "indefinite" length (to end-of-file) - - if (len > 127) { - lenlen += 1; - while (len > 255) { - lenlen += 1; - len = len >> 8; - } - } - - if (lenlen) { hex += Enc.numToHex(0x80 + lenlen); } - return hex + Enc.numToHex(str.length/2) + str; -}; - -// The Integer type has some special rules -ASN1.UInt = function UINT() { - var str = Array.prototype.slice.call(arguments).join(''); - var first = parseInt(str.slice(0, 2), 16); - - // If the first byte is 0x80 or greater, the number is considered negative - // Therefore we add a '00' prefix if the 0x80 bit is set - if (0x80 & first) { str = '00' + str; } - - return ASN1('02', str); -}; - -// The Bit String type also has a special rule -ASN1.BitStr = function BITSTR() { - var str = Array.prototype.slice.call(arguments).join(''); - // '00' is a mask of how many bits of the next byte to ignore - return ASN1('03', '00' + str); -}; - EC.toPem = EC.export = EC.pack; diff --git a/lib/ssh.js b/lib/ssh.js new file mode 100644 index 0000000..c765242 --- /dev/null +++ b/lib/ssh.js @@ -0,0 +1,55 @@ +'use strict'; + +var SSH = module.exports; +var Enc = require('./encoding.js'); + + // 19 e c d s a - s h a 2 - n i s t p 2 5 6 +var SSH_EC_P256 = '00000013 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36' + .replace(/\s+/g, '').toLowerCase(); + // 19 e c d s a - s h a 2 - n i s t p 3 8 4 +var SSH_EC_P384 = '00000013 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34' + .replace(/\s+/g, '').toLowerCase(); + +SSH.parseSsh = function (pem) { + var jwk = { kty: 'EC', crv: null, x: null, y: null }; + var b64 = pem.split(/\s+/g)[1]; + var buf = Buffer.from(b64, 'base64'); + var hex = Enc.bufToHex(buf); + var index = 40; + var len; + if (0 === hex.indexOf(SSH_EC_P256)) { + jwk.crv = 'P-256'; + len = 32; + } else if (0 === hex.indexOf(SSH_EC_P384)) { + jwk.crv = 'P-384'; + len = 48; + } + var x = buf.slice(index, index + len); + var y = buf.slice(index + len, index + len + len); + jwk.x = Enc.bufToUrlBase64(x); + jwk.y = Enc.bufToUrlBase64(y); + return jwk; +}; + + +SSH.packSsh = function (jwk) { + // Custom SSH format + var typ = 'ecdsa-sha2-nistp256'; + var a = '32 35 36'; + var b = '41'; + var comment = jwk.crv + '@localhost'; + if ('P-256' !== jwk.crv) { + typ = 'ecdsa-sha2-nistp384'; + a = '33 38 34'; + b = '61'; + } + var x = Enc.base64ToHex(jwk.x); + var y = Enc.base64ToHex(jwk.y); + var ssh = Enc.hexToUint8( + ('00 00 00 13 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70' + + a + '00 00 00 08 6e 69 73 74 70' + a + '00 00 00' + b + + '04' + x + y).replace(/\s+/g, '').toLowerCase() + ); + + return typ + ' ' + Enc.bufToBase64(ssh) + ' ' + comment; +}; diff --git a/lib/x509.js b/lib/x509.js new file mode 100644 index 0000000..41da3a2 --- /dev/null +++ b/lib/x509.js @@ -0,0 +1,170 @@ +'use strict'; + +var ASN1 = require('./asn1.js'); +var Enc = require('./encoding.js'); +var x509 = module.exports; + +// 1.2.840.10045.3.1.7 +// prime256v1 (ANSI X9.62 named elliptic curve) +var OBJ_ID_EC = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase(); +// 1.3.132.0.34 +// secp384r1 (SECG (Certicom) named elliptic curve) +var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase(); +// 1.2.840.10045.2.1 +// ecPublicKey (ANSI X9.62 public key type) +var OBJ_ID_EC_PUB = '06 07 2A8648CE3D0201'.replace(/\s+/g, '').toLowerCase(); + +x509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) { + var index = 7; + var len = 32; + var olen = OBJ_ID_EC.length/2; + + if ("P-384" === jwk.crv) { + olen = OBJ_ID_EC_384.length/2; + index = 8; + len = 48; + } + if (len !== u8[index - 1]) { + throw new Error("Unexpected bitlength " + len); + } + + // private part is d + var d = u8.slice(index, index + len); + // compression bit index + var ci = index + len + 2 + olen + 2 + 3; + var c = u8[ci]; + var x, y; + + if (0x04 === c) { + y = u8.slice(ci + 1 + len, ci + 1 + len + len); + } else if (0x02 !== c) { + throw new Error("not a supported EC private key"); + } + x = u8.slice(ci + 1, ci + 1 + len); + + return { + kty: jwk.kty + , crv: jwk.crv + , d: Enc.bufToUrlBase64(d) + //, dh: Enc.bufToHex(d) + , x: Enc.bufToUrlBase64(x) + //, xh: Enc.bufToHex(x) + , y: Enc.bufToUrlBase64(y) + //, yh: Enc.bufToHex(y) + }; +}; + +x509.parsePkcs8 = function parseEcPkcs8(u8, jwk) { + var index = 24 + (OBJ_ID_EC.length/2); + var len = 32; + if ("P-384" === jwk.crv) { + index = 24 + (OBJ_ID_EC_384.length/2) + 2; + len = 48; + } + + //console.log(index, u8.slice(index)); + if (0x04 !== u8[index]) { + //console.log(jwk); + throw new Error("privkey not found"); + } + var d = u8.slice(index+2, index+2+len); + var ci = index+2+len+5; + var xi = ci+1; + var x = u8.slice(xi, xi + len); + var yi = xi+len; + var y; + if (0x04 === u8[ci]) { + y = u8.slice(yi, yi + len); + } else if (0x02 !== u8[ci]) { + throw new Error("invalid compression bit (expected 0x04 or 0x02)"); + } + + return { + kty: jwk.kty + , crv: jwk.crv + , d: Enc.bufToUrlBase64(d) + //, dh: Enc.bufToHex(d) + , x: Enc.bufToUrlBase64(x) + //, xh: Enc.bufToHex(x) + , y: Enc.bufToUrlBase64(y) + //, yh: Enc.bufToHex(y) + }; +}; + +x509.parseSpki = function parsePem(u8, jwk) { + var ci = 16 + OBJ_ID_EC.length/2; + var len = 32; + + if ("P-384" === jwk.crv) { + ci = 16 + OBJ_ID_EC_384.length/2; + len = 48; + } + + var c = u8[ci]; + var xi = ci + 1; + var x = u8.slice(xi, xi + len); + var yi = xi + len; + var y; + if (0x04 === c) { + y = u8.slice(yi, yi + len); + } else if (0x02 !== c) { + throw new Error("not a supported EC private key"); + } + + return { + kty: jwk.kty + , crv: jwk.crv + , x: Enc.bufToUrlBase64(x) + //, xh: Enc.bufToHex(x) + , y: Enc.bufToUrlBase64(y) + //, yh: Enc.bufToHex(y) + }; +}; +x509.parsePkix = x509.parseSpki; + +x509.packSec1 = function (jwk) { + var d = Enc.base64ToHex(jwk.d); + var x = Enc.base64ToHex(jwk.x); + var y = Enc.base64ToHex(jwk.y); + var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; + return Enc.hexToUint8( + ASN1('30' + , ASN1.UInt('01') + , ASN1('04', d) + , ASN1('A0', objId) + , ASN1('A1', ASN1.BitStr('04' + x + y))) + ); +}; +x509.packPkcs8 = function (jwk) { + var d = Enc.base64ToHex(jwk.d); + var x = Enc.base64ToHex(jwk.x); + var y = Enc.base64ToHex(jwk.y); + var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; + return Enc.hexToUint8( + ASN1('30' + , ASN1.UInt('00') + , ASN1('30' + , OBJ_ID_EC_PUB + , objId + ) + , ASN1('04' + , ASN1('30' + , ASN1.UInt('01') + , ASN1('04', d) + , ASN1('A1', ASN1.BitStr('04' + x + y))))) + ); +}; +x509.packSpki = function (jwk) { + var x = Enc.base64ToHex(jwk.x); + var y = Enc.base64ToHex(jwk.y); + var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; + return Enc.hexToUint8( + ASN1('30' + , ASN1('30' + , OBJ_ID_EC_PUB + , objId + ) + , ASN1.BitStr('04' + x + y)) + ); +}; +x509.packPkix = x509.packSpki;