mirror of
				https://github.com/therootcompany/acme.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	make Prettier
This commit is contained in:
		
							parent
							
								
									87a12c36b0
								
							
						
					
					
						commit
						6c11446e2f
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,4 @@ | ||||
| dist/ | ||||
| *.gz | ||||
| .*.sw* | ||||
| .ignore | ||||
|  | ||||
							
								
								
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "bracketSpacing": true, | ||||
|   "printWidth": 80, | ||||
|   "singleQuote": true, | ||||
|   "tabWidth": 4, | ||||
|   "trailingComma": "none", | ||||
|   "useTabs": true | ||||
| } | ||||
							
								
								
									
										60
									
								
								bin/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								bin/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | ||||
| #!/usr/bin/env node
 | ||||
| (async function() { | ||||
| 	'use strict'; | ||||
| 
 | ||||
| 	var UglifyJS = require('uglify-js'); | ||||
| 	var path = require('path'); | ||||
| 	var fs = require('fs'); | ||||
| 	var promisify = require('util').promisify; | ||||
| 	var readFile = promisify(fs.readFile); | ||||
| 	var writeFile = promisify(fs.writeFile); | ||||
| 	var gzip = promisify(require('zlib').gzip); | ||||
| 
 | ||||
| 	// The order is specific, and it matters
 | ||||
| 	var files = await Promise.all( | ||||
| 		[ | ||||
| 			'../lib/encoding.js', | ||||
| 			'../lib/asn1-packer.js', | ||||
| 			'../lib/x509.js', | ||||
| 			'../lib/ecdsa.js', | ||||
| 			'../lib/rsa.js', | ||||
| 			'../lib/keypairs.js', | ||||
| 			'../lib/asn1-parser.js', | ||||
| 			'../lib/csr.js', | ||||
| 			'../lib/acme.js' | ||||
| 		].map(async function(file) { | ||||
| 			return (await readFile(path.join(__dirname, file), 'utf8')).trim(); | ||||
| 		}) | ||||
| 	); | ||||
| 
 | ||||
| 	var header = | ||||
| 		[ | ||||
| 			'// Copyright 2015-2019 AJ ONeal. All rights reserved', | ||||
| 			'/* This Source Code Form is subject to the terms of the Mozilla Public', | ||||
| 			' * License, v. 2.0. If a copy of the MPL was not distributed with this', | ||||
| 			' * file, You can obtain one at http://mozilla.org/MPL/2.0/. */' | ||||
| 		].join('\n') + '\n'; | ||||
| 
 | ||||
| 	var file = header + files.join('\n') + '\n'; | ||||
| 	await writeFile(path.join(__dirname, '../dist', 'acme.js'), file); | ||||
| 	await writeFile( | ||||
| 		path.join(__dirname, '../dist', 'acme.js.gz'), | ||||
| 		await gzip(file) | ||||
| 	); | ||||
| 
 | ||||
| 	// TODO source maps?
 | ||||
| 	var result = UglifyJS.minify(file, { | ||||
| 		compress: true, | ||||
| 		// mangling doesn't save significant
 | ||||
| 		mangle: false | ||||
| 	}); | ||||
| 	if (result.error) { | ||||
| 		throw result.error; | ||||
| 	} | ||||
| 	file = header + result.code; | ||||
| 	await writeFile(path.join(__dirname, '../dist', 'acme.min.js'), file); | ||||
| 	await writeFile( | ||||
| 		path.join(__dirname, '../dist', 'acme.min.js.gz'), | ||||
| 		await gzip(file) | ||||
| 	); | ||||
| })(); | ||||
							
								
								
									
										2324
									
								
								lib/acme.js
									
									
									
									
									
								
							
							
						
						
									
										2324
									
								
								lib/acme.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,127 +1,147 @@ | ||||
| ;(function (exports) { | ||||
| 'use strict'; | ||||
| (function(exports) { | ||||
| 	'use strict'; | ||||
| 
 | ||||
| if (!exports.ASN1) { exports.ASN1 = {}; } | ||||
| if (!exports.Enc) { exports.Enc = {}; } | ||||
| if (!exports.PEM) { exports.PEM = {}; } | ||||
| 	if (!exports.ASN1) { | ||||
| 		exports.ASN1 = {}; | ||||
| 	} | ||||
| 	if (!exports.Enc) { | ||||
| 		exports.Enc = {}; | ||||
| 	} | ||||
| 	if (!exports.PEM) { | ||||
| 		exports.PEM = {}; | ||||
| 	} | ||||
| 
 | ||||
| var ASN1 = exports.ASN1; | ||||
| var Enc = exports.Enc; | ||||
| var PEM = exports.PEM; | ||||
| 	var ASN1 = exports.ASN1; | ||||
| 	var Enc = exports.Enc; | ||||
| 	var PEM = exports.PEM; | ||||
| 
 | ||||
| //
 | ||||
| // Packer
 | ||||
| //
 | ||||
| 	//
 | ||||
| 	// Packer
 | ||||
| 	//
 | ||||
| 
 | ||||
| // Almost every ASN.1 type that's important for CSR
 | ||||
| // can be represented generically with only a few rules.
 | ||||
| exports.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; | ||||
| 	// Almost every ASN.1 type that's important for CSR
 | ||||
| 	// can be represented generically with only a few rules.
 | ||||
| 	exports.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"); | ||||
|   } | ||||
| 		// 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
 | ||||
| 		// 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)
 | ||||
| 		// 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 (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; | ||||
| }; | ||||
| 		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); | ||||
| 	// 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; } | ||||
| 		// 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); | ||||
| }; | ||||
| 		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); | ||||
| }; | ||||
| 	// 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); | ||||
| 	}; | ||||
| 
 | ||||
| ASN1.pack = function (arr) { | ||||
|   var typ = Enc.numToHex(arr[0]); | ||||
|   var str = ''; | ||||
|   if (Array.isArray(arr[1])) { | ||||
|     arr[1].forEach(function (a) { | ||||
|       str += ASN1.pack(a); | ||||
|     }); | ||||
|   } else if ('string' === typeof arr[1]) { | ||||
|     str = arr[1]; | ||||
|   } else { | ||||
|     throw new Error("unexpected array"); | ||||
|   } | ||||
|   if ('03' === typ) { | ||||
|     return ASN1.BitStr(str); | ||||
|   } else if ('02' === typ) { | ||||
|     return ASN1.UInt(str); | ||||
|   } else { | ||||
|     return ASN1(typ, str); | ||||
|   } | ||||
| }; | ||||
| Object.keys(ASN1).forEach(function (k) { | ||||
|   exports.ASN1[k] = ASN1[k]; | ||||
| }); | ||||
| ASN1 = exports.ASN1; | ||||
| 	ASN1.pack = function(arr) { | ||||
| 		var typ = Enc.numToHex(arr[0]); | ||||
| 		var str = ''; | ||||
| 		if (Array.isArray(arr[1])) { | ||||
| 			arr[1].forEach(function(a) { | ||||
| 				str += ASN1.pack(a); | ||||
| 			}); | ||||
| 		} else if ('string' === typeof arr[1]) { | ||||
| 			str = arr[1]; | ||||
| 		} else { | ||||
| 			throw new Error('unexpected array'); | ||||
| 		} | ||||
| 		if ('03' === typ) { | ||||
| 			return ASN1.BitStr(str); | ||||
| 		} else if ('02' === typ) { | ||||
| 			return ASN1.UInt(str); | ||||
| 		} else { | ||||
| 			return ASN1(typ, str); | ||||
| 		} | ||||
| 	}; | ||||
| 	Object.keys(ASN1).forEach(function(k) { | ||||
| 		exports.ASN1[k] = ASN1[k]; | ||||
| 	}); | ||||
| 	ASN1 = exports.ASN1; | ||||
| 
 | ||||
