-
Notifications
You must be signed in to change notification settings - Fork 1
/
bifrost.go
189 lines (170 loc) · 5.29 KB
/
bifrost.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
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Bifrost is an mTLS authentication toolkit.
package bifrost
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/google/uuid"
)
// Signature and Public Key Algorithms
const (
SignatureAlgorithm = x509.ECDSAWithSHA256
PublicKeyAlgorithm = x509.ECDSA
)
// Errors.
var (
ErrCertificateFormat = errors.New("invalid certificate format")
ErrCertificateRequestFormat = errors.New("invalid certificate request format")
ErrWrongNamespace = errors.New("wrong namespace")
)
// NewIdentity generates a new ECDSA private key.
// The private key is also returned as a PEM encoded DER bytes in the second return value.
func NewIdentity() (*ecdsa.PrivateKey, []byte, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
keyDer, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, err
}
keyPem := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY", Headers: nil, Bytes: keyDer,
})
return key, keyPem, nil
}
// UUID returns a unique identifier derived from the namespace and the client's public key identity.
// The UUID is generated by SHA-1 hashing the namesapce UUID
// with the big endian bytes of the X and Y curve points from the public key.
func UUID(ns uuid.UUID, pubkey *ecdsa.PublicKey) uuid.UUID {
// X and Y are guaranteed to by 256 bits (32 bytes) for elliptic curve P256 keys
var buf [64]byte
pubkey.X.FillBytes(buf[:32])
pubkey.Y.FillBytes(buf[32:])
return uuid.NewSHA1(ns, buf[:])
}
// ParseCertificate parses a DER encoded certificate and validates it.
// On success, it returns the bifrost namespace, certificate public key,
// and the parsed certificate.
func ParseCertificate(der []byte) (uuid.UUID, *x509.Certificate, *ecdsa.PublicKey, error) {
cert, err := x509.ParseCertificate(der)
if err != nil {
return uuid.Nil, nil, nil, err
}
ns, key, err := ValidateCertificate(cert)
if err != nil {
return uuid.Nil, nil, nil, err
}
return ns, cert, key, nil
}
// ValidateCertificate validates a bifrost certificate.
// On success, it returns the bifrost namespace and certificate public key.
func ValidateCertificate(cert *x509.Certificate) (uuid.UUID, *ecdsa.PublicKey, error) {
// Check for bifrost signature algorithm
if cert.SignatureAlgorithm != SignatureAlgorithm {
return uuid.Nil, nil, fmt.Errorf(
"%w: unsupported signature algorithm '%s'",
ErrCertificateRequestFormat,
cert.SignatureAlgorithm,
)
}
// Parse identity namespace
if len(cert.Subject.Organization) != 1 {
return uuid.Nil, nil, fmt.Errorf("%w: missing identity namespace", ErrCertificateFormat)
}
rawNS := cert.Subject.Organization[0]
ns, err := uuid.Parse(rawNS)
if err != nil {
return uuid.Nil, nil, fmt.Errorf(
"%w: invalid identity namespace %s: %w",
ErrCertificateFormat,
rawNS,
err,
)
}
pubkey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return uuid.Nil, nil, fmt.Errorf(
"%w: invalid public key type: '%T'",
ErrCertificateFormat,
cert.PublicKey,
)
}
// Check if calculated UUID matches the UUID in the certificate
id := UUID(ns, pubkey)
cid, err := uuid.Parse(cert.Subject.CommonName)
if err != nil {
return uuid.Nil, nil, fmt.Errorf("%w: invalid subj CN '%s', %v",
ErrCertificateFormat, cert.Subject.CommonName, err)
}
if cid != id {
return uuid.Nil, nil, fmt.Errorf("%w: incorrect identity", ErrCertificateFormat)
}
return ns, pubkey, nil
}
func ParseCertificateRequest(
der []byte,
) (uuid.UUID, *x509.CertificateRequest, *ecdsa.PublicKey, error) {
csr, err := x509.ParseCertificateRequest(der)
if err != nil {
return uuid.Nil, nil, nil, err
}
ns, key, err := ValidateCertificateRequest(csr)
if err != nil {
return uuid.Nil, nil, nil, err
}
return ns, csr, key, nil
}
func ValidateCertificateRequest(csr *x509.CertificateRequest) (uuid.UUID, *ecdsa.PublicKey, error) {
// Check for bifrost signature algorithm
if csr.SignatureAlgorithm != SignatureAlgorithm {
return uuid.Nil, nil, fmt.Errorf(
"%w: unsupported signature algorithm '%s'",
ErrCertificateRequestFormat,
csr.SignatureAlgorithm,
)
}
// Parse identity namespace
if len(csr.Subject.Organization) != 1 {
return uuid.Nil, nil, fmt.Errorf(
"%w: missing identity namespace",
ErrCertificateRequestFormat,
)
}
rawNS := csr.Subject.Organization[0]
ns, err := uuid.Parse(rawNS)
if err != nil {
return uuid.Nil, nil, fmt.Errorf(
"%w: invalid identity namespace %s: %w",
ErrCertificateRequestFormat,
rawNS,
err,
)
}
pubkey, ok := csr.PublicKey.(*ecdsa.PublicKey)
if !ok {
return uuid.Nil, nil, fmt.Errorf(
"%w: invalid public key type: '%T'",
ErrCertificateRequestFormat,
csr.PublicKey,
)
}
// Check if calculated UUID matches the UUID in the certificate
id := UUID(ns, pubkey)
cid, err := uuid.Parse(csr.Subject.CommonName)
if err != nil {
return uuid.Nil, nil, fmt.Errorf("%w: invalid identity '%s', %v",
ErrCertificateRequestFormat, csr.Subject.CommonName, err)
}
if cid != id {
return uuid.Nil, nil, fmt.Errorf("%w: incorrect identity", ErrCertificateRequestFormat)
}
return ns, pubkey, nil
}