464 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			464 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | var crypto = require('crypto'); | ||
|  | 
 | ||
|  | // 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(); | ||
|  | 
 | ||
|  | function fromBase64(b64) { | ||
|  |   var buf; | ||
|  |   var ab; | ||
|  |   if ('undefined' === typeof atob) { | ||
|  |     buf = Buffer.from(b64, 'base64'); | ||
|  |     return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); | ||
|  |   } | ||
|  |   buf = atob(b64); | ||
|  |   ab = new ArrayBuffer(buf.length); | ||
|  |   ab = new Uint8Array(ab); | ||
|  |   buf.split('').forEach(function (ch, i) { | ||
|  |     ab[i] = ch.charCodeAt(0); | ||
|  |   }); | ||
|  |   return ab.buffer; | ||
|  | } | ||
|  | 
 | ||
|  | function parsePem(pem) { | ||
|  |   var typ; | ||
|  |   var pub; | ||
|  |   var crv; | ||
|  |   var der = fromBase64(pem.split(/\n/).filter(function (line, i) { | ||
|  |     if (0 === i) { | ||
|  |       if (/ PUBLIC /.test(line)) { | ||
|  |         pub = true; | ||
|  |       } else if (/ PRIVATE /.test(line)) { | ||
|  |         pub = false; | ||
|  |       } | ||
|  |       if (/ EC/.test(line)) { | ||
|  |         typ = 'EC'; | ||
|  |       } | ||
|  |     } | ||
|  |     return !/---/.test(line); | ||
|  |   }).join('')); | ||
|  | 
 | ||
|  |   if (!typ || 'EC' === typ) { | ||
|  |     var hex = toHex(der).toLowerCase(); | ||
|  |     if (-1 !== hex.indexOf(OBJ_ID_EC)) { | ||
|  |       typ = 'EC'; | ||
|  |       crv = 'P-256'; | ||
|  |     } else { | ||
|  |       // TODO more than just P-256
 | ||
|  |       console.warn("unsupported ec curve"); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return { typ: typ, pub: pub, der: der, crv: crv }; | ||
|  | } | ||
|  | 
 | ||
|  | function toHex(ab) { | ||
|  |   var hex = []; | ||
|  |   var u8 = new Uint8Array(ab); | ||
|  |   var size = u8.byteLength; | ||
|  |   var i; | ||
|  |   var h; | ||
|  |   for (i = 0; i < size; i += 1) { | ||
|  |     h = u8[i].toString(16); | ||
|  |     if (2 === h.length) { | ||
|  |       hex.push(h); | ||
|  |     } else { | ||
|  |       hex.push('0' + h); | ||
|  |     } | ||
|  |   } | ||
|  |   return hex.join(''); | ||
|  | } | ||
|  | 
 | ||
|  | function fromHex(hex) { | ||
|  |   if ('undefined' !== typeof Buffer) { | ||
|  |     return Buffer.from(hex, 'hex'); | ||
|  |   } | ||
|  |   var ab = new ArrayBuffer(hex.length/2); | ||
|  |   var i; | ||
|  |   var j; | ||
|  |   ab = new Uint8Array(ab); | ||
|  |   for (i = 0, j = 0; i < (hex.length/2); i += 1) { | ||
|  |     ab[i] = parseInt(hex.slice(j, j+1), 16); | ||
|  |     j += 2; | ||
|  |   } | ||
|  |   return ab.buffer; | ||
|  | } | ||
|  | 
 | ||
|  | function readEcPubkey(der) { | ||
|  |   // the key is the last 520 bits of both the private key and the public key
 | ||
|  |   // he 3 bits prior identify the key as
 | ||
|  |   var x, y; | ||
|  |   var compressed; | ||
|  |   var keylen = 32; | ||
|  |   var offset = 64; | ||
|  |   var headerSize = 4; | ||
|  |   var header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset)); | ||
|  | 
 | ||
|  |   if ('03420004' !== header) { | ||
|  |     offset = 32; | ||
|  |     header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset)); | ||
|  |     if ('03420002' !== header) { | ||
|  |       throw new Error("not a valid EC P-256 key (expected 0x0342004 or 0x0342002 as pub key preamble, but found " + header + ")"); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // The one good thing that came from the b***kchain hysteria: good EC documentation
 | ||
|  |   // https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/
 | ||
|  |   compressed = ('2' === header[header.byteLength -1]); | ||
|  |   x = der.slice(der.byteLength - offset, (der.byteLength - offset) + keylen); | ||
|  |   if (!compressed) { | ||
|  |     y = der.slice(der.byteLength - keylen, der.byteLength); | ||
|  |   } | ||
|  | 
 | ||
|  |   return { | ||
|  |     x: x | ||
|  |   , y: y || null | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function formatAsPem(str) { | ||
|  |   var finalString = ''; | ||
|  | 
 | ||
|  |   while (str.length > 0) { | ||
|  |       finalString += str.substring(0, 64) + '\n'; | ||
|  |       str = str.substring(64); | ||
|  |   } | ||
|  |   return finalString; | ||
|  | } | ||
|  | 
 | ||
|  | function toBase64(der) { | ||
|  |   if ('undefined' === typeof btoa) { | ||
|  |     return Buffer.from(der).toString('base64'); | ||
|  |   } | ||
|  | 	var chs = []; | ||
|  | 	der.forEach(function (b) { | ||
|  | 		chs.push(String.fromCharCode(b)); | ||
|  | 	}); | ||
|  |   return btoa(chs.join('')); | ||
|  | } | ||
|  | 
 | ||
|  | // these are static ASN.1 segments
 | ||
|  | // The head specifies that there will be 3 segments and a content length
 | ||
|  | // (those segments will be content, signature header, and signature)
 | ||
|  | var csrHead = '30 82 {0seq0len}'.replace(/\s+/g, ''); | ||
|  | // The tail specifies the ES256 signature header (and is followed by the signature
 | ||
|  | var csrEcFoot = | ||
|  |   ( '30 0A' | ||
|  |       // 1.2.840.10045.4.3.2 ecdsaWithSHA256
 | ||
|  |       // (ANSI X9.62 ECDSA algorithm with SHA256)
 | ||
|  |     + '06 08 2A 86 48 CE 3D 04 03 02' | ||
|  |   + '03 {len} 00' // bit stream (why??)
 | ||
|  |     + '30 {rslen}'  // sequence
 | ||
|  |       + '02 {rlen} {r}' // integer r
 | ||
|  |       + '02 {slen} {s}' // integer s
 | ||
|  |   ).replace(/\s+/g, ''); | ||
|  | var csrDomains = '82 {dlen} {domain.tld}';  // 2+n bytes (type 82?)
 | ||
|  | 
 | ||
|  | function strToHex(str) { | ||
|  |   return str.split('').map(function (ch) { | ||
|  |     var h = ch.charCodeAt(0).toString(16); | ||
|  |     if (2 === h.length) { | ||
|  |       return h; | ||
|  |     } | ||
|  |     return '0' + h; | ||
|  |   }).join(''); | ||
|  | } | ||
|  | 
 | ||
|  | function numToHex(d) { | ||
|  |   d = d.toString(16); | ||
|  |   if (d.length % 2) { | ||
|  |     return '0' + d; | ||
|  |   } | ||
|  |   return d; | ||
|  | } | ||
|  | 
 | ||
|  | function fromHex(hex) { | ||
|  |   if ('undefined' !== typeof Buffer) { | ||
|  |     return Buffer.from(hex, 'hex'); | ||
|  |   } | ||
|  |   var ab = new ArrayBuffer(hex.length/2); | ||
|  |   var i; | ||
|  |   var j; | ||
|  |   ab = new Uint8Array(ab); | ||
|  |   for (i = 0, j = 0; i < (hex.length/2); i += 1) { | ||
|  |     ab[i] = parseInt(hex.slice(j, j+1), 16); | ||
|  |     j += 2; | ||
|  |   } | ||
|  |   return ab.buffer; | ||
|  | } | ||
|  | 
 | ||
|  | function createCsrBodyEc(domains, xy) { | ||
|  |   var altnames = domains.map(function (d) { | ||
|  |     return csrDomains.replace(/{dlen}/, numToHex(d.length)).replace(/{domain\.tld}/, strToHex(d)); | ||
|  |   }).join('').replace(/\s+/g, ''); | ||
|  |   var sublen = domains[0].length; | ||
|  |   var sanlen = (altnames.length/2); | ||
|  |   var publen = xy.x.byteLength; | ||
|  |   var compression = '04'; | ||
|  |   var hxy = ''; | ||
|  |   // 04 == x+y, 02 == x-only
 | ||
|  |   if (xy.y) { | ||
|  |     publen += xy.y.byteLength; | ||
|  |   } else { | ||
|  |     // Note: I don't intend to support compression - it isn't used by most
 | ||
|  |     // libraries and it requir more dependencies for bigint ops to deflate.
 | ||
|  |     // This is more just a placeholder. It won't work right now anyway
 | ||
|  |     // because compression requires an exta bit stored (odd vs even), which
 | ||
|  |     // I haven't learned yet, and I'm not sure if it's allowed at all
 | ||
|  |     compression = '02'; | ||
|  |   } | ||
|  |   hxy += toHex(xy.x); | ||
|  |   if (xy.y) { | ||
|  |     hxy += toHex(xy.y); | ||
|  |   } | ||
|  | 
 | ||
|  |   var body = [ '30 81 {+85+n}'                                        // 4 bytes, sequence
 | ||
|  |     .replace(/{[^}]+}/, numToHex( | ||
|  |         3 | ||
|  |       + 13 + sublen | ||
|  |       + 27 + publen // Length for EC-related P-256 stuff
 | ||
|  |       + 30 + sanlen | ||
|  |     )) | ||
|  | 
 | ||
|  |       // #0 Total 3
 | ||
|  |     , '02 01 00'                                                      // 3 bytes, int 0
 | ||
|  | 
 | ||
|  |       // Subject
 | ||
|  |       // #1 Total 2+11+n
 | ||
|  |     , '30 {3.2.0seqlen}'                                              // 2 bytes, sequence
 | ||
|  |       .replace(/{[^}]+}/, numToHex(2+2+5+2+sublen)) | ||
|  |       , '31 {4.3.0setlen}'                                            // 2 bytes, set
 | ||
|  |         .replace(/{[^}]+}/, numToHex(2+5+2+sublen)) | ||
|  |         , '30 {5.4.0seqlen}'                                          // 2 bytes, sequence
 | ||
|  |           .replace(/{[^}]+}/, numToHex(5+2+sublen)) | ||
|  |         , '06 03 55 04 03'                                            // 5 bytes, object id (commonName)
 | ||
