File: minica.go

package info (click to toggle)
golang-github-smallstep-crypto 0.63.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,800 kB
  • sloc: sh: 66; makefile: 50
file content (185 lines) | stat: -rw-r--r-- 5,382 bytes parent folder | download
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
package minica

import (
	"crypto"
	"crypto/x509"
	"fmt"
	"time"

	"golang.org/x/crypto/ssh"

	"go.step.sm/crypto/internal/utils"
	"go.step.sm/crypto/sshutil"
	"go.step.sm/crypto/x509util"
)

// CA is the implementation of a simple X.509 and SSH CA.
type CA struct {
	Root          *x509.Certificate
	RootSigner    crypto.Signer
	Intermediate  *x509.Certificate
	Signer        crypto.Signer
	SSHHostSigner ssh.Signer
	SSHUserSigner ssh.Signer
}

// New creates a new MiniCA, the custom options allows to overwrite templates,
// signer types and certificate names.
func New(opts ...Option) (*CA, error) {
	now := time.Now()
	o := newOptions().apply(opts)

	// Create root
	rootSubject := o.Name + " Root CA"
	rootSigner, err := o.GetSigner()
	if err != nil {
		return nil, err
	}
	rootCR, err := x509util.CreateCertificateRequest(rootSubject, []string{}, rootSigner)
	if err != nil {
		return nil, err
	}
	cert, err := x509util.NewCertificate(rootCR, x509util.WithTemplate(o.RootTemplate, x509util.CreateTemplateData(rootSubject, []string{})))
	if err != nil {
		return nil, err
	}
	template := cert.GetCertificate()
	template.NotBefore = now
	template.NotAfter = now.Add(24 * time.Hour)
	root, err := x509util.CreateCertificate(template, template, rootSigner.Public(), rootSigner)
	if err != nil {
		return nil, err
	}

	// Create intermediate
	intSubject := o.Name + " Intermediate CA"
	intSigner, err := o.GetSigner()
	if err != nil {
		return nil, err
	}
	intCR, err := x509util.CreateCertificateRequest(intSubject, []string{}, intSigner)
	if err != nil {
		return nil, err
	}
	cert, err = x509util.NewCertificate(intCR, x509util.WithTemplate(o.IntermediateTemplate, x509util.CreateTemplateData(intSubject, []string{})))
	if err != nil {
		return nil, err
	}
	template = cert.GetCertificate()
	template.NotBefore = now
	template.NotAfter = now.Add(24 * time.Hour)
	intermediate, err := x509util.CreateCertificate(template, root, intSigner.Public(), rootSigner)
	if err != nil {
		return nil, err
	}

	// Ssh host signer
	signer, err := o.GetSigner()
	if err != nil {
		return nil, err
	}
	sshHostSigner, err := ssh.NewSignerFromSigner(signer)
	if err != nil {
		return nil, err
	}

	// Ssh user signer
	signer, err = o.GetSigner()
	if err != nil {
		return nil, err
	}
	sshUserSigner, err := ssh.NewSignerFromSigner(signer)
	if err != nil {
		return nil, err
	}

	return &CA{
		Root:          root,
		RootSigner:    rootSigner,
		Intermediate:  intermediate,
		Signer:        intSigner,
		SSHHostSigner: sshHostSigner,
		SSHUserSigner: sshUserSigner,
	}, nil
}

// Sign signs an X.509 certificate template using the intermediate certificate.
// Sign will automatically populate the following fields if they are not
// specified:
//
//   - NotBefore will be set to the current time.
//   - NotAfter will be set to 24 hours after NotBefore.
//   - SerialNumber will be automatically generated.
//   - SubjectKeyId will be automatically generated.
func (c *CA) Sign(template *x509.Certificate) (*x509.Certificate, error) {
	mut := *template
	if mut.NotBefore.IsZero() {
		mut.NotBefore = time.Now()
	}
	if mut.NotAfter.IsZero() {
		mut.NotAfter = mut.NotBefore.Add(24 * time.Hour)
	}
	return x509util.CreateCertificate(&mut, c.Intermediate, mut.PublicKey, c.Signer)
}

// SignCSR signs an X.509 certificate signing request. The custom options allows
// to change the template used to convert the CSR to a certificate.
func (c *CA) SignCSR(csr *x509.CertificateRequest, opts ...SignOption) (*x509.Certificate, error) {
	sans := append([]string{}, csr.DNSNames...)
	sans = append(sans, csr.EmailAddresses...)
	for _, ip := range csr.IPAddresses {
		sans = append(sans, ip.String())
	}
	for _, u := range csr.URIs {
		sans = append(sans, u.String())
	}

	o := newSignOptions().apply(opts)
	crt, err := x509util.NewCertificate(csr, x509util.WithTemplate(o.Template, x509util.CreateTemplateData(csr.Subject.CommonName, sans)))
	if err != nil {
		return nil, err
	}

	cert := crt.GetCertificate()
	if o.Modify != nil {
		if err := o.Modify(cert); err != nil {
			return nil, err
		}
	}

	return c.Sign(cert)
}

// SignSSH signs an SSH host or user certificate. SignSSH will automatically
// populate the following fields if they are not specified:
//
//   - ValidAfter will be set to the current time unless ValidBefore is set to ssh.CertTimeInfinity.
//   - ValidBefore will be set to 24 hours after ValidAfter.
//   - Nonce will be automatically generated.
//   - Serial will be automatically generated.
//
// If the SSH signer is an RSA key, it will use rsa-sha2-256 instead of the
// default ssh-rsa (SHA-1), this method is currently deprecated and
// rsa-sha2-256/512 are supported since OpenSSH 7.2 (2016).
func (c *CA) SignSSH(template *ssh.Certificate) (*ssh.Certificate, error) {
	mut := *template
	if mut.ValidAfter == 0 && mut.ValidBefore != ssh.CertTimeInfinity {
		validAfter, err := utils.SafeUint64(time.Now().Unix())
		if err != nil {
			return nil, fmt.Errorf("failed converting timestamp to uint64: %w", err)
		}
		mut.ValidAfter = validAfter
	}
	if mut.ValidBefore == 0 {
		mut.ValidBefore = mut.ValidAfter + 24*60*60
	}

	switch mut.CertType {
	case ssh.HostCert:
		return sshutil.CreateCertificate(&mut, c.SSHHostSigner)
	case ssh.UserCert:
		return sshutil.CreateCertificate(&mut, c.SSHUserSigner)
	default:
		return nil, fmt.Errorf("unknown certificate type")
	}
}