account.js
4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
'use strict';
var A = module.exports;
var U = require('./utils.js');
var Keypairs = require('@root/keypairs');
var Enc = require('@root/encoding/bytes');
var agreers = {};
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
var kid =
options.kid ||
(options.account && (options.account.key && options.account.key.kid));
if (kid) {
return Promise.resolve(kid);
}
//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) {
return account.key.kid;
});
};
// 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');
function agree(agreed) {
var err;
if (!agreed) {
err = new Error("must agree to '" + me._tos + "'");
err.code = 'E_AGREE_TOS';
throw err;
}
return true;
}
function getAccount() {
return U._importKeypair(options.accountKey).then(function(pair) {
var contact;
if (options.contact) {
contact = options.contact.slice(0);
} else if (options.subscriberEmail) {
contact = ['mailto:' + options.subscriberEmail];
}
var accountRequest = {
termsOfServiceAgreed: true,
onlyReturnExisting: false,
contact: contact
};
var pub = pair.public;
return attachExtAcc(pub, accountRequest).then(function(accReq) {
var payload = JSON.stringify(accReq);
return U._jwsRequest(me, {
accountKey: options.accountKey,
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
);
}
// the account id url is the "kid"
var kid = resp.headers.location;
if (!account) {
account = { _emptyResponse: true };
}
if (!account.key) {
account.key = {};
}
account.key.kid = kid;
return account;
});
});
});
}
// 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;
});
}
return Promise.resolve()
.then(function() {
//#console.debug('[ACME.js] agreeToTerms');
var agreeToTerms = options.agreeToTerms;
if (!agreeToTerms) {
agreeToTerms = function(terms) {
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:'
);
console.info(
'ACME Subscriber Agreement:',
terms.acmeSubscriberTermsUrl
);
console.info(
'Greenlock/ACME.js Terms of Use:',
terms.acmeJsTermsUrl
);
console.info();
return true;
};
} else if (true === agreeToTerms) {
agreeToTerms = function(terms) {
return terms && true;
};
}
return agreeToTerms({
acmeSubscriberTermsUrl: me._tos,
acmeJsTermsUrl: 'https://rootprojects.org/legal/#terms'
});
})
.then(agree)
.then(getAccount);
};