Merge branch 'master' of lastlink/bluecrypt-keypairs.js into master
This commit is contained in:
		
						commit
						b2174e3923
					
				
							
								
								
									
										32
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								app.js
									
									
									
									
									
								
							| @ -34,6 +34,10 @@ function run() { | ||||
|       ev.stopPropagation(); | ||||
|       $('.js-loading').hidden = false; | ||||
|       $('.js-jwk').hidden = true; | ||||
|       $('.js-toc-der-public').hidden = true; | ||||
|       $('.js-toc-pem-public').hidden = true; | ||||
|       $('.js-toc-der-private').hidden = true; | ||||
|       $('.js-toc-pem-private').hidden = true; | ||||
|       $$('input').map(function ($el) { $el.disabled = true; }); | ||||
|       $$('button').map(function ($el) { $el.disabled = true; }); | ||||
|       var opts = { | ||||
| @ -43,8 +47,34 @@ function run() { | ||||
|       }; | ||||
|       console.log('opts', opts); | ||||
|       Keypairs.generate(opts).then(function (results) { | ||||
|         var der_public, der_private; | ||||
|         if (opts.kty == 'EC') { | ||||
|           der_public = x509.packSpki(results.public); | ||||
|           der_private = x509.packPkcs8(results.private); | ||||
|           var pem_private = Eckles.export({ jwk: results.private }) | ||||
|           var pem_public = Eckles.export({ jwk: results.public, public: true }) | ||||
|           $('.js-input-pem-public').innerText = pem_public; | ||||
|           $('.js-toc-pem-public').hidden = false; | ||||
|           $('.js-input-pem-private').innerText = pem_private; | ||||
|           $('.js-toc-pem-private').hidden = false; | ||||
|         } else { | ||||
|           der_private = x509.packPkcs8(results.private); | ||||
|           der_public = x509.packPkcs8(results.public); | ||||
|           Rasha.pack({ jwk: results.private }).then(function (pem) { | ||||
|             $('.js-input-pem-private').innerText = pem; | ||||
|             $('.js-toc-pem-private').hidden = false; | ||||
|           }) | ||||
|           Rasha.pack({ jwk: results.public }).then(function (pem) { | ||||
|             $('.js-input-pem-public').innerText = pem; | ||||
|             $('.js-toc-pem-public').hidden = false; | ||||
|           }) | ||||
|         } | ||||
| 
 | ||||
|         $('.js-der-public').innerText = der_public; | ||||
|         $('.js-toc-der-public').hidden = false; | ||||
|         $('.js-der-private').innerText = der_private; | ||||
|         $('.js-toc-der-private').hidden = false; | ||||
|         $('.js-jwk').innerText = JSON.stringify(results, null, 2); | ||||
|       //
 | ||||
|         $('.js-loading').hidden = true; | ||||
|         $('.js-jwk').hidden = false; | ||||
|         $$('input').map(function ($el) { $el.disabled = false; }); | ||||
|  | ||||
							
								
								
									
										34
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								index.html
									
									
									
									
									
								
							| @ -1,6 +1,20 @@ | ||||
| <html> | ||||
|   <head> | ||||
|     <title>BlueCrypt</title> | ||||
|     <style> | ||||
|       textarea { | ||||
|         width: 42em; | ||||
|         height: 10em; | ||||
|       } | ||||
|       /* need to word wrap the binary no space der */ | ||||
|       .js-der-public, .js-der-private{ | ||||
|         white-space: pre-wrap;      /* CSS3 */    | ||||
|         white-space: -moz-pre-wrap; /* Firefox */     | ||||
|         white-space: -pre-wrap;     /* Opera <7 */    | ||||
|         white-space: -o-pre-wrap;   /* Opera 7 */     | ||||
|         word-wrap: break-word;      /* IE */ | ||||
|       } | ||||
|     </style> | ||||
|   </head> | ||||
|   <body> | ||||
|     <h1>BlueCrypt for the Browser</h1> | ||||
| @ -58,6 +72,22 @@ | ||||
|       <summary>JWK Keypair</summary> | ||||
|       <pre><code class="js-jwk"> </code></pre> | ||||
|     </details> | ||||
|     <details class="js-toc-der-private" hidden> | ||||
|       <summary>DER Private Binary</summary> | ||||
|       <pre><code class="js-der-private"> </code></pre> | ||||
|     </details> | ||||
|     <details class="js-toc-der-public" hidden> | ||||
|       <summary>DER Public Binary</summary> | ||||
|       <pre><code class="js-der-public"> </code></pre> | ||||
|     </details> | ||||
|     <details class="js-toc-pem-private" hidden> | ||||
|       <summary>PEM Private (base64-encoded DER)</summary> | ||||
|       <pre><code  class="js-input-pem-private" ></code></pre> | ||||
|     </details> | ||||
|     <details class="js-toc-pem-public" hidden> | ||||
|       <summary>PEM Public (base64-encoded DER)</summary> | ||||
|       <pre><code  class="js-input-pem-public" ></code></pre> | ||||
|     </details> | ||||
|     <details class="js-toc-acme-account-request" hidden> | ||||
|       <summary>ACME Account Request</summary> | ||||
|       <pre><code class="js-acme-account-request"> </code></pre> | ||||
| @ -66,8 +96,10 @@ | ||||
|       <summary>ACME Account Response</summary> | ||||
|       <pre><code class="js-acme-account-response"> </code></pre> | ||||
|     </details> | ||||
| 
 | ||||
|     <script src="./lib/bluecrypt-encoding.js"></script> | ||||
|     <script src="./lib/ecdsa.js"></script> | ||||
|     <script src="./lib/asn1-packer.js"></script> | ||||
|     <script src="./lib/x509.js"></script> | ||||
|     <script src="./lib/rsa.js"></script> | ||||
|     <script src="./lib/keypairs.js"></script> | ||||
|     <script src="./lib/acme.js"></script> | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| (function (exports) { | ||||
| 
 | ||||
| var Enc = exports.BluecryptEncoding = {}; | ||||
| var Enc = exports.Enc = {}; | ||||
| 
 | ||||
| Enc.bufToBin = function (buf) { | ||||
|   var bin = ''; | ||||
|  | ||||
							
								
								
									
										53
									
								
								lib/ecdsa.js
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								lib/ecdsa.js
									
									
									
									
									
								
							| @ -51,6 +51,59 @@ EC.generate = function (opts) { | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| EC.export = function (opts) { | ||||
|   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); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 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) { | ||||
|  | ||||
| @ -3,8 +3,8 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var Keypairs = exports.Keypairs = {}; | ||||
| var Rasha = exports.Rasha || require('rasha'); | ||||
| var Eckles = exports.Eckles || require('eckles'); | ||||
| 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" | ||||
| @ -34,10 +34,12 @@ Keypairs.generate = function (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.
 | ||||
| /** | ||||
|  * 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 = Keypairs._neuter = function (opts) { | ||||
|   // trying to find the best balance of an immutable copy with custom attributes
 | ||||
|   /** 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; } | ||||
| @ -61,7 +63,7 @@ Keypairs.thumbprint = function (opts) { | ||||
| Keypairs.publish = function (opts) { | ||||
|   if ('object' !== typeof opts.jwk || !opts.jwk.kty) { throw new Error("invalid jwk: " + JSON.stringify(opts.jwk)); } | ||||
| 
 | ||||
|   // returns a copy
 | ||||
|   /** returns a copy */  | ||||
|   var jwk = Keypairs.neuter(opts); | ||||
| 
 | ||||
|   if (jwk.exp) { | ||||
|  | ||||
							
								
								
									
										61
									
								
								lib/rsa.js
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								lib/rsa.js
									
									
									
									
									
								
							| @ -3,6 +3,7 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var RSA = exports.Rasha = {}; | ||||
| var x509 = exports.x509; | ||||
| if ('undefined' !== typeof module) { module.exports = RSA; } | ||||
| var Enc = {}; | ||||
| var textEncoder = new TextEncoder(); | ||||
| @ -106,6 +107,66 @@ RSA.thumbprint = function (opts) { | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| RSA.export = function (opts) { | ||||
|   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.nueter(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); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| Enc.bufToUrlBase64 = function (u8) { | ||||
|   return Enc.bufToBase64(u8) | ||||
|     .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); | ||||
|  | ||||
							
								
								
									
										69
									
								
								lib/x509.js
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								lib/x509.js
									
									
									
									
									
								
							| @ -55,6 +55,27 @@ | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   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)); | ||||
|     } | ||||
|    | ||||
|     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; | ||||
| @ -128,7 +149,7 @@ | ||||
|     var x = Enc.base64ToHex(jwk.x); | ||||
|     var y = Enc.base64ToHex(jwk.y); | ||||
|     var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; | ||||
|     return Enc.hexToUint8( | ||||
|     return Enc.hexToBuf( | ||||
|       ASN1('30' | ||||
|         , ASN1.UInt('01') | ||||
|         , ASN1('04', d) | ||||
| @ -136,12 +157,54 @@ | ||||
|         , ASN1('A1', ASN1.BitStr('04' + x + y))) | ||||
|     ); | ||||
|   }; | ||||
|   /** | ||||
|    * take a private jwk and creates a der from it | ||||
|    * @param {*} jwk  | ||||
|    */ | ||||
|   x509.packPkcs8 = function (jwk) { | ||||
|     if (jwk.kty == 'RSA') { | ||||
|       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)) | ||||
|           ) | ||||
|         ) | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     var d = Enc.base64ToHex(jwk.d); | ||||
|     var x = Enc.base64ToHex(jwk.x); | ||||
|     var y = Enc.base64ToHex(jwk.y); | ||||
|     var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; | ||||
|     return Enc.hexToUint8( | ||||
|     return Enc.hexToBuf( | ||||
|       ASN1('30' | ||||
|         , ASN1.UInt('00') | ||||
|         , ASN1('30' | ||||
| @ -159,7 +222,7 @@ | ||||
|     var x = Enc.base64ToHex(jwk.x); | ||||
|     var y = Enc.base64ToHex(jwk.y); | ||||
|     var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; | ||||
|     return Enc.hexToUint8( | ||||
|     return Enc.hexToBuf( | ||||
|       ASN1('30' | ||||
|         , ASN1('30' | ||||
|           , OBJ_ID_EC_PUB | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user