|  |         , '0C {dlen} {domain.tld}'                                    // 2+n bytes, utf8string
 | ||
|  |           .replace(/{dlen}/, numToHex(sublen)) | ||
|  |           .replace(/{domain\.tld}/, strToHex(domains[0])) | ||
|  | 
 | ||
|  |       // P-256 Public Key
 | ||
|  |       // #2 Total 2+25+xy
 | ||
|  |     , '30 {+25+xy}'                                                   // 2 bytes, sequence
 | ||
|  |       .replace(/{[^}]+}/, numToHex(2+9+10+3+1+publen)) | ||
|  |       , '30 13'                                                       // 2 bytes, sequence
 | ||
|  |           // 1.2.840.10045.2.1 ecPublicKey
 | ||
|  |           // (ANSI X9.62 public key type)
 | ||
|  |         , '06 07 2A 86 48 CE 3D 02 01'                                // 9 bytes, object id
 | ||
|  |           // 1.2.840.10045.3.1.7 prime256v1
 | ||
|  |           // (ANSI X9.62 named elliptic curve)
 | ||
|  |         , '06 08 2A 86 48 CE 3D 03 01 07'                             // 10 bytes, object id
 | ||
|  |       , '03 {xylen} 00 {xy}'                                          // 3+1+n bytes
 | ||
