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