progress on bacme
This commit is contained in:
parent
549771a0bc
commit
4fd5fd8bd9
|
@ -1 +1,2 @@
|
||||||
js/pkijs.org
|
js/pkijs.org
|
||||||
|
js/browser-csr
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
<script src="./js/pkijs.org/v1.3.33/asn1.js"></script>
|
<script src="./js/pkijs.org/v1.3.33/asn1.js"></script>
|
||||||
<script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script>
|
<script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script>
|
||||||
<script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script>
|
<script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script>
|
||||||
|
<script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script>
|
||||||
|
|
||||||
|
<script src="./js/bacme.js"></script>
|
||||||
<script src="./js/app.js"></script>
|
<script src="./js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
mkdir -p js/pkijs.org/v1.3.33/
|
mkdir -p js/pkijs.org/v1.3.33/
|
||||||
pushd js/pkijs.org/v1.3.33/
|
pushd js/pkijs.org/v1.3.33/
|
||||||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js
|
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js
|
||||||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.js
|
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.js
|
||||||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.js
|
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.js
|
||||||
wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js
|
wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js
|
||||||
|
popd
|
||||||
|
|
||||||
|
mkdir -p js/browser-csr/v1.0.0-alpha/
|
||||||
|
pushd js/browser-csr/v1.0.0-alpha/
|
||||||
|
wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/c513a862a4e016794da800f0c2eec858b80837ab/csr.js
|
||||||
|
popd
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
console.log("Hello, World!");
|
//window.document.querySelector('.js-acme-directory-url').value = 'https://acme-v02.api.letsencrypt.org/directory';
|
||||||
|
|
||||||
window.document.querySelector('.js-acme-directory-url').value = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
window.document.querySelector('.js-acme-directory-url').value = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -0,0 +1,478 @@
|
||||||
|
(function (exports) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var BACME = exports.BACME = {};
|
||||||
|
var webFetch = exports.fetch;
|
||||||
|
var webCrypto = exports.crypto;
|
||||||
|
|
||||||
|
var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
||||||
|
var directory;
|
||||||
|
|
||||||
|
var nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce';
|
||||||
|
var nonce;
|
||||||
|
|
||||||
|
var accountKeypair;
|
||||||
|
var accountJwk;
|
||||||
|
|
||||||
|
var accountUrl = directory.newAccount;
|
||||||
|
var signedAccount;
|
||||||
|
|
||||||
|
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 (url) {
|
||||||
|
return webFetch(directoryUrl, { mode: 'cors' }).then(function (resp) {
|
||||||
|
BACME._logHeaders(resp);
|
||||||
|
return resp.json().then(function (body) {
|
||||||
|
directory = body;
|
||||||
|
BACME._logBody(body);
|
||||||
|
return body;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = {};
|
||||||
|
BACME.accounts.generateKeypair = function () {
|
||||||
|
// https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
|
||||||
|
var extractable = true;
|
||||||
|
return webCrypto.subtle.generateKey(
|
||||||
|
{ name: "ECDSA", namedCurve: "P-256" }
|
||||||
|
, extractable
|
||||||
|
, [ 'sign', 'verify' ]
|
||||||
|
).then(function (result) {
|
||||||
|
accountKeypair = result;
|
||||||
|
|
||||||
|
return webCrypto.subtle.exportKey(
|
||||||
|
"jwk"
|
||||||
|
, result.privateKey
|
||||||
|
).then(function (jwk) {
|
||||||
|
|
||||||
|
accountJwk = jwk;
|
||||||
|
console.log('private jwk:');
|
||||||
|
console.log(JSON.stringify(jwk, null, 2));
|
||||||
|
|
||||||
|
return webCrypto.subtle.exportKey(
|
||||||
|
"pkcs8"
|
||||||
|
, result.privateKey
|
||||||
|
).then(function (keydata) {
|
||||||
|
console.log('pkcs8:');
|
||||||
|
console.log(Array.from(new Uint8Array(keydata)));
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// email = john.doe@gmail.com
|
||||||
|
BACME.accounts.sign = function (email) {
|
||||||
|
var payload64 = BACME._jsto64(
|
||||||
|
{ termsOfServiceAgreed: true
|
||||||
|
, onlyReturnExisting: false
|
||||||
|
, contact: [ 'mailto:' + email ]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var protected64 = BACME._jsto64(
|
||||||
|
{ nonce: nonce
|
||||||
|
, url: accountUrl
|
||||||
|
, alg: 'ES256'
|
||||||
|
, jwk: {
|
||||||
|
kty: accountJwk.kty
|
||||||
|
, crv: accountJwk.crv
|
||||||
|
, x: accountJwk.x
|
||||||
|
, y: accountJwk.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Note: this function hashes before signing so send data, not the hash
|
||||||
|
return window.crypto.subtle.sign(
|
||||||
|
{ name: "ECDSA", hash: { name: "SHA-256" } }
|
||||||
|
, accountKeypair.privateKey
|
||||||
|
, textEncoder.encode(protected64 + '.' + payload64)
|
||||||
|
).then(function (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('URL-safe Base64 Signature:');
|
||||||
|
console.log(sig64);
|
||||||
|
|
||||||
|
signedAccount = {
|
||||||
|
protected: protected64
|
||||||
|
, payload: payload64
|
||||||
|
, signature: sig64
|
||||||
|
};
|
||||||
|
console.log('Signed Base64 Account:');
|
||||||
|
console.log(JSON.stringify(signedAccount, null, 2));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var account;
|
||||||
|
var accountId;
|
||||||
|
|
||||||
|
BACME.accounts.set = function () {
|
||||||
|
nonce = null;
|
||||||
|
return window.fetch(accountUrl, {
|
||||||
|
mode: 'cors'
|
||||||
|
, method: 'POST'
|
||||||
|
, headers: { 'Content-Type': 'application/jose+json' }
|
||||||
|
, body: JSON.stringify(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>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.json().then(function (result) {
|
||||||
|
BACME._logBody(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order";
|
||||||
|
var signedOrder;
|
||||||
|
|
||||||
|
BACME.orders = {};
|
||||||
|
|
||||||
|
// identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
|
||||||
|
BACME.orders.sign = function (identifiers) {
|
||||||
|
var payload64 = jsto64({ identifiers: identifiers });
|
||||||
|
|
||||||
|
var protected64 = jsto64(
|
||||||
|
{ nonce: nonce, alg: 'ES256', url: orderUrl, kid: accountId }
|
||||||
|
);
|
||||||
|
|
||||||
|
return window.crypto.subtle.sign(
|
||||||
|
{ name: "ECDSA", hash: { name: "SHA-256" } }
|
||||||
|
, accountKeypair.privateKey
|
||||||
|
, textEncoder.encode(protected64 + '.' + payload64)
|
||||||
|
).then(function (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('URL-safe Base64 Signature:');
|
||||||
|
console.log(sig64);
|
||||||
|
|
||||||
|
signedOrder = {
|
||||||
|
protected: protected64
|
||||||
|
, payload: payload64
|
||||||
|
, signature: sig64
|
||||||
|
};
|
||||||
|
console.log('Signed Base64 Order:');
|
||||||
|
console.log(JSON.stringify(signedAccount, null, 2));
|
||||||
|
|
||||||
|
return signedOrder;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var order;
|
||||||
|
var currentOrderUrl;
|
||||||
|
var authorizationUrls;
|
||||||
|
var finalizeUrl;
|
||||||
|
|
||||||
|
BACME.orders.create = function () {
|
||||||
|
nonce = null;
|
||||||
|
return window.fetch(orderUrl, {
|
||||||
|
mode: 'cors'
|
||||||
|
, method: 'POST'
|
||||||
|
, headers: { 'Content-Type': 'application/jose+json' }
|
||||||
|
, body: JSON.stringify(signedOrder)
|
||||||
|
}).then(function (resp) {
|
||||||
|
console.log('Headers:');
|
||||||
|
Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); });
|
||||||
|
currentOrderUrl = resp.headers.get('location');
|
||||||
|
nonce = resp.headers.get('replay-nonce');
|
||||||
|
console.log('Next nonce:', nonce);
|
||||||
|
|
||||||
|
return resp.json().then(function (result) {
|
||||||
|
authorizationUrls = result.authorizations;
|
||||||
|
finalizeUrl = result.finalize;
|
||||||
|
console.log('Body:');
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BACME.challenges = {};
|
||||||
|
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 { token: challenge.token, url: challenge.url, domain: result.identifier.value, challenges: result.challenges };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var thumbprint;
|
||||||
|
var keyAuth;
|
||||||
|
var httpPath;
|
||||||
|
var dnsAuth;
|
||||||
|
var dnsRecord;
|
||||||
|
|
||||||
|
BACME.thumbprint = function () {
|
||||||
|
// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
||||||
|
|
||||||
|
var accountPublicStr = '{' + ['crv', 'kty', 'x', 'y'].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(thumbprint);
|
||||||
|
|
||||||
|
return thumbprint;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BACME.challenges['http-01'] = function () {
|
||||||
|
// The contents of the key authorization file
|
||||||
|
keyAuth = token + '.' + thumbprint;
|
||||||
|
|
||||||
|
// Where the key authorization file goes
|
||||||
|
httpPath = 'http://' + challengeDomain + '/.well-known/acme-challenge/' + token;
|
||||||
|
|
||||||
|
console.log("echo '" + keyAuth + "' > '" + httpPath + "'");
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: httpPath
|
||||||
|
, value: keyAuth
|
||||||
|
};
|
||||||
|
});
|
||||||
|
BACME.challenges['dns-01'] = function () {
|
||||||
|
return window.crypto.subtle.digest(
|
||||||
|
{ name: "SHA-256", }
|
||||||
|
, textEncoder.encode(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.' + 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;
|
||||||
|
|
||||||
|
BACME.challenges.accept = function () {
|
||||||
|
var payload64 = jsto64(
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
var protected64 = jsto64(
|
||||||
|
{ nonce: nonce, alg: 'ES256', url: challengeUrl, kid: accountId }
|
||||||
|
);
|
||||||
|
|
||||||
|
nonce = null;
|
||||||
|
return window.crypto.subtle.sign(
|
||||||
|
{ name: "ECDSA", hash: { name: "SHA-256" } }
|
||||||
|
, accountKeypair.privateKey
|
||||||
|
, textEncoder.encode(protected64 + '.' + payload64)
|
||||||
|
).then(function (signature) {
|
||||||
|
|
||||||
|
var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) {
|
||||||
|
return String.fromCharCode(ch);
|
||||||
|
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||||||
|
|
||||||
|
var body = {
|
||||||
|
protected: protected64
|
||||||
|
, payload: payload64
|
||||||
|
, signature: sig64
|
||||||
|
};
|
||||||
|
|
||||||
|
return window.fetch(
|
||||||
|
challengeUrl
|
||||||
|
, { mode: 'cors'
|
||||||
|
, method: 'POST'
|
||||||
|
, headers: { 'Content-Type': 'application/jose+json' }
|
||||||
|
, body: JSON.stringify(body)
|
||||||
|
}
|
||||||
|
).then(function (resp) {
|
||||||
|
console.log('Headers:');
|
||||||
|
Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); });
|
||||||
|
nonce = resp.headers.get('replay-nonce');
|
||||||
|
|
||||||
|
return resp.json().then(function (reply) {
|
||||||
|
challengePollUrl = reply.url;
|
||||||
|
|
||||||
|
console.log('Challenge ACK:');
|
||||||
|
console.log(JSON.stringify(reply));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BACME.challenges.check = function () {
|
||||||
|
return window.fetch(challengePollUrl, { mode: 'cors' }).then(function (resp) {
|
||||||
|
BACME._logHeaders(resp);
|
||||||
|
nonce = resp.headers.get('replay-nonce');
|
||||||
|
|
||||||
|
return resp.json().then(function (reply) {
|
||||||
|
challengePollUrl = reply.url;
|
||||||
|
|
||||||
|
BACME._logBody(reply);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var domainKeypair;
|
||||||
|
var domainJwk;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
return window.crypto.subtle.exportKey(
|
||||||
|
"jwk"
|
||||||
|
, result.privateKey
|
||||||
|
).then(function (jwk) {
|
||||||
|
|
||||||
|
domainJwk = jwk;
|
||||||
|
console.log('private jwk:');
|
||||||
|
console.log(JSON.stringify(jwk, null, 2));
|
||||||
|
|
||||||
|
return domainKeypair;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BACME.order.generateCsr = function (keypair, domains) {
|
||||||
|
return Promise.resolve(CSR.generate(keypair, domains));
|
||||||
|
};
|
||||||
|
|
||||||
|
var certificateUrl;
|
||||||
|
|
||||||
|
BACME.order.finalize = function () {
|
||||||
|
var payload64 = jsto64(
|
||||||
|
{ csr: csr }
|
||||||
|
);
|
||||||
|
|
||||||
|
var protected64 = jsto64(
|
||||||
|
{ nonce: nonce, alg: 'ES256', url: finalizeUrl, kid: accountId }
|
||||||
|
);
|
||||||
|
|
||||||
|
nonce = null;
|
||||||
|
return window.crypto.subtle.sign(
|
||||||
|
{ name: "ECDSA", hash: { name: "SHA-256" } }
|
||||||
|
, accountKeypair.privateKey
|
||||||
|
, textEncoder.encode(protected64 + '.' + payload64)
|
||||||
|
).then(function (signature) {
|
||||||
|
|
||||||
|
var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) {
|
||||||
|
return String.fromCharCode(ch);
|
||||||
|
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||||||
|
|
||||||
|
var body = {
|
||||||
|
protected: protected64
|
||||||
|
, payload: payload64
|
||||||
|
, signature: sig64
|
||||||
|
};
|
||||||
|
|
||||||
|
return window.fetch(
|
||||||
|
finalizeUrl
|
||||||
|
, { mode: 'cors'
|
||||||
|
, method: 'POST'
|
||||||
|
, headers: { 'Content-Type': 'application/jose+json' }
|
||||||
|
, body: JSON.stringify(body)
|
||||||
|
}
|
||||||
|
).then(function (resp) {
|
||||||
|
BACME._logHeaders(resp);
|
||||||
|
nonce = resp.headers.get('replay-nonce');
|
||||||
|
|
||||||
|
return resp.json().then(function (reply) {
|
||||||
|
certificateUrl = reply.certificate;
|
||||||
|
BACME._logBody(reply);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}(window));
|
Loading…
Reference in New Issue