|  |         .replace(/{xylen}/, numToHex(publen+2)) | ||
|  |         .replace(/{xy}/, compression + hxy) | ||
|  | 
 | ||
|  |       // Altnames
 | ||
|  |       // #3 Total 2+28+n
 | ||
|  |     , 'A0 {+28}'                                                      // 2 bytes, ?? [4B]
 | ||
|  |       .replace(/{[^}]+}/, numToHex(2+11+2+2+2+5+2+2+sanlen)) | ||
|  |       , '30 {+26}'                                                    // 2 bytes, sequence
 | ||
|  |         .replace(/{[^}]+}/, numToHex(11+2+2+2+5+2+2+sanlen)) | ||
|  |           // (extensionRequest (PKCS #9 via CRMF))
 | ||
|  |         , '06 09 2A 86 48 86 F7 0D 01 09 0E'                          // 11 bytes, object id
 | ||
|  |           , '31 {+13}'                                                // 2 bytes, set
 | ||
|  |             .replace(/{[^}]+}/, numToHex(2+2+5+2+2+sanlen)) | ||
|  |             , '30 {+11}'                                              // 2 bytes, sequence
 | ||
|  |               .replace(/{[^}]+}/, numToHex(2+5+2+2+sanlen)) | ||
|  |               , '30 {+9}'                                             // 2 bytes, sequence
 | ||
