-
Notifications
You must be signed in to change notification settings - Fork 11
/
revocations.go
173 lines (145 loc) · 4.92 KB
/
revocations.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
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package extensions
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/binary"
"time"
"github.com/zeebo/errs"
"storj.io/common/peertls"
"storj.io/common/pkcrypto"
)
var (
// RevocationCheckHandler ensures that a remote peer's certificate chain
// doesn't contain any revoked certificates.
RevocationCheckHandler = NewHandlerFactory(&RevocationExtID, revocationChecker)
// RevocationUpdateHandler looks for certificate revocation extensions on a
// remote peer's certificate chain, adding them to the revocation DB if valid.
RevocationUpdateHandler = NewHandlerFactory(&RevocationExtID, revocationUpdater)
)
// ErrRevocation is used when an error occurs involving a certificate revocation.
var ErrRevocation = errs.Class("revocation processing")
// ErrRevocationDB is used when an error occurs involving the revocations database.
var ErrRevocationDB = errs.Class("revocation database")
// ErrRevokedCert is used when a certificate in the chain is revoked and not expected to be.
var ErrRevokedCert = ErrRevocation.New("a certificate in the chain is revoked")
// ErrRevocationTimestamp is used when a revocation's timestamp is older than the last recorded revocation.
var ErrRevocationTimestamp = Error.New("revocation timestamp is older than last known revocation")
// Revocation represents a certificate revocation for storage in the revocation
// database and for use in a TLS extension.
type Revocation struct {
Timestamp int64
KeyHash []byte
Signature []byte
}
// RevocationDB stores certificate revocation data.
type RevocationDB interface {
Get(ctx context.Context, chain []*x509.Certificate) (*Revocation, error)
Put(ctx context.Context, chain []*x509.Certificate, ext pkix.Extension) error
List(ctx context.Context) ([]*Revocation, error)
}
// NewRevocationExt generates a revocation extension for a certificate.
func NewRevocationExt(key crypto.PrivateKey, revokedCert *x509.Certificate) (pkix.Extension, error) {
nowUnix := time.Now().Unix()
keyHash, err := peertls.DoubleSHA256PublicKey(revokedCert.PublicKey)
if err != nil {
return pkix.Extension{}, err
}
rev := Revocation{
Timestamp: nowUnix,
KeyHash: keyHash[:],
}
if err := rev.Sign(key); err != nil {
return pkix.Extension{}, err
}
revBytes, err := rev.Marshal()
if err != nil {
return pkix.Extension{}, err
}
ext := pkix.Extension{
Id: RevocationExtID,
Value: revBytes,
}
return ext, nil
}
func revocationChecker(opts *Options) HandlerFunc {
return func(_ pkix.Extension, chains [][]*x509.Certificate) error {
ca, leaf := chains[0][peertls.CAIndex], chains[0][peertls.LeafIndex]
lastRev, lastRevErr := opts.RevocationDB.Get(context.TODO(), chains[0])
if lastRevErr != nil {
return Error.Wrap(lastRevErr)
}
if lastRev == nil {
return nil
}
nodeID, err := peertls.DoubleSHA256PublicKey(ca.PublicKey)
if err != nil {
return err
}
leafKeyHash, err := peertls.DoubleSHA256PublicKey(leaf.PublicKey)
if err != nil {
return err
}
// NB: we trust that anything that made it into the revocation DB is valid
// (i.e. no need for further verification)
switch {
case bytes.Equal(lastRev.KeyHash, nodeID[:]):
fallthrough
case bytes.Equal(lastRev.KeyHash, leafKeyHash[:]):
return ErrRevokedCert
default:
return nil
}
}
}
func revocationUpdater(opts *Options) HandlerFunc {
return func(ext pkix.Extension, chains [][]*x509.Certificate) error {
err := opts.RevocationDB.Put(context.TODO(), chains[0], ext)
return err
}
}
// Verify checks if the signature of the revocation was produced by the passed cert's public key.
func (r Revocation) Verify(signingCert *x509.Certificate) error {
pubKey, ok := signingCert.PublicKey.(crypto.PublicKey)
if !ok {
return pkcrypto.ErrUnsupportedKey.New("%T", signingCert.PublicKey)
}
data := r.TBSBytes()
err := pkcrypto.HashAndVerifySignature(pubKey, data, r.Signature)
return err
}
// TBSBytes (ToBeSigned) returns the hash of the revoked certificate key hash
// and the timestamp (i.e. hash(hash(cert bytes) + timestamp)).
func (r *Revocation) TBSBytes() []byte {
var tsBytes [binary.MaxVarintLen64]byte
binary.PutVarint(tsBytes[:], r.Timestamp)
toHash := append(append([]byte{}, r.KeyHash...), tsBytes[:]...)
return pkcrypto.SHA256Hash(toHash)
}
// Sign generates a signature using the passed key and attaches it to the revocation.
func (r *Revocation) Sign(key crypto.PrivateKey) error {
data := r.TBSBytes()
sig, err := pkcrypto.HashAndSign(key, data)
if err != nil {
return err
}
r.Signature = sig
return nil
}
// Marshal serializes a revocation to bytes.
func (r Revocation) Marshal() ([]byte, error) {
return (&revocationEncoder{}).encode(r)
}
// Unmarshal deserializes a revocation from bytes.
func (r *Revocation) Unmarshal(data []byte) error {
revocation, err := (&revocationDecoder{}).decode(data)
if err != nil {
return err
}
*r = revocation
return nil
}