forked from TrueCloudLab/certificates
Add types for certificate flexibility.
This is a first implementation, not the final one.
This commit is contained in:
parent
c1e6c0285a
commit
2556b57906
6 changed files with 822 additions and 0 deletions
263
x509util/certificate.go
Normal file
263
x509util/certificate.go
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
package x509util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Certificate struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
Subject Subject `json:"subject"`
|
||||||
|
SerialNumber SerialNumber `json:"serialNumber"`
|
||||||
|
DNSNames MultiString `json:"dnsNames"`
|
||||||
|
EmailAddresses MultiString `json:"emailAddresses"`
|
||||||
|
IPAddresses MultiIP `json:"ipAddresses"`
|
||||||
|
URIs MultiURL `json:"uris"`
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subject is the JSON representation of the X509 subject field.
|
||||||
|
type Subject 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (s *Subject) UnmarshalJSON(data []byte) error {
|
||||||
|
if cn, ok := maybeString(data); ok {
|
||||||
|
s.CommonName = cn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type subjectAlias Subject
|
||||||
|
var ss subjectAlias
|
||||||
|
if err := json.Unmarshal(data, &ss); err != nil {
|
||||||
|
return errors.Wrap(err, "error unmarshaling json")
|
||||||
|
}
|
||||||
|
*s = Subject(ss)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithm is the JSON representation of the X509 signature algorithms
|
||||||
|
type SignatureAlgorithm x509.SignatureAlgorithm
|
||||||
|
|
||||||
|
// Set sets the signature algorithm in the given certificate.
|
||||||
|
func (s SignatureAlgorithm) Set(c *x509.Certificate) {
|
||||||
|
c.SignatureAlgorithm = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (s *SignatureAlgorithm) UnmarshalJSON(data []byte) error {
|
||||||
|
s, err := unmarshalString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sa x509.SignatureAlgorithm
|
||||||
|
switch strings.ToUpper(s) {
|
||||||
|
case "MD2-RSA":
|
||||||
|
sa = x509.MD2WithRSA
|
||||||
|
case "MD5-RSA":
|
||||||
|
sa = x509.MD5WithRSA
|
||||||
|
case "SHA1-RSA":
|
||||||
|
sa = x509.SHA1WithRSA
|
||||||
|
case "SHA1-RSA":
|
||||||
|
sa = x509.SHA1WithRSA
|
||||||
|
case "SHA256-RSA":
|
||||||
|
sa = x509.SHA256WithRSA
|
||||||
|
case "SHA384-RSA":
|
||||||
|
sa = x509.SHA384WithRSA
|
||||||
|
case "SHA512-RSA":
|
||||||
|
sa = x509.SHA512WithRSA
|
||||||
|
case "SHA256-RSAPSS":
|
||||||
|
sa = x509.SHA256WithRSAPSS
|
||||||
|
case "SHA384-RSAPSS":
|
||||||
|
sa = x509.SHA384WithRSAPSS
|
||||||
|
case "SHA512-RSAPSS":
|
||||||
|
sa = x509.SHA512WithRSAPSS
|
||||||
|
case "DSA-SHA1":
|
||||||
|
sa = x509.DSAWithSHA1
|
||||||
|
case "DSA-SHA256":
|
||||||
|
sa = x509.DSAWithSHA256
|
||||||
|
case "ECDSA-SHA1":
|
||||||
|
sa = x509.ECDSAWithSHA1
|
||||||
|
case "ECDSA-SHA256":
|
||||||
|
sa = x509.ECDSAWithSHA256
|
||||||
|
case "ECDSA-SHA384":
|
||||||
|
sa = x509.ECDSAWithSHA384
|
||||||
|
case "ECDSA-SHA512":
|
||||||
|
sa = x509.ECDSAWithSHA512
|
||||||
|
case "ED25519":
|
||||||
|
sa = x509.PureEd25519
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unsupported signatureAlgorithm %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = SignatureAlgorithm(sa)
|
||||||
|
return nil
|
||||||
|
}
|
48
x509util/certificate_request.go
Normal file
48
x509util/certificate_request.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package x509util
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
type CertificateRequest struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
Subject Subject `json:"subject"`
|
||||||
|
DNSNames MultiString `json:"dnsNames"`
|
||||||
|
EmailAddresses MultiString `json:"emailAddresses"`
|
||||||
|
IPAddresses MultiIP `json:"ipAddresses"`
|
||||||
|
URIs MultiURL `json:"uris"`
|
||||||
|
Extensions []Extension `json:"extensions"`
|
||||||
|
PublicKey interface{} `json:"-"`
|
||||||
|
PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"-"`
|
||||||
|
Signature []byte `json:"-"`
|
||||||
|
SignatureAlgorithm x509.SignatureAlgorithm `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCertificateRequest(cr *x509.CertificateRequest) *CertificateRequest {
|
||||||
|
return &CertificateRequest{
|
||||||
|
Version: cr.Version,
|
||||||
|
Subject: newSubject(cr.Subject),
|
||||||
|
DNSNames: cr.DNSNames,
|
||||||
|
EmailAddresses: cr.EmailAddresses,
|
||||||
|
IPAddresses: cr.IPAddresses,
|
||||||
|
URIs: cr.URIs,
|
||||||
|
Extensions: nil,
|
||||||
|
PublicKey: cr.PublicKey,
|
||||||
|
PublicKeyAlgorithm: cr.PublicKeyAlgorithm,
|
||||||
|
Signature: cr.Signature,
|
||||||
|
SignatureAlgorithm: cr.SignatureAlgorithm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CertificateRequest) GetCertificate() *Certificate {
|
||||||
|
return &Certificate{
|
||||||
|
Subject: c.Subject,
|
||||||
|
DNSNames: c.DNSNames,
|
||||||
|
EmailAddresses: c.EmailAddresses,
|
||||||
|
IPAddresses: c.IPAddresses,
|
||||||
|
URIs: c.URIs,
|
||||||
|
Extensions: c.Extensions,
|
||||||
|
PublicKey: c.PublicKey,
|
||||||
|
PublicKeyAlgorithm: c.PublicKeyAlgorithm,
|
||||||
|
PublicKey: c.PublicKey,
|
||||||
|
PublicKeyAlgorithm: c.PublicKeyAlgorithm,
|
||||||
|
}
|
||||||
|
}
|
294
x509util/extensions.go
Normal file
294
x509util/extensions.go
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
package x509util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convertName(s string) string {
|
||||||
|
return strings.ReplaceAll(strings.ToLower(s), "_", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names used for key usages.
|
||||||
|
var (
|
||||||
|
KeyUsageDigitalSignature = convertName("DigitalSignature")
|
||||||
|
KeyUsageContentCommitment = convertName("ContentCommitment")
|
||||||
|
KeyUsageKeyEncipherment = convertName("KeyEncipherment")
|
||||||
|
KeyUsageDataEncipherment = convertName("DataEncipherment")
|
||||||
|
KeyUsageKeyAgreement = convertName("KeyAgreement")
|
||||||
|
KeyUsageCertSign = convertName("CertSign")
|
||||||
|
KeyUsageCRLSign = convertName("CRLSign")
|
||||||
|
KeyUsageEncipherOnly = convertName("EncipherOnly")
|
||||||
|
KeyUsageDecipherOnly = convertName("DecipherOnly")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Names used for extended key usages.
|
||||||
|
var (
|
||||||
|
ExtKeyUsageAny = convertName("Any")
|
||||||
|
ExtKeyUsageServerAuth = convertName("ServerAuth")
|
||||||
|
ExtKeyUsageClientAuth = convertName("ClientAuth")
|
||||||
|
ExtKeyUsageCodeSigning = convertName("CodeSigning")
|
||||||
|
ExtKeyUsageEmailProtection = convertName("EmailProtection")
|
||||||
|
ExtKeyUsageIPSECEndSystem = convertName("IPSECEndSystem")
|
||||||
|
ExtKeyUsageIPSECTunnel = convertName("IPSECTunnel")
|
||||||
|
ExtKeyUsageIPSECUser = convertName("IPSECUser")
|
||||||
|
ExtKeyUsageTimeStamping = convertName("TimeStamping")
|
||||||
|
ExtKeyUsageOCSPSigning = convertName("OCSPSigning")
|
||||||
|
ExtKeyUsageMicrosoftServerGatedCrypto = convertName("MicrosoftServerGatedCrypto")
|
||||||
|
ExtKeyUsageNetscapeServerGatedCrypto = convertName("NetscapeServerGatedCrypto")
|
||||||
|
ExtKeyUsageMicrosoftCommercialCodeSigning = convertName("MicrosoftCommercialCodeSigning")
|
||||||
|
ExtKeyUsageMicrosoftKernelCodeSigning = convertName("MicrosoftKernelCodeSigning")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Extension is the JSON representation of a raw X.509 extensions.
|
||||||
|
type Extension struct {
|
||||||
|
ID ObjectIdentifier `json:"id"`
|
||||||
|
Critical bool `json:"critical"`
|
||||||
|
Value []byte `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set adds the extension to the given X509 certificate.
|
||||||
|
func (e Extension) Set(c *x509.Certificate) {
|
||||||
|
c.ExtraExtensions = append(c.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier(e.ID),
|
||||||
|
Critical: e.Critical,
|
||||||
|
Value: e.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectIdentifier represents a JSON strings that unmarshals into an ASN1
|
||||||
|
// object identifier or OID.
|
||||||
|
type ObjectIdentifier asn1.ObjectIdentifier
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface and coverts a strings
|
||||||
|
// like "2.5.29.17" into an ASN1 object identifier.
|
||||||
|
func (o *ObjectIdentifier) UnmarshalJSON(data []byte) error {
|
||||||
|
s, err := unmarshalString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oid, err := parseObjectIdentifier(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*o = ObjectIdentifier(oid)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyUsage type represents the JSON array used to represent the key usages of a
|
||||||
|
// X509 certificate.
|
||||||
|
type KeyUsage x509.KeyUsage
|
||||||
|
|
||||||
|
// Set sets the key usage to the given certificate.
|
||||||
|
func (k KeyUsage) Set(c *x509.Certificate) {
|
||||||
|
c.KeyUsage = x509.KeyUsage(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface and coverts a string
|
||||||
|
// or a list of strings into a key usage.
|
||||||
|
func (k *KeyUsage) UnmarshalJSON(data []byte) error {
|
||||||
|
ms, err := unmarshalMultiString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*k = 0
|
||||||
|
|
||||||
|
for _, s := range ms {
|
||||||
|
var ku x509.KeyUsage
|
||||||
|
switch convertName(s) {
|
||||||
|
case KeyUsageDigitalSignature:
|
||||||
|
ku = x509.KeyUsageDigitalSignature
|
||||||
|
case KeyUsageContentCommitment:
|
||||||
|
ku = x509.KeyUsageContentCommitment
|
||||||
|
case KeyUsageKeyEncipherment:
|
||||||
|
ku = x509.KeyUsageKeyEncipherment
|
||||||
|
case KeyUsageDataEncipherment:
|
||||||
|
ku = x509.KeyUsageDataEncipherment
|
||||||
|
case KeyUsageKeyAgreement:
|
||||||
|
ku = x509.KeyUsageKeyAgreement
|
||||||
|
case KeyUsageCertSign:
|
||||||
|
ku = x509.KeyUsageCertSign
|
||||||
|
case KeyUsageCRLSign:
|
||||||
|
ku = x509.KeyUsageCRLSign
|
||||||
|
case KeyUsageEncipherOnly:
|
||||||
|
ku = x509.KeyUsageEncipherOnly
|
||||||
|
case KeyUsageDecipherOnly:
|
||||||
|
ku = x509.KeyUsageDecipherOnly
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unsupported keyUsage %s", s)
|
||||||
|
}
|
||||||
|
*k |= KeyUsage(ku)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtKeyUsage represents a JSON array of extended key usages.
|
||||||
|
type ExtKeyUsage []x509.ExtKeyUsage
|
||||||
|
|
||||||
|
// Set sets the extended key usages in the given certificate.
|
||||||
|
func (k ExtKeyUsage) Set(c *x509.Certificate) {
|
||||||
|
c.ExtKeyUsage = []x509.ExtKeyUsage(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface and coverts a string
|
||||||
|
// or a list of strings into a list of extended key usages.
|
||||||
|
func (k *ExtKeyUsage) UnmarshalJSON(data []byte) error {
|
||||||
|
ms, err := unmarshalMultiString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
eku := make([]x509.ExtKeyUsage, len(ms))
|
||||||
|
for i, s := range ms {
|
||||||
|
var ku x509.ExtKeyUsage
|
||||||
|
switch convertName(s) {
|
||||||
|
case ExtKeyUsageAny:
|
||||||
|
ku = x509.ExtKeyUsageAny
|
||||||
|
case ExtKeyUsageServerAuth:
|
||||||
|
ku = x509.ExtKeyUsageServerAuth
|
||||||
|
case ExtKeyUsageClientAuth:
|
||||||
|
ku = x509.ExtKeyUsageClientAuth
|
||||||
|
case ExtKeyUsageCodeSigning:
|
||||||
|
ku = x509.ExtKeyUsageCodeSigning
|
||||||
|
case ExtKeyUsageEmailProtection:
|
||||||
|
ku = x509.ExtKeyUsageEmailProtection
|
||||||
|
case ExtKeyUsageIPSECEndSystem:
|
||||||
|
ku = x509.ExtKeyUsageIPSECEndSystem
|
||||||
|
case ExtKeyUsageIPSECTunnel:
|
||||||
|
ku = x509.ExtKeyUsageIPSECTunnel
|
||||||
|
case ExtKeyUsageIPSECUser:
|
||||||
|
ku = x509.ExtKeyUsageIPSECUser
|
||||||
|
case ExtKeyUsageTimeStamping:
|
||||||
|
ku = x509.ExtKeyUsageTimeStamping
|
||||||
|
case ExtKeyUsageOCSPSigning:
|
||||||
|
ku = x509.ExtKeyUsageOCSPSigning
|
||||||
|
case ExtKeyUsageMicrosoftServerGatedCrypto:
|
||||||
|
ku = x509.ExtKeyUsageMicrosoftServerGatedCrypto
|
||||||
|
case ExtKeyUsageNetscapeServerGatedCrypto:
|
||||||
|
ku = x509.ExtKeyUsageNetscapeServerGatedCrypto
|
||||||
|
case ExtKeyUsageMicrosoftCommercialCodeSigning:
|
||||||
|
ku = x509.ExtKeyUsageMicrosoftCommercialCodeSigning
|
||||||
|
case ExtKeyUsageMicrosoftKernelCodeSigning:
|
||||||
|
ku = x509.ExtKeyUsageMicrosoftKernelCodeSigning
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unsupported extKeyUsage %s", s)
|
||||||
|
}
|
||||||
|
eku[i] = ku
|
||||||
|
}
|
||||||
|
|
||||||
|
*k = ExtKeyUsage(eku)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubjectKeyID represents the binary value of the subject key identifier
|
||||||
|
// extension, this should be the SHA-1 hash of the public key. In JSON this
|
||||||
|
// value should be a base64-encoded string, and in most cases it should not be
|
||||||
|
// set because it will be automatically generated.
|
||||||
|
type SubjectKeyID []byte
|
||||||
|
|
||||||
|
// Set sets the subject key identifier to the given certificate.
|
||||||
|
func (id SubjectKeyID) Set(c *x509.Certificate) {
|
||||||
|
c.SubjectKeyId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorityKeyID represents the binary value of the authority key identifier
|
||||||
|
// extension. It should be the subject key identifier of the parent certificate.
|
||||||
|
// In JSON this value should be a base64-encoded string, and in most cases it
|
||||||
|
// should not be set, as it will be automatically provided.
|
||||||
|
type AuthorityKeyID []byte
|
||||||
|
|
||||||
|
// Set sets the authority key identifier to the given certificate.
|
||||||
|
func (id AuthorityKeyID) Set(c *x509.Certificate) {
|
||||||
|
c.AuthorityKeyId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSPServer contains the list of OSCP servers that will be encoded in the
|
||||||
|
// authority information access extension.
|
||||||
|
type OCSPServer MultiString
|
||||||
|
|
||||||
|
// Set sets the list of OSCP servers to the given certificate.
|
||||||
|
func (o OCSPServer) Set(c *x509.Certificate) {
|
||||||
|
c.OCSPServer = o
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssuingCertificateURL contains the list of the issuing certificate url that
|
||||||
|
// will be encoded in the authority information access extension.
|
||||||
|
type IssuingCertificateURL MultiString
|
||||||
|
|
||||||
|
// Set sets the list of issuing certificate urls to the given certificate.
|
||||||
|
func (u IssuingCertificateURL) Set(c *x509.Certificate) {
|
||||||
|
c.IssuingCertificateURL = u
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRLDistributionPoints contains the list of CRL distribution points that will
|
||||||
|
// be encoded in the CRL distribution points extension.
|
||||||
|
type CRLDistributionPoints MultiString
|
||||||
|
|
||||||
|
// Set sets the CRL distribution points to the given certificate.
|
||||||
|
func (u CRLDistributionPoints) Set(c *x509.Certificate) {
|
||||||
|
c.CRLDistributionPoints = u
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyIdentifiers represents the list of OIDs to set in the certificate
|
||||||
|
// policies extension.
|
||||||
|
type PolicyIdentifiers MultiObjectIdentifier
|
||||||
|
|
||||||
|
// Sets sets the policy identifiers to the given certificate.
|
||||||
|
func (p PolicyIdentifiers) Set(c *x509.Certificate) {
|
||||||
|
c.PolicyIdentifiers = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicConstraints represents the X509 basic constraints exception and defines
|
||||||
|
// if a certificate is a CA and then maximum depth of valid certification paths
|
||||||
|
// that include the certificate. A MaxPathLen of zero indicates that no non-
|
||||||
|
// self-issued intermediate CA certificates may follow in a valid certification
|
||||||
|
// path. To do not impose a limit the MaxPathLen should be set to -1.
|
||||||
|
type BasicConstraints struct {
|
||||||
|
IsCA bool `json:"isCA"`
|
||||||
|
MaxPathLen int `json:"maxPathLen"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the basic constraints to the given certificate.
|
||||||
|
func (b BasicConstraints) Set(c *x509.Certificate) {
|
||||||
|
c.IsCA = b.IsCA
|
||||||
|
c.MaxPathLen = b.MaxPathLen
|
||||||
|
if c.MaxPathLen < 0 {
|
||||||
|
c.MaxPathLen = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameConstraints represents the X509 Name constraints extension and defines a
|
||||||
|
// names space within which all subject names in subsequent certificates in a
|
||||||
|
// certificate path must be located. The name constraints extension must be used
|
||||||
|
// only in a CA.
|
||||||
|
type NameConstraints struct {
|
||||||
|
Critical bool `json:"critical"`
|
||||||
|
PermittedDNSDomains MultiString `json:"permittedDNSDomains"`
|
||||||
|
ExcludedDNSDomains MultiString `json:"excludedDNSDomains"`
|
||||||
|
PermittedIPRanges MultiIPNet `json:"permittedIPRanges"`
|
||||||
|
ExcludedIPRanges MultiIPNet `json:"excludedIPRanges"`
|
||||||
|
PermittedEmailAddresses MultiString `json:"permittedEmailAddresses"`
|
||||||
|
ExcludedEmailAddresses MultiString `json:"excludedEmailAddresses"`
|
||||||
|
PermittedURIDomains MultiString `json:"permittedURIDomains"`
|
||||||
|
ExcludedURIDomains MultiString `json:"excludedURIDomains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets sets the name constraints in the given certificate.
|
||||||
|
func (n NameConstraints) Set(c *x509.Certificate) {
|
||||||
|
c.PermittedDNSDomainsCritical = n.Critical
|
||||||
|
c.PermittedDNSDomains = n.PermittedDNSDomains
|
||||||
|
c.ExcludedDNSDomains = n.ExcludedDNSDomains
|
||||||
|
c.PermittedIPRanges = n.PermittedIPRanges
|
||||||
|
c.ExcludedIPRanges = n.ExcludedIPRanges
|
||||||
|
c.PermittedEmailAddresses = n.PermittedEmailAddresses
|
||||||
|
c.ExcludedEmailAddresses = n.ExcludedEmailAddresses
|
||||||
|
c.PermittedURIDomains = n.PermittedURIDomains
|
||||||
|
c.ExcludedURIDomains = n.ExcludedURIDomains
|
||||||
|
}
|
168
x509util/marshal_utils.go
Normal file
168
x509util/marshal_utils.go
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
package x509util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MultiString is a type used to unmarshal a JSON string or an array of strings
|
||||||
|
// into a []string.
|
||||||
|
type MultiString []string
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface for MultiString.
|
||||||
|
func (m *MultiString) UnmarshalJSON(data []byte) error {
|
||||||
|
if s, ok := maybeString(data); ok {
|
||||||
|
*m = MultiString([]string{s})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var v []string
|
||||||
|
if err := json.Unmarshal(data, &v); err != nil {
|
||||||
|
return errors.Wrap(err, "error unmarshaling json")
|
||||||
|
}
|
||||||
|
*m = MultiString(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiIP is a type used to unmarshal a JSON string or an array of strings into
|
||||||
|
// a []net.IP.
|
||||||
|
type MultiIP []net.IP
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface for MultiIP.
|
||||||
|
func (m *MultiIP) UnmarshalJSON(data []byte) error {
|
||||||
|
ms, err := unmarshalMultiString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ips := make([]net.IP, len(ms))
|
||||||
|
for i, s := range ms {
|
||||||
|
ip := net.ParseIP(s)
|
||||||
|
if ip == nil {
|
||||||
|
return errors.Errorf("error unmarshaling json: ip %s is not valid", s)
|
||||||
|
}
|
||||||
|
ips[i] = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = MultiIP(ips)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiIPNet is a type used to unmarshal a JSON string or an array of strings
|
||||||
|
// into a []*net.IPNet.
|
||||||
|
type MultiIPNet []*net.IPNet
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface for MultiIPNet.
|
||||||
|
func (m *MultiIPNet) UnmarshalJSON(data []byte) error {
|
||||||
|
ms, err := unmarshalMultiString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipNets := make([]*net.IPNet, len(ms))
|
||||||
|
for i, s := range ms {
|
||||||
|
_, ipNet, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error unmarshaling json")
|
||||||
|
}
|
||||||
|
ipNets[i] = ipNet
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = MultiIPNet(ipNets)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiURL is a type used to unmarshal a JSON string or an array of strings
|
||||||
|
// into a []*url.URL.
|
||||||
|
type MultiURL []*url.URL
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface for MultiURL.
|
||||||
|
func (m *MultiURL) UnmarshalJSON(data []byte) error {
|
||||||
|
ms, err := unmarshalMultiString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := make([]*url.URL, len(ms))
|
||||||
|
for i, s := range ms {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error unmarshaling json")
|
||||||
|
}
|
||||||
|
urls[i] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = MultiURL(urls)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiObjectIdentifier is a type used to unmarshal a JSON string or an array
|
||||||
|
// of strings into a []asn1.ObjectIdentifier.
|
||||||
|
type MultiObjectIdentifier []asn1.ObjectIdentifier
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface for
|
||||||
|
// MultiObjectIdentifier.
|
||||||
|
func (m *MultiObjectIdentifier) UnmarshalJSON(data []byte) error {
|
||||||
|
ms, err := unmarshalMultiString(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oids := make([]asn1.ObjectIdentifier, len(ms))
|
||||||
|
for i, s := range ms {
|
||||||
|
oid, err := parseObjectIdentifier(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oids[i] = oid
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = MultiObjectIdentifier(oids)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeString(data []byte) (string, bool) {
|
||||||
|
if len(data) > 0 && data[0] == '"' {
|
||||||
|
var v string
|
||||||
|
if err := json.Unmarshal(data, &v); err == nil {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalString(data []byte) (string, error) {
|
||||||
|
var v string
|
||||||
|
if err := json.Unmarshal(data, &v); err != nil {
|
||||||
|
return v, errors.Wrap(err, "error unmarshaling json")
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalMultiString(data []byte) ([]string, error) {
|
||||||
|
var v MultiString
|
||||||
|
if err := json.Unmarshal(data, &v); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error unmarshaling json")
|
||||||
|
}
|
||||||
|
return []string(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseObjectIdentifier(oid string) (asn1.ObjectIdentifier, error) {
|
||||||
|
parts := strings.Split(oid, ".")
|
||||||
|
oids := make([]int, len(parts))
|
||||||
|
|
||||||
|
for i, s := range parts {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return asn1.ObjectIdentifier{}, errors.Errorf("error unmarshaling json: %s is not an ASN1 object identifier", oid)
|
||||||
|
}
|
||||||
|
oids[i] = n
|
||||||
|
}
|
||||||
|
return asn1.ObjectIdentifier(oids), nil
|
||||||
|
}
|
48
x509util/options.go
Normal file
48
x509util/options.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package x509util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/cli/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
CertBuffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) apply(opts []Option) (*Options, error) {
|
||||||
|
for _, fn := range opts {
|
||||||
|
if err := fn(o); err != nil {
|
||||||
|
return o, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(o *Options) error
|
||||||
|
|
||||||
|
func WithTemplate(path string, data map[string]interface{}) Option {
|
||||||
|
return func(o *Options) error {
|
||||||
|
filename := config.StepAbs(path)
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error reading %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New(path).Funcs(sprig.TxtFuncMap()).Parse(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error parsing %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := tmpl.Execute(buf, data); err != nil {
|
||||||
|
return errors.Wrapf(err, "error executing %s", path)
|
||||||
|
}
|
||||||
|
o.CertBuffer = buf
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
1
x509util/sign_options.go
Normal file
1
x509util/sign_options.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package x509util
|
Loading…
Reference in a new issue