-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial implementation of the minica.
- Loading branch information
Showing
3 changed files
with
269 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package minica | ||
|
||
import ( | ||
"crypto" | ||
"crypto/x509" | ||
"fmt" | ||
"time" | ||
|
||
"go.step.sm/crypto/sshutil" | ||
"go.step.sm/crypto/x509util" | ||
"golang.org/x/crypto/ssh" | ||
) | ||
|
||
// MiniCA is the implementation of a simple X.509 and SSH CA. | ||
type MiniCA struct { | ||
Root *x509.Certificate | ||
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) (*MiniCA, 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(x509util.DefaultRootTemplate, 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(x509util.DefaultIntermediateTemplate, 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 &MiniCA{ | ||
Root: root, | ||
Intermediate: intermediate, | ||
Signer: intSigner, | ||
SSHHostSigner: sshHostSigner, | ||
SSHUserSigner: sshUserSigner, | ||
}, nil | ||
} | ||
|
||
// Sign signs an X.509 certificate template using the intermediate certificate. | ||
func (c *MiniCA) Sign(template *x509.Certificate) (*x509.Certificate, error) { | ||
if template.NotBefore.IsZero() { | ||
template.NotBefore = time.Now() | ||
} | ||
if template.NotAfter.IsZero() { | ||
template.NotAfter = template.NotBefore.Add(24 * time.Hour) | ||
} | ||
return x509util.CreateCertificate(template, c.Intermediate, template.PublicKey, c.Signer) | ||
} | ||
|
||
// SignCSR signs an X.509 certificate signing request. The custom options allows to change the template used for | ||
func (c *MiniCA) 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. | ||
func (c *MiniCA) SignSSH(cert *ssh.Certificate) (*ssh.Certificate, error) { | ||
if cert.ValidAfter == 0 { | ||
cert.ValidAfter = uint64(time.Now().Unix()) | ||
} | ||
if cert.ValidBefore == 0 { | ||
cert.ValidBefore = cert.ValidAfter + 24*60*60 | ||
} | ||
|
||
switch cert.CertType { | ||
case ssh.HostCert: | ||
return sshutil.CreateCertificate(cert, c.SSHHostSigner) | ||
case ssh.UserCert: | ||
return sshutil.CreateCertificate(cert, c.SSHUserSigner) | ||
default: | ||
return nil, fmt.Errorf("unknown certificate type") | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package minica | ||
|
||
import ( | ||
"crypto" | ||
"crypto/x509" | ||
|
||
"go.step.sm/crypto/keyutil" | ||
"go.step.sm/crypto/x509util" | ||
) | ||
|
||
type options struct { | ||
Name string | ||
RootTemplate string | ||
IntermediateTemplate string | ||
GetSigner func() (crypto.Signer, error) | ||
} | ||
|
||
// Option is the type used to pass custom attributes to the constructor. | ||
type Option func(o *options) | ||
|
||
func newOptions() *options { | ||
return &options{ | ||
Name: "MiniCA", | ||
RootTemplate: x509util.DefaultRootTemplate, | ||
IntermediateTemplate: x509util.DefaultIntermediateTemplate, | ||
GetSigner: keyutil.GenerateDefaultSigner, | ||
} | ||
} | ||
|
||
func (o *options) apply(opts []Option) *options { | ||
for _, fn := range opts { | ||
fn(o) | ||
} | ||
return o | ||
} | ||
|
||
// WithName is an option that allows to overwrite the default name MiniCA. With | ||
// the default templates, the root and intermediate certificate common names | ||
// would be "<name> Root CA" and "<name> Intermediate CA". | ||
func WithName(name string) Option { | ||
return func(o *options) { | ||
o.Name = name | ||
} | ||
} | ||
|
||
// WithRootTemplate is an option that allows to overwrite the template used to | ||
// create the root certificate. | ||
func WithRootTemplate(template string) Option { | ||
return func(o *options) { | ||
o.RootTemplate = template | ||
} | ||
} | ||
|
||
// WithIntermediateTemplate is an option that allows to overwrite the template | ||
// used to create the intermediate certificate. | ||
func WithIntermediateTemplate(template string) Option { | ||
return func(o *options) { | ||
o.IntermediateTemplate = template | ||
} | ||
} | ||
|
||
// WithGetSignerFunc is an option that allows to overwrite the default function to | ||
// create a signer. | ||
func WithGetSignerFunc(fn func() (crypto.Signer, error)) Option { | ||
return func(o *options) { | ||
o.GetSigner = fn | ||
} | ||
} | ||
|
||
type signOptions struct { | ||
Template string | ||
Modify func(*x509.Certificate) error | ||
} | ||
|
||
// SignOption is the type used to pass custom attributes when signing a | ||
// certificate request. | ||
type SignOption func(o *signOptions) | ||
|
||
func newSignOptions() *signOptions { | ||
return &signOptions{ | ||
Template: x509util.DefaultLeafTemplate, | ||
} | ||
} | ||
|
||
func (o *signOptions) apply(opts []SignOption) *signOptions { | ||
for _, fn := range opts { | ||
fn(o) | ||
} | ||
return o | ||
} | ||
|
||
// WithTemplate allows to update the template used to convert a CSR into a | ||
// certificate. | ||
func WithTemplate(template string) SignOption { | ||
return func(o *signOptions) { | ||
o.Template = template | ||
} | ||
} | ||
|
||
// WithModifyFunc allows to update the certificate template before the signing | ||
// it. | ||
func WithModifyFunc(fn func(*x509.Certificate) error) SignOption { | ||
return func(o *signOptions) { | ||
o.Modify = fn | ||
} | ||
} |