|  |                 .replace(/{[^}]+}/, numToHex(5+2+2+sanlen)) | ||
|  |                   // (subjectAltName (X.509 extension))
 | ||
|  |                 , '06 03 55 1D 11'                                    // 5 bytes, object id
 | ||
|  |                 , '04 {+2}'                                           // 2 bytes, octet string
 | ||
|  |                   .replace(/{[^}]+}/, numToHex(2+sanlen)) | ||
|  |                   , '30 {+n}'                                         // 2 bytes, sequence
 | ||
|  |                     .replace(/{[^}]+}/, numToHex(sanlen)) | ||
|  |                     , '{altnames}'                                    // n (elements of sequence)
 | ||
|  |                       .replace(/{altnames}/, altnames) | ||
|  |   ]; | ||
|  |   body = body.join('').replace(/\s+/g, ''); | ||
|  |   return fromHex(body); | ||
|  | } | ||
|  | 
 | ||
|  | // https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | ||
|  | function signEc(keypem, ab) { | ||
|  |   // Signer is a stream
 | ||
|  |   var sign = crypto.createSign('SHA256'); | ||
|  |   sign.write(new Uint8Array(ab)); | ||
|  |   sign.end(); | ||
|  | 
 | ||
|  |   // The signature is ASN1 encoded
 | ||
|  |   var sig = sign.sign(keypem); | ||
|  | 
 | ||
|  |   // Convert to a JavaScript ArrayBuffer just because
 | ||
|  |   sig = new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength)); | ||
|  | 
 | ||
|  |   // The first two bytes '30 xx' signify SEQUENCE and LENGTH
 | ||
|  |   // The sequence length byte will be a single byte because the signature is less that 128 bytes (0x80, 1024-bit)
 | ||
|  |   // (this would not be true for P-521, but I'm not supporting that yet)
 | ||
|  |   // The 3rd byte will be '02', signifying INTEGER
 | ||
|  |   // The 4th byte will tell us the length of 'r' (which, on occassion, will be less than the full 255 bytes)
 | ||
|  |   var rIndex = 3; | ||
|  |   var rLen = sig[rIndex]; | ||
|  |   var rEnd = rIndex + 1 + rLen; | ||
|  |   var sIndex = rEnd + 1; | ||
|  |   var sLen = sig[sIndex]; | ||
|  |   var sEnd = sIndex + 1 + sLen; | ||
|  |   var r = sig.slice(rIndex + 1, rEnd); | ||
|  |   var s = sig.slice(sIndex + 1, sEnd); // this should be end-of-file
 | ||
|  | 
 | ||
|  |   // ASN1 INTEGER types use the high-order bit to signify a negative number,
 | ||
|  |   // hence a leading '00' is used for numbers that begin with '80' or greater
 | ||
