Skip to content

Commit

Permalink
Add initial implementation of the minica.
Browse files Browse the repository at this point in the history
  • Loading branch information
maraino committed Mar 15, 2022
1 parent 00b91e2 commit 7cd2dec
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 1 deletion.
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.
158 changes: 158 additions & 0 deletions minica/minica.go
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")
}

}
106 changes: 106 additions & 0 deletions minica/options.go
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
}
}

0 comments on commit 7cd2dec

Please sign in to comment.