1// Package localca implements a localca that is useful for testing the 2// transport package. To use the localca, see the New and Load 3// functions. 4package localca 5 6import ( 7 "crypto/x509" 8 "encoding/pem" 9 "errors" 10 "time" 11 12 "github.com/cloudflare/cfssl/config" 13 "github.com/cloudflare/cfssl/csr" 14 "github.com/cloudflare/cfssl/helpers" 15 "github.com/cloudflare/cfssl/initca" 16 "github.com/cloudflare/cfssl/signer" 17 "github.com/cloudflare/cfssl/signer/local" 18 "github.com/kisom/goutils/assert" 19) 20 21// CA is a local transport CertificateAuthority that is useful for 22// tests. 23type CA struct { 24 s *local.Signer 25 disabled bool 26 27 // Label and Profile are used to select the CFSSL signer 28 // components if they should be anything but the default. 29 Label string `json:"label"` 30 Profile string `json:"profile"` 31 32 // The KeyFile and CertFile are required when using Load to 33 // construct a CA. 34 KeyFile string `json:"private_key,omitempty"` 35 CertFile string `json:"certificate,omitempty"` 36} 37 38// Toggle switches the CA between operable mode and inoperable 39// mode. This is useful in testing to verify behaviours when a CA is 40// unavailable. 41func (lca *CA) Toggle() { 42 lca.disabled = !lca.disabled 43} 44 45var errNotSetup = errors.New("transport: local CA has not been setup") 46 47// CACertificate returns the certificate authority's certificate. 48func (lca *CA) CACertificate() ([]byte, error) { 49 if lca.s == nil { 50 return nil, errNotSetup 51 } 52 53 cert, err := lca.s.Certificate(lca.Label, lca.Profile) 54 if err != nil { 55 return nil, err 56 } 57 58 p := &pem.Block{ 59 Type: "CERTIFICATE", 60 Bytes: cert.Raw, 61 } 62 return pem.EncodeToMemory(p), nil 63} 64 65var errDisabled = errors.New("transport: local CA is deactivated") 66 67// SignCSR submits a PKCS #10 certificate signing request to a CA for 68// signing. 69func (lca *CA) SignCSR(csrPEM []byte) ([]byte, error) { 70 if lca == nil || lca.s == nil { 71 return nil, errNotSetup 72 } 73 74 if lca.disabled { 75 return nil, errDisabled 76 } 77 78 p, _ := pem.Decode(csrPEM) 79 if p == nil || p.Type != "CERTIFICATE REQUEST" { 80 return nil, errors.New("transport: invalid PEM-encoded certificate signing request") 81 } 82 83 csr, err := x509.ParseCertificateRequest(p.Bytes) 84 if err != nil { 85 return nil, err 86 } 87 88 hosts := make([]string, 0, len(csr.DNSNames)+len(csr.IPAddresses)) 89 copy(hosts, csr.DNSNames) 90 91 for i := range csr.IPAddresses { 92 hosts = append(hosts, csr.IPAddresses[i].String()) 93 } 94 95 sreq := signer.SignRequest{ 96 Hosts: hosts, 97 Request: string(csrPEM), 98 Profile: lca.Profile, 99 Label: lca.Label, 100 } 101 102 return lca.s.Sign(sreq) 103} 104 105// ExampleRequest can be used as a sample request, or the returned 106// request can be modified. 107func ExampleRequest() *csr.CertificateRequest { 108 return &csr.CertificateRequest{ 109 Hosts: []string{"localhost"}, 110 KeyRequest: &csr.KeyRequest{ 111 A: "ecdsa", 112 S: 256, 113 }, 114 CN: "Transport Failover Test Local CA", 115 CA: &csr.CAConfig{ 116 PathLength: 1, 117 Expiry: "30m", 118 }, 119 } 120} 121 122// ExampleSigningConfig returns a sample config.Signing with only a 123// default profile. 124func ExampleSigningConfig() *config.Signing { 125 return &config.Signing{ 126 Default: &config.SigningProfile{ 127 Expiry: 15 * time.Minute, 128 Usage: []string{ 129 "server auth", "client auth", 130 "signing", "key encipherment", 131 }, 132 }, 133 } 134} 135 136// New generates a new CA from a certificate request and signing profile. 137func New(req *csr.CertificateRequest, profiles *config.Signing) (*CA, error) { 138 certPEM, _, keyPEM, err := initca.New(req) 139 if err != nil { 140 return nil, err 141 } 142 143 // If initca returns successfully, the following (which are 144 // all CFSSL internal functions) should not return an 145 // error. If they do, we should abort --- something about 146 // CFSSL has become inconsistent, and it can't be trusted. 147 148 priv, err := helpers.ParsePrivateKeyPEM(keyPEM) 149 assert.NoError(err, "CFSSL-generated private key can't be parsed") 150 151 cert, err := helpers.ParseCertificatePEM(certPEM) 152 assert.NoError(err, "CFSSL-generated certificate can't be parsed") 153 154 s, err := local.NewSigner(priv, cert, helpers.SignerAlgo(priv), profiles) 155 assert.NoError(err, "a signer could not be constructed") 156 157 return NewFromSigner(s), nil 158} 159 160// NewFromSigner constructs a local CA from a CFSSL signer. 161func NewFromSigner(s *local.Signer) *CA { 162 return &CA{s: s} 163} 164 165// Load reads the key and certificate from the files specified in the 166// CA. 167func Load(lca *CA, profiles *config.Signing) (err error) { 168 lca.s, err = local.NewSignerFromFile(lca.CertFile, lca.KeyFile, profiles) 169 return err 170} 171