From 738304bc6ff81961b1eb7e48ad9fe55ec59cce84 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 1 Jul 2020 18:27:29 -0700 Subject: [PATCH] Add support for SubjectalternativeName type. Move code around and some fixes. --- x509util/algorithms.go | 87 ++++++++++++++++ x509util/certificate.go | 176 +++++++++++++++----------------- x509util/certificate_request.go | 2 - x509util/extensions.go | 37 +++++++ 4 files changed, 204 insertions(+), 98 deletions(-) create mode 100644 x509util/algorithms.go diff --git a/x509util/algorithms.go b/x509util/algorithms.go new file mode 100644 index 00000000..9684cf1e --- /dev/null +++ b/x509util/algorithms.go @@ -0,0 +1,87 @@ +package x509util + +import ( + "crypto/x509" + "strings" + + "github.com/pkg/errors" +) + +// List of signature algorithms, all of them have values in upper case to match +// them with the string representation. +const ( + MD2_RSA = "MD2-RSA" + MD5_RSA = "MD5-RSA" + SHA1_RSA = "SHA1-RSA" + SHA256_RSA = "SHA256-RSA" + SHA384_RSA = "SHA384-RSA" + SHA512_RSA = "SHA512-RSA" + SHA256_RSAPSS = "SHA256-RSAPSS" + SHA384_RSAPSS = "SHA384-RSAPSS" + SHA512_RSAPSS = "SHA512-RSAPSS" + DSA_SHA1 = "DSA-SHA1" + DSA_SHA256 = "DSA-SHA256" + ECDSA_SHA1 = "ECDSA-SHA1" + ECDSA_SHA256 = "ECDSA-SHA256" + ECDSA_SHA384 = "ECDSA-SHA384" + ECDSA_SHA512 = "ECDSA-SHA512" + Ed25519 = "ED25519" +) + +// 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 = x509.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 { + name, err := unmarshalString(data) + if err != nil { + return err + } + + var sa x509.SignatureAlgorithm + switch strings.ToUpper(name) { + case MD2_RSA: + sa = x509.MD2WithRSA + case MD5_RSA: + sa = x509.MD5WithRSA + 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", name) + } + + *s = SignatureAlgorithm(sa) + return nil +} diff --git a/x509util/certificate.go b/x509util/certificate.go index 9e71de5c..3909d83f 100644 --- a/x509util/certificate.go +++ b/x509util/certificate.go @@ -5,33 +5,34 @@ import ( "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:"-"` + 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) { @@ -104,8 +105,9 @@ func (c *Certificate) GetCertificate() *x509.Certificate { return cert } -// Subject is the JSON representation of the X509 subject field. -type Subject struct { +// 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"` @@ -117,6 +119,26 @@ type Subject struct { 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, @@ -146,21 +168,36 @@ func (s Subject) Set(c *x509.Certificate) { } } -// 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 +// 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, } +} - type subjectAlias Subject - var ss subjectAlias - if err := json.Unmarshal(data, &ss); err != nil { - return errors.Wrap(err, "error unmarshaling json") +// 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, } - *s = Subject(ss) - return nil } // SerialNumber is the JSON representation of the X509 serial number. @@ -173,6 +210,13 @@ 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 @@ -201,63 +245,3 @@ func (s *SerialNumber) UnmarshalJSON(data []byte) error { } 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 index c43c8b16..6cc14141 100644 --- a/x509util/certificate_request.go +++ b/x509util/certificate_request.go @@ -42,7 +42,5 @@ func (c *CertificateRequest) GetCertificate() *Certificate { 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 index 09cfb90d..d8beecca 100644 --- a/x509util/extensions.go +++ b/x509util/extensions.go @@ -4,8 +4,13 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "fmt" + "net" + "net/url" "strings" + "github.com/smallstep/cli/crypto/x509util" + "github.com/pkg/errors" ) @@ -80,6 +85,38 @@ func (o *ObjectIdentifier) UnmarshalJSON(data []byte) error { return nil } +type SubjectAlternativeName struct { + Type string `json:"type"` + Value string `json:"value"` +} + +func (s SubjectAlternativeName) Set(c *x509.Certificate) { + switch strings.ToLower(s.Type) { + case "dns": + c.DNSNames = append(c.DNSNames, s.Value) + case "email": + c.EmailAddresses = append(c.EmailAddresses, s.Value) + case "ip": + // The validation of the IP would happen in the unmarshaling, but just + // to be sure we are only adding valid IPs. + if ip := net.ParseIP(s.Value); ip != nil { + c.IPAddresses = append(c.IPAddresses, ip) + } + case "uri": + if u, err := url.Parse(s.Value); err != nil { + c.URIs = append(c.URIs, u) + } + case "auto", "": + dnsNames, ips, emails, uris := x509util.SplitSANs([]string{s.Value}) + c.DNSNames = append(c.DNSNames, dnsNames...) + c.IPAddresses = append(c.IPAddresses, ips...) + c.EmailAddresses = append(c.EmailAddresses, emails...) + c.URIs = append(c.URIs, uris...) + default: + panic(fmt.Sprintf("unsupported subject alternative name type %s", s.Type)) + } +} + // KeyUsage type represents the JSON array used to represent the key usages of a // X509 certificate. type KeyUsage x509.KeyUsage