github.com/outbrain/consul@v1.4.5/agent/connect/testing_ca.go (about) 1 package connect 2 3 import ( 4 "bytes" 5 "crypto" 6 "crypto/ecdsa" 7 "crypto/elliptic" 8 "crypto/rand" 9 "crypto/x509" 10 "crypto/x509/pkix" 11 "encoding/pem" 12 "fmt" 13 "math/big" 14 "net/url" 15 "sync/atomic" 16 "time" 17 18 "github.com/hashicorp/consul/agent/structs" 19 "github.com/hashicorp/go-uuid" 20 "github.com/mitchellh/go-testing-interface" 21 ) 22 23 // TestClusterID is the Consul cluster ID for testing. 24 const TestClusterID = "11111111-2222-3333-4444-555555555555" 25 26 // testCACounter is just an atomically incremented counter for creating 27 // unique names for the CA certs. 28 var testCACounter uint64 29 30 // TestCA creates a test CA certificate and signing key and returns it 31 // in the CARoot structure format. The returned CA will be set as Active = true. 32 // 33 // If xc is non-nil, then the returned certificate will have a signing cert 34 // that is cross-signed with the previous cert, and this will be set as 35 // SigningCert. 36 func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot { 37 var result structs.CARoot 38 result.Active = true 39 result.Name = fmt.Sprintf("Test CA %d", atomic.AddUint64(&testCACounter, 1)) 40 41 // Create the private key we'll use for this CA cert. 42 signer, keyPEM := testPrivateKey(t) 43 result.SigningKey = keyPEM 44 result.SigningKeyID = HexString(testKeyID(t, signer.Public())) 45 46 // The serial number for the cert 47 sn, err := testSerialNumber() 48 if err != nil { 49 t.Fatalf("error generating serial number: %s", err) 50 } 51 52 // The URI (SPIFFE compatible) for the cert 53 id := &SpiffeIDSigning{ClusterID: TestClusterID, Domain: "consul"} 54 55 // Create the CA cert 56 template := x509.Certificate{ 57 SerialNumber: sn, 58 Subject: pkix.Name{CommonName: result.Name}, 59 URIs: []*url.URL{id.URI()}, 60 BasicConstraintsValid: true, 61 KeyUsage: x509.KeyUsageCertSign | 62 x509.KeyUsageCRLSign | 63 x509.KeyUsageDigitalSignature, 64 IsCA: true, 65 NotAfter: time.Now().AddDate(10, 0, 0), 66 NotBefore: time.Now(), 67 AuthorityKeyId: testKeyID(t, signer.Public()), 68 SubjectKeyId: testKeyID(t, signer.Public()), 69 } 70 71 bs, err := x509.CreateCertificate( 72 rand.Reader, &template, &template, signer.Public(), signer) 73 if err != nil { 74 t.Fatalf("error generating CA certificate: %s", err) 75 } 76 77 var buf bytes.Buffer 78 err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) 79 if err != nil { 80 t.Fatalf("error encoding private key: %s", err) 81 } 82 result.RootCert = buf.String() 83 result.ID, err = CalculateCertFingerprint(result.RootCert) 84 if err != nil { 85 t.Fatalf("error generating CA ID fingerprint: %s", err) 86 } 87 result.SerialNumber = uint64(sn.Int64()) 88 result.NotBefore = template.NotBefore.UTC() 89 result.NotAfter = template.NotAfter.UTC() 90 91 // If there is a prior CA to cross-sign with, then we need to create that 92 // and set it as the signing cert. 93 if xc != nil { 94 xccert, err := ParseCert(xc.RootCert) 95 if err != nil { 96 t.Fatalf("error parsing CA cert: %s", err) 97 } 98 xcsigner, err := ParseSigner(xc.SigningKey) 99 if err != nil { 100 t.Fatalf("error parsing signing key: %s", err) 101 } 102 103 // Set the authority key to be the previous one. 104 // NOTE(mitchellh): From Paul Banks: if we have to cross-sign a cert 105 // that came from outside (e.g. vault) we can't rely on them using the 106 // same KeyID hashing algo we do so we'd need to actually copy this 107 // from the xc cert's subjectKeyIdentifier extension. 108 template.AuthorityKeyId = testKeyID(t, xcsigner.Public()) 109 110 // Create the new certificate where the parent is the previous 111 // CA, the public key is the new public key, and the signing private 112 // key is the old private key. 113 bs, err := x509.CreateCertificate( 114 rand.Reader, &template, xccert, signer.Public(), xcsigner) 115 if err != nil { 116 t.Fatalf("error generating CA certificate: %s", err) 117 } 118 119 var buf bytes.Buffer 120 err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) 121 if err != nil { 122 t.Fatalf("error encoding private key: %s", err) 123 } 124 result.SigningCert = buf.String() 125 } 126 127 return &result 128 } 129 130 // TestLeaf returns a valid leaf certificate and it's private key for the named 131 // service with the given CA Root. 132 func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string) { 133 // Parse the CA cert and signing key from the root 134 cert := root.SigningCert 135 if cert == "" { 136 cert = root.RootCert 137 } 138 caCert, err := ParseCert(cert) 139 if err != nil { 140 t.Fatalf("error parsing CA cert: %s", err) 141 } 142 caSigner, err := ParseSigner(root.SigningKey) 143 if err != nil { 144 t.Fatalf("error parsing signing key: %s", err) 145 } 146 147 // Build the SPIFFE ID 148 spiffeId := &SpiffeIDService{ 149 Host: fmt.Sprintf("%s.consul", TestClusterID), 150 Namespace: "default", 151 Datacenter: "dc1", 152 Service: service, 153 } 154 155 // The serial number for the cert 156 sn, err := testSerialNumber() 157 if err != nil { 158 t.Fatalf("error generating serial number: %s", err) 159 } 160 161 // Generate fresh private key 162 pkSigner, pkPEM, err := GeneratePrivateKey() 163 if err != nil { 164 t.Fatalf("failed to generate private key: %s", err) 165 } 166 167 // Cert template for generation 168 template := x509.Certificate{ 169 SerialNumber: sn, 170 Subject: pkix.Name{CommonName: service}, 171 URIs: []*url.URL{spiffeId.URI()}, 172 SignatureAlgorithm: x509.ECDSAWithSHA256, 173 BasicConstraintsValid: true, 174 KeyUsage: x509.KeyUsageDataEncipherment | 175 x509.KeyUsageKeyAgreement | 176 x509.KeyUsageDigitalSignature | 177 x509.KeyUsageKeyEncipherment, 178 ExtKeyUsage: []x509.ExtKeyUsage{ 179 x509.ExtKeyUsageClientAuth, 180 x509.ExtKeyUsageServerAuth, 181 }, 182 NotAfter: time.Now().AddDate(10, 0, 0), 183 NotBefore: time.Now(), 184 AuthorityKeyId: testKeyID(t, caSigner.Public()), 185 SubjectKeyId: testKeyID(t, pkSigner.Public()), 186 } 187 188 // Create the certificate, PEM encode it and return that value. 189 var buf bytes.Buffer 190 bs, err := x509.CreateCertificate( 191 rand.Reader, &template, caCert, pkSigner.Public(), caSigner) 192 if err != nil { 193 t.Fatalf("error generating certificate: %s", err) 194 } 195 err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) 196 if err != nil { 197 t.Fatalf("error encoding private key: %s", err) 198 } 199 200 return buf.String(), pkPEM 201 } 202 203 // TestCSR returns a CSR to sign the given service along with the PEM-encoded 204 // private key for this certificate. 205 func TestCSR(t testing.T, uri CertURI) (string, string) { 206 template := &x509.CertificateRequest{ 207 URIs: []*url.URL{uri.URI()}, 208 SignatureAlgorithm: x509.ECDSAWithSHA256, 209 } 210 211 // Create the private key we'll use 212 signer, pkPEM := testPrivateKey(t) 213 214 // Create the CSR itself 215 var csrBuf bytes.Buffer 216 bs, err := x509.CreateCertificateRequest(rand.Reader, template, signer) 217 if err != nil { 218 t.Fatalf("error creating CSR: %s", err) 219 } 220 221 err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs}) 222 if err != nil { 223 t.Fatalf("error encoding CSR: %s", err) 224 } 225 226 return csrBuf.String(), pkPEM 227 } 228 229 // testKeyID returns a KeyID from the given public key. This just calls 230 // KeyId but handles errors for tests. 231 func testKeyID(t testing.T, raw interface{}) []byte { 232 result, err := KeyId(raw) 233 if err != nil { 234 t.Fatalf("KeyId error: %s", err) 235 } 236 237 return result 238 } 239 240 // testPrivateKey creates an ECDSA based private key. Both a crypto.Signer and 241 // the key in PEM form are returned. 242 // 243 // NOTE(banks): this was memoized to save entropy during tests but it turns out 244 // crypto/rand will never block and always reads from /dev/urandom on unix OSes 245 // which does not consume entropy. 246 // 247 // If we find by profiling it's taking a lot of cycles we could optimize/cache 248 // again but we at least need to use different keys for each distinct CA (when 249 // multiple CAs are generated at once e.g. to test cross-signing) and a 250 // different one again for the leafs otherwise we risk tests that have false 251 // positives since signatures from different logical cert's keys are 252 // indistinguishable, but worse we build validation chains using AuthorityKeyID 253 // which will be the same for multiple CAs/Leafs. Also note that our UUID 254 // generator also reads from crypto rand and is called far more often during 255 // tests than this will be. 256 func testPrivateKey(t testing.T) (crypto.Signer, string) { 257 pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 258 if err != nil { 259 t.Fatalf("error generating private key: %s", err) 260 } 261 262 bs, err := x509.MarshalECPrivateKey(pk) 263 if err != nil { 264 t.Fatalf("error generating private key: %s", err) 265 } 266 267 var buf bytes.Buffer 268 err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs}) 269 if err != nil { 270 t.Fatalf("error encoding private key: %s", err) 271 } 272 273 return pk, buf.String() 274 } 275 276 // testSerialNumber generates a serial number suitable for a certificate. For 277 // testing, this just sets it to a random number, but one that can fit in a 278 // uint64 since we use that in our datastructures and assume cert serials will 279 // fit in that for now. 280 func testSerialNumber() (*big.Int, error) { 281 return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(63), nil)) 282 } 283 284 // testUUID generates a UUID for testing. 285 func testUUID(t testing.T) string { 286 ret, err := uuid.GenerateUUID() 287 if err != nil { 288 t.Fatalf("Unable to generate a UUID, %s", err) 289 } 290 291 return ret 292 } 293 294 // TestAgentRPC is an interface that an RPC client must implement. This is a 295 // helper interface that is implemented by the agent delegate so that test 296 // helpers can make RPCs without introducing an import cycle on `agent`. 297 type TestAgentRPC interface { 298 RPC(method string, args interface{}, reply interface{}) error 299 } 300 301 // TestCAConfigSet sets a CARoot returned by TestCA into the TestAgent state. It 302 // requires that TestAgent had connect enabled in it's config. If ca is nil, a 303 // new CA is created. 304 // 305 // It returns the CARoot passed or created. 306 // 307 // Note that we have to use an interface for the TestAgent.RPC method since we 308 // can't introduce an import cycle by importing `agent.TestAgent` here directly. 309 // It also means this will work in a few other places we mock that method. 310 func TestCAConfigSet(t testing.T, a TestAgentRPC, 311 ca *structs.CARoot) *structs.CARoot { 312 t.Helper() 313 314 if ca == nil { 315 ca = TestCA(t, nil) 316 } 317 newConfig := &structs.CAConfiguration{ 318 Provider: "consul", 319 Config: map[string]interface{}{ 320 "PrivateKey": ca.SigningKey, 321 "RootCert": ca.RootCert, 322 "RotationPeriod": 180 * 24 * time.Hour, 323 }, 324 } 325 args := &structs.CARequest{ 326 Datacenter: "dc1", 327 Config: newConfig, 328 } 329 var reply interface{} 330 331 err := a.RPC("ConnectCA.ConfigurationSet", args, &reply) 332 if err != nil { 333 t.Fatalf("failed to set test CA config: %s", err) 334 } 335 return ca 336 }