-
Notifications
You must be signed in to change notification settings - Fork 2
/
cert.coffee
172 lines (134 loc) · 5.04 KB
/
cert.coffee
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
util = require "util"
Vault = require "node-vault"
Flannel = require "flannel"
errify = require "errify"
LetsEncrypt = require "./letsencrypt"
Challenge = require "./challenge"
VaultModel = require "./vault"
unique = (array) ->
(elem for elem, i in array when (array.indexOf elem) is i)
class KeyPair
@logPrefix: "(KeyPair)"
@path: "secret/ten-ply/keypair"
@setup: ({@storeopts, @Flannel}) ->
@Flannel.shirt this
@info "initializing"
@load: (callback) ->
ideally = errify callback
vault = new Vault @storeopts
@debug "loading"
await vault.read @path, defer err, result
if (keypair = result?.data) and keypair.publicKey
keypair =
publicKey: new Buffer(keypair.publicKey)
privateKey: new Buffer(keypair.privateKey)
else
@warn "no keypair found, creating"
await @create ideally defer keypair
keypairStrings =
publicKey: keypair.publicKey.toString()
privateKey: keypair.privateKey.toString()
await vault.write @path, keypairStrings, ideally defer()
@info "new keypair stored in vault"
callback null, keypair
@create: (callback) ->
keypair = LetsEncrypt.createKeypair()
callback null, keypair
class Cert extends VaultModel
@logPrefix: "(Cert)"
@configure "Cert",
# "id", # domain
"domain", # helper fn
"country",
"country_short",
"locality",
"organization",
"organization_short",
"password",
"unstructured",
"subject_alt_names",
"cert",
"key",
"ciphers",
"lease_expires",
# "renewing" # timestamp for when we last started renewing, false if not renewing. so we don't double up on a slow renew
"parent_id", # if this is a san, it will have a parent cert
# Optional
"npn"
# npn: [ ... ]
# ticket_key: ""
@belongsTo Cert, "parent", "parent_id"
@hasOne Challenge, "challenge", "id"
@setup: (storeopts, {@email, @keypair, @account_uri, @Flannel, @url}, callback) ->
@Flannel or= Flannel.init Console: level: "debug"
@Flannel.shirt this
@info "initializing"
ideally = errify callback
unless @keypair?
@warn "no keypair found, loading from backend"
KeyPair.setup {storeopts, @Flannel}
await KeyPair.load ideally defer @keypair
@le = new LetsEncrypt {@keypair, @Flannel, @url}
await @le.setup ideally defer()
super
await @setupAccount ideally defer()
await @holdIntermediateCert ideally defer @intermediateCert
callback null, @keypair
@setupAccount: (callback = ->) ->
@info "setting up Let's Encrypt account for #{@email}"
ideally = errify callback
unless @account_url
await @le.createAccount @email, ideally defer @account_uri
@info "account URI for #{@email}: #{@account_uri}"
await @le.getTosLink @account_uri, ideally defer link
await @le.agreeToTos @account_uri, link, ideally defer()
callback()
@holdIntermediateCert: (callback) ->
@info "retrieving intermediate cert"
LetsEncrypt.intermediateCert callback
@findPrimary: (id, callback) ->
ideally = errify callback
await @find id, ideally defer record
await record.parent defer _, parent
return callback null, parent if parent
callback null, record
constructor: (attributes) ->
attributes = util._extend (util._extend {}, @defaults), attributes if @defaults
super attributes
domain: -> @id
addSANs: (subject_alt_names) ->
@subject_alt_names or= []
@subject_alt_names = @subject_alt_names.concat subject_alt_names
@subject_alt_names = unique @subject_alt_names.sort()
register: (callback) ->
@constructor.info "registering domain #{@id}"
return callback err if err = @validate()
ideally = errify callback
await @constructor.le.authorize @id, ideally defer challenge, validation_location
await @constructor.le.acceptChallenge challenge, ideally defer()
await @challenge challenge, ideally defer()
await @constructor.le.waitForValidation validation_location, ideally defer()
for name in @subject_alt_names? and @subject_alt_names when name isnt @id
await @constructor.find name, ideally defer child
await child.register ideally defer()
callback()
fetchCert: (callback) ->
@constructor.info "fetching cert for #{@id}"
ideally = errify callback
{csr, privateKey} = @constructor.le.signedCsr @attributes()
await @constructor.le.requestSigning csr, @id, ideally defer poll_location
await @constructor.le.waitForCert poll_location, ideally defer @cert
@cert += @constructor.intermediateCert
@constructor.info "acquired cert for #{@id}"
@key = privateKey.toString()
callback null, @cert
save: (callback) ->
ideally = errify callback
for name in @subject_alt_names? and @subject_alt_names when name isnt @id
child = new @constructor id: name, parent_id: @id
await child.save ideally defer()
super
## https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#certificate-revocation
## POST to resource "revoke-cert"
# remove: ->
module.exports = Cert