certificates.js
9.72 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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
"use strict";
var certificates = module.exports;
var store = certificates;
var U = require("./utils.js");
var fs = require("fs");
var path = require("path");
var PromiseA = require("./promise.js");
var sfs = require("safe-replace");
var readFileAsync = PromiseA.promisify(fs.readFile);
var writeFileAsync = PromiseA.promisify(fs.writeFile);
var mkdirpAsync = PromiseA.promisify(require("@root/mkdirp"));
// Certificates.check
//
// Use certificate.id, or subject, if id hasn't been set, to find a certificate.
// Return an object with string PEMs for cert and chain (or null, not undefined)
certificates.check = function(opts) {
// { directoryUrl, subject, certificate.id, ... }
var id = (opts.certificate && opts.certificate.id) || opts.subject;
//console.log('certificates.check for', opts);
// For advanced use cases:
// This just goes to show that any options set in approveDomains() will be available here
// (the same is true for all of the hooks in this file)
if (opts.exampleThrowError) {
return Promise.reject(new Error("You want an error? You got it!"));
}
if (opts.exampleReturnNull) {
return Promise.resolve(null);
}
if (opts.exampleReturnCerts) {
return Promise.resolve(opts.exampleReturnCerts);
}
return Promise.all([
readFileAsync(U._tameWild(privkeyPath(store, opts), id), "ascii"), // 0 // all other PEM types are just
readFileAsync(U._tameWild(certPath(store, opts), id), "ascii"), // 1 // some arrangement of these 3
readFileAsync(U._tameWild(chainPath(store, opts), id), "ascii") // 2 // (bundle, combined, fullchain, etc)
])
.then(function(all) {
////////////////////////
// PAY ATTENTION HERE //
////////////////////////
// This is all you have to return: cert, chain
return {
cert: all[1], // string PEM. the bare cert, half of the concatonated fullchain.pem you need
chain: all[2], // string PEM. the bare chain, the second half of the fullchain.pem
privkey: all[0] // string PEM. optional, allows checkKeypair to be skipped
// These can be useful to store in your database,
// but otherwise they're easy to derive from the cert.
// (when not available they'll be generated from cert-info)
//, subject: certinfo.subject // string domain name
//, altnames: certinfo.altnames // array of domain name strings
//, issuedAt: certinfo.issuedAt // number in ms (a.k.a. NotBefore)
//, expiresAt: certinfo.expiresAt // number in ms (a.k.a. NotAfter)
};
})
.catch(function(err) {
// Treat non-exceptional failures as null returns (not undefined)
if ("ENOENT" === err.code) {
return null;
}
throw err; // True exceptions should be thrown
});
};
// Certificates.checkKeypair
//
// Use certificate.kid, certificate.id, or subject to find a certificate keypair
// Return an object with string privateKeyPem and/or object privateKeyJwk (or null, not undefined)
certificates.checkKeypair = function(opts) {
//console.log('certificates.checkKeypair:', opts);
return readFileAsync(
U._tameWild(privkeyPath(store, opts), opts.subject),
"ascii"
)
.then(function(key) {
////////////////////////
// PAY ATTENTION HERE //
////////////////////////
return {
privateKeyPem: key // In this case we only saved privateKeyPem, so we only return it
//privateKeyJwk: null // (but it's fine, just different encodings of the same thing)
};
})
.catch(function(err) {
if ("ENOENT" === err.code) {
return null;
}
throw err;
});
};
// Certificates.setKeypair({ certificate, subject, keypair, ... }):
//
// Use certificate.kid (or certificate.id or subject if no kid is present) to find a certificate keypair
// Return null (not undefined) on success, or throw on error
certificates.setKeypair = function(opts) {
var keypair = opts.keypair || keypair;
// Ignore.
// Just specific implementation details.
return mkdirpAsync(
U._tameWild(path.dirname(privkeyPath(store, opts)), opts.subject)
).then(function() {
// keypair is normally an opaque object, but here it's a PEM for the FS (for things like Apache and Nginx)
return writeFileAsync(
U._tameWild(privkeyPath(store, opts), opts.subject),
keypair.privateKeyPem,
"ascii"
).then(function() {
return null;
});
});
};
// Certificates.set({ subject, pems, ... }):
//
// Use certificate.id (or subject if no ki is present) to save a certificate
// Return null (not undefined) on success, or throw on error
certificates.set = function(opts) {
//console.log('certificates.set:', opts);
var pems = {
cert: opts.pems.cert, // string PEM the first half of the concatonated fullchain.pem cert
chain: opts.pems.chain, // string PEM the second half (yes, you need this too)
privkey: opts.pems.privkey // Ignore. string PEM, useful if you have to create bundle.pem
};
// Ignore
// Just implementation specific details (writing lots of combinatons of files)
return mkdirpAsync(path.dirname(certPath(store, opts)))
.then(function() {
return mkdirpAsync(
path.dirname(U._tameWild(chainPath(store, opts), opts.subject))
).then(function() {
return mkdirpAsync(
path.dirname(
U._tameWild(fullchainPath(store, opts), opts.subject)
)
).then(function() {
return mkdirpAsync(
path.dirname(
U._tameWild(bundlePath(store, opts), opts.subject)
)
).then(function() {
var fullchainPem = [
pems.cert.trim() + "\n",
pems.chain.trim() + "\n"
].join("\n"); // for Apache, Nginx, etc
var bundlePem = [
pems.privkey,
pems.cert,
pems.chain
].join("\n"); // for HAProxy
return PromiseA.all([
sfs.writeFileAsync(
U._tameWild(
certPath(store, opts),
opts.subject
),
pems.cert,
"ascii"
),
sfs.writeFileAsync(
U._tameWild(
chainPath(store, opts),
opts.subject
),
pems.chain,
"ascii"
),
// Most web servers need these two
sfs.writeFileAsync(
U._tameWild(
fullchainPath(store, opts),
opts.subject
),
fullchainPem,
"ascii"
),
// HAProxy needs "bundle.pem" aka "combined.pem"
sfs.writeFileAsync(
U._tameWild(
bundlePath(store, opts),
opts.subject
),
bundlePem,
"ascii"
)
]);
});
});
});
})
.then(function() {
// That's your job: return null
return null;
});
};
function liveDir(store, opts) {
return opts.liveDir || path.join(opts.configDir, "live", opts.subject);
}
function privkeyPath(store, opts) {
var dir = U._tpl(
store,
opts,
opts.serverKeyPath ||
opts.privkeyPath ||
opts.domainKeyPath ||
store.options.serverKeyPath ||
store.options.privkeyPath ||
store.options.domainKeyPath ||
path.join(liveDir(), "privkey.pem")
);
return U._tameWild(dir, opts.subject || "");
}
function certPath(store, opts) {
var pathname =
opts.certPath ||
store.options.certPath ||
path.join(liveDir(), "cert.pem");
var dir = U._tpl(store, opts, pathname);
return U._tameWild(dir, opts.subject || "");
}
function fullchainPath(store, opts) {
var dir = U._tpl(
store,
opts,
opts.fullchainPath ||
store.options.fullchainPath ||
path.join(liveDir(), "fullchain.pem")
);
return U._tameWild(dir, opts.subject || "");
}
function chainPath(store, opts) {
var dir = U._tpl(
store,
opts,
opts.chainPath ||
store.options.chainPath ||
path.join(liveDir(), "chain.pem")
);
return U._tameWild(dir, opts.subject || "");
}
function bundlePath(store, opts) {
var dir = U._tpl(
store,
opts,
opts.bundlePath ||
store.options.bundlePath ||
path.join(liveDir(), "bundle.pem")
);
return U._tameWild(dir, opts.subject || "");
}