| PEM.packBlock = function (opts) { | ||||
|   // TODO allow for headers?
 | ||||
|   return '-----BEGIN ' + opts.type + '-----\n' | ||||
|     + Enc.bufToBase64(opts.bytes).match(/.{1,64}/g).join('\n') + '\n' | ||||
|     + '-----END ' + opts.type + '-----' | ||||
|   ; | ||||
| }; | ||||
| 	PEM.packBlock = function(opts) { | ||||
| 		// TODO allow for headers?
 | ||||
| 		return ( | ||||
| 			'-----BEGIN ' + | ||||
| 			opts.type + | ||||
| 			'-----\n' + | ||||
| 			Enc.bufToBase64(opts.bytes) | ||||
| 				.match(/.{1,64}/g) | ||||
| 				.join('\n') + | ||||
| 			'\n' + | ||||
| 			'-----END ' + | ||||
| 			opts.type + | ||||
| 			'-----' | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToBase64 = function (u8) { | ||||
|   var bin = ''; | ||||
|   u8.forEach(function (i) { | ||||
|     bin += String.fromCharCode(i); | ||||
|   }); | ||||
|   return btoa(bin); | ||||
| }; | ||||
| 	Enc.bufToBase64 = function(u8) { | ||||
| 		var bin = ''; | ||||
| 		u8.forEach(function(i) { | ||||
| 			bin += String.fromCharCode(i); | ||||
| 		}); | ||||
| 		return btoa(bin); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.hexToBuf = function (hex) { | ||||
|   var arr = []; | ||||
|   hex.match(/.{2}/g).forEach(function (h) { | ||||
|     arr.push(parseInt(h, 16)); | ||||
|   }); | ||||
|   return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| }; | ||||
| 	Enc.hexToBuf = function(hex) { | ||||
| 		var arr = []; | ||||
| 		hex.match(/.{2}/g).forEach(function(h) { | ||||
| 			arr.push(parseInt(h, 16)); | ||||
| 		}); | ||||
| 		return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| 	}; | ||||
| 
 | ||||
| Enc.numToHex = function (d) { | ||||
|   d = d.toString(16); | ||||
|   if (d.length % 2) { | ||||
|     return '0' + d; | ||||
|   } | ||||
|   return d; | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof window ? window : module.exports)); | ||||
| 	Enc.numToHex = function(d) { | ||||
| 		d = d.toString(16); | ||||
| 		if (d.length % 2) { | ||||
| 			return '0' + d; | ||||
| 		} | ||||
| 		return d; | ||||
| 	}; | ||||
| })('undefined' !== typeof window ? window : module.exports); | ||||
|  | ||||
| @ -2,160 +2,221 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| ;(function (exports) { | ||||
| 'use strict'; | ||||
| (function(exports) { | ||||
| 	'use strict'; | ||||
| 
 | ||||
| if (!exports.ASN1) { exports.ASN1 = {}; } | ||||
| if (!exports.Enc) { exports.Enc = {}; } | ||||
| if (!exports.PEM) { exports.PEM = {}; } | ||||
| 	if (!exports.ASN1) { | ||||
| 		exports.ASN1 = {}; | ||||
| 	} | ||||
| 	if (!exports.Enc) { | ||||
| 		exports.Enc = {}; | ||||
| 	} | ||||
| 	if (!exports.PEM) { | ||||
| 		exports.PEM = {}; | ||||
| 	} | ||||
| 
 | ||||
| var ASN1 = exports.ASN1; | ||||
| var Enc = exports.Enc; | ||||
| var PEM = exports.PEM; | ||||
| 	var ASN1 = exports.ASN1; | ||||
| 	var Enc = exports.Enc; | ||||
| 	var PEM = exports.PEM; | ||||
| 
 | ||||
| //
 | ||||
| // Parser
 | ||||
| //
 | ||||
| 	//
 | ||||
| 	// Parser
 | ||||
| 	//
 | ||||
| 
 | ||||
| // Although I've only seen 9 max in https certificates themselves,
 | ||||
| // but each domain list could have up to 100
 | ||||
| ASN1.ELOOPN = 102; | ||||
| ASN1.ELOOP = "uASN1.js Error: iterated over " + ASN1.ELOOPN + "+ elements (probably a malformed file)"; | ||||
| // I've seen https certificates go 29 deep
 | ||||
| ASN1.EDEEPN = 60; | ||||
| ASN1.EDEEP = "uASN1.js Error: element nested " + ASN1.EDEEPN + "+ layers deep (probably a malformed file)"; | ||||
| // Container Types are Sequence 0x30, Container Array? (0xA0, 0xA1)
 | ||||
| // Value Types are Boolean 0x01, Integer 0x02, Null 0x05, Object ID 0x06, String 0x0C, 0x16, 0x13, 0x1e Value Array? (0x82)
 | ||||
| // Bit String (0x03) and Octet String (0x04) may be values or containers
 | ||||
| // Sometimes Bit String is used as a container (RSA Pub Spki)
 | ||||
| ASN1.CTYPES = [ 0x30, 0x31, 0xa0, 0xa1 ]; | ||||
| ASN1.VTYPES = [ 0x01, 0x02, 0x05, 0x06, 0x0c, 0x82 ]; | ||||
| ASN1.parse = function parseAsn1Helper(buf) { | ||||
|   //var ws = '  ';
 | ||||
|   function parseAsn1(buf, depth, eager) { | ||||
|     if (depth.length >= ASN1.EDEEPN) { throw new Error(ASN1.EDEEP); } | ||||
| 	// Although I've only seen 9 max in https certificates themselves,
 | ||||
| 	// but each domain list could have up to 100
 | ||||
| 	ASN1.ELOOPN = 102; | ||||
| 	ASN1.ELOOP = | ||||
| 		'uASN1.js Error: iterated over ' + | ||||
| 		ASN1.ELOOPN + | ||||
| 		'+ elements (probably a malformed file)'; | ||||
| 	// I've seen https certificates go 29 deep
 | ||||
| 	ASN1.EDEEPN = 60; | ||||
| 	ASN1.EDEEP = | ||||
| 		'uASN1.js Error: element nested ' + | ||||
| 		ASN1.EDEEPN + | ||||
| 		'+ layers deep (probably a malformed file)'; | ||||
| 	// Container Types are Sequence 0x30, Container Array? (0xA0, 0xA1)
 | ||||
| 	// Value Types are Boolean 0x01, Integer 0x02, Null 0x05, Object ID 0x06, String 0x0C, 0x16, 0x13, 0x1e Value Array? (0x82)
 | ||||
| 	// Bit String (0x03) and Octet String (0x04) may be values or containers
 | ||||
| 	// Sometimes Bit String is used as a container (RSA Pub Spki)
 | ||||
| 	ASN1.CTYPES = [0x30, 0x31, 0xa0, 0xa1]; | ||||
| 	ASN1.VTYPES = [0x01, 0x02, 0x05, 0x06, 0x0c, 0x82]; | ||||
| 	ASN1.parse = function parseAsn1Helper(buf) { | ||||
| 		//var ws = '  ';
 | ||||
| 		function parseAsn1(buf, depth, eager) { | ||||
| 			if (depth.length >= ASN1.EDEEPN) { | ||||
| 				throw new Error(ASN1.EDEEP); | ||||
| 			} | ||||
| 
 | ||||
|     var index = 2; // we know, at minimum, data starts after type (0) and lengthSize (1)
 | ||||
|     var asn1 = { type: buf[0], lengthSize: 0, length: buf[1] }; | ||||
|     var child; | ||||
|     var iters = 0; | ||||
|     var adjust = 0; | ||||
|     var adjustedLen; | ||||
| 			var index = 2; // we know, at minimum, data starts after type (0) and lengthSize (1)
 | ||||
| 			var asn1 = { type: buf[0], lengthSize: 0, length: buf[1] }; | ||||
| 			var child; | ||||
| 			var iters = 0; | ||||
| 			var adjust = 0; | ||||
| 			var adjustedLen; | ||||
| 
 | ||||
|     // Determine how many bytes the length uses, and what it is
 | ||||
|     if (0x80 & asn1.length) { | ||||
|       asn1.lengthSize = 0x7f & asn1.length; | ||||
|       // I think that buf->hex->int solves the problem of Endianness... not sure
 | ||||
|       asn1.length = parseInt(Enc.bufToHex(buf.slice(index, index + asn1.lengthSize)), 16); | ||||
|       index += asn1.lengthSize; | ||||
|     } | ||||
| 			// Determine how many bytes the length uses, and what it is
 | ||||
| 			if (0x80 & asn1.length) { | ||||
| 				asn1.lengthSize = 0x7f & asn1.length; | ||||
| 				// I think that buf->hex->int solves the problem of Endianness... not sure
 | ||||
| 				asn1.length = parseInt( | ||||
| 					Enc.bufToHex(buf.slice(index, index + asn1.lengthSize)), | ||||
| 					16 | ||||
| 				); | ||||
| 				index += asn1.lengthSize; | ||||
| 			} | ||||
| 
 | ||||
|     // High-order bit Integers have a leading 0x00 to signify that they are positive.
 | ||||
|     // Bit Streams use the first byte to signify padding, which x.509 doesn't use.
 | ||||
|     if (0x00 === buf[index] && (0x02 === asn1.type || 0x03 === asn1.type)) { | ||||
|       // However, 0x00 on its own is a valid number
 | ||||
|       if (asn1.length > 1) { | ||||
|         index += 1; | ||||
|         adjust = -1; | ||||
|       } | ||||
|     } | ||||
|     adjustedLen = asn1.length + adjust; | ||||
| 			// High-order bit Integers have a leading 0x00 to signify that they are positive.
 | ||||
| 			// Bit Streams use the first byte to signify padding, which x.509 doesn't use.
 | ||||
| 			if ( | ||||
| 				0x00 === buf[index] && | ||||
| 				(0x02 === asn1.type || 0x03 === asn1.type) | ||||
| 			) { | ||||
| 				// However, 0x00 on its own is a valid number
 | ||||
| 				if (asn1.length > 1) { | ||||
| 					index += 1; | ||||
| 					adjust = -1; | ||||
| 				} | ||||
| 			} | ||||
| 			adjustedLen = asn1.length + adjust; | ||||
| 
 | ||||
|     //console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
 | ||||
| 			//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
 | ||||
| 
 | ||||
|     function parseChildren(eager) { | ||||
|       asn1.children = []; | ||||
|       //console.warn('1 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', 0);
 | ||||
|       while (iters < ASN1.ELOOPN && index < (2 + asn1.length + asn1.lengthSize)) { | ||||
|         iters += 1; | ||||
|         depth.length += 1; | ||||
|         child = parseAsn1(buf.slice(index, index + adjustedLen), depth, eager); | ||||
|         depth.length -= 1; | ||||
|         // The numbers don't match up exactly and I don't remember why...
 | ||||
|         // probably something with adjustedLen or some such, but the tests pass
 | ||||
|         index += (2 + child.lengthSize + child.length); | ||||
|         //console.warn('2 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', (2 + child.lengthSize + child.length));
 | ||||
|         if (index > (2 + asn1.lengthSize + asn1.length)) { | ||||
|           if (!eager) { console.error(JSON.stringify(asn1, ASN1._replacer, 2)); } | ||||
|           throw new Error("Parse error: child value length (" + child.length | ||||
|             + ") is greater than remaining parent length (" + (asn1.length - index) | ||||
|             + " = " + asn1.length + " - " + index + ")"); | ||||
|         } | ||||
|         asn1.children.push(child); | ||||
|         //console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
 | ||||
|       } | ||||
|       if (index !== (2 + asn1.lengthSize + asn1.length)) { | ||||
|         //console.warn('index:', index, 'length:', (2 + asn1.lengthSize + asn1.length));
 | ||||
|         throw new Error("premature end-of-file"); | ||||
|       } | ||||
|       if (iters >= ASN1.ELOOPN) { throw new Error(ASN1.ELOOP); } | ||||
| 			function parseChildren(eager) { | ||||
| 				asn1.children = []; | ||||
| 				//console.warn('1 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', 0);
 | ||||
| 				while ( | ||||
| 					iters < ASN1.ELOOPN && | ||||
| 					index < 2 + asn1.length + asn1.lengthSize | ||||
| 				) { | ||||
| 					iters += 1; | ||||
| 					depth.length += 1; | ||||
| 					child = parseAsn1( | ||||
| 						buf.slice(index, index + adjustedLen), | ||||
| 						depth, | ||||
| 						eager | ||||
| 					); | ||||
| 					depth.length -= 1; | ||||
| 					// The numbers don't match up exactly and I don't remember why...
 | ||||
| 					// probably something with adjustedLen or some such, but the tests pass
 | ||||
| 					index += 2 + child.lengthSize + child.length; | ||||
| 					//console.warn('2 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', (2 + child.lengthSize + child.length));
 | ||||
| 					if (index > 2 + asn1.lengthSize + asn1.length) { | ||||
| 						if (!eager) { | ||||
| 							console.error( | ||||
| 								JSON.stringify(asn1, ASN1._replacer, 2) | ||||
| 							); | ||||
| 						} | ||||
| 						throw new Error( | ||||
| 							'Parse error: child value length (' + | ||||
| 								child.length + | ||||
| 								') is greater than remaining parent length (' + | ||||
| 								(asn1.length - index) + | ||||
| 								' = ' + | ||||
| 								asn1.length + | ||||
| 								' - ' + | ||||
| 								index + | ||||
| 								')' | ||||
| 						); | ||||
| 					} | ||||
| 					asn1.children.push(child); | ||||
| 					//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
 | ||||
| 				} | ||||
| 				if (index !== 2 + asn1.lengthSize + asn1.length) { | ||||
| 					//console.warn('index:', index, 'length:', (2 + asn1.lengthSize + asn1.length));
 | ||||
| 					throw new Error('premature end-of-file'); | ||||
| 				} | ||||
| 				if (iters >= ASN1.ELOOPN) { | ||||
| 					throw new Error(ASN1.ELOOP); | ||||
| 				} | ||||
| 
 | ||||
|       delete asn1.value; | ||||
|       return asn1; | ||||
|     } | ||||
| 				delete asn1.value; | ||||
| 				return asn1; | ||||
| 			} | ||||
| 
 | ||||
|     // Recurse into types that are _always_ containers
 | ||||
|     if (-1 !== ASN1.CTYPES.indexOf(asn1.type)) { return parseChildren(eager); } | ||||
| 			// Recurse into types that are _always_ containers
 | ||||
| 			if (-1 !== ASN1.CTYPES.indexOf(asn1.type)) { | ||||
| 				return parseChildren(eager); | ||||
| 			} | ||||
| 
 | ||||
|     // Return types that are _always_ values
 | ||||
|     asn1.value = buf.slice(index, index + adjustedLen); | ||||
|     if (-1 !== ASN1.VTYPES.indexOf(asn1.type)) { return asn1; } | ||||
| 			// Return types that are _always_ values
 | ||||
| 			asn1.value = buf.slice(index, index + adjustedLen); | ||||
| 			if (-1 !== ASN1.VTYPES.indexOf(asn1.type)) { | ||||
| 				return asn1; | ||||
| 			} | ||||
| 
 | ||||
|     // For ambigious / unknown types, recurse and return on failure
 | ||||
|     // (and return child array size to zero)
 | ||||
|     try { return parseChildren(true); } | ||||
|     catch(e) { asn1.children.length = 0; return asn1; } | ||||
|   } | ||||
| 			// For ambigious / unknown types, recurse and return on failure
 | ||||
| 			// (and return child array size to zero)
 | ||||
| 			try { | ||||
| 				return parseChildren(true); | ||||
| 			} catch (e) { | ||||
| 				asn1.children.length = 0; | ||||
| 				return asn1; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|   var asn1 = parseAsn1(buf, []); | ||||
|   var len = buf.byteLength || buf.length; | ||||
|   if (len !== 2 + asn1.lengthSize + asn1.length) { | ||||
|     throw new Error("Length of buffer does not match length of ASN.1 sequence."); | ||||
|   } | ||||
|   return asn1; | ||||
| }; | ||||
| ASN1._replacer = function (k, v) { | ||||
|   if ('type' === k) { return '0x' + Enc.numToHex(v); } | ||||
|   if (v && 'value' === k) { return '0x' + Enc.bufToHex(v.data || v); } | ||||
|   return v; | ||||
| }; | ||||
| 		var asn1 = parseAsn1(buf, []); | ||||
| 		var len = buf.byteLength || buf.length; | ||||
| 		if (len !== 2 + asn1.lengthSize + asn1.length) { | ||||
| 			throw new Error( | ||||
| 				'Length of buffer does not match length of ASN.1 sequence.' | ||||
| 			); | ||||
| 		} | ||||
| 		return asn1; | ||||
| 	}; | ||||
| 	ASN1._replacer = function(k, v) { | ||||
| 		if ('type' === k) { | ||||
| 			return '0x' + Enc.numToHex(v); | ||||
| 		} | ||||
| 		if (v && 'value' === k) { | ||||
| 			return '0x' + Enc.bufToHex(v.data || v); | ||||
| 		} | ||||
| 		return v; | ||||
| 	}; | ||||
| 
 | ||||
| // don't replace the full parseBlock, if it exists
 | ||||
| PEM.parseBlock = PEM.parseBlock || function (str) { | ||||
|   var der = str.split(/\n/).filter(function (line) { | ||||
|     return !/-----/.test(line); | ||||
|   }).join(''); | ||||
|   return { bytes: Enc.base64ToBuf(der) }; | ||||
| }; | ||||
| 	// don't replace the full parseBlock, if it exists
 | ||||
| 	PEM.parseBlock = | ||||
| 		PEM.parseBlock || | ||||
| 		function(str) { | ||||
| 			var der = str | ||||
| 				.split(/\n/) | ||||
| 				.filter(function(line) { | ||||
| 					return !/-----/.test(line); | ||||
| 				}) | ||||
| 				.join(''); | ||||
| 			return { bytes: Enc.base64ToBuf(der) }; | ||||
| 		}; | ||||
| 
 | ||||
| Enc.base64ToBuf = function (b64) { | ||||
|   return Enc.binToBuf(atob(b64)); | ||||
| }; | ||||
| Enc.binToBuf = function (bin) { | ||||
|   var arr = bin.split('').map(function (ch) { | ||||
|     return ch.charCodeAt(0); | ||||
|   }); | ||||
|   return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| }; | ||||
| Enc.bufToHex = function (u8) { | ||||
|   var hex = []; | ||||
|   var i, h; | ||||
|   var len = (u8.byteLength || u8.length); | ||||
| 	Enc.base64ToBuf = function(b64) { | ||||
| 		return Enc.binToBuf(atob(b64)); | ||||
| 	}; | ||||
| 	Enc.binToBuf = function(bin) { | ||||
| 		var arr = bin.split('').map(function(ch) { | ||||
| 			return ch.charCodeAt(0); | ||||
| 		}); | ||||
| 		return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| 	}; | ||||
| 	Enc.bufToHex = function(u8) { | ||||
| 		var hex = []; | ||||
| 		var i, h; | ||||
| 		var len = u8.byteLength || u8.length; | ||||
| 
 | ||||
|   for (i = 0; i < len; i += 1) { | ||||
|     h = u8[i].toString(16); | ||||
|     if (h.length % 2) { h = '0' + h; } | ||||
|     hex.push(h); | ||||
|   } | ||||
| 		for (i = 0; i < len; i += 1) { | ||||
| 			h = u8[i].toString(16); | ||||
| 			if (h.length % 2) { | ||||
| 				h = '0' + h; | ||||
| 			} | ||||
| 			hex.push(h); | ||||
| 		} | ||||
| 
 | ||||
|   return hex.join('').toLowerCase(); | ||||
| }; | ||||
| Enc.numToHex = function (d) { | ||||
|   d = d.toString(16); | ||||
|   if (d.length % 2) { | ||||
|     return '0' + d; | ||||
|   } | ||||
|   return d; | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof window ? window : module.exports)); | ||||
| 		return hex.join('').toLowerCase(); | ||||
| 	}; | ||||
| 	Enc.numToHex = function(d) { | ||||
| 		d = d.toString(16); | ||||
| 		if (d.length % 2) { | ||||
| 			return '0' + d; | ||||
| 		} | ||||
| 		return d; | ||||
| 	}; | ||||
| })('undefined' !== typeof window ? window : module.exports); | ||||
|  | ||||
							
								
								
									
										644
									
								
								lib/csr.js
									
									
									
									
									
								
							
							
						
						
									
										644
									
								
								lib/csr.js
									
									
									
									
									
								
							| @ -2,297 +2,401 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| /*global Promise*/ | ||||
| (function(exports) { | ||||
| 	'use strict'; | ||||
| 	/*global Promise*/ | ||||
| 
 | ||||
| var ASN1 = exports.ASN1; | ||||
| var Enc = exports.Enc; | ||||
| var PEM = exports.PEM; | ||||
| var X509 = exports.x509; | ||||
| var Keypairs = exports.Keypairs; | ||||
| 	var ASN1 = exports.ASN1; | ||||
| 	var Enc = exports.Enc; | ||||
| 	var PEM = exports.PEM; | ||||
| 	var X509 = exports.x509; | ||||
| 	var Keypairs = exports.Keypairs; | ||||
| 
 | ||||
| // TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
 | ||||
| var CSR = exports.CSR = function (opts) { | ||||
|   // We're using a Promise here to be compatible with the browser version
 | ||||
|   // which will probably use the webcrypto API for some of the conversions
 | ||||
|   return CSR._prepare(opts).then(function (opts) { | ||||
|     return CSR.create(opts).then(function (bytes) { | ||||
|       return CSR._encode(opts, bytes); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 	// TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
 | ||||
| 	var CSR = (exports.CSR = function(opts) { | ||||
| 		// We're using a Promise here to be compatible with the browser version
 | ||||
| 		// which will probably use the webcrypto API for some of the conversions
 | ||||
| 		return CSR._prepare(opts).then(function(opts) { | ||||
| 			return CSR.create(opts).then(function(bytes) { | ||||
| 				return CSR._encode(opts, bytes); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| CSR._prepare = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     var Keypairs; | ||||
|     opts = JSON.parse(JSON.stringify(opts)); | ||||
| 	CSR._prepare = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			var Keypairs; | ||||
| 			opts = JSON.parse(JSON.stringify(opts)); | ||||
| 
 | ||||
|     // 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 rsacsr"); } | ||||
|     if (!Array.isArray(opts.domains) || 0 === opts.domains.length) { | ||||
|       new Error("You must pass options.domains as a non-empty array"); | ||||
|     } | ||||
| 			// 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 rsacsr' | ||||
| 				); | ||||
| 			} | ||||
| 			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 strings"); | ||||
|     } | ||||
| 			// 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 strings'); | ||||
| 			} | ||||
| 
 | ||||
|     if (opts.jwk) { return opts; } | ||||
|     if (opts.key && opts.key.kty) { | ||||
|       opts.jwk = opts.key; | ||||
|       return opts; | ||||
|     } | ||||
|     if (!opts.pem && !opts.key) { | ||||
|       throw new Error("You must pass options.key as a JSON web key"); | ||||
|     } | ||||
| 			if (opts.jwk) { | ||||
| 				return opts; | ||||
| 			} | ||||
| 			if (opts.key && opts.key.kty) { | ||||
| 				opts.jwk = opts.key; | ||||
| 				return opts; | ||||
| 			} | ||||
| 			if (!opts.pem && !opts.key) { | ||||
| 				throw new Error('You must pass options.key as a JSON web key'); | ||||
| 			} | ||||
| 
 | ||||
|     Keypairs = exports.Keypairs; | ||||
|     if (!exports.Keypairs) { | ||||
|       throw new Error("Keypairs.js is an optional dependency for PEM-to-JWK.\n" | ||||
|         + "Install it if you'd like to use it:\n" | ||||
|         + "\tnpm install --save rasha\n" | ||||
|         + "Otherwise supply a jwk as the private key." | ||||
|       ); | ||||
|     } | ||||
| 			Keypairs = exports.Keypairs; | ||||
| 			if (!exports.Keypairs) { | ||||
| 				throw new Error( | ||||
| 					'Keypairs.js is an optional dependency for PEM-to-JWK.\n' + | ||||
| 						"Install it if you'd like to use it:\n" + | ||||
| 						'\tnpm install --save rasha\n' + | ||||
| 						'Otherwise supply a jwk as the private key.' | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
|     return Keypairs.import({ pem: opts.pem || opts.key }).then(function (pair) { | ||||
|       opts.jwk = pair.private; | ||||
|       return opts; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 			return Keypairs.import({ pem: opts.pem || opts.key }).then(function( | ||||
| 				pair | ||||
| 			) { | ||||
| 				opts.jwk = pair.private; | ||||
| 				return opts; | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| CSR._encode = function (opts, bytes) { | ||||
|   if ('der' === (opts.encoding||'').toLowerCase()) { | ||||
|     return bytes; | ||||
|   } | ||||
|   return PEM.packBlock({ | ||||
|     type: "CERTIFICATE REQUEST" | ||||
|   , bytes: bytes /* { jwk: jwk, domains: opts.domains } */ | ||||
|   }); | ||||
| }; | ||||
| 	CSR._encode = function(opts, bytes) { | ||||
| 		if ('der' === (opts.encoding || '').toLowerCase()) { | ||||
| 			return bytes; | ||||
| 		} | ||||
| 		return PEM.packBlock({ | ||||
| 			type: 'CERTIFICATE REQUEST', | ||||
| 			bytes: bytes /* { jwk: jwk, domains: opts.domains } */ | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| CSR.create = function createCsr(opts) { | ||||
|   var hex = CSR.request(opts.jwk, opts.domains); | ||||
|   return CSR._sign(opts.jwk, hex).then(function (csr) { | ||||
|     return Enc.hexToBuf(csr); | ||||
|   }); | ||||
| }; | ||||
| 	CSR.create = function createCsr(opts) { | ||||
| 		var hex = CSR.request(opts.jwk, opts.domains); | ||||
| 		return CSR._sign(opts.jwk, hex).then(function(csr) { | ||||
| 			return Enc.hexToBuf(csr); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| //
 | ||||
| // EC / RSA
 | ||||
| //
 | ||||
| CSR.request = function createCsrBodyEc(jwk, domains) { | ||||
|   var asn1pub; | ||||
|   if (/^EC/i.test(jwk.kty)) { | ||||
|     asn1pub = X509.packCsrEcPublicKey(jwk); | ||||
|   } else { | ||||
|     asn1pub = X509.packCsrRsaPublicKey(jwk); | ||||
|   } | ||||
|   return X509.packCsr(asn1pub, domains); | ||||
| }; | ||||
| 	//
 | ||||
| 	// EC / RSA
 | ||||
| 	//
 | ||||
| 	CSR.request = function createCsrBodyEc(jwk, domains) { | ||||
| 		var asn1pub; | ||||
| 		if (/^EC/i.test(jwk.kty)) { | ||||
| 			asn1pub = X509.packCsrEcPublicKey(jwk); | ||||
| 		} else { | ||||
| 			asn1pub = X509.packCsrRsaPublicKey(jwk); | ||||
| 		} | ||||
| 		return X509.packCsr(asn1pub, domains); | ||||
| 	}; | ||||
| 
 | ||||
| CSR._sign = function csrEcSig(jwk, request) { | ||||
|   // Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | ||||
|   // TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
 | ||||
|   // TODO have a consistent non-private way to sign
 | ||||
|   return Keypairs._sign({ jwk: jwk, format: 'x509' }, Enc.hexToBuf(request)).then(function (sig) { | ||||
|     return CSR._toDer({ request: request, signature: sig, kty: jwk.kty }); | ||||
|   }); | ||||
| }; | ||||
| 	CSR._sign = function csrEcSig(jwk, request) { | ||||
| 		// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | ||||
| 		// TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
 | ||||
| 		// TODO have a consistent non-private way to sign
 | ||||
| 		return Keypairs._sign( | ||||
| 			{ jwk: jwk, format: 'x509' }, | ||||
| 			Enc.hexToBuf(request) | ||||
| 		).then(function(sig) { | ||||
| 			return CSR._toDer({ | ||||
| 				request: request, | ||||
| 				signature: sig, | ||||
| 				kty: jwk.kty | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| CSR._toDer = function encode(opts) { | ||||
|   var sty; | ||||
|   if (/^EC/i.test(opts.kty)) { | ||||
|     // 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA algorithm with SHA256)
 | ||||
|     sty = ASN1('30', ASN1('06', '2a8648ce3d040302')); | ||||
|   } else { | ||||
|     // 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
 | ||||
|     sty = ASN1('30', ASN1('06', '2a864886f70d01010b'), ASN1('05')); | ||||
|   } | ||||
|   return ASN1('30' | ||||
|     // The Full CSR Request Body
 | ||||
|   , opts.request | ||||
|     // The Signature Type
 | ||||
|   , sty | ||||
|     // The Signature
 | ||||
|   , ASN1.BitStr(Enc.bufToHex(opts.signature)) | ||||
|   ); | ||||
| }; | ||||
| 	CSR._toDer = function encode(opts) { | ||||
| 		var sty; | ||||
| 		if (/^EC/i.test(opts.kty)) { | ||||
| 			// 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA algorithm with SHA256)
 | ||||
| 			sty = ASN1('30', ASN1('06', '2a8648ce3d040302')); | ||||
| 		} else { | ||||
| 			// 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
 | ||||
| 			sty = ASN1('30', ASN1('06', '2a864886f70d01010b'), ASN1('05')); | ||||
| 		} | ||||
| 		return ASN1( | ||||
| 			'30', | ||||
| 			// The Full CSR Request Body
 | ||||
| 			opts.request, | ||||
| 			// The Signature Type
 | ||||
| 			sty, | ||||
| 			// The Signature
 | ||||
| 			ASN1.BitStr(Enc.bufToHex(opts.signature)) | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| X509.packCsr = function (asn1pubkey, domains) { | ||||
|   return ASN1('30' | ||||
|     // Version (0)
 | ||||
|   , ASN1.UInt('00') | ||||
| 	X509.packCsr = function(asn1pubkey, domains) { | ||||
| 		return ASN1( | ||||
| 			'30', | ||||
| 			// Version (0)
 | ||||
| 			ASN1.UInt('00'), | ||||
| 
 | ||||
|     // 2.5.4.3 commonName (X.520 DN component)
 | ||||
|   , ASN1('30', ASN1('31', ASN1('30', ASN1('06', '550403'), ASN1('0c', Enc.utf8ToHex(domains[0]))))) | ||||
| 			// 2.5.4.3 commonName (X.520 DN component)
 | ||||
| 			ASN1( | ||||
| 				'30', | ||||
| 				ASN1( | ||||
| 					'31', | ||||
| 					ASN1( | ||||
| 						'30', | ||||
| 						ASN1('06', '550403'), | ||||
| 						ASN1('0c', Enc.utf8ToHex(domains[0])) | ||||
| 					) | ||||
| 				) | ||||
| 			), | ||||
| 
 | ||||
|     // Public Key (RSA or EC)
 | ||||
|   , asn1pubkey | ||||
| 			// Public Key (RSA or EC)
 | ||||
| 			asn1pubkey, | ||||
| 
 | ||||
|     // Request Body
 | ||||
|   , ASN1('a0' | ||||
|     , ASN1('30' | ||||
|         // 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
|       , ASN1('06', '2a864886f70d01090e') | ||||
|       , ASN1('31' | ||||
|         , ASN1('30' | ||||
|           , ASN1('30' | ||||
|               // 2.5.29.17 subjectAltName (X.509 extension)
 | ||||
|             , ASN1('06', '551d11') | ||||
|             , ASN1('04' | ||||
|               , ASN1('30', domains.map(function (d) { | ||||
|                   return ASN1('82', Enc.utf8ToHex(d)); | ||||
|                 }).join('')))))))) | ||||
|   ); | ||||
| }; | ||||
| 			// Request Body
 | ||||
| 			ASN1( | ||||
| 				'a0', | ||||
| 				ASN1( | ||||
| 					'30', | ||||
| 					// 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
| 					ASN1('06', '2a864886f70d01090e'), | ||||
| 					ASN1( | ||||
| 						'31', | ||||
| 						ASN1( | ||||
| 							'30', | ||||
| 							ASN1( | ||||
| 								'30', | ||||
| 								// 2.5.29.17 subjectAltName (X.509 extension)
 | ||||
| 								ASN1('06', '551d11'), | ||||
| 								ASN1( | ||||
| 									'04', | ||||
| 									ASN1( | ||||
| 										'30', | ||||
| 										domains | ||||
| 											.map(function(d) { | ||||
| 												return ASN1( | ||||
| 													'82', | ||||
| 													Enc.utf8ToHex(d) | ||||
| 												); | ||||
| 											}) | ||||
| 											.join('') | ||||
| 									) | ||||
| 								) | ||||
| 							) | ||||
| 						) | ||||
| 					) | ||||
| 				) | ||||
| 			) | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| // TODO finish this later
 | ||||
| // we want to parse the domains, the public key, and verify the signature
 | ||||
| CSR._info = function (der) { | ||||
|   // standard base64 PEM
 | ||||
|   if ('string' === typeof der && '-' === der[0]) { | ||||
|     der = PEM.parseBlock(der).bytes; | ||||
|   } | ||||
|   // jose urlBase64 not-PEM
 | ||||
|   if ('string' === typeof der) { | ||||
|     der = Enc.base64ToBuf(der); | ||||
|   } | ||||
|   // not supporting binary-encoded bas64
 | ||||
|   var c = ASN1.parse(der); | ||||
|   var kty; | ||||
|   // A cert has 3 parts: cert, signature meta, signature
 | ||||
|   if (c.children.length !== 3) { | ||||
|     throw new Error("doesn't look like a certificate request: expected 3 parts of header"); | ||||
|   } | ||||
|   var sig = c.children[2]; | ||||
|   if (sig.children.length) { | ||||
|     // ASN1/X509 EC
 | ||||
|     sig = sig.children[0]; | ||||
|     sig = ASN1('30', ASN1.UInt(Enc.bufToHex(sig.children[0].value)), ASN1.UInt(Enc.bufToHex(sig.children[1].value))); | ||||
|     sig = Enc.hexToBuf(sig); | ||||
|     kty = 'EC'; | ||||
|   } else { | ||||
|     // Raw RSA Sig
 | ||||
|     sig = sig.value; | ||||
|     kty = 'RSA'; | ||||
|   } | ||||
|   //c.children[1]; // signature type
 | ||||
|   var req = c.children[0]; | ||||
|   // TODO utf8
 | ||||
|   if (4 !== req.children.length) { | ||||
|     throw new Error("doesn't look like a certificate request: expected 4 parts to request"); | ||||
|   } | ||||
|   // 0 null
 | ||||
|   // 1 commonName / subject
 | ||||
|   var sub = Enc.bufToBin(req.children[1].children[0].children[0].children[1].value); | ||||
|   // 3 public key (type, key)
 | ||||
|   //console.log('oid', Enc.bufToHex(req.children[2].children[0].children[0].value));
 | ||||
|   var pub; | ||||
|   // TODO reuse ASN1 parser for these?
 | ||||
|   if ('EC' === kty) { | ||||
|     // throw away compression byte
 | ||||
|     pub = req.children[2].children[1].value.slice(1); | ||||
|     pub = { kty: kty, x: pub.slice(0, 32), y: pub.slice(32) }; | ||||
|     while (0 === pub.x[0]) { pub.x = pub.x.slice(1); } | ||||
|     while (0 === pub.y[0]) { pub.y = pub.y.slice(1); } | ||||
|     if ((pub.x.length || pub.x.byteLength) > 48) { | ||||
|       pub.crv = 'P-521'; | ||||
|     } else if ((pub.x.length || pub.x.byteLength) > 32) { | ||||
|       pub.crv = 'P-384'; | ||||
|     } else { | ||||
|       pub.crv = 'P-256'; | ||||
|     } | ||||
|     pub.x = Enc.bufToUrlBase64(pub.x); | ||||
|     pub.y = Enc.bufToUrlBase64(pub.y); | ||||
|   } else { | ||||
|     pub = req.children[2].children[1].children[0]; | ||||
|     pub = { kty: kty, n: pub.children[0].value, e: pub.children[1].value }; | ||||
|     while (0 === pub.n[0]) { pub.n = pub.n.slice(1); } | ||||
|     while (0 === pub.e[0]) { pub.e = pub.e.slice(1); } | ||||
|     pub.n = Enc.bufToUrlBase64(pub.n); | ||||
|     pub.e = Enc.bufToUrlBase64(pub.e); | ||||
|   } | ||||
|   // 4 extensions
 | ||||
|   var domains = req.children[3].children.filter(function (seq) { | ||||
|     //  1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
|     if ('2a864886f70d01090e' === Enc.bufToHex(seq.children[0].value)) { | ||||
|       return true; | ||||
|     } | ||||
|   }).map(function (seq) { | ||||
|     return seq.children[1].children[0].children.filter(function (seq2) { | ||||
|       // subjectAltName (X.509 extension)
 | ||||
|       if ('551d11' === Enc.bufToHex(seq2.children[0].value)) { | ||||
|         return true; | ||||
|       } | ||||
|     }).map(function (seq2) { | ||||
|       return seq2.children[1].children[0].children.map(function (name) { | ||||
|         // TODO utf8
 | ||||
|         return Enc.bufToBin(name.value); | ||||
|       }); | ||||
|     })[0]; | ||||
|   })[0]; | ||||
| 	// TODO finish this later
 | ||||
| 	// we want to parse the domains, the public key, and verify the signature
 | ||||
| 	CSR._info = function(der) { | ||||
| 		// standard base64 PEM
 | ||||
| 		if ('string' === typeof der && '-' === der[0]) { | ||||
| 			der = PEM.parseBlock(der).bytes; | ||||
| 		} | ||||
| 		// jose urlBase64 not-PEM
 | ||||
| 		if ('string' === typeof der) { | ||||
| 			der = Enc.base64ToBuf(der); | ||||
| 		} | ||||
| 		// not supporting binary-encoded bas64
 | ||||
| 		var c = ASN1.parse(der); | ||||
| 		var kty; | ||||
| 		// A cert has 3 parts: cert, signature meta, signature
 | ||||
| 		if (c.children.length !== 3) { | ||||
| 			throw new Error( | ||||
| 				"doesn't look like a certificate request: expected 3 parts of header" | ||||
| 			); | ||||
| 		} | ||||
| 		var sig = c.children[2]; | ||||
| 		if (sig.children.length) { | ||||
| 			// ASN1/X509 EC
 | ||||
| 			sig = sig.children[0]; | ||||
| 			sig = ASN1( | ||||
| 				'30', | ||||
| 				ASN1.UInt(Enc.bufToHex(sig.children[0].value)), | ||||
| 				ASN1.UInt(Enc.bufToHex(sig.children[1].value)) | ||||
| 			); | ||||
| 			sig = Enc.hexToBuf(sig); | ||||
| 			kty = 'EC'; | ||||
| 		} else { | ||||
| 			// Raw RSA Sig
 | ||||
| 			sig = sig.value; | ||||
| 			kty = 'RSA'; | ||||
| 		} | ||||
| 		//c.children[1]; // signature type
 | ||||
| 		var req = c.children[0]; | ||||
| 		// TODO utf8
 | ||||
| 		if (4 !== req.children.length) { | ||||
| 			throw new Error( | ||||
| 				"doesn't look like a certificate request: expected 4 parts to request" | ||||
| 			); | ||||
| 		} | ||||
| 		// 0 null
 | ||||
| 		// 1 commonName / subject
 | ||||
| 		var sub = Enc.bufToBin( | ||||
| 			req.children[1].children[0].children[0].children[1].value | ||||
| 		); | ||||
| 		// 3 public key (type, key)
 | ||||
| 		//console.log('oid', Enc.bufToHex(req.children[2].children[0].children[0].value));
 | ||||
| 		var pub; | ||||
| 		// TODO reuse ASN1 parser for these?
 | ||||
| 		if ('EC' === kty) { | ||||
| 			// throw away compression byte
 | ||||
| 			pub = req.children[2].children[1].value.slice(1); | ||||
| 			pub = { kty: kty, x: pub.slice(0, 32), y: pub.slice(32) }; | ||||
| 			while (0 === pub.x[0]) { | ||||
| 				pub.x = pub.x.slice(1); | ||||
| 			} | ||||
| 			while (0 === pub.y[0]) { | ||||
| 				pub.y = pub.y.slice(1); | ||||
| 			} | ||||
| 			if ((pub.x.length || pub.x.byteLength) > 48) { | ||||
| 				pub.crv = 'P-521'; | ||||
| 			} else if ((pub.x.length || pub.x.byteLength) > 32) { | ||||
| 				pub.crv = 'P-384'; | ||||
| 			} else { | ||||
| 				pub.crv = 'P-256'; | ||||
| 			} | ||||
| 			pub.x = Enc.bufToUrlBase64(pub.x); | ||||
| 			pub.y = Enc.bufToUrlBase64(pub.y); | ||||
| 		} else { | ||||
| 			pub = req.children[2].children[1].children[0]; | ||||
| 			pub = { | ||||
| 				kty: kty, | ||||
| 				n: pub.children[0].value, | ||||
| 				e: pub.children[1].value | ||||
| 			}; | ||||
| 			while (0 === pub.n[0]) { | ||||
| 				pub.n = pub.n.slice(1); | ||||
| 			} | ||||
| 			while (0 === pub.e[0]) { | ||||
| 				pub.e = pub.e.slice(1); | ||||
| 			} | ||||
| 			pub.n = Enc.bufToUrlBase64(pub.n); | ||||
| 			pub.e = Enc.bufToUrlBase64(pub.e); | ||||
| 		} | ||||
| 		// 4 extensions
 | ||||
| 		var domains = req.children[3].children | ||||
| 			.filter(function(seq) { | ||||
| 				//  1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
| 				if ( | ||||
| 					'2a864886f70d01090e' === Enc.bufToHex(seq.children[0].value) | ||||
| 				) { | ||||
| 					return true; | ||||
| 				} | ||||
| 			}) | ||||
| 			.map(function(seq) { | ||||
| 				return seq.children[1].children[0].children | ||||
| 					.filter(function(seq2) { | ||||
| 						// subjectAltName (X.509 extension)
 | ||||
| 						if ('551d11' === Enc.bufToHex(seq2.children[0].value)) { | ||||
| 							return true; | ||||
| 						} | ||||
| 					}) | ||||
| 					.map(function(seq2) { | ||||
| 						return seq2.children[1].children[0].children.map( | ||||
| 							function(name) { | ||||
| 								// TODO utf8
 | ||||
| 								return Enc.bufToBin(name.value); | ||||
| 							} | ||||
| 						); | ||||
| 					})[0]; | ||||
| 			})[0]; | ||||
| 
 | ||||
|   return { | ||||
|     subject: sub | ||||
|   , altnames: domains | ||||
|   , jwk: pub | ||||
|   , signature: sig | ||||
|   }; | ||||
| }; | ||||
| 		return { | ||||
| 			subject: sub, | ||||
| 			altnames: domains, | ||||
| 			jwk: pub, | ||||
| 			signature: sig | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| X509.packCsrRsaPublicKey = function (jwk) { | ||||
|   // Sequence the key
 | ||||
|   var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||
|   var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); | ||||
|   var asn1pub = ASN1('30', n, e); | ||||
| 	X509.packCsrRsaPublicKey = function(jwk) { | ||||
| 		// Sequence the key
 | ||||
| 		var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||
| 		var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); | ||||
| 		var asn1pub = ASN1('30', n, e); | ||||
| 
 | ||||
|   // Add the CSR pub key header
 | ||||
|   return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub)); | ||||
| }; | ||||
| 		// Add the CSR pub key header
 | ||||
| 		return ASN1( | ||||
| 			'30', | ||||
| 			ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), | ||||
| 			ASN1.BitStr(asn1pub) | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| X509.packCsrEcPublicKey = function (jwk) { | ||||
|   var ecOid = X509._oids[jwk.crv]; | ||||
|   if (!ecOid) { | ||||
|     throw new Error("Unsupported namedCurve '" + jwk.crv + "'. Supported types are " + Object.keys(X509._oids)); | ||||
|   } | ||||
|   var cmp = '04'; // 04 == x+y, 02 == x-only
 | ||||
|   var hxy = ''; | ||||
|   // Placeholder. I'm not even sure if compression should be supported.
 | ||||
|   if (!jwk.y) { cmp = '02'; } | ||||
|   hxy += Enc.base64ToHex(jwk.x); | ||||
|   if (jwk.y) { hxy += Enc.base64ToHex(jwk.y); } | ||||
| 	X509.packCsrEcPublicKey = function(jwk) { | ||||
| 		var ecOid = X509._oids[jwk.crv]; | ||||
| 		if (!ecOid) { | ||||
| 			throw new Error( | ||||
| 				"Unsupported namedCurve '" + | ||||
| 					jwk.crv + | ||||
| 					"'. Supported types are " + | ||||
| 					Object.keys(X509._oids) | ||||
| 			); | ||||
| 		} | ||||
| 		var cmp = '04'; // 04 == x+y, 02 == x-only
 | ||||
| 		var hxy = ''; | ||||
| 		// Placeholder. I'm not even sure if compression should be supported.
 | ||||
| 		if (!jwk.y) { | ||||
| 			cmp = '02'; | ||||
| 		} | ||||
| 		hxy += Enc.base64ToHex(jwk.x); | ||||
| 		if (jwk.y) { | ||||
| 			hxy += Enc.base64ToHex(jwk.y); | ||||
| 		} | ||||
| 
 | ||||
|   // 1.2.840.10045.2.1 ecPublicKey
 | ||||
|   return ASN1('30', ASN1('30', ASN1('06', '2a8648ce3d0201'), ASN1('06', ecOid)), ASN1.BitStr(cmp + hxy)); | ||||
| }; | ||||
| X509._oids = { | ||||
|   // 1.2.840.10045.3.1.7 prime256v1
 | ||||
|   // (ANSI X9.62 named elliptic curve) (06 08 - 2A 86 48 CE 3D 03 01 07)
 | ||||
|   'P-256': '2a8648ce3d030107' | ||||
|   // 1.3.132.0.34 P-384 (06 05 - 2B 81 04 00 22)
 | ||||
|   // (SEC 2 recommended EC domain secp256r1)
 | ||||
| , 'P-384': '2b81040022' | ||||
|   // requires more logic and isn't a recommended standard
 | ||||
|   // 1.3.132.0.35 P-521 (06 05 - 2B 81 04 00 23)
 | ||||
|   // (SEC 2 alternate P-521)
 | ||||
| //, 'P-521': '2B 81 04 00 23'
 | ||||
| }; | ||||
| 		// 1.2.840.10045.2.1 ecPublicKey
 | ||||
| 		return ASN1( | ||||
| 			'30', | ||||
| 			ASN1('30', ASN1('06', '2a8648ce3d0201'), ASN1('06', ecOid)), | ||||
| 			ASN1.BitStr(cmp + hxy) | ||||
| 		); | ||||
| 	}; | ||||
| 	X509._oids = { | ||||
| 		// 1.2.840.10045.3.1.7 prime256v1
 | ||||
| 		// (ANSI X9.62 named elliptic curve) (06 08 - 2A 86 48 CE 3D 03 01 07)
 | ||||
| 		'P-256': '2a8648ce3d030107', | ||||
| 		// 1.3.132.0.34 P-384 (06 05 - 2B 81 04 00 22)
 | ||||
| 		// (SEC 2 recommended EC domain secp256r1)
 | ||||
| 		'P-384': '2b81040022' | ||||
| 		// requires more logic and isn't a recommended standard
 | ||||
| 		// 1.3.132.0.35 P-521 (06 05 - 2B 81 04 00 23)
 | ||||
| 		// (SEC 2 alternate P-521)
 | ||||
| 		//, 'P-521': '2B 81 04 00 23'
 | ||||
| 	}; | ||||
| 
 | ||||
| // don't replace the full parseBlock, if it exists
 | ||||
| PEM.parseBlock = PEM.parseBlock || function (str) { | ||||
|   var der = str.split(/\n/).filter(function (line) { | ||||
|     return !/-----/.test(line); | ||||
|   }).join(''); | ||||
|   return { bytes: Enc.base64ToBuf(der) }; | ||||
| }; | ||||
| 
 | ||||
| }('undefined' === typeof window ? module.exports : window)); | ||||
| 	// don't replace the full parseBlock, if it exists
 | ||||
| 	PEM.parseBlock = | ||||
| 		PEM.parseBlock || | ||||
| 		function(str) { | ||||
| 			var der = str | ||||
| 				.split(/\n/) | ||||
| 				.filter(function(line) { | ||||
| 					return !/-----/.test(line); | ||||
| 				}) | ||||
| 				.join(''); | ||||
| 			return { bytes: Enc.base64ToBuf(der) }; | ||||
| 		}; | ||||
| })('undefined' === typeof window ? module.exports : window); | ||||
|  | ||||
							
								
								
									
										375
									
								
								lib/ecdsa.js
									
									
									
									
									
								
							
							
						
						
									
										375
									
								
								lib/ecdsa.js
									
									
									
									
									
								
							| @ -1,172 +1,227 @@ | ||||
| /*global Promise*/ | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| (function(exports) { | ||||
| 	'use strict'; | ||||
| 
 | ||||
| var EC = exports.Eckles = {}; | ||||
| var x509 = exports.x509; | ||||
| if ('undefined' !== typeof module) { module.exports = EC; } | ||||
| var PEM = exports.PEM; | ||||
| var SSH = exports.SSH; | ||||
| var Enc = {}; | ||||
| var textEncoder = new TextEncoder(); | ||||
| 	var EC = (exports.Eckles = {}); | ||||
| 	var x509 = exports.x509; | ||||
| 	if ('undefined' !== typeof module) { | ||||
| 		module.exports = EC; | ||||
| 	} | ||||
| 	var PEM = exports.PEM; | ||||
| 	var SSH = exports.SSH; | ||||
| 	var Enc = {}; | ||||
| 	var textEncoder = new TextEncoder(); | ||||
| 
 | ||||
| EC._stance = "We take the stance that if you're knowledgeable enough to" | ||||
|   + " properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| EC._universal = "Bluecrypt only supports crypto with standard cross-browser and cross-platform support."; | ||||
| EC.generate = function (opts) { | ||||
|   var wcOpts = {}; | ||||
|   if (!opts) { opts = {}; } | ||||
|   if (!opts.kty) { opts.kty = 'EC'; } | ||||
| 	EC._stance = | ||||
| 		"We take the stance that if you're knowledgeable enough to" + | ||||
| 		" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| 	EC._universal = | ||||
| 		'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; | ||||
| 	EC.generate = function(opts) { | ||||
| 		var wcOpts = {}; | ||||
| 		if (!opts) { | ||||
| 			opts = {}; | ||||
| 		} | ||||
| 		if (!opts.kty) { | ||||
| 			opts.kty = 'EC'; | ||||
| 		} | ||||
| 
 | ||||
|   // ECDSA has only the P curves and an associated bitlength
 | ||||
|   wcOpts.name = 'ECDSA'; | ||||
|   if (!opts.namedCurve) { | ||||
|     opts.namedCurve = 'P-256'; | ||||
|   } | ||||
|   wcOpts.namedCurve = opts.namedCurve; // true for supported curves
 | ||||
|   if (/256/.test(wcOpts.namedCurve)) { | ||||
|     wcOpts.namedCurve = 'P-256'; | ||||
|     wcOpts.hash = { name: "SHA-256" }; | ||||
|   } else if (/384/.test(wcOpts.namedCurve)) { | ||||
|     wcOpts.namedCurve = 'P-384'; | ||||
|     wcOpts.hash = { name: "SHA-384" }; | ||||
|   } else { | ||||
|     return Promise.Reject(new Error("'" + wcOpts.namedCurve + "' is not an NIST approved ECDSA namedCurve. " | ||||
|       + " Please choose either 'P-256' or 'P-384'. " | ||||
|       + EC._stance)); | ||||
|   } | ||||
| 		// ECDSA has only the P curves and an associated bitlength
 | ||||
| 		wcOpts.name = 'ECDSA'; | ||||
| 		if (!opts.namedCurve) { | ||||
| 			opts.namedCurve = 'P-256'; | ||||
| 		} | ||||
| 		wcOpts.namedCurve = opts.namedCurve; // true for supported curves
 | ||||
| 		if (/256/.test(wcOpts.namedCurve)) { | ||||
| 			wcOpts.namedCurve = 'P-256'; | ||||
| 			wcOpts.hash = { name: 'SHA-256' }; | ||||
| 		} else if (/384/.test(wcOpts.namedCurve)) { | ||||
| 			wcOpts.namedCurve = 'P-384'; | ||||
| 			wcOpts.hash = { name: 'SHA-384' }; | ||||
| 		} else { | ||||
| 			return Promise.Reject( | ||||
| 				new Error( | ||||
| 					"'" + | ||||
| 						wcOpts.namedCurve + | ||||
| 						"' is not an NIST approved ECDSA namedCurve. " + | ||||
| 						" Please choose either 'P-256' or 'P-384'. " + | ||||
| 						EC._stance | ||||
| 				) | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
|   var extractable = true; | ||||
|   return window.crypto.subtle.generateKey( | ||||
|     wcOpts | ||||
|   , extractable | ||||
|   , [ 'sign', 'verify' ] | ||||
|   ).then(function (result) { | ||||
|     return window.crypto.subtle.exportKey( | ||||
|       "jwk" | ||||
|     , result.privateKey | ||||
|     ).then(function (privJwk) { | ||||
|       privJwk.key_ops = undefined; | ||||
|       privJwk.ext = undefined; | ||||
|       return { | ||||
|         private: privJwk | ||||
|       , public: EC.neuter({ jwk: privJwk }) | ||||
|       }; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 		var extractable = true; | ||||
| 		return window.crypto.subtle | ||||
| 			.generateKey(wcOpts, extractable, ['sign', 'verify']) | ||||
| 			.then(function(result) { | ||||
| 				return window.crypto.subtle | ||||
| 					.exportKey('jwk', result.privateKey) | ||||
| 					.then(function(privJwk) { | ||||
| 						privJwk.key_ops = undefined; | ||||
| 						privJwk.ext = undefined; | ||||
| 						return { | ||||
| 							private: privJwk, | ||||
| 							public: EC.neuter({ jwk: privJwk }) | ||||
| 						}; | ||||
| 					}); | ||||
| 			}); | ||||
| 	}; | ||||
| 
 | ||||
| EC.export = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { | ||||
|       throw new Error("must pass { jwk: jwk } as a JSON object"); | ||||
|     } | ||||
|     var jwk = JSON.parse(JSON.stringify(opts.jwk)); | ||||
|     var format = opts.format; | ||||
|     if (opts.public || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) { | ||||
|       jwk.d = null; | ||||
|     } | ||||
|     if ('EC' !== jwk.kty) { | ||||
|       throw new Error("options.jwk.kty must be 'EC' for EC keys"); | ||||
|     } | ||||
|     if (!jwk.d) { | ||||
|       if (!format || -1 !== [ 'spki', 'pkix' ].indexOf(format)) { | ||||
|         format = 'spki'; | ||||
|       } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { | ||||
|         format = 'ssh'; | ||||
|       } else { | ||||
|         throw new Error("options.format must be 'spki' or 'ssh' for public EC keys, not (" | ||||
|           + typeof format + ") " + format); | ||||
|       } | ||||
|     } else { | ||||
|       if (!format || 'sec1' === format) { | ||||
|         format = 'sec1'; | ||||
|       } else if ('pkcs8' !== format) { | ||||
|         throw new Error("options.format must be 'sec1' or 'pkcs8' for private EC keys, not '" + format + "'"); | ||||
|       } | ||||
|     } | ||||
|     if (-1 === [ 'P-256', 'P-384' ].indexOf(jwk.crv)) { | ||||
|       throw new Error("options.jwk.crv must be either P-256 or P-384 for EC keys, not '" + jwk.crv + "'"); | ||||
|     } | ||||
|     if (!jwk.y) { | ||||
|       throw new Error("options.jwk.y must be a urlsafe base64-encoded either P-256 or P-384"); | ||||
|     } | ||||
| 	EC.export = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { | ||||
| 				throw new Error('must pass { jwk: jwk } as a JSON object'); | ||||
| 			} | ||||
| 			var jwk = JSON.parse(JSON.stringify(opts.jwk)); | ||||
| 			var format = opts.format; | ||||
| 			if ( | ||||
| 				opts.public || | ||||
| 				-1 !== ['spki', 'pkix', 'ssh', 'rfc4716'].indexOf(format) | ||||
| 			) { | ||||
| 				jwk.d = null; | ||||
| 			} | ||||
| 			if ('EC' !== jwk.kty) { | ||||
| 				throw new Error("options.jwk.kty must be 'EC' for EC keys"); | ||||
| 			} | ||||
| 			if (!jwk.d) { | ||||
| 				if (!format || -1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 					format = 'spki'; | ||||
| 				} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 					format = 'ssh'; | ||||
| 				} else { | ||||
| 					throw new Error( | ||||
| 						"options.format must be 'spki' or 'ssh' for public EC keys, not (" + | ||||
| 							typeof format + | ||||
| 							') ' + | ||||
| 							format | ||||
| 					); | ||||
| 				} | ||||
| 			} else { | ||||
| 				if (!format || 'sec1' === format) { | ||||
| 					format = 'sec1'; | ||||
| 				} else if ('pkcs8' !== format) { | ||||
| 					throw new Error( | ||||
| 						"options.format must be 'sec1' or 'pkcs8' for private EC keys, not '" + | ||||
| 							format + | ||||
| 							"'" | ||||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 			if (-1 === ['P-256', 'P-384'].indexOf(jwk.crv)) { | ||||
| 				throw new Error( | ||||
| 					"options.jwk.crv must be either P-256 or P-384 for EC keys, not '" + | ||||
| 						jwk.crv + | ||||
| 						"'" | ||||
| 				); | ||||
| 			} | ||||
| 			if (!jwk.y) { | ||||
| 				throw new Error( | ||||
| 					'options.jwk.y must be a urlsafe base64-encoded either P-256 or P-384' | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
|     if ('sec1' === format) { | ||||
|       return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: x509.packSec1(jwk) }); | ||||
|     } else if ('pkcs8' === format) { | ||||
|       return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) }); | ||||
|     } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { | ||||
|       return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) }); | ||||
|     } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { | ||||
|       return SSH.packSsh(jwk); | ||||
|     } else { | ||||
|       throw new Error("Sanity Error: reached unreachable code block with format: " + format); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| EC.pack = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     return EC.exportSync(opts); | ||||
|   }); | ||||
| }; | ||||
| 			if ('sec1' === format) { | ||||
| 				return PEM.packBlock({ | ||||
| 					type: 'EC PRIVATE KEY', | ||||
| 					bytes: x509.packSec1(jwk) | ||||
| 				}); | ||||
| 			} else if ('pkcs8' === format) { | ||||
| 				return PEM.packBlock({ | ||||
| 					type: 'PRIVATE KEY', | ||||
| 					bytes: x509.packPkcs8(jwk) | ||||
| 				}); | ||||
| 			} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 				return PEM.packBlock({ | ||||
| 					type: 'PUBLIC KEY', | ||||
| 					bytes: x509.packSpki(jwk) | ||||
| 				}); | ||||
| 			} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 				return SSH.packSsh(jwk); | ||||
| 			} else { | ||||
| 				throw new Error( | ||||
| 					'Sanity Error: reached unreachable code block with format: ' + | ||||
| 						format | ||||
| 				); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| 	EC.pack = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			return EC.exportSync(opts); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| // Chopping off the private parts is now part of the public API.
 | ||||
| // I thought it sounded a little too crude at first, but it really is the best name in every possible way.
 | ||||
| EC.neuter = function (opts) { | ||||
|   // trying to find the best balance of an immutable copy with custom attributes
 | ||||
|   var jwk = {}; | ||||
|   Object.keys(opts.jwk).forEach(function (k) { | ||||
|     if ('undefined' === typeof opts.jwk[k]) { return; } | ||||
|     // ignore EC private parts
 | ||||
|     if ('d' === k) { return; } | ||||
|     jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
|   }); | ||||
|   return jwk; | ||||
| }; | ||||
| 	// Chopping off the private parts is now part of the public API.
 | ||||
| 	// I thought it sounded a little too crude at first, but it really is the best name in every possible way.
 | ||||
| 	EC.neuter = function(opts) { | ||||
| 		// trying to find the best balance of an immutable copy with custom attributes
 | ||||
| 		var jwk = {}; | ||||
| 		Object.keys(opts.jwk).forEach(function(k) { | ||||
| 			if ('undefined' === typeof opts.jwk[k]) { | ||||
| 				return; | ||||
| 			} | ||||
| 			// ignore EC private parts
 | ||||
| 			if ('d' === k) { | ||||
| 				return; | ||||
| 			} | ||||
| 			jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
| 		}); | ||||
| 		return jwk; | ||||
| 	}; | ||||
| 
 | ||||
| // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||
| EC.__thumbprint = function (jwk) { | ||||
|   // Use the same entropy for SHA as for key
 | ||||
|   var alg = 'SHA-256'; | ||||
|   if (/384/.test(jwk.crv)) { | ||||
|     alg = 'SHA-384'; | ||||
|   } | ||||
|   return window.crypto.subtle.digest( | ||||
|     { name: alg } | ||||
|   , textEncoder.encode('{"crv":"' + jwk.crv + '","kty":"EC","x":"' + jwk.x + '","y":"' + jwk.y + '"}') | ||||
|   ).then(function (hash) { | ||||
|     return Enc.bufToUrlBase64(new Uint8Array(hash)); | ||||
|   }); | ||||
| }; | ||||
| 	// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||
| 	EC.__thumbprint = function(jwk) { | ||||
| 		// Use the same entropy for SHA as for key
 | ||||
| 		var alg = 'SHA-256'; | ||||
| 		if (/384/.test(jwk.crv)) { | ||||
| 			alg = 'SHA-384'; | ||||
| 		} | ||||
| 		return window.crypto.subtle | ||||
| 			.digest( | ||||
| 				{ name: alg }, | ||||
| 				textEncoder.encode( | ||||
| 					'{"crv":"' + | ||||
| 						jwk.crv + | ||||
| 						'","kty":"EC","x":"' + | ||||
| 						jwk.x + | ||||
| 						'","y":"' + | ||||
| 						jwk.y + | ||||
| 						'"}' | ||||
| 				) | ||||
| 			) | ||||
| 			.then(function(hash) { | ||||
| 				return Enc.bufToUrlBase64(new Uint8Array(hash)); | ||||
| 			}); | ||||
| 	}; | ||||
| 
 | ||||
| EC.thumbprint = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     var jwk; | ||||
|     if ('EC' === opts.kty) { | ||||
|       jwk = opts; | ||||
|     } else if (opts.jwk) { | ||||
|       jwk = opts.jwk; | ||||
|     } else { | ||||
|       return EC.import(opts).then(function (jwk) { | ||||
|         return EC.__thumbprint(jwk); | ||||
|       }); | ||||
|     } | ||||
|     return EC.__thumbprint(jwk); | ||||
|   }); | ||||
| }; | ||||
| 	EC.thumbprint = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			var jwk; | ||||
| 			if ('EC' === opts.kty) { | ||||
| 				jwk = opts; | ||||
| 			} else if (opts.jwk) { | ||||
| 				jwk = opts.jwk; | ||||
| 			} else { | ||||
| 				return EC.import(opts).then(function(jwk) { | ||||
| 					return EC.__thumbprint(jwk); | ||||
| 				}); | ||||
| 			} | ||||
| 			return EC.__thumbprint(jwk); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToUrlBase64 = function (u8) { | ||||
|   return Enc.bufToBase64(u8) | ||||
|     .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); | ||||
| }; | ||||
| 	Enc.bufToUrlBase64 = function(u8) { | ||||
| 		return Enc.bufToBase64(u8) | ||||
| 			.replace(/\+/g, '-') | ||||
| 			.replace(/\//g, '_') | ||||
| 			.replace(/=/g, ''); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToBase64 = function (u8) { | ||||
|   var bin = ''; | ||||
|   u8.forEach(function (i) { | ||||
|     bin += String.fromCharCode(i); | ||||
|   }); | ||||
|   return btoa(bin); | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof module ? module.exports : window)); | ||||
| 	Enc.bufToBase64 = function(u8) { | ||||
| 		var bin = ''; | ||||
| 		u8.forEach(function(i) { | ||||
| 			bin += String.fromCharCode(i); | ||||
| 		}); | ||||
| 		return btoa(bin); | ||||
| 	}; | ||||
| })('undefined' !== typeof module ? module.exports : window); | ||||
|  | ||||
							
								
								
									
										255
									
								
								lib/encoding.js
									
									
									
									
									
								
							
							
						
						
									
										255
									
								
								lib/encoding.js
									
									
									
									
									
								
							| @ -1,140 +1,151 @@ | ||||
| (function (exports) { | ||||
| (function(exports) { | ||||
| 	var Enc = (exports.Enc = {}); | ||||
| 
 | ||||
| var Enc = exports.Enc = {}; | ||||
| 	Enc.bufToBin = function(buf) { | ||||
| 		var bin = ''; | ||||
| 		// cannot use .map() because Uint8Array would return only 0s
 | ||||
| 		buf.forEach(function(ch) { | ||||
| 			bin += String.fromCharCode(ch); | ||||
| 		}); | ||||
| 		return bin; | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToBin = function (buf) { | ||||
|   var bin = ''; | ||||
|   // cannot use .map() because Uint8Array would return only 0s
 | ||||
|   buf.forEach(function (ch) { | ||||
|     bin += String.fromCharCode(ch); | ||||
|   }); | ||||
|   return bin; | ||||
| }; | ||||
| 	Enc.bufToHex = function toHex(u8) { | ||||
| 		var hex = []; | ||||
| 		var i, h; | ||||
| 		var len = u8.byteLength || u8.length; | ||||
| 
 | ||||
| Enc.bufToHex = function toHex(u8) { | ||||
|   var hex = []; | ||||
|   var i, h; | ||||
|   var len = (u8.byteLength || u8.length); | ||||
| 		for (i = 0; i < len; i += 1) { | ||||
| 			h = u8[i].toString(16); | ||||
| 			if (h.length % 2) { | ||||
| 				h = '0' + h; | ||||
| 			} | ||||
| 			hex.push(h); | ||||
| 		} | ||||
| 
 | ||||
|   for (i = 0; i < len; i += 1) { | ||||
|     h = u8[i].toString(16); | ||||
|     if (h.length % 2) { h = '0' + h; } | ||||
|     hex.push(h); | ||||
|   } | ||||
| 		return hex.join('').toLowerCase(); | ||||
| 	}; | ||||
| 
 | ||||
|   return hex.join('').toLowerCase(); | ||||
| }; | ||||
| 	Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) { | ||||
| 		var r = str % 4; | ||||
| 		if (2 === r) { | ||||
| 			str += '=='; | ||||
| 		} else if (3 === r) { | ||||
| 			str += '='; | ||||
| 		} | ||||
| 		return str.replace(/-/g, '+').replace(/_/g, '/'); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) { | ||||
|   var r = str % 4; | ||||
|   if (2 === r) { | ||||
|     str += '=='; | ||||
|   } else if (3 === r) { | ||||
|     str += '='; | ||||
|   } | ||||
|   return str.replace(/-/g, '+').replace(/_/g, '/'); | ||||
| }; | ||||
| 	Enc.base64ToBuf = function(b64) { | ||||
| 		return Enc.binToBuf(atob(b64)); | ||||
| 	}; | ||||
| 	Enc.binToBuf = function(bin) { | ||||
| 		var arr = bin.split('').map(function(ch) { | ||||
| 			return ch.charCodeAt(0); | ||||
| 		}); | ||||
| 		return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| 	}; | ||||
| 	Enc.bufToHex = function(u8) { | ||||
| 		var hex = []; | ||||
| 		var i, h; | ||||
| 		var len = u8.byteLength || u8.length; | ||||
| 
 | ||||
| Enc.base64ToBuf = function (b64) { | ||||
|   return Enc.binToBuf(atob(b64)); | ||||
| }; | ||||
| Enc.binToBuf = function (bin) { | ||||
|   var arr = bin.split('').map(function (ch) { | ||||
|     return ch.charCodeAt(0); | ||||
|   }); | ||||
|   return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| }; | ||||
| Enc.bufToHex = function (u8) { | ||||
|   var hex = []; | ||||
|   var i, h; | ||||
|   var len = (u8.byteLength || u8.length); | ||||
| 		for (i = 0; i < len; i += 1) { | ||||
| 			h = u8[i].toString(16); | ||||
| 			if (h.length % 2) { | ||||
| 				h = '0' + h; | ||||
| 			} | ||||
| 			hex.push(h); | ||||
| 		} | ||||
| 
 | ||||
|   for (i = 0; i < len; i += 1) { | ||||
|     h = u8[i].toString(16); | ||||
|     if (h.length % 2) { h = '0' + h; } | ||||
|     hex.push(h); | ||||
|   } | ||||
| 		return hex.join('').toLowerCase(); | ||||
| 	}; | ||||
| 	Enc.numToHex = function(d) { | ||||
| 		d = d.toString(16); | ||||
| 		if (d.length % 2) { | ||||
| 			return '0' + d; | ||||
| 		} | ||||
| 		return d; | ||||
| 	}; | ||||
| 
 | ||||
|   return hex.join('').toLowerCase(); | ||||
| }; | ||||
| Enc.numToHex = function (d) { | ||||
|   d = d.toString(16); | ||||
|   if (d.length % 2) { | ||||
|     return '0' + d; | ||||
|   } | ||||
|   return d; | ||||
| }; | ||||
| 	Enc.bufToUrlBase64 = function(u8) { | ||||
| 		return Enc.base64ToUrlBase64(Enc.bufToBase64(u8)); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToUrlBase64 = function (u8) { | ||||
|   return Enc.base64ToUrlBase64(Enc.bufToBase64(u8)); | ||||
| }; | ||||
| 	Enc.base64ToUrlBase64 = function(str) { | ||||
| 		return str | ||||
| 			.replace(/\+/g, '-') | ||||
| 			.replace(/\//g, '_') | ||||
| 			.replace(/=/g, ''); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.base64ToUrlBase64 = function (str) { | ||||
|   return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); | ||||
| }; | ||||
| 	Enc.bufToBase64 = function(u8) { | ||||
| 		var bin = ''; | ||||
| 		u8.forEach(function(i) { | ||||
| 			bin += String.fromCharCode(i); | ||||
| 		}); | ||||
| 		return btoa(bin); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToBase64 = function (u8) { | ||||
|   var bin = ''; | ||||
|   u8.forEach(function (i) { | ||||
|     bin += String.fromCharCode(i); | ||||
|   }); | ||||
|   return btoa(bin); | ||||
| }; | ||||
| 	Enc.hexToBuf = function(hex) { | ||||
| 		var arr = []; | ||||
| 		hex.match(/.{2}/g).forEach(function(h) { | ||||
| 			arr.push(parseInt(h, 16)); | ||||
| 		}); | ||||
| 		return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| 	}; | ||||
| 
 | ||||
| Enc.hexToBuf = function (hex) { | ||||
|   var arr = []; | ||||
|   hex.match(/.{2}/g).forEach(function (h) { | ||||
|     arr.push(parseInt(h, 16)); | ||||
|   }); | ||||
|   return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| }; | ||||
| 	Enc.numToHex = function(d) { | ||||
| 		d = d.toString(16); | ||||
| 		if (d.length % 2) { | ||||
| 			return '0' + d; | ||||
| 		} | ||||
| 		return d; | ||||
| 	}; | ||||
| 
 | ||||
| Enc.numToHex = function (d) { | ||||
|   d = d.toString(16); | ||||
|   if (d.length % 2) { | ||||
|     return '0' + d; | ||||
|   } | ||||
|   return d; | ||||
| }; | ||||
| 	//
 | ||||
| 	// JWK to SSH (tested working)
 | ||||
| 	//
 | ||||
| 	Enc.base64ToHex = function(b64) { | ||||
| 		var bin = atob(Enc.urlBase64ToBase64(b64)); | ||||
| 		return Enc.binToHex(bin); | ||||
| 	}; | ||||
| 
 | ||||
| 	Enc.binToHex = function(bin) { | ||||
| 		return bin | ||||
| 			.split('') | ||||
| 			.map(function(ch) { | ||||
| 				var h = ch.charCodeAt(0).toString(16); | ||||
| 				if (h.length % 2) { | ||||
| 					h = '0' + h; | ||||
| 				} | ||||
| 				return h; | ||||
| 			}) | ||||
| 			.join(''); | ||||
| 	}; | ||||
| 	// TODO are there any nuance differences here?
 | ||||
| 	Enc.utf8ToHex = Enc.binToHex; | ||||
| 
 | ||||
| //
 | ||||
| // JWK to SSH (tested working)
 | ||||
| //
 | ||||
| Enc.base64ToHex = function (b64) { | ||||
|   var bin = atob(Enc.urlBase64ToBase64(b64)); | ||||
|   return Enc.binToHex(bin); | ||||
| }; | ||||
| 	Enc.hexToBase64 = function(hex) { | ||||
| 		return btoa(Enc.hexToBin(hex)); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.binToHex = function (bin) { | ||||
|   return bin.split('').map(function (ch) { | ||||
|     var h = ch.charCodeAt(0).toString(16); | ||||
|     if (h.length % 2) { h = '0' + h; } | ||||
|     return h; | ||||
|   }).join(''); | ||||
| }; | ||||
| // TODO are there any nuance differences here?
 | ||||
| Enc.utf8ToHex = Enc.binToHex; | ||||
| 	Enc.hexToBin = function(hex) { | ||||
| 		return hex | ||||
| 			.match(/.{2}/g) | ||||
| 			.map(function(h) { | ||||
| 				return String.fromCharCode(parseInt(h, 16)); | ||||
| 			}) | ||||
| 			.join(''); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.hexToBase64 = function (hex) { | ||||
|   return btoa(Enc.hexToBin(hex)); | ||||
| }; | ||||
| 
 | ||||
| Enc.hexToBin = function (hex) { | ||||
|   return hex.match(/.{2}/g).map(function (h) { | ||||
|     return String.fromCharCode(parseInt(h, 16)); | ||||
|   }).join(''); | ||||
| }; | ||||
| 
 | ||||
| Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) { | ||||
|   var r = str % 4; | ||||
|   if (2 === r) { | ||||
|     str += '=='; | ||||
|   } else if (3 === r) { | ||||
|     str += '='; | ||||
|   } | ||||
|   return str.replace(/-/g, '+').replace(/_/g, '/'); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| }('undefined' !== typeof exports ? module.exports : window )); | ||||
| 	Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) { | ||||
| 		var r = str % 4; | ||||
| 		if (2 === r) { | ||||
| 			str += '=='; | ||||
| 		} else if (3 === r) { | ||||
| 			str += '='; | ||||
| 		} | ||||
| 		return str.replace(/-/g, '+').replace(/_/g, '/'); | ||||
| 	}; | ||||
| })('undefined' !== typeof exports ? module.exports : window); | ||||
|  | ||||
							
								
								
									
										712
									
								
								lib/keypairs.js
									
									
									
									
									
								
							
							
						
						
									
										712
									
								
								lib/keypairs.js
									
									
									
									
									
								
							| @ -1,348 +1,432 @@ | ||||
| /*global Promise*/ | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| (function(exports) { | ||||
| 	'use strict'; | ||||
| 
 | ||||
| var Keypairs = exports.Keypairs = {}; | ||||
| var Rasha = exports.Rasha; | ||||
| var Eckles = exports.Eckles; | ||||
| var Enc = exports.Enc || {}; | ||||
| 	var Keypairs = (exports.Keypairs = {}); | ||||
| 	var Rasha = exports.Rasha; | ||||
| 	var Eckles = exports.Eckles; | ||||
| 	var Enc = exports.Enc || {}; | ||||
| 
 | ||||
| Keypairs._stance = "We take the stance that if you're knowledgeable enough to" | ||||
|   + " properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| Keypairs._universal = "Bluecrypt only supports crypto with standard cross-browser and cross-platform support."; | ||||
| Keypairs.generate = function (opts) { | ||||
|   opts = opts || {}; | ||||
|   var p; | ||||
|   if (!opts.kty) { opts.kty = opts.type; } | ||||
|   if (!opts.kty) { opts.kty = 'EC'; } | ||||
|   if (/^EC/i.test(opts.kty)) { | ||||
|     p = Eckles.generate(opts); | ||||
|   } else if (/^RSA$/i.test(opts.kty)) { | ||||
|     p = Rasha.generate(opts); | ||||
|   } else { | ||||
|     return Promise.Reject(new Error("'" + opts.kty + "' is not a well-supported key type." | ||||
|       + Keypairs._universal | ||||
|       + " Please choose 'EC', or 'RSA' if you have good reason to.")); | ||||
|   } | ||||
|   return p.then(function (pair) { | ||||
|     return Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) { | ||||
|       pair.private.kid = thumb; // maybe not the same id on the private key?
 | ||||
|       pair.public.kid = thumb; | ||||
|       return pair; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 	Keypairs._stance = | ||||
| 		"We take the stance that if you're knowledgeable enough to" + | ||||
| 		" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| 	Keypairs._universal = | ||||
| 		'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; | ||||
| 	Keypairs.generate = function(opts) { | ||||
| 		opts = opts || {}; | ||||
| 		var p; | ||||
| 		if (!opts.kty) { | ||||
| 			opts.kty = opts.type; | ||||
| 		} | ||||
| 		if (!opts.kty) { | ||||
| 			opts.kty = 'EC'; | ||||
| 		} | ||||
| 		if (/^EC/i.test(opts.kty)) { | ||||
| 			p = Eckles.generate(opts); | ||||
| 		} else if (/^RSA$/i.test(opts.kty)) { | ||||
| 			p = Rasha.generate(opts); | ||||
| 		} else { | ||||
| 			return Promise.Reject( | ||||
| 				new Error( | ||||
| 					"'" + | ||||
| 						opts.kty + | ||||
| 						"' is not a well-supported key type." + | ||||
| 						Keypairs._universal + | ||||
| 						" Please choose 'EC', or 'RSA' if you have good reason to." | ||||
| 				) | ||||
| 			); | ||||
| 		} | ||||
| 		return p.then(function(pair) { | ||||
| 			return Keypairs.thumbprint({ jwk: pair.public }).then(function( | ||||
| 				thumb | ||||
| 			) { | ||||
| 				pair.private.kid = thumb; // maybe not the same id on the private key?
 | ||||
| 				pair.public.kid = thumb; | ||||
| 				return pair; | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| Keypairs.export = function (opts) { | ||||
|   return Eckles.export(opts).catch(function (err) { | ||||
|     return Rasha.export(opts).catch(function () { | ||||
|       return Promise.reject(err); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 	Keypairs.export = function(opts) { | ||||
| 		return Eckles.export(opts).catch(function(err) { | ||||
| 			return Rasha.export(opts).catch(function() { | ||||
| 				return Promise.reject(err); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Chopping off the private parts is now part of the public API. | ||||
| 	 * I thought it sounded a little too crude at first, but it really is the best name in every possible way. | ||||
| 	 */ | ||||
| 	Keypairs.neuter = function(opts) { | ||||
| 		/** trying to find the best balance of an immutable copy with custom attributes */ | ||||
| 		var jwk = {}; | ||||
| 		Object.keys(opts.jwk).forEach(function(k) { | ||||
| 			if ('undefined' === typeof opts.jwk[k]) { | ||||
| 				return; | ||||
| 			} | ||||
| 			// ignore RSA and EC private parts
 | ||||
| 			if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { | ||||
| 				return; | ||||
| 			} | ||||
| 			jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
| 		}); | ||||
| 		return jwk; | ||||
| 	}; | ||||
| 
 | ||||
| /** | ||||
|  * Chopping off the private parts is now part of the public API. | ||||
|  * I thought it sounded a little too crude at first, but it really is the best name in every possible way. | ||||
|  */ | ||||
| Keypairs.neuter = function (opts) { | ||||
|   /** trying to find the best balance of an immutable copy with custom attributes */ | ||||
|   var jwk = {}; | ||||
|   Object.keys(opts.jwk).forEach(function (k) { | ||||
|     if ('undefined' === typeof opts.jwk[k]) { return; } | ||||
|     // ignore RSA and EC private parts
 | ||||
|     if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { return; } | ||||
|     jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
|   }); | ||||
|   return jwk; | ||||
| }; | ||||
| 	Keypairs.thumbprint = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			if (/EC/i.test(opts.jwk.kty)) { | ||||
| 				return Eckles.thumbprint(opts); | ||||
| 			} else { | ||||
| 				return Rasha.thumbprint(opts); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| Keypairs.thumbprint = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     if (/EC/i.test(opts.jwk.kty)) { | ||||
|       return Eckles.thumbprint(opts); | ||||
|     } else { | ||||
|       return Rasha.thumbprint(opts); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| 	Keypairs.publish = function(opts) { | ||||
| 		if ('object' !== typeof opts.jwk || !opts.jwk.kty) { | ||||
| 			throw new Error('invalid jwk: ' + JSON.stringify(opts.jwk)); | ||||
| 		} | ||||
| 
 | ||||
| Keypairs.publish = function (opts) { | ||||
|   if ('object' !== typeof opts.jwk || !opts.jwk.kty) { throw new Error("invalid jwk: " + JSON.stringify(opts.jwk)); } | ||||
| 		/** returns a copy */ | ||||
| 		var jwk = Keypairs.neuter(opts); | ||||
| 
 | ||||
|   /** returns a copy */ | ||||
|   var jwk = Keypairs.neuter(opts); | ||||
| 		if (jwk.exp) { | ||||
| 			jwk.exp = setTime(jwk.exp); | ||||
| 		} else { | ||||
| 			if (opts.exp) { | ||||
| 				jwk.exp = setTime(opts.exp); | ||||
| 			} else if (opts.expiresIn) { | ||||
| 				jwk.exp = Math.round(Date.now() / 1000) + opts.expiresIn; | ||||
| 			} else if (opts.expiresAt) { | ||||
| 				jwk.exp = opts.expiresAt; | ||||
| 			} | ||||
| 		} | ||||
| 		if (!jwk.use && false !== jwk.use) { | ||||
| 			jwk.use = 'sig'; | ||||
| 		} | ||||
| 
 | ||||
|   if (jwk.exp) { | ||||
|     jwk.exp = setTime(jwk.exp); | ||||
|   } else { | ||||
|     if (opts.exp) { jwk.exp = setTime(opts.exp); } | ||||
|     else if (opts.expiresIn) { jwk.exp = Math.round(Date.now()/1000) + opts.expiresIn; } | ||||
|     else if (opts.expiresAt) { jwk.exp = opts.expiresAt; } | ||||
|   } | ||||
|   if (!jwk.use && false !== jwk.use) { jwk.use = "sig"; } | ||||
| 		if (jwk.kid) { | ||||
| 			return Promise.resolve(jwk); | ||||
| 		} | ||||
| 		return Keypairs.thumbprint({ jwk: jwk }).then(function(thumb) { | ||||
| 			jwk.kid = thumb; | ||||
| 			return jwk; | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
|   if (jwk.kid) { return Promise.resolve(jwk); } | ||||
|   return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) { jwk.kid = thumb; return jwk; }); | ||||
| }; | ||||
| 	// JWT a.k.a. JWS with Claims using Compact Serialization
 | ||||
| 	Keypairs.signJwt = function(opts) { | ||||
| 		return Keypairs.thumbprint({ jwk: opts.jwk }).then(function(thumb) { | ||||
| 			var header = opts.header || {}; | ||||
| 			var claims = JSON.parse(JSON.stringify(opts.claims || {})); | ||||
| 			header.typ = 'JWT'; | ||||
| 
 | ||||
| // JWT a.k.a. JWS with Claims using Compact Serialization
 | ||||
| Keypairs.signJwt = function (opts) { | ||||
|   return Keypairs.thumbprint({ jwk: opts.jwk }).then(function (thumb) { | ||||
|     var header = opts.header || {}; | ||||
|     var claims = JSON.parse(JSON.stringify(opts.claims || {})); | ||||
|     header.typ = 'JWT'; | ||||
| 			if (!header.kid) { | ||||
| 				header.kid = thumb; | ||||
| 			} | ||||
| 			if (!header.alg && opts.alg) { | ||||
| 				header.alg = opts.alg; | ||||
| 			} | ||||
| 			if (!claims.iat && (false === claims.iat || false === opts.iat)) { | ||||
| 				claims.iat = undefined; | ||||
| 			} else if (!claims.iat) { | ||||
| 				claims.iat = Math.round(Date.now() / 1000); | ||||
| 			} | ||||
| 
 | ||||
|     if (!header.kid) { header.kid = thumb; } | ||||
|     if (!header.alg && opts.alg) { header.alg = opts.alg; } | ||||
|     if (!claims.iat && (false === claims.iat || false === opts.iat)) { | ||||
|       claims.iat = undefined; | ||||
|     } else if (!claims.iat) { | ||||
|       claims.iat = Math.round(Date.now()/1000); | ||||
|     } | ||||
| 			if (opts.exp) { | ||||
| 				claims.exp = setTime(opts.exp); | ||||
| 			} else if ( | ||||
| 				!claims.exp && | ||||
| 				(false === claims.exp || false === opts.exp) | ||||
| 			) { | ||||
| 				claims.exp = undefined; | ||||
| 			} else if (!claims.exp) { | ||||
| 				throw new Error( | ||||
| 					"opts.claims.exp should be the expiration date as seconds, human form (i.e. '1h' or '15m') or false" | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
|     if (opts.exp) { | ||||
|       claims.exp = setTime(opts.exp); | ||||
|     } else if (!claims.exp && (false === claims.exp || false === opts.exp)) { | ||||
|       claims.exp = undefined; | ||||
|     } else if (!claims.exp) { | ||||
|       throw new Error("opts.claims.exp should be the expiration date as seconds, human form (i.e. '1h' or '15m') or false"); | ||||
|     } | ||||
| 			if (opts.iss) { | ||||
| 				claims.iss = opts.iss; | ||||
| 			} | ||||
| 			if (!claims.iss && (false === claims.iss || false === opts.iss)) { | ||||
| 				claims.iss = undefined; | ||||
| 			} else if (!claims.iss) { | ||||
| 				throw new Error( | ||||
| 					'opts.claims.iss should be in the form of https://example.com/, a secure OIDC base url' | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
|     if (opts.iss) { claims.iss = opts.iss; } | ||||
|     if (!claims.iss && (false === claims.iss || false === opts.iss)) { | ||||
|       claims.iss = undefined; | ||||
|     } else if (!claims.iss) { | ||||
|       throw new Error("opts.claims.iss should be in the form of https://example.com/, a secure OIDC base url"); | ||||
|     } | ||||
| 			return Keypairs.signJws({ | ||||
| 				jwk: opts.jwk, | ||||
| 				pem: opts.pem, | ||||
| 				protected: header, | ||||
| 				header: undefined, | ||||
| 				payload: claims | ||||
| 			}).then(function(jws) { | ||||
| 				return [jws.protected, jws.payload, jws.signature].join('.'); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
|     return Keypairs.signJws({ | ||||
|       jwk: opts.jwk | ||||
|     , pem: opts.pem | ||||
|     , protected: header | ||||
|     , header: undefined | ||||
|     , payload: claims | ||||
|     }).then(function (jws) { | ||||
|       return [ jws.protected, jws.payload, jws.signature ].join('.'); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 	Keypairs.signJws = function(opts) { | ||||
| 		return Keypairs.thumbprint(opts).then(function(thumb) { | ||||
| 			function alg() { | ||||
| 				if (!opts.jwk) { | ||||
| 					throw new Error( | ||||
| 						"opts.jwk must exist and must declare 'typ'" | ||||
| 					); | ||||
| 				} | ||||
| 				if (opts.jwk.alg) { | ||||
| 					return opts.jwk.alg; | ||||
| 				} | ||||
| 				var typ = 'RSA' === opts.jwk.kty ? 'RS' : 'ES'; | ||||
| 				return typ + Keypairs._getBits(opts); | ||||
| 			} | ||||
| 
 | ||||
| Keypairs.signJws = function (opts) { | ||||
|   return Keypairs.thumbprint(opts).then(function (thumb) { | ||||
| 			function sign() { | ||||
| 				var protect = opts.protected; | ||||
| 				var payload = opts.payload; | ||||
| 
 | ||||
|     function alg() { | ||||
|       if (!opts.jwk) { | ||||
|         throw new Error("opts.jwk must exist and must declare 'typ'"); | ||||
|       } | ||||
|       if (opts.jwk.alg) { return opts.jwk.alg; } | ||||
|       var typ = ('RSA' === opts.jwk.kty) ? "RS" : "ES"; | ||||
|       return typ + Keypairs._getBits(opts); | ||||
|     } | ||||
| 				// Compute JWS signature
 | ||||
| 				var protectedHeader = ''; | ||||
| 				// Because unprotected headers are allowed, regrettably...
 | ||||
| 				// https://stackoverflow.com/a/46288694
 | ||||
| 				if (false !== protect) { | ||||
| 					if (!protect) { | ||||
| 						protect = {}; | ||||
| 					} | ||||
| 					if (!protect.alg) { | ||||
| 						protect.alg = alg(); | ||||
| 					} | ||||
| 					// There's a particular request where ACME / Let's Encrypt explicitly doesn't use a kid
 | ||||
| 					if (false === protect.kid) { | ||||
| 						protect.kid = undefined; | ||||
| 					} else if (!protect.kid) { | ||||
| 						protect.kid = thumb; | ||||
| 					} | ||||
| 					protectedHeader = JSON.stringify(protect); | ||||
| 				} | ||||
| 
 | ||||
|     function sign() { | ||||
|       var protect = opts.protected; | ||||
|       var payload = opts.payload; | ||||
| 				// Not sure how to handle the empty case since ACME POST-as-GET must be empty
 | ||||
| 				//if (!payload) {
 | ||||
| 				//  throw new Error("opts.payload should be JSON, string, or ArrayBuffer (it may be empty, but that must be explicit)");
 | ||||
| 				//}
 | ||||
| 				// Trying to detect if it's a plain object (not Buffer, ArrayBuffer, Array, Uint8Array, etc)
 | ||||
| 				if ( | ||||
| 					payload && | ||||
| 					'string' !== typeof payload && | ||||
| 					'undefined' === typeof payload.byteLength && | ||||
| 					'undefined' === typeof payload.buffer | ||||
| 				) { | ||||
| 					payload = JSON.stringify(payload); | ||||
| 				} | ||||
| 				// Converting to a buffer, even if it was just converted to a string
 | ||||
| 				if ('string' === typeof payload) { | ||||
| 					payload = Enc.binToBuf(payload); | ||||
| 				} | ||||
| 
 | ||||
|       // Compute JWS signature
 | ||||
|       var protectedHeader = ""; | ||||
|       // Because unprotected headers are allowed, regrettably...
 | ||||
|       // https://stackoverflow.com/a/46288694
 | ||||
|       if (false !== protect) { | ||||
|         if (!protect) { protect = {}; } | ||||
|         if (!protect.alg) { protect.alg = alg(); } | ||||
|         // There's a particular request where ACME / Let's Encrypt explicitly doesn't use a kid
 | ||||
|         if (false === protect.kid) { protect.kid = undefined; } | ||||
|         else if (!protect.kid) { protect.kid = thumb; } | ||||
|         protectedHeader = JSON.stringify(protect); | ||||
|       } | ||||
| 				// node specifies RSA-SHAxxx even when it's actually ecdsa (it's all encoded x509 shasums anyway)
 | ||||
| 				var protected64 = Enc.strToUrlBase64(protectedHeader); | ||||
| 				var payload64 = Enc.bufToUrlBase64(payload); | ||||
| 				var msg = protected64 + '.' + payload64; | ||||
| 
 | ||||
|       // Not sure how to handle the empty case since ACME POST-as-GET must be empty
 | ||||
|       //if (!payload) {
 | ||||
|       //  throw new Error("opts.payload should be JSON, string, or ArrayBuffer (it may be empty, but that must be explicit)");
 | ||||
|       //}
 | ||||
|       // Trying to detect if it's a plain object (not Buffer, ArrayBuffer, Array, Uint8Array, etc)
 | ||||
|       if (payload && ('string' !== typeof payload) | ||||
|         && ('undefined' === typeof payload.byteLength) | ||||
|         && ('undefined' === typeof payload.buffer) | ||||
|       ) { | ||||
|         payload = JSON.stringify(payload); | ||||
|       } | ||||
|       // Converting to a buffer, even if it was just converted to a string
 | ||||
|       if ('string' === typeof payload) { | ||||
|         payload = Enc.binToBuf(payload); | ||||
|       } | ||||
| 				return Keypairs._sign(opts, msg).then(function(buf) { | ||||
| 					var signedMsg = { | ||||
| 						protected: protected64, | ||||
| 						payload: payload64, | ||||
| 						signature: Enc.bufToUrlBase64(buf) | ||||
| 					}; | ||||
| 
 | ||||
|       // node specifies RSA-SHAxxx even when it's actually ecdsa (it's all encoded x509 shasums anyway)
 | ||||
|       var protected64 = Enc.strToUrlBase64(protectedHeader); | ||||
|       var payload64 = Enc.bufToUrlBase64(payload); | ||||
|       var msg = protected64 + '.' + payload64; | ||||
| 					return signedMsg; | ||||
| 				}); | ||||
| 			} | ||||
| 
 | ||||
|       return Keypairs._sign(opts, msg).then(function (buf) { | ||||
|         var signedMsg = { | ||||
|           protected: protected64 | ||||
|         , payload: payload64 | ||||
|         , signature: Enc.bufToUrlBase64(buf) | ||||
|         }; | ||||
| 			if (opts.jwk) { | ||||
| 				return sign(); | ||||
| 			} else { | ||||
| 				return Keypairs.import({ pem: opts.pem }).then(function(pair) { | ||||
| 					opts.jwk = pair.private; | ||||
| 					return sign(); | ||||
| 				}); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
|         return signedMsg; | ||||
|       }); | ||||
|     } | ||||
| 	Keypairs._sign = function(opts, payload) { | ||||
| 		return Keypairs._import(opts).then(function(privkey) { | ||||
| 			if ('string' === typeof payload) { | ||||
| 				payload = new TextEncoder().encode(payload); | ||||
| 			} | ||||
| 			return window.crypto.subtle | ||||
| 				.sign( | ||||
| 					{ | ||||
| 						name: Keypairs._getName(opts), | ||||
| 						hash: { name: 'SHA-' + Keypairs._getBits(opts) } | ||||
| 					}, | ||||
| 					privkey, | ||||
| 					payload | ||||
| 				) | ||||
| 				.then(function(signature) { | ||||
| 					signature = new Uint8Array(signature); // ArrayBuffer -> u8
 | ||||
| 					// This will come back into play for CSRs, but not for JOSE
 | ||||
| 					if ( | ||||
| 						'EC' === opts.jwk.kty && | ||||
| 						/x509|asn1/i.test(opts.format) | ||||
| 					) { | ||||
| 						return Keypairs._ecdsaJoseSigToAsn1Sig(signature); | ||||
| 					} else { | ||||
| 						// jose/jws/jwt
 | ||||
| 						return signature; | ||||
| 					} | ||||
| 				}); | ||||
| 		}); | ||||
| 	}; | ||||
| 	Keypairs._getBits = function(opts) { | ||||
| 		if (opts.alg) { | ||||
| 			return opts.alg.replace(/[a-z\-]/gi, ''); | ||||
| 		} | ||||
| 		// base64 len to byte len
 | ||||
| 		var len = Math.floor((opts.jwk.n || '').length * 0.75); | ||||
| 
 | ||||
|     if (opts.jwk) { | ||||
|       return sign(); | ||||
|     } else { | ||||
|       return Keypairs.import({ pem: opts.pem }).then(function (pair) { | ||||
|         opts.jwk = pair.private; | ||||
|         return sign(); | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| 		// TODO this may be a bug
 | ||||
| 		// need to confirm that the padding is no more or less than 1 byte
 | ||||
| 		if (/521/.test(opts.jwk.crv) || len >= 511) { | ||||
| 			return '512'; | ||||
| 		} else if (/384/.test(opts.jwk.crv) || len >= 383) { | ||||
| 			return '384'; | ||||
| 		} | ||||
| 
 | ||||
| Keypairs._sign = function (opts, payload) { | ||||
|   return Keypairs._import(opts).then(function (privkey) { | ||||
|     if ('string' === typeof payload) { | ||||
|       payload = (new TextEncoder()).encode(payload); | ||||
|     } | ||||
|     return window.crypto.subtle.sign( | ||||
|       { name: Keypairs._getName(opts) | ||||
|       , hash: { name: 'SHA-' + Keypairs._getBits(opts) } | ||||
|       } | ||||
|     , privkey | ||||
|     , payload | ||||
|     ).then(function (signature) { | ||||
|       signature = new Uint8Array(signature); // ArrayBuffer -> u8
 | ||||
|       // This will come back into play for CSRs, but not for JOSE
 | ||||
|       if ('EC' === opts.jwk.kty && /x509|asn1/i.test(opts.format)) { | ||||
|         return Keypairs._ecdsaJoseSigToAsn1Sig(signature); | ||||
|       } else { | ||||
|         // jose/jws/jwt
 | ||||
|         return signature; | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| Keypairs._getBits = function (opts) { | ||||
|   if (opts.alg) { return opts.alg.replace(/[a-z\-]/ig, ''); } | ||||
|   // base64 len to byte len
 | ||||
|   var len = Math.floor((opts.jwk.n||'').length * 0.75); | ||||
| 		return '256'; | ||||
| 	}; | ||||
| 	Keypairs._getName = function(opts) { | ||||
| 		if (/EC/i.test(opts.jwk.kty)) { | ||||
| 			return 'ECDSA'; | ||||
| 		} else { | ||||
| 			return 'RSASSA-PKCS1-v1_5'; | ||||
| 		} | ||||
| 	}; | ||||
| 	Keypairs._import = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			var ops; | ||||
| 			// all private keys just happen to have a 'd'
 | ||||
| 			if (opts.jwk.d) { | ||||
| 				ops = ['sign']; | ||||
| 			} else { | ||||
| 				ops = ['verify']; | ||||
| 			} | ||||
| 			// gotta mark it as extractable, as if it matters
 | ||||
| 			opts.jwk.ext = true; | ||||
| 			opts.jwk.key_ops = ops; | ||||
| 
 | ||||
|   // TODO this may be a bug
 | ||||
|   // need to confirm that the padding is no more or less than 1 byte
 | ||||
|   if (/521/.test(opts.jwk.crv) || len >= 511) { | ||||
|     return '512'; | ||||
|   } else if (/384/.test(opts.jwk.crv) || len >= 383) { | ||||
|     return '384'; | ||||
|   } | ||||
| 			return window.crypto.subtle | ||||
| 				.importKey( | ||||
| 					'jwk', | ||||
| 					opts.jwk, | ||||
| 					{ | ||||
| 						name: Keypairs._getName(opts), | ||||
| 						namedCurve: opts.jwk.crv, | ||||
| 						hash: { name: 'SHA-' + Keypairs._getBits(opts) } | ||||
| 					}, | ||||
| 					true, | ||||
| 					ops | ||||
| 				) | ||||
| 				.then(function(privkey) { | ||||
| 					delete opts.jwk.ext; | ||||
| 					return privkey; | ||||
| 				}); | ||||
| 		}); | ||||
| 	}; | ||||
| 	// ECDSA JOSE / JWS / JWT signatures differ from "normal" ASN1/X509 ECDSA signatures
 | ||||
| 	// https://tools.ietf.org/html/rfc7518#section-3.4
 | ||||
| 	Keypairs._ecdsaJoseSigToAsn1Sig = function(bufsig) { | ||||
| 		// it's easier to do the manipulation in the browser with an array
 | ||||
| 		bufsig = Array.from(bufsig); | ||||
| 		var hlen = bufsig.length / 2; // should be even
 | ||||
| 		var r = bufsig.slice(0, hlen); | ||||
| 		var s = bufsig.slice(hlen); | ||||
| 		// unpad positive ints less than 32 bytes wide
 | ||||
| 		while (!r[0]) { | ||||
| 			r = r.slice(1); | ||||
| 		} | ||||
| 		while (!s[0]) { | ||||
| 			s = s.slice(1); | ||||
| 		} | ||||
| 		// pad (or re-pad) ambiguously non-negative BigInts, up to 33 bytes wide
 | ||||
| 		if (0x80 & r[0]) { | ||||
| 			r.unshift(0); | ||||
| 		} | ||||
| 		if (0x80 & s[0]) { | ||||
| 			s.unshift(0); | ||||
| 		} | ||||
| 
 | ||||
|   return '256'; | ||||
| }; | ||||
| Keypairs._getName = function (opts) { | ||||
|   if (/EC/i.test(opts.jwk.kty)) { | ||||
|     return 'ECDSA'; | ||||
|   } else { | ||||
|     return 'RSASSA-PKCS1-v1_5'; | ||||
|   } | ||||
| }; | ||||
| Keypairs._import = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     var ops; | ||||
|     // all private keys just happen to have a 'd'
 | ||||
|     if (opts.jwk.d) { | ||||
|       ops = [ 'sign' ]; | ||||
|     } else { | ||||
|       ops = [ 'verify' ]; | ||||
|     } | ||||
|     // gotta mark it as extractable, as if it matters
 | ||||
|     opts.jwk.ext = true; | ||||
|     opts.jwk.key_ops = ops; | ||||
| 		var len = 2 + r.length + 2 + s.length; | ||||
| 		var head = [0x30]; | ||||
| 		// hard code 0x80 + 1 because it won't be longer than
 | ||||
| 		// two SHA512 plus two pad bytes (130 bytes <= 256)
 | ||||
| 		if (len >= 0x80) { | ||||
| 			head.push(0x81); | ||||
| 		} | ||||
| 		head.push(len); | ||||
| 
 | ||||
|     return window.crypto.subtle.importKey( | ||||
|       "jwk" | ||||
|     , opts.jwk | ||||
|     , { name: Keypairs._getName(opts) | ||||
|       , namedCurve: opts.jwk.crv | ||||
|       , hash: { name: 'SHA-' + Keypairs._getBits(opts) } } | ||||
|     , true | ||||
|     , ops | ||||
|     ).then(function (privkey) { | ||||
|       delete opts.jwk.ext; | ||||
|       return privkey; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| // ECDSA JOSE / JWS / JWT signatures differ from "normal" ASN1/X509 ECDSA signatures
 | ||||
| // https://tools.ietf.org/html/rfc7518#section-3.4
 | ||||
| Keypairs._ecdsaJoseSigToAsn1Sig = function (bufsig) { | ||||
|   // it's easier to do the manipulation in the browser with an array
 | ||||
|   bufsig = Array.from(bufsig); | ||||
|   var hlen = bufsig.length / 2; // should be even
 | ||||
|   var r = bufsig.slice(0, hlen); | ||||
|   var s = bufsig.slice(hlen); | ||||
|   // unpad positive ints less than 32 bytes wide
 | ||||
|   while (!r[0]) { r = r.slice(1); } | ||||
|   while (!s[0]) { s = s.slice(1); } | ||||
|   // pad (or re-pad) ambiguously non-negative BigInts, up to 33 bytes wide
 | ||||
|   if (0x80 & r[0]) { r.unshift(0); } | ||||
|   if (0x80 & s[0]) { s.unshift(0); } | ||||
| 		return Uint8Array.from( | ||||
| 			head.concat([0x02, r.length], r, [0x02, s.length], s) | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
|   var len = 2 + r.length + 2 + s.length; | ||||
|   var head = [0x30]; | ||||
|   // hard code 0x80 + 1 because it won't be longer than
 | ||||
|   // two SHA512 plus two pad bytes (130 bytes <= 256)
 | ||||
|   if (len >= 0x80) { head.push(0x81); } | ||||
|   head.push(len); | ||||
| 	function setTime(time) { | ||||
| 		if ('number' === typeof time) { | ||||
| 			return time; | ||||
| 		} | ||||
| 
 | ||||
|   return Uint8Array.from(head.concat([0x02, r.length], r, [0x02, s.length], s)); | ||||
| }; | ||||
| 		var t = time.match(/^(\-?\d+)([dhms])$/i); | ||||
| 		if (!t || !t[0]) { | ||||
| 			throw new Error( | ||||
| 				"'" + | ||||
| 					time + | ||||
| 					"' should be datetime in seconds or human-readable format (i.e. 3d, 1h, 15m, 30s" | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| function setTime(time) { | ||||
|   if ('number' === typeof time) { return time; } | ||||
| 		var now = Math.round(Date.now() / 1000); | ||||
| 		var num = parseInt(t[1], 10); | ||||
| 		var unit = t[2]; | ||||
| 		var mult = 1; | ||||
| 		switch (unit) { | ||||
| 			// fancy fallthrough, what fun!
 | ||||
| 			case 'd': | ||||
| 				mult *= 24; | ||||
| 			/*falls through*/ | ||||
| 			case 'h': | ||||
| 				mult *= 60; | ||||
| 			/*falls through*/ | ||||
| 			case 'm': | ||||
| 				mult *= 60; | ||||
| 			/*falls through*/ | ||||
| 			case 's': | ||||
| 				mult *= 1; | ||||
| 		} | ||||
| 
 | ||||
|   var t = time.match(/^(\-?\d+)([dhms])$/i); | ||||
|   if (!t || !t[0]) { | ||||
|     throw new Error("'" + time + "' should be datetime in seconds or human-readable format (i.e. 3d, 1h, 15m, 30s"); | ||||
|   } | ||||
| 		return now + mult * num; | ||||
| 	} | ||||
| 
 | ||||
|   var now = Math.round(Date.now()/1000); | ||||
|   var num = parseInt(t[1], 10); | ||||
|   var unit = t[2]; | ||||
|   var mult = 1; | ||||
|   switch(unit) { | ||||
|     // fancy fallthrough, what fun!
 | ||||
|     case 'd': | ||||
|       mult *= 24; | ||||
|       /*falls through*/ | ||||
|     case 'h': | ||||
|       mult *= 60; | ||||
|       /*falls through*/ | ||||
|     case 'm': | ||||
|       mult *= 60; | ||||
|       /*falls through*/ | ||||
|     case 's': | ||||
|       mult *= 1; | ||||
|   } | ||||
| 
 | ||||
|   return now + (mult * num); | ||||
| } | ||||
| 
 | ||||
| Enc.hexToBuf = function (hex) { | ||||
|   var arr = []; | ||||
|   hex.match(/.{2}/g).forEach(function (h) { | ||||
|     arr.push(parseInt(h, 16)); | ||||
|   }); | ||||
|   return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| }; | ||||
| Enc.strToUrlBase64 = function (str) { | ||||
|   return Enc.bufToUrlBase64(Enc.binToBuf(str)); | ||||
| }; | ||||
| Enc.binToBuf = function (bin) { | ||||
|   var arr = bin.split('').map(function (ch) { | ||||
|     return ch.charCodeAt(0); | ||||
|   }); | ||||
|   return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof module ? module.exports : window)); | ||||
| 	Enc.hexToBuf = function(hex) { | ||||
| 		var arr = []; | ||||
| 		hex.match(/.{2}/g).forEach(function(h) { | ||||
| 			arr.push(parseInt(h, 16)); | ||||
| 		}); | ||||
| 		return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| 	}; | ||||
| 	Enc.strToUrlBase64 = function(str) { | ||||
| 		return Enc.bufToUrlBase64(Enc.binToBuf(str)); | ||||
| 	}; | ||||
| 	Enc.binToBuf = function(bin) { | ||||
| 		var arr = bin.split('').map(function(ch) { | ||||
| 			return ch.charCodeAt(0); | ||||
| 		}); | ||||
| 		return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; | ||||
| 	}; | ||||
| })('undefined' !== typeof module ? module.exports : window); | ||||
|  | ||||
							
								
								
									
										398
									
								
								lib/rsa.js
									
									
									
									
									
								
							
							
						
						
									
										398
									
								
								lib/rsa.js
									
									
									
									
									
								
							| @ -1,187 +1,235 @@ | ||||
| /*global Promise*/ | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| (function(exports) { | ||||
| 	'use strict'; | ||||
| 
 | ||||
| var RSA = exports.Rasha = {}; | ||||
| var x509 = exports.x509; | ||||
| if ('undefined' !== typeof module) { module.exports = RSA; } | ||||
| var PEM = exports.PEM; | ||||
| var SSH = exports.SSH; | ||||
| var Enc = {}; | ||||
| var textEncoder = new TextEncoder(); | ||||
| 	var RSA = (exports.Rasha = {}); | ||||
| 	var x509 = exports.x509; | ||||
| 	if ('undefined' !== typeof module) { | ||||
| 		module.exports = RSA; | ||||
| 	} | ||||
| 	var PEM = exports.PEM; | ||||
| 	var SSH = exports.SSH; | ||||
| 	var Enc = {}; | ||||
| 	var textEncoder = new TextEncoder(); | ||||
| 
 | ||||
| RSA._stance = "We take the stance that if you're knowledgeable enough to" | ||||
|   + " properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| RSA._universal = "Bluecrypt only supports crypto with standard cross-browser and cross-platform support."; | ||||
| RSA.generate = function (opts) { | ||||
|   var wcOpts = {}; | ||||
|   if (!opts) { opts = {}; } | ||||
|   if (!opts.kty) { opts.kty = 'RSA'; } | ||||
| 	RSA._stance = | ||||
| 		"We take the stance that if you're knowledgeable enough to" + | ||||
| 		" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| 	RSA._universal = | ||||
| 		'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; | ||||
| 	RSA.generate = function(opts) { | ||||
| 		var wcOpts = {}; | ||||
| 		if (!opts) { | ||||
| 			opts = {}; | ||||
| 		} | ||||
| 		if (!opts.kty) { | ||||
| 			opts.kty = 'RSA'; | ||||
| 		} | ||||
| 
 | ||||
|   // Support PSS? I don't think it's used for Let's Encrypt
 | ||||
|   wcOpts.name = 'RSASSA-PKCS1-v1_5'; | ||||
|   if (!opts.modulusLength) { | ||||
|     opts.modulusLength = 2048; | ||||
|   } | ||||
|   wcOpts.modulusLength = opts.modulusLength; | ||||
|   if (wcOpts.modulusLength >= 2048 && wcOpts.modulusLength < 3072) { | ||||
|     // erring on the small side... for no good reason
 | ||||
|     wcOpts.hash = { name: "SHA-256" }; | ||||
|   } else if (wcOpts.modulusLength >= 3072 && wcOpts.modulusLength < 4096) { | ||||
|     wcOpts.hash = { name: "SHA-384" }; | ||||
|   } else if (wcOpts.modulusLength < 4097) { | ||||
|     wcOpts.hash = { name: "SHA-512" }; | ||||
|   } else { | ||||
|     // Public key thumbprints should be paired with a hash of similar length,
 | ||||
|     // so anything above SHA-512's keyspace would be left under-represented anyway.
 | ||||
|     return Promise.Reject(new Error("'" + wcOpts.modulusLength + "' is not within the safe and universally" | ||||
|       + " acceptable range of 2048-4096. Typically you should pick 2048, 3072, or 4096, though other values" | ||||
|       + " divisible by 8 are allowed. " + RSA._stance)); | ||||
|   } | ||||
|   // TODO maybe allow this to be set to any of the standard values?
 | ||||
|   wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); | ||||
| 		// Support PSS? I don't think it's used for Let's Encrypt
 | ||||
| 		wcOpts.name = 'RSASSA-PKCS1-v1_5'; | ||||
| 		if (!opts.modulusLength) { | ||||
| 			opts.modulusLength = 2048; | ||||
| 		} | ||||
| 		wcOpts.modulusLength = opts.modulusLength; | ||||
| 		if (wcOpts.modulusLength >= 2048 && wcOpts.modulusLength < 3072) { | ||||
| 			// erring on the small side... for no good reason
 | ||||
| 			wcOpts.hash = { name: 'SHA-256' }; | ||||
| 		} else if ( | ||||
| 			wcOpts.modulusLength >= 3072 && | ||||
| 			wcOpts.modulusLength < 4096 | ||||
| 		) { | ||||
| 			wcOpts.hash = { name: 'SHA-384' }; | ||||
| 		} else if (wcOpts.modulusLength < 4097) { | ||||
| 			wcOpts.hash = { name: 'SHA-512' }; | ||||
| 		} else { | ||||
| 			// Public key thumbprints should be paired with a hash of similar length,
 | ||||
| 			// so anything above SHA-512's keyspace would be left under-represented anyway.
 | ||||
| 			return Promise.Reject( | ||||
| 				new Error( | ||||
| 					"'" + | ||||
| 						wcOpts.modulusLength + | ||||
| 						"' is not within the safe and universally" + | ||||
| 						' acceptable range of 2048-4096. Typically you should pick 2048, 3072, or 4096, though other values' + | ||||
| 						' divisible by 8 are allowed. ' + | ||||
| 						RSA._stance | ||||
| 				) | ||||
| 			); | ||||
| 		} | ||||
| 		// TODO maybe allow this to be set to any of the standard values?
 | ||||
| 		wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); | ||||
| 
 | ||||
|   var extractable = true; | ||||
|   return window.crypto.subtle.generateKey( | ||||
|     wcOpts | ||||
|   , extractable | ||||
|   , [ 'sign', 'verify' ] | ||||
|   ).then(function (result) { | ||||
|     return window.crypto.subtle.exportKey( | ||||
|       "jwk" | ||||
|     , result.privateKey | ||||
|     ).then(function (privJwk) { | ||||
|       return { | ||||
|         private: privJwk | ||||
|       , public: RSA.neuter({ jwk: privJwk }) | ||||
|       }; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 		var extractable = true; | ||||
| 		return window.crypto.subtle | ||||
| 			.generateKey(wcOpts, extractable, ['sign', 'verify']) | ||||
| 			.then(function(result) { | ||||
| 				return window.crypto.subtle | ||||
| 					.exportKey('jwk', result.privateKey) | ||||
| 					.then(function(privJwk) { | ||||
| 						return { | ||||
| 							private: privJwk, | ||||
| 							public: RSA.neuter({ jwk: privJwk }) | ||||
| 						}; | ||||
| 					}); | ||||
| 			}); | ||||
| 	}; | ||||
| 
 | ||||
| // Chopping off the private parts is now part of the public API.
 | ||||
| // I thought it sounded a little too crude at first, but it really is the best name in every possible way.
 | ||||
| RSA.neuter = function (opts) { | ||||
|   // trying to find the best balance of an immutable copy with custom attributes
 | ||||
|   var jwk = {}; | ||||
|   Object.keys(opts.jwk).forEach(function (k) { | ||||
|     if ('undefined' === typeof opts.jwk[k]) { return; } | ||||
|     // ignore RSA private parts
 | ||||
|     if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { return; } | ||||
|     jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
|   }); | ||||
|   return jwk; | ||||
| }; | ||||
| 	// Chopping off the private parts is now part of the public API.
 | ||||
| 	// I thought it sounded a little too crude at first, but it really is the best name in every possible way.
 | ||||
| 	RSA.neuter = function(opts) { | ||||
| 		// trying to find the best balance of an immutable copy with custom attributes
 | ||||
| 		var jwk = {}; | ||||
| 		Object.keys(opts.jwk).forEach(function(k) { | ||||
| 			if ('undefined' === typeof opts.jwk[k]) { | ||||
| 				return; | ||||
| 			} | ||||
| 			// ignore RSA private parts
 | ||||
| 			if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { | ||||
| 				return; | ||||
| 			} | ||||
| 			jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
| 		}); | ||||
| 		return jwk; | ||||
| 	}; | ||||
| 
 | ||||
| // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||
| RSA.__thumbprint = function (jwk) { | ||||
|   // Use the same entropy for SHA as for key
 | ||||
|   var len = Math.floor(jwk.n.length * 0.75); | ||||
|   var alg = 'SHA-256'; | ||||
|   // TODO this may be a bug
 | ||||
|   // need to confirm that the padding is no more or less than 1 byte
 | ||||
|   if (len >= 511) { | ||||
|     alg = 'SHA-512'; | ||||
|   } else if (len >= 383) { | ||||
|     alg = 'SHA-384'; | ||||
|   } | ||||
|   return window.crypto.subtle.digest( | ||||
|     { name: alg } | ||||
|   , textEncoder.encode('{"e":"' + jwk.e + '","kty":"RSA","n":"' + jwk.n + '"}') | ||||
|   ).then(function (hash) { | ||||
|     return Enc.bufToUrlBase64(new Uint8Array(hash)); | ||||
|   }); | ||||
| }; | ||||
| 	// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||
| 	RSA.__thumbprint = function(jwk) { | ||||
| 		// Use the same entropy for SHA as for key
 | ||||
| 		var len = Math.floor(jwk.n.length * 0.75); | ||||
| 		var alg = 'SHA-256'; | ||||
| 		// TODO this may be a bug
 | ||||
| 		// need to confirm that the padding is no more or less than 1 byte
 | ||||
| 		if (len >= 511) { | ||||
| 			alg = 'SHA-512'; | ||||
| 		} else if (len >= 383) { | ||||
| 			alg = 'SHA-384'; | ||||
| 		} | ||||
| 		return window.crypto.subtle | ||||
| 			.digest( | ||||
| 				{ name: alg }, | ||||
| 				textEncoder.encode( | ||||
| 					'{"e":"' + jwk.e + '","kty":"RSA","n":"' + jwk.n + '"}' | ||||
| 				) | ||||
| 			) | ||||
| 			.then(function(hash) { | ||||
| 				return Enc.bufToUrlBase64(new Uint8Array(hash)); | ||||
| 			}); | ||||
| 	}; | ||||
| 
 | ||||
| RSA.thumbprint = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     var jwk; | ||||
|     if ('EC' === opts.kty) { | ||||
|       jwk = opts; | ||||
|     } else if (opts.jwk) { | ||||
|       jwk = opts.jwk; | ||||
|     } else { | ||||
|       return RSA.import(opts).then(function (jwk) { | ||||
|         return RSA.__thumbprint(jwk); | ||||
|       }); | ||||
|     } | ||||
|     return RSA.__thumbprint(jwk); | ||||
|   }); | ||||
| }; | ||||
| 	RSA.thumbprint = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			var jwk; | ||||
| 			if ('EC' === opts.kty) { | ||||
| 				jwk = opts; | ||||
| 			} else if (opts.jwk) { | ||||
| 				jwk = opts.jwk; | ||||
| 			} else { | ||||
| 				return RSA.import(opts).then(function(jwk) { | ||||
| 					return RSA.__thumbprint(jwk); | ||||
| 				}); | ||||
| 			} | ||||
| 			return RSA.__thumbprint(jwk); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| RSA.export = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { | ||||
|       throw new Error("must pass { jwk: jwk }"); | ||||
|     } | ||||
|     var jwk = JSON.parse(JSON.stringify(opts.jwk)); | ||||
|     var format = opts.format; | ||||
|     var pub = opts.public; | ||||
|     if (pub || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) { | ||||
|       jwk = RSA.neuter({ jwk: jwk }); | ||||
|     } | ||||
|     if ('RSA' !== jwk.kty) { | ||||
|       throw new Error("options.jwk.kty must be 'RSA' for RSA keys"); | ||||
|     } | ||||
|     if (!jwk.p) { | ||||
|       // TODO test for n and e
 | ||||
|       pub = true; | ||||
|       if (!format || 'pkcs1' === format) { | ||||
|         format = 'pkcs1'; | ||||
|       } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { | ||||
|         format = 'spki'; | ||||
|       } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { | ||||
|         format = 'ssh'; | ||||
|       } else { | ||||
|         throw new Error("options.format must be 'spki', 'pkcs1', or 'ssh' for public RSA keys, not (" | ||||
|           + typeof format + ") " + format); | ||||
|       } | ||||
|     } else { | ||||
|       // TODO test for all necessary keys (d, p, q ...)
 | ||||
|       if (!format || 'pkcs1' === format) { | ||||
|         format = 'pkcs1'; | ||||
|       } else if ('pkcs8' !== format) { | ||||
|         throw new Error("options.format must be 'pkcs1' or 'pkcs8' for private RSA keys"); | ||||
|       } | ||||
|     } | ||||
| 	RSA.export = function(opts) { | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { | ||||
| 				throw new Error('must pass { jwk: jwk }'); | ||||
| 			} | ||||
| 			var jwk = JSON.parse(JSON.stringify(opts.jwk)); | ||||
| 			var format = opts.format; | ||||
| 			var pub = opts.public; | ||||
| 			if ( | ||||
| 				pub || | ||||
| 				-1 !== ['spki', 'pkix', 'ssh', 'rfc4716'].indexOf(format) | ||||
| 			) { | ||||
| 				jwk = RSA.neuter({ jwk: jwk }); | ||||
| 			} | ||||
| 			if ('RSA' !== jwk.kty) { | ||||
| 				throw new Error("options.jwk.kty must be 'RSA' for RSA keys"); | ||||
| 			} | ||||
| 			if (!jwk.p) { | ||||
| 				// TODO test for n and e
 | ||||
| 				pub = true; | ||||
| 				if (!format || 'pkcs1' === format) { | ||||
| 					format = 'pkcs1'; | ||||
| 				} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 					format = 'spki'; | ||||
| 				} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 					format = 'ssh'; | ||||
| 				} else { | ||||
| 					throw new Error( | ||||
| 						"options.format must be 'spki', 'pkcs1', or 'ssh' for public RSA keys, not (" + | ||||
| 							typeof format + | ||||
| 							') ' + | ||||
| 							format | ||||
| 					); | ||||
| 				} | ||||
| 			} else { | ||||
| 				// TODO test for all necessary keys (d, p, q ...)
 | ||||
| 				if (!format || 'pkcs1' === format) { | ||||
| 					format = 'pkcs1'; | ||||
| 				} else if ('pkcs8' !== format) { | ||||
| 					throw new Error( | ||||
| 						"options.format must be 'pkcs1' or 'pkcs8' for private RSA keys" | ||||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|     if ('pkcs1' === format) { | ||||
|       if (jwk.d) { | ||||
|         return PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: x509.packPkcs1(jwk) }); | ||||
|       } else { | ||||
|         return PEM.packBlock({ type: "RSA PUBLIC KEY", bytes: x509.packPkcs1(jwk) }); | ||||
|       } | ||||
|     } else if ('pkcs8' === format) { | ||||
|       return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) }); | ||||
|     } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { | ||||
|       return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) }); | ||||
|     } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { | ||||
|       return SSH.pack({ jwk: jwk, comment: opts.comment }); | ||||
|     } else { | ||||
|       throw new Error("Sanity Error: reached unreachable code block with format: " + format); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| RSA.pack = function (opts) { | ||||
|   // wrapped in a promise for API compatibility
 | ||||
|   // with the forthcoming browser version
 | ||||
|   // (and potential future native node capability)
 | ||||
|   return Promise.resolve().then(function () { | ||||
|     return RSA.export(opts); | ||||
|   }); | ||||
| }; | ||||
| 			if ('pkcs1' === format) { | ||||
| 				if (jwk.d) { | ||||
| 					return PEM.packBlock({ | ||||
| 						type: 'RSA PRIVATE KEY', | ||||
| 						bytes: x509.packPkcs1(jwk) | ||||
| 					}); | ||||
| 				} else { | ||||
| 					return PEM.packBlock({ | ||||
| 						type: 'RSA PUBLIC KEY', | ||||
| 						bytes: x509.packPkcs1(jwk) | ||||
| 					}); | ||||
| 				} | ||||
| 			} else if ('pkcs8' === format) { | ||||
| 				return PEM.packBlock({ | ||||
| 					type: 'PRIVATE KEY', | ||||
| 					bytes: x509.packPkcs8(jwk) | ||||
| 				}); | ||||
| 			} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 				return PEM.packBlock({ | ||||
| 					type: 'PUBLIC KEY', | ||||
| 					bytes: x509.packSpki(jwk) | ||||
| 				}); | ||||
| 			} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 				return SSH.pack({ jwk: jwk, comment: opts.comment }); | ||||
| 			} else { | ||||
| 				throw new Error( | ||||
| 					'Sanity Error: reached unreachable code block with format: ' + | ||||
| 						format | ||||
| 				); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| 	RSA.pack = function(opts) { | ||||
| 		// wrapped in a promise for API compatibility
 | ||||
| 		// with the forthcoming browser version
 | ||||
| 		// (and potential future native node capability)
 | ||||
| 		return Promise.resolve().then(function() { | ||||
| 			return RSA.export(opts); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToUrlBase64 = function (u8) { | ||||
|   return Enc.bufToBase64(u8) | ||||
|     .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); | ||||
| }; | ||||
| 	Enc.bufToUrlBase64 = function(u8) { | ||||
| 		return Enc.bufToBase64(u8) | ||||
| 			.replace(/\+/g, '-') | ||||
| 			.replace(/\//g, '_') | ||||
| 			.replace(/=/g, ''); | ||||
| 	}; | ||||
| 
 | ||||
| Enc.bufToBase64 = function (u8) { | ||||
|   var bin = ''; | ||||
|   u8.forEach(function (i) { | ||||
|     bin += String.fromCharCode(i); | ||||
|   }); | ||||
|   return btoa(bin); | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof module ? module.exports : window)); | ||||
| 	Enc.bufToBase64 = function(u8) { | ||||
| 		var bin = ''; | ||||
| 		u8.forEach(function(i) { | ||||
| 			bin += String.fromCharCode(i); | ||||
| 		}); | ||||
| 		return btoa(bin); | ||||
| 	}; | ||||
| })('undefined' !== typeof module ? module.exports : window); | ||||
|  | ||||
							
								
								
									
										538
									
								
								lib/x509.js
									
									
									
									
									
								
							
							
						
						
									
										538
									
								
								lib/x509.js
									
									
									
									
									
								
							| @ -1,279 +1,301 @@ | ||||
| (function (exports) { | ||||
|   'use strict'; | ||||
| (function(exports) { | ||||
| 	'use strict'; | ||||
| 
 | ||||
|   var x509 = exports.x509 = {}; | ||||
|   var ASN1 = exports.ASN1; | ||||
|   var Enc = exports.Enc; | ||||
| 	var x509 = (exports.x509 = {}); | ||||
| 	var ASN1 = exports.ASN1; | ||||
| 	var Enc = exports.Enc; | ||||
| 
 | ||||
|   // 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(); | ||||
| 	// 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; | ||||
| 	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); | ||||
|     } | ||||
| 		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; | ||||
| 		// 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); | ||||
| 		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)
 | ||||
|     }; | ||||
|   }; | ||||
| 		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.packPkcs1 = function (jwk) { | ||||
|     var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||
|     var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); | ||||
| 	x509.packPkcs1 = function(jwk) { | ||||
| 		var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||
| 		var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); | ||||
| 
 | ||||
|     if (!jwk.d) { | ||||
|       return Enc.hexToBuf(ASN1('30', n, e)); | ||||
|     } | ||||
| 		if (!jwk.d) { | ||||
| 			return Enc.hexToBuf(ASN1('30', n, e)); | ||||
| 		} | ||||
| 
 | ||||
|     return Enc.hexToBuf(ASN1('30' | ||||
|     , ASN1.UInt('00') | ||||
|     , n | ||||
|     , e | ||||
|     , ASN1.UInt(Enc.base64ToHex(jwk.d)) | ||||
|     , ASN1.UInt(Enc.base64ToHex(jwk.p)) | ||||
|     , ASN1.UInt(Enc.base64ToHex(jwk.q)) | ||||
|     , ASN1.UInt(Enc.base64ToHex(jwk.dp)) | ||||
|     , ASN1.UInt(Enc.base64ToHex(jwk.dq)) | ||||
|     , ASN1.UInt(Enc.base64ToHex(jwk.qi)) | ||||
|     )); | ||||
|   }; | ||||
| 		return Enc.hexToBuf( | ||||
| 			ASN1( | ||||
| 				'30', | ||||
| 				ASN1.UInt('00'), | ||||
| 				n, | ||||
| 				e, | ||||
| 				ASN1.UInt(Enc.base64ToHex(jwk.d)), | ||||
| 				ASN1.UInt(Enc.base64ToHex(jwk.p)), | ||||
| 				ASN1.UInt(Enc.base64ToHex(jwk.q)), | ||||
| 				ASN1.UInt(Enc.base64ToHex(jwk.dp)), | ||||
| 				ASN1.UInt(Enc.base64ToHex(jwk.dq)), | ||||
| 				ASN1.UInt(Enc.base64ToHex(jwk.qi)) | ||||
| 			) | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
|   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; | ||||
|     } | ||||
| 	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)"); | ||||
|     } | ||||
| 		//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)
 | ||||
|     }; | ||||
|   }; | ||||
| 		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; | ||||
| 	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; | ||||
|     } | ||||
| 		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"); | ||||
|     } | ||||
| 		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; | ||||
| 		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.hexToBuf( | ||||
|       ASN1('30' | ||||
|         , ASN1.UInt('01') | ||||
|         , ASN1('04', d) | ||||
|         , ASN1('A0', objId) | ||||
|         , ASN1('A1', ASN1.BitStr('04' + x + y))) | ||||
|     ); | ||||
|   }; | ||||
|   /** | ||||
|    * take a private jwk and creates a der from it | ||||
|    * @param {*} jwk | ||||
|    */ | ||||
|   x509.packPkcs8 = function (jwk) { | ||||
|     if ('RSA' === jwk.kty) { | ||||
|       if (!jwk.d) { | ||||
|         // Public RSA
 | ||||
|         return Enc.hexToBuf(ASN1('30' | ||||
|           , ASN1('30' | ||||
|             , ASN1('06', '2a864886f70d010101') | ||||
|             , ASN1('05') | ||||
|           ) | ||||
|           , ASN1.BitStr(ASN1('30' | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.n)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.e)) | ||||
|           )) | ||||
|         )); | ||||
|       } | ||||
| 	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.hexToBuf( | ||||
| 			ASN1( | ||||
| 				'30', | ||||
| 				ASN1.UInt('01'), | ||||
| 				ASN1('04', d), | ||||
| 				ASN1('A0', objId), | ||||
| 				ASN1('A1', ASN1.BitStr('04' + x + y)) | ||||
| 			) | ||||
| 		); | ||||
| 	}; | ||||
| 	/** | ||||
| 	 * take a private jwk and creates a der from it | ||||
| 	 * @param {*} jwk | ||||
| 	 */ | ||||
| 	x509.packPkcs8 = function(jwk) { | ||||
| 		if ('RSA' === jwk.kty) { | ||||
| 			if (!jwk.d) { | ||||
| 				// Public RSA
 | ||||
| 				return Enc.hexToBuf( | ||||
| 					ASN1( | ||||
| 						'30', | ||||
| 						ASN1( | ||||
| 							'30', | ||||
| 							ASN1('06', '2a864886f70d010101'), | ||||
| 							ASN1('05') | ||||
| 						), | ||||
| 						ASN1.BitStr( | ||||
| 							ASN1( | ||||
| 								'30', | ||||
| 								ASN1.UInt(Enc.base64ToHex(jwk.n)), | ||||
| 								ASN1.UInt(Enc.base64ToHex(jwk.e)) | ||||
| 							) | ||||
| 						) | ||||
| 					) | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
|       // Private RSA
 | ||||
|       return Enc.hexToBuf(ASN1('30' | ||||
|         , ASN1.UInt('00') | ||||
|         , ASN1('30' | ||||
|           , ASN1('06', '2a864886f70d010101') | ||||
|           , ASN1('05') | ||||
|         ) | ||||
|         , ASN1('04' | ||||
|           , ASN1('30' | ||||
|             , ASN1.UInt('00') | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.n)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.e)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.d)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.p)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.q)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.dp)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.dq)) | ||||
|             , ASN1.UInt(Enc.base64ToHex(jwk.qi)) | ||||
|           ) | ||||
|         ) | ||||
|       )); | ||||
|     } | ||||
| 			// Private RSA
 | ||||
| 			return Enc.hexToBuf( | ||||
| 				ASN1( | ||||
| 					'30', | ||||
| 					ASN1.UInt('00'), | ||||
| 					ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), | ||||
| 					ASN1( | ||||
| 						'04', | ||||
| 						ASN1( | ||||
| 							'30', | ||||
| 							ASN1.UInt('00'), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.n)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.e)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.d)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.p)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.q)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.dp)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.dq)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.qi)) | ||||
| 						) | ||||
| 					) | ||||
| 				) | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
|     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.hexToBuf( | ||||
|       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) { | ||||
|     if (/EC/i.test(jwk.kty)) { | ||||
|       return x509.packSpkiEc(jwk); | ||||
|     } | ||||
|     return x509.packSpkiRsa(jwk); | ||||
|   }; | ||||
|   x509.packSpkiRsa = function (jwk) { | ||||
|   if (!jwk.d) { | ||||
|     // Public RSA
 | ||||
|     return Enc.hexToBuf(ASN1('30' | ||||
|       , ASN1('30' | ||||
|         , ASN1('06', '2a864886f70d010101') | ||||
|         , ASN1('05') | ||||
|       ) | ||||
|       , ASN1.BitStr(ASN1('30' | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.n)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.e)) | ||||
|       )) | ||||
|     )); | ||||
|   } | ||||
| 		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.hexToBuf( | ||||
| 			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) { | ||||
| 		if (/EC/i.test(jwk.kty)) { | ||||
| 			return x509.packSpkiEc(jwk); | ||||
| 		} | ||||
| 		return x509.packSpkiRsa(jwk); | ||||
| 	}; | ||||
| 	x509.packSpkiRsa = function(jwk) { | ||||
| 		if (!jwk.d) { | ||||
| 			// Public RSA
 | ||||
| 			return Enc.hexToBuf( | ||||
| 				ASN1( | ||||
| 					'30', | ||||
| 					ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), | ||||
| 					ASN1.BitStr( | ||||
| 						ASN1( | ||||
| 							'30', | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.n)), | ||||
| 							ASN1.UInt(Enc.base64ToHex(jwk.e)) | ||||
| 						) | ||||
| 					) | ||||
| 				) | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
|   // Private RSA
 | ||||
|   return Enc.hexToBuf(ASN1('30' | ||||
|     , ASN1.UInt('00') | ||||
|     , ASN1('30' | ||||
|       , ASN1('06', '2a864886f70d010101') | ||||
|       , ASN1('05') | ||||
|     ) | ||||
|     , ASN1('04' | ||||
|       , ASN1('30' | ||||
|         , ASN1.UInt('00') | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.n)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.e)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.d)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.p)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.q)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.dp)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.dq)) | ||||
|         , ASN1.UInt(Enc.base64ToHex(jwk.qi)) | ||||
|       ) | ||||
|     ) | ||||
|   )); | ||||
| }; | ||||
|   x509.packSpkiEc = 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.hexToBuf( | ||||
|       ASN1('30' | ||||
|         , ASN1('30' | ||||
|           , OBJ_ID_EC_PUB | ||||
|           , objId | ||||
|         ) | ||||
|         , ASN1.BitStr('04' + x + y)) | ||||
|     ); | ||||
|   }; | ||||
|   x509.packPkix = x509.packSpki; | ||||
| 
 | ||||
| }('undefined' !== typeof module ? module.exports : window)); | ||||
| 		// Private RSA
 | ||||
| 		return Enc.hexToBuf( | ||||
| 			ASN1( | ||||
| 				'30', | ||||
| 				ASN1.UInt('00'), | ||||
| 				ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), | ||||
| 				ASN1( | ||||
| 					'04', | ||||
| 					ASN1( | ||||
| 						'30', | ||||
| 						ASN1.UInt('00'), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.n)), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.e)), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.d)), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.p)), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.q)), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.dp)), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.dq)), | ||||
| 						ASN1.UInt(Enc.base64ToHex(jwk.qi)) | ||||
| 					) | ||||
| 				) | ||||
| 			) | ||||
| 		); | ||||
| 	}; | ||||
| 	x509.packSpkiEc = 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.hexToBuf( | ||||
| 			ASN1( | ||||
| 				'30', | ||||
| 				ASN1('30', OBJ_ID_EC_PUB, objId), | ||||
| 				ASN1.BitStr('04' + x + y) | ||||
| 			) | ||||
| 		); | ||||
| 	}; | ||||
| 	x509.packPkix = x509.packSpki; | ||||
| })('undefined' !== typeof module ? module.exports : window); | ||||
|  | ||||
							
								
								
									
										26
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										26
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@bluecrypt/acme", | ||||
|   "version": "1.0.0", | ||||
|   "name": "acme", | ||||
|   "version": "2.0.0-wip.0", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @ -82,6 +82,12 @@ | ||||
|         "glob": "^7.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "commander": { | ||||
|       "version": "2.20.1", | ||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz", | ||||
|       "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "concat-map": { | ||||
|       "version": "0.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | ||||
| @ -509,6 +515,12 @@ | ||||
|       "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "source-map": { | ||||
|       "version": "0.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", | ||||
|       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "statuses": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", | ||||
| @ -525,6 +537,16 @@ | ||||
|         "mime-types": "~2.1.24" | ||||
|       } | ||||
|     }, | ||||
|     "uglify-js": { | ||||
|       "version": "3.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", | ||||
|       "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "commander": "~2.20.0", | ||||
|         "source-map": "~0.6.1" | ||||
|       } | ||||
|     }, | ||||
|     "unpipe": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", | ||||
|  | ||||
							
								
								
									
										82
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								package.json
									
									
									
									
									
								
							| @ -1,42 +1,44 @@ | ||||
| { | ||||
|   "name": "@bluecrypt/acme", | ||||
|   "version": "1.0.1", | ||||
|   "description": "Free SSL certificates through Let's Encrypt, right in your browser", | ||||
|   "main": "bluecrypt-acme.js", | ||||
|   "homepage": "https://rootprojects.org/acme/", | ||||
|   "directories": { | ||||
|     "lib": "lib" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "lib", | ||||
|     "bluecrypt-acme.js", | ||||
|     "bluecrypt-acme.min.js" | ||||
|   ], | ||||
|   "scripts": { | ||||
|     "test": "node server.js", | ||||
|     "start": "node server.js" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://git.coolaj86.com/coolaj86/bluecrypt-acme.js.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "ACME", | ||||
|     "Let's Encrypt", | ||||
|     "browser", | ||||
|     "EC", | ||||
|     "RSA", | ||||
|     "CSR", | ||||
|     "greenlock", | ||||
|     "VanillaJS", | ||||
|     "ZeroSSL" | ||||
|   ], | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "MPL-2.0", | ||||
|   "devDependencies": { | ||||
|     "@root/request": "^1.3.10", | ||||
|     "dig.js": "^1.3.9", | ||||
|     "dns-suite": "^1.2.12", | ||||
|     "express": "^4.16.4" | ||||
|   } | ||||
| 	"name": "acme", | ||||
| 	"version": "2.0.0-wip.0", | ||||
| 	"description": "Free SSL certificates through Let's Encrypt, right in your browser", | ||||
| 	"main": "bluecrypt-acme.js", | ||||
| 	"homepage": "https://rootprojects.org/acme/", | ||||
| 	"directories": { | ||||
| 		"lib": "lib" | ||||
| 	}, | ||||
| 	"files": [ | ||||
| 		"lib", | ||||
| 		"dist" | ||||
| 	], | ||||
| 	"scripts": { | ||||
| 		"build": "node bin/bundle.js", | ||||
| 		"lint": "jshint lib bin", | ||||
| 		"test": "node server.js", | ||||
| 		"start": "node server.js" | ||||
| 	}, | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| 		"url": "https://git.coolaj86.com/coolaj86/bluecrypt-acme.js.git" | ||||
| 	}, | ||||
| 	"keywords": [ | ||||
| 		"ACME", | ||||
| 		"Let's Encrypt", | ||||
| 		"browser", | ||||
| 		"EC", | ||||
| 		"RSA", | ||||
| 		"CSR", | ||||
| 		"greenlock", | ||||
| 		"VanillaJS", | ||||
| 		"ZeroSSL" | ||||
| 	], | ||||
| 	"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
| 	"license": "MPL-2.0", | ||||
| 	"devDependencies": { | ||||
| 		"@root/request": "^1.3.10", | ||||
| 		"dig.js": "^1.3.9", | ||||
| 		"dns-suite": "^1.2.12", | ||||
| 		"express": "^4.16.4", | ||||
| 		"uglify-js": "^3.6.0" | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user