-
Notifications
You must be signed in to change notification settings - Fork 107
/
identity.go
322 lines (270 loc) · 9.77 KB
/
identity.go
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
// Package identity encapsulates the node identity.
package identity
import (
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"path/filepath"
"sync"
"github.com/oasisprotocol/oasis-core/go/common/crypto/pvss"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory"
tlsCert "github.com/oasisprotocol/oasis-core/go/common/crypto/tls"
"github.com/oasisprotocol/oasis-core/go/common/errors"
"github.com/oasisprotocol/oasis-core/go/common/pubsub"
)
const (
// NodeKeyPubFilename is the filename of the PEM encoded node public key.
NodeKeyPubFilename = "identity_pub.pem"
// P2PKeyPubFilename is the filename of the PEM encoded p2p public key.
P2PKeyPubFilename = "p2p_pub.pem"
// ConsensusKeyPubFilename is the filename of the PEM encoded consensus
// public key.
ConsensusKeyPubFilename = "consensus_pub.pem"
// CommonName is the CommonName to use when generating TLS certificates.
CommonName = "oasis-node"
beaconScalarFilename = "beacon.pem"
tlsKeyFilename = "tls_identity.pem"
tlsCertFilename = "tls_identity_cert.pem"
// These are used for the sentry client connection to the sentry node and are never rotated.
tlsSentryClientKeyFilename = "sentry_client_tls_identity.pem"
tlsSentryClientCertFilename = "sentry_client_tls_identity_cert.pem"
)
// ErrCertificateRotationForbidden is returned by RotateCertificates if
// TLS certificate rotation is forbidden. This happens when rotation is
// enabled and an existing TLS certificate was successfully loaded
// (or a new one was generated and persisted to disk).
var ErrCertificateRotationForbidden = errors.New("identity", 1, "identity: TLS certificate rotation forbidden")
// Identity is a node identity.
type Identity struct {
sync.RWMutex
// NodeSigner is a node identity key signer.
NodeSigner signature.Signer
// P2PSigner is a node P2P link key signer.
P2PSigner signature.Signer
// ConsensusSigner is a node consensus key signer.
ConsensusSigner signature.Signer
// BeaconScalar is a node beacon scalar.
BeaconScalar pvss.Scalar
// TLSSentryClientCertificate is the client certificate used for
// connecting to the sentry node's control connection. It is never rotated.
TLSSentryClientCertificate *tls.Certificate
// DoNotRotateTLS flag is true if we mustn't rotate the TLS certificates below.
DoNotRotateTLS bool
// tlsSigner is a node TLS certificate signer.
tlsSigner signature.Signer
// tlsCertificate is a certificate that can be used for TLS.
tlsCertificate *tls.Certificate
// nextTLSSigner is a node TLS certificate signer that can be used in the next rotation.
nextTLSSigner signature.Signer
// nextTLSCertificate is a certificate that can be used for TLS in the next rotation.
nextTLSCertificate *tls.Certificate
// tlsRotationNotifier is a notifier for certificate rotations.
tlsRotationNotifier *pubsub.Broker
}
// WatchCertificateRotations subscribes to TLS certificate rotation notifications.
func (i *Identity) WatchCertificateRotations() (<-chan struct{}, pubsub.ClosableSubscription) {
typedCh := make(chan struct{})
sub := i.tlsRotationNotifier.Subscribe()
sub.Unwrap(typedCh)
return typedCh, sub
}
// RotateCertificates rotates the identity's TLS certificates.
// This is called from worker/registration/worker.go every
// CfgRegistrationRotateCerts epochs (if it's non-zero).
func (i *Identity) RotateCertificates() error {
if i.DoNotRotateTLS {
// If we loaded an existing certificate or persisted a generated one
// to disk, certificate rotation must be disabled.
// This behaviour is required for sentry nodes to work.
return ErrCertificateRotationForbidden
}
i.Lock()
defer i.Unlock()
if i.tlsCertificate != nil {
// Use the prepared certificate.
if i.nextTLSCertificate != nil {
i.tlsCertificate = i.nextTLSCertificate
i.tlsSigner = i.nextTLSSigner
}
// Generate a new TLS certificate to be used in the next rotation.
var err error
i.nextTLSCertificate, err = tlsCert.Generate(CommonName)
if err != nil {
return err
}
i.nextTLSSigner = memory.NewFromRuntime(i.nextTLSCertificate.PrivateKey.(ed25519.PrivateKey))
i.tlsRotationNotifier.Broadcast(struct{}{})
}
return nil
}
// GetTLSSigner returns the current TLS signer.
func (i *Identity) GetTLSSigner() signature.Signer {
i.RLock()
defer i.RUnlock()
return i.tlsSigner
}
// GetTLSCertificate returns the current TLS certificate.
func (i *Identity) GetTLSCertificate() *tls.Certificate {
i.RLock()
defer i.RUnlock()
return i.tlsCertificate
}
// SetTLSCertificate sets the current TLS certificate.
func (i *Identity) SetTLSCertificate(cert *tls.Certificate) {
i.Lock()
defer i.Unlock()
i.tlsCertificate = cert
i.tlsSigner = memory.NewFromRuntime(cert.PrivateKey.(ed25519.PrivateKey))
}
// GetNextTLSSigner returns the next TLS signer.
func (i *Identity) GetNextTLSSigner() signature.Signer {
i.RLock()
defer i.RUnlock()
return i.nextTLSSigner
}
// GetNextTLSCertificate returns the next TLS certificate.
func (i *Identity) GetNextTLSCertificate() *tls.Certificate {
i.RLock()
defer i.RUnlock()
return i.nextTLSCertificate
}
// GetTLSPubKeys returns a list of currently valid TLS public keys.
func (i *Identity) GetTLSPubKeys() []signature.PublicKey {
i.RLock()
defer i.RUnlock()
var pubKeys []signature.PublicKey
if i.tlsSigner != nil {
pubKeys = append(pubKeys, i.tlsSigner.Public())
}
if i.nextTLSSigner != nil {
pubKeys = append(pubKeys, i.nextTLSSigner.Public())
}
return pubKeys
}
// Load loads an identity.
func Load(dataDir string, signerFactory signature.SignerFactory) (*Identity, error) {
return doLoadOrGenerate(dataDir, signerFactory, false, false)
}
// LoadOrGenerate loads or generates an identity.
// If persistTLS is true, it saves the generated TLS certificates to disk.
func LoadOrGenerate(dataDir string, signerFactory signature.SignerFactory, persistTLS bool) (*Identity, error) {
return doLoadOrGenerate(dataDir, signerFactory, true, persistTLS)
}
func doLoadOrGenerate(dataDir string, signerFactory signature.SignerFactory, shouldGenerate, persistTLS bool) (*Identity, error) {
var signers []signature.Signer
for _, v := range []struct {
role signature.SignerRole
pubFn string
}{
{signature.SignerNode, NodeKeyPubFilename},
{signature.SignerP2P, P2PKeyPubFilename},
{signature.SignerConsensus, ConsensusKeyPubFilename},
} {
signer, err := signerFactory.Load(v.role)
switch err {
case nil:
case signature.ErrNotExist:
if !shouldGenerate {
return nil, err
}
if signer, err = signerFactory.Generate(v.role, rand.Reader); err != nil {
return nil, err
}
default:
return nil, err
}
var checkPub signature.PublicKey
if err = checkPub.LoadPEM(filepath.Join(dataDir, v.pubFn), signer); err != nil {
return nil, err
}
signers = append(signers, signer)
}
var (
nextCert *tls.Certificate
nextSigner signature.Signer
dnr bool
)
// First, check if we can load the TLS certificate from disk.
tlsCertPath, tlsKeyPath := TLSCertPaths(dataDir)
cert, err := tlsCert.Load(tlsCertPath, tlsKeyPath)
if err == nil {
// Load successful, ensure that we won't ever rotate the certificates.
dnr = true
} else {
// Freshly generate TLS certificates.
cert, err = tlsCert.Generate(CommonName)
if err != nil {
return nil, err
}
if persistTLS {
// Save generated TLS certificate to disk.
err = tlsCert.Save(tlsCertPath, tlsKeyPath, cert)
if err != nil {
return nil, err
}
// Disable TLS rotation if we're persisting TLS certificates.
dnr = true
} else {
// Not persisting TLS certificate to disk, generate a new
// certificate to be used in the next rotation.
nextCert, err = tlsCert.Generate(CommonName)
if err != nil {
return nil, err
}
nextSigner = memory.NewFromRuntime(nextCert.PrivateKey.(ed25519.PrivateKey))
}
}
// Load or generate the sentry client certificate for this node.
tlsSentryClientCertPath, tlsSentryClientKeyPath := TLSSentryClientCertPaths(dataDir)
sentryClientCert, err := tlsCert.Load(tlsSentryClientCertPath, tlsSentryClientKeyPath)
if err != nil {
// Load failed, generate fresh sentry client cert.
sentryClientCert, err = tlsCert.Generate(CommonName)
if err != nil {
return nil, err
}
// And save it to disk.
err = tlsCert.Save(tlsSentryClientCertPath, tlsSentryClientKeyPath, sentryClientCert)
if err != nil {
return nil, err
}
}
// Load or generate the beacon scalar for this node.
beaconScalarPath := filepath.Join(dataDir, beaconScalarFilename)
var beaconScalar pvss.Scalar
if err := beaconScalar.LoadOrGeneratePEM(beaconScalarPath); err != nil {
return nil, err
}
return &Identity{
NodeSigner: signers[0],
P2PSigner: signers[1],
ConsensusSigner: signers[2],
BeaconScalar: beaconScalar,
tlsSigner: memory.NewFromRuntime(cert.PrivateKey.(ed25519.PrivateKey)),
tlsCertificate: cert,
nextTLSSigner: nextSigner,
nextTLSCertificate: nextCert,
DoNotRotateTLS: dnr,
TLSSentryClientCertificate: sentryClientCert,
tlsRotationNotifier: pubsub.NewBroker(false),
}, nil
}
// TLSCertPaths returns the TLS private key and certificate paths relative
// to the passed data directory.
func TLSCertPaths(dataDir string) (string, string) {
var (
tlsKeyPath = filepath.Join(dataDir, tlsKeyFilename)
tlsCertPath = filepath.Join(dataDir, tlsCertFilename)
)
return tlsCertPath, tlsKeyPath
}
// TLSSentryClientCertPaths returns the sentry client TLS private key and
// certificate paths relative to the passed data directory.
func TLSSentryClientCertPaths(dataDir string) (string, string) {
var (
tlsKeyPath = filepath.Join(dataDir, tlsSentryClientKeyFilename)
tlsCertPath = filepath.Join(dataDir, tlsSentryClientCertFilename)
)
return tlsCertPath, tlsKeyPath
}