acme.js/account.js

176 lines
4.3 KiB
JavaScript
Raw Permalink Normal View History

2019-10-23 07:44:55 +00:00
'use strict';
var A = module.exports;
var U = require('./utils.js');
var Keypairs = require('@root/keypairs');
var Enc = require('@root/encoding/bytes');
2019-10-28 08:26:27 +00:00
var agreers = {};
2019-10-23 07:44:55 +00:00
A._getAccountKid = function(me, options) {
// It's just fine if there's no account, we'll go get the key id we need via the existing key
2019-10-25 00:49:42 +00:00
var kid =
options.kid ||
(options.account && (options.account.key && options.account.key.kid));
2019-10-23 07:44:55 +00:00
2019-10-25 00:49:42 +00:00
if (kid) {
return Promise.resolve(kid);
2019-10-23 07:44:55 +00:00
}
//return Promise.reject(new Error("must include KeyID"));
// This is an idempotent request. It'll return the same account for the same public key.
return A._registerAccount(me, options).then(function(account) {
2019-10-25 00:49:42 +00:00
return account.key.kid;
2019-10-23 07:44:55 +00:00
});
};
// ACME RFC Section 7.3 Account Creation
/*
{
"protected": base64url({
"alg": "ES256",
"jwk": {...},
"nonce": "6S8IqOGY7eL2lsGoTZYifg",
"url": "https://example.com/acme/new-account"
}),
"payload": base64url({
"termsOfServiceAgreed": true,
"onlyReturnExisting": false,
"contact": [
"mailto:cert-admin@example.com",
"mailto:admin@example.com"
]
}),
"signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I"
}
*/
A._registerAccount = function(me, options) {
//#console.debug('[ACME.js] accounts.create');
2019-10-26 06:03:43 +00:00
function agree(agreed) {
2019-10-23 07:44:55 +00:00
var err;
2019-10-26 06:03:43 +00:00
if (!agreed) {
err = new Error("must agree to '" + me._tos + "'");
2019-10-23 07:44:55 +00:00
err.code = 'E_AGREE_TOS';
throw err;
}
2019-10-25 00:49:42 +00:00
return true;
}
2019-10-23 07:44:55 +00:00
2019-10-25 00:49:42 +00:00
function getAccount() {
return U._importKeypair(options.accountKey).then(function(pair) {
2019-10-23 07:44:55 +00:00
var contact;
if (options.contact) {
contact = options.contact.slice(0);
2019-10-25 00:49:42 +00:00
} else if (options.subscriberEmail) {
contact = ['mailto:' + options.subscriberEmail];
2019-10-23 07:44:55 +00:00
}
2019-10-25 00:49:42 +00:00
2019-10-23 07:44:55 +00:00
var accountRequest = {
2019-10-25 00:49:42 +00:00
termsOfServiceAgreed: true,
2019-10-23 07:44:55 +00:00
onlyReturnExisting: false,
contact: contact
};
2019-10-25 00:49:42 +00:00
var pub = pair.public;
return attachExtAcc(pub, accountRequest).then(function(accReq) {
var payload = JSON.stringify(accReq);
2019-10-23 07:44:55 +00:00
return U._jwsRequest(me, {
2019-10-25 00:49:42 +00:00
accountKey: options.accountKey,
2019-10-23 07:44:55 +00:00
url: me._directoryUrls.newAccount,
protected: { kid: false, jwk: pair.public },
payload: Enc.strToBuf(payload)
}).then(function(resp) {
var account = resp.body;
if (resp.statusCode < 200 || resp.statusCode >= 300) {
if ('string' !== typeof account) {
account = JSON.stringify(account);
}
throw new Error(
'account error: ' +
resp.statusCode +
' ' +
account +
'\n' +
payload
);
}
2019-10-25 00:49:42 +00:00
// the account id url is the "kid"
var kid = resp.headers.location;
2019-10-23 07:44:55 +00:00
if (!account) {
account = { _emptyResponse: true };
}
if (!account.key) {
account.key = {};
}
2019-10-25 00:49:42 +00:00
account.key.kid = kid;
2019-10-23 07:44:55 +00:00
return account;
});
});
});
}
2019-10-25 00:49:42 +00:00
// for external accounts (probably useless, but spec'd)
function attachExtAcc(pubkey, accountRequest) {
if (!options.externalAccount) {
return Promise.resolve(accountRequest);
}
return Keypairs.signJws({
// TODO is HMAC the standard, or is this arbitrary?
secret: options.externalAccount.secret,
protected: {
alg: options.externalAccount.alg || 'HS256',
kid: options.externalAccount.id,
url: me._directoryUrls.newAccount
},
payload: Enc.strToBuf(JSON.stringify(pubkey))
}).then(function(jws) {
accountRequest.externalAccountBinding = jws;
return accountRequest;
});
}
2019-10-23 07:44:55 +00:00
return Promise.resolve()
.then(function() {
//#console.debug('[ACME.js] agreeToTerms');
var agreeToTerms = options.agreeToTerms;
2019-10-26 06:03:43 +00:00
if (!agreeToTerms) {
agreeToTerms = function(terms) {
2019-10-28 08:26:27 +00:00
if (agreers[options.subscriberEmail]) {
return true;
}
agreers[options.subscriberEmail] = true;
console.info();
console.info(
'By using this software you (' +
options.subscriberEmail +
') are agreeing to the following:'
2019-10-26 06:03:43 +00:00
);
console.info(
'ACME Subscriber Agreement:',
terms.acmeSubscriberTermsUrl
);
console.info(
'Greenlock/ACME.js Terms of Use:',
2019-10-27 09:07:19 +00:00
terms.acmeJsTermsUrl
2019-10-26 06:03:43 +00:00
);
2019-10-28 08:26:27 +00:00
console.info();
2019-10-26 06:03:43 +00:00
return true;
};
} else if (true === agreeToTerms) {
agreeToTerms = function(terms) {
return terms && true;
2019-10-23 07:44:55 +00:00
};
}
2019-10-26 06:03:43 +00:00
return agreeToTerms({
2019-10-27 09:58:22 +00:00
acmeSubscriberTermsUrl: me._tos,
acmeJsTermsUrl: 'https://rootprojects.org/legal/#terms'
2019-10-26 06:03:43 +00:00
});
2019-10-23 07:44:55 +00:00
})
2019-10-25 00:49:42 +00:00
.then(agree)
.then(getAccount);
2019-10-23 07:44:55 +00:00
};