|  |   // which is why r length is sometimes a byte longer than its bit length
 | ||
|  |   if (0 === s[0]) { s = s.slice(1); } | ||
|  |   if (0 === r[0]) { r = r.slice(1); } | ||
|  | 
 | ||
|  |   return { raw: sig.buffer, r: r.buffer, s: s.buffer }; | ||
|  | } | ||
|  | 
 | ||
|  | function createEcCsr(domains, keypem, ecpub) { | ||
|  |   // TODO get pub from priv
 | ||
|  | 
 | ||
|  |   var csrBody = createCsrBodyEc(domains, ecpub); | ||
|  |   var sig = signEc(keypem, csrBody); | ||
|  |   var rLen = sig.r.byteLength; | ||
|  |   var rc = ''; | ||
|  |   var sLen = sig.s.byteLength; | ||
|  |   var sc = ''; | ||
|  | 
 | ||
|  |   if (0x80 & new Uint8Array(sig.r)[0]) { rc = '00'; rLen += 1; } | ||
|  |   if (0x80 & new Uint8Array(sig.s)[0]) { sc = '00'; sLen += 1; } | ||
|  | 
 | ||
|  |   var csrSig = csrEcFoot | ||
|  |     .replace(/{len}/, numToHex(1 + 2 + 2 + 2 + rLen + sLen)) | ||
|  |     .replace(/{rslen}/, numToHex(2 + 2 + rLen + sLen)) | ||
|  |     .replace(/{rlen}/, numToHex(rLen)) | ||
|  |     .replace(/{r}/, rc + toHex(sig.r)) | ||
|  |     .replace(/{slen}/, numToHex(sLen)) | ||
|  |     .replace(/{s}/, sc + toHex(sig.s)) | ||
|  |   ; | ||
|  | 
 | ||
|  |   // Note: If we supported P-521 a number of the lengths would change
 | ||
|  |   // by one byte and that would be... annoying to update
 | ||
|  |   var len = csrBody.byteLength + (csrSig.length/2); | ||
|  |   /* | ||
|  |   console.log('sig:', sig.raw.byteLength, toHex(sig.raw)); | ||
|  |   console.log('r:', sig.r.byteLength, toHex(sig.r)); | ||
|  |   console.log('s:', sig.s.byteLength, toHex(sig.s)); | ||
|  |   console.log('csr sig:', csrSig.length / 2, csrSig); | ||
|  |   console.log('csrBodyLen + csrSigLen', numToHex(len)); | ||
|  |   */ | ||
|  |   var head = csrHead.replace(/{[^}]+}/, numToHex(len)); | ||
|  |   var ab = new Uint8Array(new ArrayBuffer((head.length/2) + len)); | ||
|  |   var i = 0; | ||
|  |   fromHex(head).forEach(function (b) { | ||
|  |     ab[i] = b; | ||
|  |     i += 1; | ||
|  |   }); | ||
|  |   csrBody.forEach(function (b) { | ||
|  |     ab[i] = b; | ||
|  |     i += 1; | ||
|  |   }); | ||
|  |   fromHex(csrSig).forEach(function (b) { | ||
|  |     ab[i] = b; | ||
|  |     i += 1; | ||
|  |   }); | ||
|  | 
 | ||
|  |   return ab; | ||
|  | } | ||
|  | 
 | ||
|  | function createEcCsrPem(domains, keypem) { | ||
|  |   var pemblock = parsePem(keypem); | ||
|  |   var ecpub = readEcPubkey(pemblock.der); | ||
|  |   var ab = createEcCsr(domains, keypem, ecpub); | ||
|  |   var pem = formatAsPem(toBase64(ab)); | ||
|  |   return '-----BEGIN CERTIFICATE REQUEST-----\n' + pem + '-----END CERTIFICATE REQUEST-----'; | ||
|  | } | ||
|  | 
 | ||
|  | // Taken from Unibabel
 | ||
|  | // https://git.coolaj86.com/coolaj86/unibabel.js#readme
 | ||
|  | // https://coolaj86.com/articles/base64-unicode-utf-8-javascript-and-you/
 | ||
