Skip to content

Commit 7d0f0dd

Browse files
authored
Merge pull request #40 from smallstep/minica
MiniCA
2 parents 00b91e2 + 98fa65a commit 7d0f0dd

File tree

4 files changed

+830
-1
lines changed

4 files changed

+830
-1
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,8 @@ utilities to parse and generate JWT, JWK and JWKSets.
6060
### x25519
6161

6262
Package `x25519` adds support for X25519 keys and the
63-
[XEdDSA](https://signal.org/docs/specifications/xeddsa/) signature scheme.
63+
[XEdDSA](https://signal.org/docs/specifications/xeddsa/) signature scheme.
64+
65+
### minica
66+
67+
Package `minica` implements a simple certificate authority.

minica/minica.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package minica
2+
3+
import (
4+
"crypto"
5+
"crypto/x509"
6+
"fmt"
7+
"time"
8+
9+
"go.step.sm/crypto/sshutil"
10+
"go.step.sm/crypto/x509util"
11+
"golang.org/x/crypto/ssh"
12+
)
13+
14+
// CA is the implementation of a simple X.509 and SSH CA.
15+
type CA struct {
16+
Root *x509.Certificate
17+
Intermediate *x509.Certificate
18+
Signer crypto.Signer
19+
SSHHostSigner ssh.Signer
20+
SSHUserSigner ssh.Signer
21+
}
22+
23+
// New creates a new MiniCA, the custom options allows to overwrite templates,
24+
// signer types and certificate names.
25+
func New(opts ...Option) (*CA, error) {
26+
now := time.Now()
27+
o := newOptions().apply(opts)
28+
29+
// Create root
30+
rootSubject := o.Name + " Root CA"
31+
rootSigner, err := o.GetSigner()
32+
if err != nil {
33+
return nil, err
34+
}
35+
rootCR, err := x509util.CreateCertificateRequest(rootSubject, []string{}, rootSigner)
36+
if err != nil {
37+
return nil, err
38+
}
39+
cert, err := x509util.NewCertificate(rootCR, x509util.WithTemplate(o.RootTemplate, x509util.CreateTemplateData(rootSubject, []string{})))
40+
if err != nil {
41+
return nil, err
42+
}
43+
template := cert.GetCertificate()
44+
template.NotBefore = now
45+
template.NotAfter = now.Add(24 * time.Hour)
46+
root, err := x509util.CreateCertificate(template, template, rootSigner.Public(), rootSigner)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
// Create intermediate
52+
intSubject := o.Name + " Intermediate CA"
53+
intSigner, err := o.GetSigner()
54+
if err != nil {
55+
return nil, err
56+
}
57+
intCR, err := x509util.CreateCertificateRequest(intSubject, []string{}, intSigner)
58+
if err != nil {
59+
return nil, err
60+
}
61+
cert, err = x509util.NewCertificate(intCR, x509util.WithTemplate(o.IntermediateTemplate, x509util.CreateTemplateData(intSubject, []string{})))
62+
if err != nil {
63+
return nil, err
64+
}
65+
template = cert.GetCertificate()
66+
template.NotBefore = now
67+
template.NotAfter = now.Add(24 * time.Hour)
68+
intermediate, err := x509util.CreateCertificate(template, root, intSigner.Public(), rootSigner)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
// Ssh host signer
74+
signer, err := o.GetSigner()
75+
if err != nil {
76+
return nil, err
77+
}
78+
sshHostSigner, err := ssh.NewSignerFromSigner(signer)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
// Ssh user signer
84+
signer, err = o.GetSigner()
85+
if err != nil {
86+
return nil, err
87+
}
88+
sshUserSigner, err := ssh.NewSignerFromSigner(signer)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
return &CA{
94+
Root: root,
95+
Intermediate: intermediate,
96+
Signer: intSigner,
97+
SSHHostSigner: sshHostSigner,
98+
SSHUserSigner: sshUserSigner,
99+
}, nil
100+
}
101+
102+
// Sign signs an X.509 certificate template using the intermediate certificate.
103+
// Sign will automatically populate the following fields if they are not
104+
// specified:
105+
//
106+
// - NotBefore will be set to the current time.
107+
// - NotAfter will be set to 24 hours after NotBefore.
108+
// - SerialNumber will be automatically generated.
109+
// - SubjectKeyId will be automatically generated.
110+
func (c *CA) Sign(template *x509.Certificate) (*x509.Certificate, error) {
111+
mut := *template
112+
if mut.NotBefore.IsZero() {
113+
mut.NotBefore = time.Now()
114+
}
115+
if mut.NotAfter.IsZero() {
116+
mut.NotAfter = mut.NotBefore.Add(24 * time.Hour)
117+
}
118+
return x509util.CreateCertificate(&mut, c.Intermediate, mut.PublicKey, c.Signer)
119+
}
120+
121+
// SignCSR signs an X.509 certificate signing request. The custom options allows
122+
// to change the template used to convert the CSR to a certificate.
123+
func (c *CA) SignCSR(csr *x509.CertificateRequest, opts ...SignOption) (*x509.Certificate, error) {
124+
sans := append([]string{}, csr.DNSNames...)
125+
sans = append(sans, csr.EmailAddresses...)
126+
for _, ip := range csr.IPAddresses {
127+
sans = append(sans, ip.String())
128+
}
129+
for _, u := range csr.URIs {
130+
sans = append(sans, u.String())
131+
}
132+
133+
o := newSignOptions().apply(opts)
134+
crt, err := x509util.NewCertificate(csr, x509util.WithTemplate(o.Template, x509util.CreateTemplateData(csr.Subject.CommonName, sans)))
135+
if err != nil {
136+
return nil, err
137+
}
138+
139+
cert := crt.GetCertificate()
140+
if o.Modify != nil {
141+
if err := o.Modify(cert); err != nil {
142+
return nil, err
143+
}
144+
}
145+
146+
return c.Sign(cert)
147+
}
148+
149+
// SignSSH signs an SSH host or user certificate. SignSSH will automatically
150+
// populate the following fields if they are not specified:
151+
//
152+
// - ValidAfter will be set to the current time unless ValidBefore is set to ssh.CertTimeInfinity.
153+
// - ValidBefore will be set to 24 hours after ValidAfter.
154+
// - Nonce will be automatically generated.
155+
// - Serial will be automatically generated.
156+
//
157+
// If the SSH signer is an RSA key, it will use rsa-sha2-256 instead of the
158+
// default ssh-rsa (SHA-1), this method is currently deprecated and
159+
// rsa-sha2-256/512 are supported since OpenSSH 7.2 (2016).
160+
func (c *CA) SignSSH(template *ssh.Certificate) (*ssh.Certificate, error) {
161+
mut := *template
162+
if mut.ValidAfter == 0 && mut.ValidBefore != ssh.CertTimeInfinity {
163+
mut.ValidAfter = uint64(time.Now().Unix())
164+
}
165+
if mut.ValidBefore == 0 {
166+
mut.ValidBefore = mut.ValidAfter + 24*60*60
167+
}
168+
169+
switch mut.CertType {
170+
case ssh.HostCert:
171+
return sshutil.CreateCertificate(&mut, c.SSHHostSigner)
172+
case ssh.UserCert:
173+
return sshutil.CreateCertificate(&mut, c.SSHUserSigner)
174+
default:
175+
return nil, fmt.Errorf("unknown certificate type")
176+
}
177+
178+
}

0 commit comments

Comments
 (0)