From 2556b57906e569c7165b6c2ea5d96d95235e2ace Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 30 Jun 2020 19:19:52 -0700 Subject: [PATCH] Add types for certificate flexibility. This is a first implementation, not the final one. --- x509util/certificate.go | 263 ++++++++++++++++++++++++++++ x509util/certificate_request.go | 48 ++++++ x509util/extensions.go | 294 ++++++++++++++++++++++++++++++++ x509util/marshal_utils.go | 168 ++++++++++++++++++ x509util/options.go | 48 ++++++ x509util/sign_options.go | 1 + 6 files changed, 822 insertions(+) create mode 100644 x509util/certificate.go create mode 100644 x509util/certificate_request.go create mode 100644 x509util/extensions.go create mode 100644 x509util/marshal_utils.go create mode 100644 x509util/options.go create mode 100644 x509util/sign_options.go diff --git a/x509util/certificate.go b/x509util/certificate.go new file mode 100644 index 00000000..9e71de5c --- /dev/null +++ b/x509util/certificate.go @@ -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 +} diff --git a/x509util/certificate_request.go b/x509util/certificate_request.go new file mode 100644 index 00000000..c43c8b16 --- /dev/null +++ b/x509util/certificate_request.go @@ -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, + } +} diff --git a/x509util/extensions.go b/x509util/extensions.go new file mode 100644 index 00000000..09cfb90d --- /dev/null +++ b/x509util/extensions.go @@ -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 +} diff --git a/x509util/marshal_utils.go b/x509util/marshal_utils.go new file mode 100644 index 00000000..7f3bb4af --- /dev/null +++ b/x509util/marshal_utils.go @@ -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 +} diff --git a/x509util/options.go b/x509util/options.go new file mode 100644 index 00000000..c508232c --- /dev/null +++ b/x509util/options.go @@ -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 + } +} diff --git a/x509util/sign_options.go b/x509util/sign_options.go new file mode 100644 index 00000000..2078ca02 --- /dev/null +++ b/x509util/sign_options.go @@ -0,0 +1 @@ +package x509util