|  | function utf8ToUint8Array(str) { | ||
|  |   var escstr = encodeURIComponent(str); | ||
|  |   // replaces any uri escape sequence, such as %0A,
 | ||
|  |   // with binary escape, such as 0x0A
 | ||
|  |   var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(match, p1) { | ||
|  |     return String.fromCharCode('0x' + p1); | ||
|  |   }); | ||
|  |   var buf = new Uint8Array(binstr.length); | ||
|  |   binstr.split('').forEach(function (ch, i) { | ||
|  |     buf[i] = ch.charCodeAt(0); | ||
|  |   }); | ||
|  | 
 | ||
|  |   return buf; | ||
|  | } | ||
|  | 
 | ||
|  | function ensurePem(key) { | ||
|  |   if (!key) { throw new Error("no private key given"); } | ||
|  |   // whether PEM or DER, convert to Uint8Array
 | ||
|  |   if ('string' === typeof key) { key = utf8ToUint8Array(key); } | ||
|  | 
 | ||
|  |   // for consistency
 | ||
|  |   if (key instanceof Buffer) { key = new Uint8Array(key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength)); } | ||
|  | 
 | ||
|  |   // just as a sanity check
 | ||
|  |   if (key instanceof Array) { | ||
|  |     key = Uint8Array.from(key); | ||
|  |     if (!key.every(function (el) { | ||
|  |       return ('number' === typeof el) && (el >= 0) && (el <= 255); | ||
|  |     })) { | ||
|  |       throw new Error("key was an array, but not an array of ints between 0 and 255"); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // no matter which path we take, we should arrive at a Uint8Array
 | ||
|  |   if (!(key instanceof Uint8Array)) { | ||
|  |     throw new Error("typeof key is '" + typeof key + "', not any of the supported types: utf8 string," | ||
|  |       + " binary string, node Buffer, Uint8Array, or Array of ints between 0 and 255"); | ||
|  |   } | ||
|  | 
 | ||
|  |   // if DER, convert to PEM
 | ||
|  |   if ((0x30 === key[0]) && (0x80 & key[1])) { | ||
|  |     key = toBase64(key); | ||
|  |   } | ||
|  |   key = [].map.call(key, function (i) { | ||
|  |     return String.fromCharCode(i); | ||
|  |   }).join(''); | ||
|  |   if ('M' === key[0]) { | ||
|  |     key = '-----BEGIN EC PRIVATE KEY-----\n' + key + '-----END EC PRIVATE KEY-----'; | ||
|  |   } | ||
|  |   if ('-' === key[0]) { | ||
|  |     return key; | ||
|  |   } else { | ||
|  |     throw new Error("key does not appear to be in PEM formt (does not begin with either '-' or 'M')," | ||
|  |       + " nor DER format (does not begin with 0x308X)"); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | /*global Promise*/ | ||
|  | module.exports = function (opts) { | ||
|  |   // We're using a Promise here to be compatible with the browser version
 | ||
|  |   // which uses the webcrypto API for some of the conversions
 | ||
|  |   return Promise.resolve().then(function () { | ||
|  | 
 | ||
|  |     // We do a bit of extra error checking for user convenience
 | ||
|  |     if (!opts) { throw new Error("You must pass options with key and domains to ecdsacsr"); } | ||
|  |     if (!Array.isArray(opts.domains) || 0 === opts.domains.length) { | ||
|  |       new Error("You must pass options.domains as a non-empty array"); | ||
|  |     } | ||
|  | 
 | ||
|  |     // I need to check that 例.中国 is a valid domain name
 | ||
|  |     if (!opts.domains.every(function (d) { | ||
|  |       // allow punycode? xn--
 | ||
|  |       if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) { | ||
|  |         return true; | ||
|  |       } | ||
|  |     })) { | ||
|  |       throw new Error("You must pass options.domains as utf8 strings (not punycode)"); | ||
|  |     } | ||
|  |     var key = ensurePem(opts.key); | ||
|  | 
 | ||
|  |     return createEcCsrPem(opts.domains, key); | ||
|  |   }); | ||
|  | }; |