forked from TrueCloudLab/certificates
abc0a63e32
This wrapper generates some data if needed and cleans key usages in templates.
290 lines
8.6 KiB
Go
290 lines
8.6 KiB
Go
package x509util
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"math/big"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Certificate struct {
|
|
Version int `json:"version"`
|
|
Subject Subject `json:"subject"`
|
|
Issuer Issuer `json:"issuer"`
|
|
SerialNumber SerialNumber `json:"serialNumber"`
|
|
DNSNames MultiString `json:"dnsNames"`
|
|
EmailAddresses MultiString `json:"emailAddresses"`
|
|
IPAddresses MultiIP `json:"ipAddresses"`
|
|
URIs MultiURL `json:"uris"`
|
|
SANs []SubjectAlternativeName `json:"sans"`
|
|
Extensions []Extension `json:"extensions"`
|
|
KeyUsage KeyUsage `json:"keyUsage"`
|
|
ExtKeyUsage ExtKeyUsage `json:"extKeyUsage"`
|
|
SubjectKeyID SubjectKeyID `json:"subjectKeyId"`
|
|
AuthorityKeyID AuthorityKeyID `json:"authorityKeyId"`
|
|
OCSPServer OCSPServer `json:"ocspServer"`
|
|
IssuingCertificateURL IssuingCertificateURL `json:"issuingCertificateURL"`
|
|
CRLDistributionPoints CRLDistributionPoints `json:"crlDistributionPoints"`
|
|
PolicyIdentifiers PolicyIdentifiers `json:"policyIdentifiers"`
|
|
BasicConstraints *BasicConstraints `json:"basicConstraints"`
|
|
NameConstaints *NameConstraints `json:"nameConstraints"`
|
|
SignatureAlgorithm SignatureAlgorithm `json:"signatureAlgorithm"`
|
|
PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"-"`
|
|
PublicKey interface{} `json:"-"`
|
|
}
|
|
|
|
func NewCertificate(cr *x509.CertificateRequest, opts ...Option) (*Certificate, error) {
|
|
if err := cr.CheckSignature(); err != nil {
|
|
return nil, errors.Wrap(err, "error validating certificate request")
|
|
}
|
|
|
|
o, err := new(Options).apply(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If no template use only the certificate request.
|
|
if o.CertBuffer == nil {
|
|
return newCertificateRequest(cr).GetCertificate(), nil
|
|
}
|
|
|
|
// With templates
|
|
var cert Certificate
|
|
if err := json.NewDecoder(o.CertBuffer).Decode(&cert); err != nil {
|
|
return nil, errors.Wrap(err, "error unmarshaling certificate")
|
|
}
|
|
|
|
// Complete with certificate request
|
|
cert.PublicKey = cr.PublicKey
|
|
cert.PublicKeyAlgorithm = cr.PublicKeyAlgorithm
|
|
|
|
return &cert, nil
|
|
}
|
|
|
|
func (c *Certificate) GetCertificate() *x509.Certificate {
|
|
cert := new(x509.Certificate)
|
|
// Unparsed data
|
|
cert.PublicKey = c.PublicKey
|
|
cert.PublicKeyAlgorithm = c.PublicKeyAlgorithm
|
|
|
|
// SANs are directly converted.
|
|
cert.DNSNames = c.DNSNames
|
|
cert.EmailAddresses = c.EmailAddresses
|
|
cert.IPAddresses = c.IPAddresses
|
|
cert.URIs = c.URIs
|
|
|
|
// SANs slice.
|
|
for _, san := range c.SANs {
|
|
san.Set(cert)
|
|
}
|
|
|
|
// Subject.
|
|
c.Subject.Set(cert)
|
|
|
|
// Defined extensions.
|
|
c.KeyUsage.Set(cert)
|
|
c.ExtKeyUsage.Set(cert)
|
|
c.SubjectKeyID.Set(cert)
|
|
c.AuthorityKeyID.Set(cert)
|
|
c.OCSPServer.Set(cert)
|
|
c.IssuingCertificateURL.Set(cert)
|
|
c.PolicyIdentifiers.Set(cert)
|
|
if c.BasicConstraints != nil {
|
|
c.BasicConstraints.Set(cert)
|
|
}
|
|
if c.NameConstaints != nil {
|
|
c.NameConstaints.Set(cert)
|
|
}
|
|
|
|
// Custom Extensions.
|
|
for _, e := range c.Extensions {
|
|
e.Set(cert)
|
|
}
|
|
|
|
// Others.
|
|
c.SerialNumber.Set(cert)
|
|
c.SignatureAlgorithm.Set(cert)
|
|
|
|
return cert
|
|
}
|
|
|
|
func CreateCertificate(template, parent *x509.Certificate, pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) {
|
|
var err error
|
|
// Complete certificate.
|
|
if template.SerialNumber == nil {
|
|
if template.SerialNumber, err = generateSerialNumber(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if template.SubjectKeyId == nil {
|
|
if template.SubjectKeyId, err = generateSubjectKeyID(pub); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Remove KeyEncipherment and DataEncipherment for non-rsa keys.
|
|
// See:
|
|
// https://github.com/golang/go/issues/36499
|
|
// https://tools.ietf.org/html/draft-ietf-lamps-5480-ku-clarifications-02
|
|
if _, ok := pub.(*rsa.PublicKey); !ok {
|
|
template.KeyUsage &= ^x509.KeyUsageKeyEncipherment
|
|
template.KeyUsage &= ^x509.KeyUsageDataEncipherment
|
|
}
|
|
|
|
// Sign certificate
|
|
asn1Data, err := x509.CreateCertificate(rand.Reader, template, parent, pub, signer)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error creating certificate")
|
|
}
|
|
cert, err := x509.ParseCertificate(asn1Data)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error parsing certificate")
|
|
}
|
|
return cert, nil
|
|
}
|
|
|
|
// Name is the JSON representation of X.501 type Name, used in the X.509 subject
|
|
// and issuer fields.
|
|
type Name struct {
|
|
Country MultiString `json:"country"`
|
|
Organization MultiString `json:"organization"`
|
|
OrganizationalUnit MultiString `json:"organizationUnit"`
|
|
Locality MultiString `json:"locality"`
|
|
Province MultiString `json:"province"`
|
|
StreetAddress MultiString `json:"streetAddress"`
|
|
PostalCode MultiString `json:"postalCode"`
|
|
SerialNumber string `json:"serialNumber"`
|
|
CommonName string `json:"commonName"`
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshal interface and unmarshals a JSON
|
|
// object in the Subject struct or a string as just the subject common name.
|
|
func (n *Name) UnmarshalJSON(data []byte) error {
|
|
if cn, ok := maybeString(data); ok {
|
|
n.CommonName = cn
|
|
return nil
|
|
}
|
|
|
|
type nameAlias Name
|
|
var nn nameAlias
|
|
if err := json.Unmarshal(data, &nn); err != nil {
|
|
return errors.Wrap(err, "error unmarshaling json")
|
|
}
|
|
*n = Name(nn)
|
|
return nil
|
|
}
|
|
|
|
// Subject is the JSON representation of the X.509 subject field.
|
|
type Subject Name
|
|
|
|
func newSubject(n pkix.Name) Subject {
|
|
return Subject{
|
|
Country: n.Country,
|
|
Organization: n.Organization,
|
|
OrganizationalUnit: n.OrganizationalUnit,
|
|
Locality: n.Locality,
|
|
Province: n.Province,
|
|
StreetAddress: n.StreetAddress,
|
|
PostalCode: n.PostalCode,
|
|
SerialNumber: n.SerialNumber,
|
|
CommonName: n.CommonName,
|
|
}
|
|
}
|
|
|
|
// Set sets the subject in the given certificate.
|
|
func (s Subject) Set(c *x509.Certificate) {
|
|
c.Subject = pkix.Name{
|
|
Country: s.Country,
|
|
Organization: s.Organization,
|
|
OrganizationalUnit: s.OrganizationalUnit,
|
|
Locality: s.Locality,
|
|
Province: s.Province,
|
|
StreetAddress: s.StreetAddress,
|
|
PostalCode: s.PostalCode,
|
|
SerialNumber: s.SerialNumber,
|
|
CommonName: s.CommonName,
|
|
}
|
|
}
|
|
|
|
// Issuer is the JSON representation of the X.509 issuer field.
|
|
type Issuer Name
|
|
|
|
func newIssuer(n pkix.Name) Issuer {
|
|
return Issuer{
|
|
Country: n.Country,
|
|
Organization: n.Organization,
|
|
OrganizationalUnit: n.OrganizationalUnit,
|
|
Locality: n.Locality,
|
|
Province: n.Province,
|
|
StreetAddress: n.StreetAddress,
|
|
PostalCode: n.PostalCode,
|
|
SerialNumber: n.SerialNumber,
|
|
CommonName: n.CommonName,
|
|
}
|
|
}
|
|
|
|
// Set sets the issuer in the given certificate.
|
|
func (i Issuer) Set(c *x509.Certificate) {
|
|
c.Issuer = pkix.Name{
|
|
Country: i.Country,
|
|
Organization: i.Organization,
|
|
OrganizationalUnit: i.OrganizationalUnit,
|
|
Locality: i.Locality,
|
|
Province: i.Province,
|
|
StreetAddress: i.StreetAddress,
|
|
PostalCode: i.PostalCode,
|
|
SerialNumber: i.SerialNumber,
|
|
CommonName: i.CommonName,
|
|
}
|
|
}
|
|
|
|
// SerialNumber is the JSON representation of the X509 serial number.
|
|
type SerialNumber struct {
|
|
*big.Int
|
|
}
|
|
|
|
// Set sets the serial number in the given certificate.
|
|
func (s SerialNumber) Set(c *x509.Certificate) {
|
|
c.SerialNumber = s.Int
|
|
}
|
|
|
|
func (s *SerialNumber) MarshalJSON() ([]byte, error) {
|
|
if s == nil || s.Int == nil {
|
|
return []byte(`null`), nil
|
|
}
|
|
return s.Int.MarshalJSON()
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshal interface and unmarshals an
|
|
// integer or a string into a serial number. If a string is used, a prefix of
|
|
// “0b” or “0B” selects base 2, “0”, “0o” or “0O” selects base 8, and “0x” or
|
|
// “0X” selects base 16. Otherwise, the selected base is 10 and no prefix is
|
|
// accepted.
|
|
func (s *SerialNumber) UnmarshalJSON(data []byte) error {
|
|
if sn, ok := maybeString(data); ok {
|
|
// Using base 0 to accept prefixes 0b, 0o, 0x but defaults as base 10.
|
|
b, ok := new(big.Int).SetString(sn, 0)
|
|
if !ok {
|
|
return errors.Errorf("error unmarshaling json: serialNumber %s is not valid", sn)
|
|
}
|
|
*s = SerialNumber{
|
|
Int: b,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Assume a number.
|
|
var i int64
|
|
if err := json.Unmarshal(data, &i); err != nil {
|
|
return errors.Wrap(err, "error unmarshaling json")
|
|
}
|
|
*s = SerialNumber{
|
|
Int: new(big.Int).SetInt64(i),
|
|
}
|
|
return nil
|
|
}
|