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
|
package depot
import (
"crypto/rand"
"crypto/x509"
"time"
"github.com/micromdm/scep/v2/cryptoutil"
"github.com/smallstep/scep"
)
// Signer signs x509 certificates and stores them in a Depot
type Signer struct {
depot Depot
caPass string
allowRenewalDays int
validityDays int
serverAttrs bool
signatureAlgo x509.SignatureAlgorithm
}
// Option customizes Signer
type Option func(*Signer)
// NewSigner creates a new Signer
func NewSigner(depot Depot, opts ...Option) *Signer {
s := &Signer{
depot: depot,
allowRenewalDays: 14,
validityDays: 365,
signatureAlgo: 0,
}
for _, opt := range opts {
opt(s)
}
return s
}
// WithSignatureAlgorithm sets the signature algorithm to be used to sign certificates.
// When set to a non-zero value, this would take preference over the default behaviour of
// matching the signing algorithm from the x509 CSR.
func WithSignatureAlgorithm(a x509.SignatureAlgorithm) Option {
return func(s *Signer) {
s.signatureAlgo = a
}
}
// WithCAPass specifies the password to use with an encrypted CA key
func WithCAPass(pass string) Option {
return func(s *Signer) {
s.caPass = pass
}
}
// WithAllowRenewalDays sets the allowable renewal time for existing certs
func WithAllowRenewalDays(r int) Option {
return func(s *Signer) {
s.allowRenewalDays = r
}
}
// WithValidityDays sets the validity period new certs will use
func WithValidityDays(v int) Option {
return func(s *Signer) {
s.validityDays = v
}
}
func WithSeverAttrs() Option {
return func(s *Signer) {
s.serverAttrs = true
}
}
// SignCSR signs a certificate using Signer's Depot CA
func (s *Signer) SignCSR(m *scep.CSRReqMessage) (*x509.Certificate, error) {
id, err := cryptoutil.GenerateSubjectKeyID(m.CSR.PublicKey)
if err != nil {
return nil, err
}
serial, err := s.depot.Serial()
if err != nil {
return nil, err
}
var signatureAlgo x509.SignatureAlgorithm
if s.signatureAlgo != 0 {
signatureAlgo = s.signatureAlgo
}
// create cert template
tmpl := &x509.Certificate{
SerialNumber: serial,
Subject: m.CSR.Subject,
NotBefore: time.Now().Add(time.Second * -600).UTC(),
NotAfter: time.Now().AddDate(0, 0, s.validityDays).UTC(),
SubjectKeyId: id,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
SignatureAlgorithm: signatureAlgo,
DNSNames: m.CSR.DNSNames,
EmailAddresses: m.CSR.EmailAddresses,
IPAddresses: m.CSR.IPAddresses,
URIs: m.CSR.URIs,
}
if s.serverAttrs {
tmpl.KeyUsage |= x509.KeyUsageDataEncipherment | x509.KeyUsageKeyEncipherment
tmpl.ExtKeyUsage = append(tmpl.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
}
caCerts, caKey, err := s.depot.CA([]byte(s.caPass))
if err != nil {
return nil, err
}
crtBytes, err := x509.CreateCertificate(rand.Reader, tmpl, caCerts[0], m.CSR.PublicKey, caKey)
if err != nil {
return nil, err
}
crt, err := x509.ParseCertificate(crtBytes)
if err != nil {
return nil, err
}
name := certName(crt)
// Test if this certificate is already in the CADB, revoke if needed
// revocation is done if the validity of the existing certificate is
// less than allowRenewalDays
_, err = s.depot.HasCN(name, s.allowRenewalDays, crt, false)
if err != nil {
return nil, err
}
if err := s.depot.Put(name, crt); err != nil {
return nil, err
}
return crt, nil
}
func certName(crt *x509.Certificate) string {
if crt.Subject.CommonName != "" {
return crt.Subject.CommonName
}
return string(crt.Signature)
}
|