Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MiniCA #40

Merged
merged 5 commits into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,8 @@ utilities to parse and generate JWT, JWK and JWKSets.
### x25519

Package `x25519` adds support for X25519 keys and the
[XEdDSA](https://signal.org/docs/specifications/xeddsa/) signature scheme.
[XEdDSA](https://signal.org/docs/specifications/xeddsa/) signature scheme.

### minica

Package `minica` implements a simple certificate authority.
178 changes: 178 additions & 0 deletions minica/minica.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package minica

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

"go.step.sm/crypto/sshutil"
"go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh"
)

// CA is the implementation of a simple X.509 and SSH CA.
type CA struct {
Root *x509.Certificate
azazeal marked this conversation as resolved.
Show resolved Hide resolved
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,
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 {
mut.ValidAfter = uint64(time.Now().Unix())
}
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")
}

}
Loading