diff --git a/app.js b/app.js index 45a5024..bbac95c 100644 --- a/app.js +++ b/app.js @@ -18,8 +18,11 @@ } function checkTos(tos) { - console.log("TODO checkbox for agree to terms"); - return tos; + if ($('input[name="tos"]:checked')) { + return tos; + } else { + return ''; + } } function run() { @@ -139,6 +142,8 @@ accountStuff.email = email; accountStuff.acme = acme; $('.js-create-order').hidden = false; + $('.js-toc-acme-account-response').hidden = false; + $('.js-acme-account-response').innerText = JSON.stringify(account, null, 2); }).catch(function (err) { console.error("A bad thing happened:"); console.error(err); @@ -163,14 +168,17 @@ var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g); - return getDomainPrivkey().then(function () { + return getDomainPrivkey().then(function (domainPrivJwk) { + console.log('Has CSR already?'); + console.log(accountStuff.csr); return acme.certificates.create({ accountKeypair: { privateKeyJwk: privJwk } , account: account - //, domainKeypair: { privateKeyJwk: accountStuff.domainPrivateJwk } + , domainKeypair: { privateKeyJwk: domainPrivJwk } , csr: accountStuff.csr , email: email , domains: domains + , skipDryRun: $('input[name="skip-dryrun"]:checked') && true , agreeToTerms: checkTos , challenges: { 'dns-01': { @@ -215,7 +223,14 @@ } } , challengeTypes: [$('input[name="acme-challenge-type"]:checked').value] + }).then(function (results) { + console.log('Got Certificates:'); + console.log(results); + $('.js-toc-acme-order-response').hidden = false; + $('.js-acme-order-response').innerText = JSON.stringify(results, null, 2); }).catch(function (err) { + console.error("challenge failed:"); + console.error(err); window.alert("failed! " + err.message || JSON.stringify(err)); }); }); @@ -245,7 +260,7 @@ return CSR({ jwk: privJwk, domains: domains }).then(function (pem) { // Verify with https://www.sslshopper.com/csr-decoder.html accountStuff.csr = pem; - console.log('CSR:'); + console.log('Created CSR:'); console.log(pem); console.log('CSR info:'); diff --git a/index.html b/index.html index 5978ffd..27f0aa5 100644 --- a/index.html +++ b/index.html @@ -56,7 +56,10 @@

ACME Account

- + +
+
@@ -64,7 +67,7 @@

Certificate Signing Request

- +
@@ -77,6 +80,9 @@
+ +
@@ -114,14 +120,14 @@ PEM Public (base64-encoded SPKI/PKIX DER)
- + diff --git a/lib/acme.js b/lib/acme.js index a5f95d9..ad966ec 100644 --- a/lib/acme.js +++ b/lib/acme.js @@ -837,6 +837,7 @@ ACME._generateCsrWeb64 = function (me, options, validatedDomains) { csr = Enc.base64ToUrlBase64(csr.trim().replace(/\s+/g, '')); return Promise.resolve(csr); } + return ACME._importKeypair(me, options.domainKeypair).then(function (pair) { return me.CSR({ jwk: pair.private, domains: validatedDomains, encoding: 'der' }).then(function (der) { return Enc.bufToUrlBase64(der); diff --git a/lib/browser-acme.js b/lib/browser-acme.js deleted file mode 100644 index 4fba0fe..0000000 --- a/lib/browser-acme.js +++ /dev/null @@ -1,699 +0,0 @@ -/*global CSR*/ -// CSR takes a while to load after the page load -(function (exports) { -'use strict'; - -var BACME = exports.ACME = {}; -var webFetch = exports.fetch; -var Keypairs = exports.Keypairs; -var Promise = exports.Promise; - -var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; -var directory; - -var nonceUrl; -var nonce; - -var accountKeypair; -var accountJwk; - -var accountUrl; - -BACME.challengePrefixes = { - 'http-01': '/.well-known/acme-challenge' -, 'dns-01': '_acme-challenge' -}; - -BACME._logHeaders = function (resp) { - console.log('Headers:'); - Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); -}; - -BACME._logBody = function (body) { - console.log('Body:'); - console.log(JSON.stringify(body, null, 2)); - console.log(''); -}; - -BACME.directory = function (opts) { - return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) { - BACME._logHeaders(resp); - return resp.json().then(function (reply) { - if (/error/.test(reply.type)) { - return Promise.reject(new Error(reply.detail || reply.type)); - } - directory = reply; - nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce'; - accountUrl = directory.newAccount || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-account'; - orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order"; - BACME._logBody(reply); - return reply; - }); - }); -}; - -BACME.nonce = function () { - return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) { - BACME._logHeaders(resp); - nonce = resp.headers.get('replay-nonce'); - console.log('Nonce:', nonce); - // resp.body is empty - return resp.headers.get('replay-nonce'); - }); -}; - -BACME.accounts = {}; - -// type = ECDSA -// bitlength = 256 -BACME.accounts.generateKeypair = function (opts) { - return BACME.generateKeypair(opts).then(function (result) { - accountKeypair = result; - - return webCrypto.subtle.exportKey( - "jwk" - , result.privateKey - ).then(function (privJwk) { - - accountJwk = privJwk; - console.log('private jwk:'); - console.log(JSON.stringify(privJwk, null, 2)); - - return privJwk; - /* - return webCrypto.subtle.exportKey( - "pkcs8" - , result.privateKey - ).then(function (keydata) { - console.log('pkcs8:'); - console.log(Array.from(new Uint8Array(keydata))); - - return privJwk; - //return accountKeypair; - }); - */ - }); - }); -}; - -// json to url-safe base64 -BACME._jsto64 = function (json) { - return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); -}; - -var textEncoder = new TextEncoder(); - -BACME._importKey = function (jwk) { - var alg; // I think the 256 refers to the hash - var wcOpts = {}; - var extractable = true; // TODO make optionally false? - var priv = jwk; - var pub; - - // ECDSA - if (/^EC/i.test(jwk.kty)) { - wcOpts.name = 'ECDSA'; - wcOpts.namedCurve = jwk.crv; - alg = 'ES256'; - pub = { - crv: priv.crv - , kty: priv.kty - , x: priv.x - , y: priv.y - }; - if (!priv.d) { - priv = null; - } - } - - // RSA - if (/^RS/i.test(jwk.kty)) { - wcOpts.name = 'RSASSA-PKCS1-v1_5'; - wcOpts.hash = { name: "SHA-256" }; - alg = 'RS256'; - pub = { - e: priv.e - , kty: priv.kty - , n: priv.n - }; - if (!priv.p) { - priv = null; - } - } - - return window.crypto.subtle.importKey( - "jwk" - , pub - , wcOpts - , extractable - , [ "verify" ] - ).then(function (publicKey) { - function give(privateKey) { - return { - wcPub: publicKey - , wcKey: privateKey - , wcKeypair: { publicKey: publicKey, privateKey: privateKey } - , meta: { - alg: alg - , name: wcOpts.name - , hash: wcOpts.hash - } - , jwk: jwk - }; - } - if (!priv) { - return give(); - } - return window.crypto.subtle.importKey( - "jwk" - , priv - , wcOpts - , extractable - , [ "sign"/*, "verify"*/ ] - ).then(give); - }); -}; -BACME._sign = function (opts) { - var wcPrivKey = opts.abstractKey.wcKeypair.privateKey; - var wcOpts = opts.abstractKey.meta; - var alg = opts.abstractKey.meta.alg; // I think the 256 refers to the hash - var signHash; - - console.log('kty', opts.abstractKey.jwk.kty); - signHash = { name: "SHA-" + alg.replace(/[a-z]+/ig, '') }; - - var msg = textEncoder.encode(opts.protected64 + '.' + opts.payload64); - console.log('msg:', msg); - return window.crypto.subtle.sign( - { name: wcOpts.name, hash: signHash } - , wcPrivKey - , msg - ).then(function (signature) { - //console.log('sig1:', signature); - //console.log('sig2:', new Uint8Array(signature)); - //console.log('sig3:', Array.prototype.slice.call(new Uint8Array(signature))); - // convert buffer to urlsafe base64 - var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) { - return String.fromCharCode(ch); - }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); - - console.log('[1] URL-safe Base64 Signature:'); - console.log(sig64); - - var signedMsg = { - protected: opts.protected64 - , payload: opts.payload64 - , signature: sig64 - }; - - console.log('Signed Base64 Msg:'); - console.log(JSON.stringify(signedMsg, null, 2)); - - return signedMsg; - }); -}; -// email = john.doe@gmail.com -// jwk = { ... } -// agree = true -BACME.accounts.sign = function (opts) { - - return BACME._importKey(opts.jwk).then(function (abstractKey) { - - var payloadJson = - { termsOfServiceAgreed: opts.agree - , onlyReturnExisting: false - , contact: opts.contacts || [ 'mailto:' + opts.email ] - }; - console.log('payload:'); - console.log(payloadJson); - var payload64 = BACME._jsto64( - payloadJson - ); - - var protectedJson = - { nonce: opts.nonce - , url: accountUrl - , alg: abstractKey.meta.alg - , jwk: null - }; - - if (/EC/i.test(opts.jwk.kty)) { - protectedJson.jwk = { - crv: opts.jwk.crv - , kty: opts.jwk.kty - , x: opts.jwk.x - , y: opts.jwk.y - }; - } else if (/RS/i.test(opts.jwk.kty)) { - protectedJson.jwk = { - e: opts.jwk.e - , kty: opts.jwk.kty - , n: opts.jwk.n - }; - } else { - return Promise.reject(new Error("[acme.accounts.sign] unsupported key type '" + opts.jwk.kty + "'")); - } - - console.log('protected:'); - console.log(protectedJson); - var protected64 = BACME._jsto64( - protectedJson - ); - - // Note: this function hashes before signing so send data, not the hash - return BACME._sign({ - abstractKey: abstractKey - , payload64: payload64 - , protected64: protected64 - }); - }); -}; - -var accountId; - -BACME.accounts.set = function (opts) { - nonce = null; - return window.fetch(accountUrl, { - mode: 'cors' - , method: 'POST' - , headers: { 'Content-Type': 'application/jose+json' } - , body: JSON.stringify(opts.signedAccount) - }).then(function (resp) { - BACME._logHeaders(resp); - nonce = resp.headers.get('replay-nonce'); - accountId = resp.headers.get('location'); - console.log('Next nonce:', nonce); - console.log('Location/kid:', accountId); - - if (!resp.headers.get('content-type')) { - console.log('Body: '); - - return { kid: accountId }; - } - - return resp.json().then(function (result) { - if (/^Error/i.test(result.detail)) { - return Promise.reject(new Error(result.detail)); - } - result.kid = accountId; - BACME._logBody(result); - - return result; - }); - }); -}; - -var orderUrl; - -BACME.orders = {}; - -// identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ] -// signedAccount -BACME.orders.sign = function (opts) { - var payload64 = BACME._jsto64({ identifiers: opts.identifiers }); - - return BACME._importKey(opts.jwk).then(function (abstractKey) { - var protected64 = BACME._jsto64( - { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid } - ); - console.log('abstractKey:'); - console.log(abstractKey); - return BACME._sign({ - abstractKey: abstractKey - , payload64: payload64 - , protected64: protected64 - }).then(function (sig) { - if (!sig) { - throw new Error('sig is undefined... nonsense!'); - } - console.log('newsig', sig); - return sig; - }); - }); -}; - -var currentOrderUrl; -var authorizationUrls; -var finalizeUrl; - -BACME.orders.create = function (opts) { - nonce = null; - return window.fetch(orderUrl, { - mode: 'cors' - , method: 'POST' - , headers: { 'Content-Type': 'application/jose+json' } - , body: JSON.stringify(opts.signedOrder) - }).then(function (resp) { - BACME._logHeaders(resp); - currentOrderUrl = resp.headers.get('location'); - nonce = resp.headers.get('replay-nonce'); - console.log('Next nonce:', nonce); - - return resp.json().then(function (result) { - if (/^Error/i.test(result.detail)) { - return Promise.reject(new Error(result.detail)); - } - authorizationUrls = result.authorizations; - finalizeUrl = result.finalize; - BACME._logBody(result); - - result.url = currentOrderUrl; - return result; - }); - }); -}; - -BACME.challenges = {}; -BACME.challenges.all = function () { - var challenges = []; - - function next() { - if (!authorizationUrls.length) { - return challenges; - } - - return BACME.challenges.view().then(function (challenge) { - challenges.push(challenge); - return next(); - }); - } - - return next(); -}; -BACME.challenges.view = function () { - var authzUrl = authorizationUrls.pop(); - var token; - var challengeDomain; - var challengeUrl; - - return window.fetch(authzUrl, { - mode: 'cors' - }).then(function (resp) { - BACME._logHeaders(resp); - - return resp.json().then(function (result) { - // Note: select the challenge you wish to use - var challenge = result.challenges.slice(0).pop(); - token = challenge.token; - challengeUrl = challenge.url; - challengeDomain = result.identifier.value; - - BACME._logBody(result); - - return { - challenges: result.challenges - , expires: result.expires - , identifier: result.identifier - , status: result.status - , wildcard: result.wildcard - //, token: challenge.token - //, url: challenge.url - //, domain: result.identifier.value, - }; - }); - }); -}; - -var thumbprint; -var keyAuth; -var httpPath; -var dnsAuth; -var dnsRecord; - -BACME.thumbprint = function (opts) { - // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk - - var accountJwk = opts.jwk; - var keys; - - if (/^EC/i.test(opts.jwk.kty)) { - keys = [ 'crv', 'kty', 'x', 'y' ]; - } else if (/^RS/i.test(opts.jwk.kty)) { - keys = [ 'e', 'kty', 'n' ]; - } - - var accountPublicStr = '{' + keys.map(function (key) { - return '"' + key + '":"' + accountJwk[key] + '"'; - }).join(',') + '}'; - - return window.crypto.subtle.digest( - { name: "SHA-256" } // SHA-256 is spec'd, non-optional - , textEncoder.encode(accountPublicStr) - ).then(function (hash) { - thumbprint = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { - return String.fromCharCode(ch); - }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); - - console.log('Thumbprint:'); - console.log(opts); - console.log(accountPublicStr); - console.log(thumbprint); - - return thumbprint; - }); -}; - -// { token, thumbprint, challengeDomain } -BACME.challenges['http-01'] = function (opts) { - // The contents of the key authorization file - keyAuth = opts.token + '.' + opts.thumbprint; - - // Where the key authorization file goes - httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token; - - console.log("echo '" + keyAuth + "' > '" + httpPath + "'"); - - return { - path: httpPath - , value: keyAuth - }; -}; - -// { keyAuth } -BACME.challenges['dns-01'] = function (opts) { - console.log('opts.keyAuth for DNS:'); - console.log(opts.keyAuth); - return window.crypto.subtle.digest( - { name: "SHA-256", } - , textEncoder.encode(opts.keyAuth) - ).then(function (hash) { - dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { - return String.fromCharCode(ch); - }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); - - dnsRecord = '_acme-challenge.' + opts.challengeDomain; - - console.log('DNS TXT Auth:'); - // The name of the record - console.log(dnsRecord); - // The TXT record value - console.log(dnsAuth); - - return { - type: 'TXT' - , host: dnsRecord - , answer: dnsAuth - }; - }); -}; - -var challengePollUrl; - -// { jwk, challengeUrl, accountId (kid) } -BACME.challenges.accept = function (opts) { - var payload64 = BACME._jsto64({}); - - return BACME._importKey(opts.jwk).then(function (abstractKey) { - var protected64 = BACME._jsto64( - { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId } - ); - return BACME._sign({ - abstractKey: abstractKey - , payload64: payload64 - , protected64: protected64 - }); - }).then(function (signedAccept) { - - nonce = null; - return window.fetch( - opts.challengeUrl - , { mode: 'cors' - , method: 'POST' - , headers: { 'Content-Type': 'application/jose+json' } - , body: JSON.stringify(signedAccept) - } - ).then(function (resp) { - BACME._logHeaders(resp); - nonce = resp.headers.get('replay-nonce'); - console.log("ACCEPT NONCE:", nonce); - - return resp.json().then(function (reply) { - challengePollUrl = reply.url; - - console.log('Challenge ACK:'); - console.log(JSON.stringify(reply)); - return reply; - }); - }); - }); -}; - -BACME.challenges.check = function (opts) { - return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) { - BACME._logHeaders(resp); - - return resp.json().then(function (reply) { - if (/error/.test(reply.type)) { - return Promise.reject(new Error(reply.detail || reply.type)); - } - challengePollUrl = reply.url; - - BACME._logBody(reply); - - return reply; - }); - }); -}; - -var domainKeypair; -var domainJwk; - -BACME.generateKeypair = function (opts) { - var wcOpts = {}; - - // ECDSA has only the P curves and an associated bitlength - if (/^EC/i.test(opts.type)) { - wcOpts.name = 'ECDSA'; - if (/256/.test(opts.bitlength)) { - wcOpts.namedCurve = 'P-256'; - } - } - - // RSA-PSS is another option, but I don't think it's used for Let's Encrypt - // I think the hash is only necessary for signing, not generation or import - if (/^RS/i.test(opts.type)) { - wcOpts.name = 'RSASSA-PKCS1-v1_5'; - wcOpts.modulusLength = opts.bitlength; - if (opts.bitlength < 2048) { - wcOpts.modulusLength = opts.bitlength * 8; - } - wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); - wcOpts.hash = { name: "SHA-256" }; - } - var extractable = true; - return window.crypto.subtle.generateKey( - wcOpts - , extractable - , [ 'sign', 'verify' ] - ); -}; -BACME.domains = {}; -// TODO factor out from BACME.accounts.generateKeypair even more -BACME.domains.generateKeypair = function (opts) { - return BACME.generateKeypair(opts).then(function (result) { - domainKeypair = result; - - return window.crypto.subtle.exportKey( - "jwk" - , result.privateKey - ).then(function (privJwk) { - - domainJwk = privJwk; - console.log('private jwk:'); - console.log(JSON.stringify(privJwk, null, 2)); - - return privJwk; - }); - }); -}; - -// { serverJwk, domains } -BACME.orders.generateCsr = function (opts) { - return BACME._importKey(opts.serverJwk).then(function (abstractKey) { - return Promise.resolve(CSR.generate({ keypair: abstractKey.wcKeypair, domains: opts.domains })); - }); -}; - -var certificateUrl; - -// { csr, jwk, finalizeUrl, accountId } -BACME.orders.finalize = function (opts) { - var payload64 = BACME._jsto64( - { csr: opts.csr } - ); - - return BACME._importKey(opts.jwk).then(function (abstractKey) { - var protected64 = BACME._jsto64( - { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId } - ); - return BACME._sign({ - abstractKey: abstractKey - , payload64: payload64 - , protected64: protected64 - }); - }).then(function (signedFinal) { - - nonce = null; - return window.fetch( - opts.finalizeUrl - , { mode: 'cors' - , method: 'POST' - , headers: { 'Content-Type': 'application/jose+json' } - , body: JSON.stringify(signedFinal) - } - ).then(function (resp) { - BACME._logHeaders(resp); - nonce = resp.headers.get('replay-nonce'); - - return resp.json().then(function (reply) { - if (/error/.test(reply.type)) { - return Promise.reject(new Error(reply.detail || reply.type)); - } - certificateUrl = reply.certificate; - BACME._logBody(reply); - - return reply; - }); - }); - }); -}; - -BACME.orders.receive = function (opts) { - return window.fetch( - opts.certificateUrl - , { mode: 'cors' - , method: 'GET' - } - ).then(function (resp) { - BACME._logHeaders(resp); - nonce = resp.headers.get('replay-nonce'); - - return resp.text().then(function (reply) { - BACME._logBody(reply); - - return reply; - }); - }); -}; - -BACME.orders.check = function (opts) { - return window.fetch( - opts.orderUrl - , { mode: 'cors' - , method: 'GET' - } - ).then(function (resp) { - BACME._logHeaders(resp); - - return resp.json().then(function (reply) { - if (/error/.test(reply.type)) { - return Promise.reject(new Error(reply.detail || reply.type)); - } - BACME._logBody(reply); - - return reply; - }); - }); -}; - -}(window)); diff --git a/lib/keypairs.js.min2 b/lib/keypairs.js.min2 deleted file mode 100644 index bf530b8..0000000 --- a/lib/keypairs.js.min2 +++ /dev/null @@ -1,86 +0,0 @@ -/*global Promise*/ -(function (exports) { -'use strict'; - -var Keypairs = exports.Keypairs = {}; - -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) { - var wcOpts = {}; - if (!opts) { - opts = {}; - } - if (!opts.kty) { - opts.kty = 'EC'; - } - - // ECDSA has only the P curves and an associated bitlength - if (/^EC/i.test(opts.kty)) { - 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'. " - + Keypairs._stance)); - } - } else if (/^RSA$/i.test(opts.kty)) { - // 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. " + Keypairs._stance)); - } - // TODO maybe allow this to be set to any of the standard values? - wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); - } else { - return Promise.Reject(new Error("'" + opts.kty + "' is not a well-supported key type." - + Keypairs._universal - + " Please choose either 'EC' or 'RSA' keys.")); - } - - 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) { - // TODO remove - console.log('private jwk:'); - console.log(JSON.stringify(privJwk, null, 2)); - return { - privateKey: privJwk - }; - }); - }); -}; - -}(window));