lint and fix and use domains.generateKeypair
This commit is contained in:
parent
8a0e582962
commit
cda26c98b5
|
@ -444,7 +444,7 @@
|
|||
};
|
||||
}
|
||||
|
||||
return BACME.accounts.generateKeypair(opts).then(function (serverJwk) {
|
||||
return BACME.domains.generateKeypair(opts).then(function (serverJwk) {
|
||||
localStorage.setItem('server:' + key, JSON.stringify(serverJwk));
|
||||
return serverJwk;
|
||||
});
|
||||
|
|
434
app/js/bacme.js
434
app/js/bacme.js
|
@ -4,6 +4,8 @@
|
|||
var BACME = exports.BACME = {};
|
||||
var webFetch = exports.fetch;
|
||||
var webCrypto = exports.crypto;
|
||||
var Promise = window.Promise;
|
||||
var CSR = window.CSR;
|
||||
|
||||
var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
||||
var directory;
|
||||
|
@ -15,7 +17,6 @@ var accountKeypair;
|
|||
var accountJwk;
|
||||
|
||||
var accountUrl;
|
||||
var signedAccount;
|
||||
|
||||
BACME.challengePrefixes = {
|
||||
'http-01': '/.well-known/acme-challenge'
|
||||
|
@ -23,38 +24,38 @@ BACME.challengePrefixes = {
|
|||
};
|
||||
|
||||
BACME._logHeaders = function (resp) {
|
||||
console.log('Headers:');
|
||||
Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); });
|
||||
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('');
|
||||
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 (body) {
|
||||
directory = body;
|
||||
return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) {
|
||||
BACME._logHeaders(resp);
|
||||
return resp.json().then(function (body) {
|
||||
directory = body;
|
||||
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(body);
|
||||
return body;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
BACME.nonce = function () {
|
||||
return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) {
|
||||
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');
|
||||
});
|
||||
nonce = resp.headers.get('replay-nonce');
|
||||
console.log('Nonce:', nonce);
|
||||
// resp.body is empty
|
||||
return resp.headers.get('replay-nonce');
|
||||
});
|
||||
};
|
||||
|
||||
BACME.accounts = {};
|
||||
|
@ -62,66 +63,38 @@ BACME.accounts = {};
|
|||
// type = ECDSA
|
||||
// bitlength = 256
|
||||
BACME.accounts.generateKeypair = function (opts) {
|
||||
var wcOpts = {};
|
||||
return BACME.generateKeypair(opts).then(function (result) {
|
||||
accountKeypair = result;
|
||||
|
||||
// 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';
|
||||
}
|
||||
}
|
||||
return webCrypto.subtle.exportKey(
|
||||
"jwk"
|
||||
, result.privateKey
|
||||
).then(function (privJwk) {
|
||||
|
||||
// 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" };
|
||||
}
|
||||
|
||||
// https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
|
||||
var extractable = true;
|
||||
return webCrypto.subtle.generateKey(
|
||||
wcOpts
|
||||
, extractable
|
||||
, [ 'sign', 'verify' ]
|
||||
).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));
|
||||
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 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, '');
|
||||
return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||||
};
|
||||
|
||||
var textEncoder = new TextEncoder();
|
||||
|
@ -158,7 +131,7 @@ BACME._importKey = function (jwk) {
|
|||
e: priv.e
|
||||
, kty: priv.kty
|
||||
, n: priv.n
|
||||
}
|
||||
};
|
||||
if (!priv.p) {
|
||||
priv = null;
|
||||
}
|
||||
|
@ -167,7 +140,7 @@ BACME._importKey = function (jwk) {
|
|||
return window.crypto.subtle.importKey(
|
||||
"jwk"
|
||||
, pub
|
||||
, wcOpts
|
||||
, wcOpts
|
||||
, extractable
|
||||
, [ "verify" ]
|
||||
).then(function (publicKey) {
|
||||
|
@ -271,8 +244,8 @@ BACME.accounts.sign = function (opts) {
|
|||
protectedJson
|
||||
);
|
||||
|
||||
// Note: this function hashes before signing so send data, not the hash
|
||||
return BACME._sign({
|
||||
// Note: this function hashes before signing so send data, not the hash
|
||||
return BACME._sign({
|
||||
abstractKey: abstractKey
|
||||
, payload64: payload64
|
||||
, protected64: protected64
|
||||
|
@ -280,30 +253,29 @@ BACME.accounts.sign = function (opts) {
|
|||
});
|
||||
};
|
||||
|
||||
var account;
|
||||
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);
|
||||
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: <none>');
|
||||
if (!resp.headers.get('content-type')) {
|
||||
console.log('Body: <none>');
|
||||
|
||||
return { kid: accountId };
|
||||
}
|
||||
return { kid: accountId };
|
||||
}
|
||||
|
||||
return resp.json().then(function (result) {
|
||||
return resp.json().then(function (result) {
|
||||
if (/^Error/i.test(result.detail)) {
|
||||
return Promise.reject(new Error(result.detail));
|
||||
}
|
||||
|
@ -311,21 +283,20 @@ BACME.accounts.set = function (opts) {
|
|||
BACME._logBody(result);
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var orderUrl;
|
||||
var signedOrder;
|
||||
|
||||
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 });
|
||||
var payload64 = BACME._jsto64({ identifiers: opts.identifiers });
|
||||
|
||||
return BACME._importKey(opts.jwk).then(function (abstractKey) {
|
||||
return BACME._importKey(opts.jwk).then(function (abstractKey) {
|
||||
var protected64 = BACME._jsto64(
|
||||
{ nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid }
|
||||
);
|
||||
|
@ -345,36 +316,35 @@ BACME.orders.sign = function (opts) {
|
|||
});
|
||||
};
|
||||
|
||||
var order;
|
||||
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) {
|
||||
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);
|
||||
currentOrderUrl = resp.headers.get('location');
|
||||
nonce = resp.headers.get('replay-nonce');
|
||||
console.log('Next nonce:', nonce);
|
||||
|
||||
return resp.json().then(function (result) {
|
||||
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;
|
||||
authorizationUrls = result.authorizations;
|
||||
finalizeUrl = result.finalize;
|
||||
BACME._logBody(result);
|
||||
|
||||
result.url = currentOrderUrl;
|
||||
return result;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
BACME.challenges = {};
|
||||
|
@ -395,22 +365,22 @@ BACME.challenges.all = function () {
|
|||
return next();
|
||||
};
|
||||
BACME.challenges.view = function () {
|
||||
var authzUrl = authorizationUrls.pop();
|
||||
var token;
|
||||
var challengeDomain;
|
||||
var challengeUrl;
|
||||
var authzUrl = authorizationUrls.pop();
|
||||
var token;
|
||||
var challengeDomain;
|
||||
var challengeUrl;
|
||||
|
||||
return window.fetch(authzUrl, {
|
||||
mode: 'cors'
|
||||
}).then(function (resp) {
|
||||
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;
|
||||
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);
|
||||
|
||||
|
@ -424,8 +394,8 @@ BACME.challenges.view = function () {
|
|||
//, url: challenge.url
|
||||
//, domain: result.identifier.value,
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var thumbprint;
|
||||
|
@ -435,7 +405,7 @@ var dnsAuth;
|
|||
var dnsRecord;
|
||||
|
||||
BACME.thumbprint = function (opts) {
|
||||
// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
||||
// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
||||
|
||||
var accountJwk = opts.jwk;
|
||||
var keys;
|
||||
|
@ -446,34 +416,34 @@ BACME.thumbprint = function (opts) {
|
|||
keys = [ 'e', 'kty', 'n' ];
|
||||
}
|
||||
|
||||
var accountPublicStr = '{' + keys.map(function (key) {
|
||||
return '"' + key + '":"' + accountJwk[key] + '"';
|
||||
}).join(',') + '}';
|
||||
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, '');
|
||||
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);
|
||||
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;
|
||||
// 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;
|
||||
// Where the key authorization file goes
|
||||
httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token;
|
||||
|
||||
console.log("echo '" + keyAuth + "' > '" + httpPath + "'");
|
||||
|
||||
|
@ -487,28 +457,28 @@ BACME.challenges['http-01'] = function (opts) {
|
|||
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, '');
|
||||
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;
|
||||
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);
|
||||
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;
|
||||
|
@ -516,84 +486,108 @@ 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({
|
||||
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) {
|
||||
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');
|
||||
nonce = resp.headers.get('replay-nonce');
|
||||
console.log("ACCEPT NONCE:", nonce);
|
||||
|
||||
return resp.json().then(function (reply) {
|
||||
return resp.json().then(function (reply) {
|
||||
challengePollUrl = reply.url;
|
||||
|
||||
console.log('Challenge ACK:');
|
||||
console.log(JSON.stringify(reply));
|
||||
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) {
|
||||
return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) {
|
||||
BACME._logHeaders(resp);
|
||||
|
||||
return resp.json().then(function (reply) {
|
||||
challengePollUrl = reply.url;
|
||||
return resp.json().then(function (reply) {
|
||||
challengePollUrl = reply.url;
|
||||
|
||||
BACME._logBody(reply);
|
||||
|
||||
return 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(
|
||||
{ name: "ECDSA", namedCurve: "P-256" }
|
||||
, extractable
|
||||
, [ 'sign', 'verify' ]
|
||||
);
|
||||
};
|
||||
BACME.domains = {};
|
||||
// TODO factor out from BACME.accounts.generateKeypair
|
||||
BACME.domains.generateKeypair = function () {
|
||||
var extractable = true;
|
||||
return window.crypto.subtle.generateKey(
|
||||
{ name: "ECDSA", namedCurve: "P-256" }
|
||||
, extractable
|
||||
, [ 'sign', 'verify' ]
|
||||
).then(function (result) {
|
||||
domainKeypair = result;
|
||||
// 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 (jwk) {
|
||||
return window.crypto.subtle.exportKey(
|
||||
"jwk"
|
||||
, result.privateKey
|
||||
).then(function (privJwk) {
|
||||
|
||||
domainJwk = jwk;
|
||||
console.log('private jwk:');
|
||||
console.log(JSON.stringify(jwk, null, 2));
|
||||
domainJwk = privJwk;
|
||||
console.log('private jwk:');
|
||||
console.log(JSON.stringify(privJwk, null, 2));
|
||||
|
||||
return domainKeypair;
|
||||
})
|
||||
});
|
||||
return privJwk;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// { serverJwk, domains }
|
||||
|
@ -607,41 +601,41 @@ var certificateUrl;
|
|||
|
||||
// { csr, jwk, finalizeUrl, accountId }
|
||||
BACME.orders.finalize = function (opts) {
|
||||
var payload64 = BACME._jsto64(
|
||||
{ csr: opts.csr }
|
||||
);
|
||||
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({
|
||||
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) {
|
||||
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');
|
||||
nonce = resp.headers.get('replay-nonce');
|
||||
|
||||
return resp.json().then(function (reply) {
|
||||
certificateUrl = reply.certificate;
|
||||
return resp.json().then(function (reply) {
|
||||
certificateUrl = reply.certificate;
|
||||
BACME._logBody(reply);
|
||||
|
||||
return reply;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
BACME.orders.receive = function (opts) {
|
||||
|
|
Loading…
Reference in New Issue