Skip to content

Commit

Permalink
Merge branch 'unreality-extended-sans'
Browse files Browse the repository at this point in the history
  • Loading branch information
maraino committed Aug 5, 2022
2 parents 1750c6f + 0b98886 commit 72ddf42
Show file tree
Hide file tree
Showing 5 changed files with 464 additions and 50 deletions.
28 changes: 4 additions & 24 deletions tlsutil/utils.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,18 @@
package tlsutil

import (
"errors"
"net"

"golang.org/x/net/idna"
"go.step.sm/crypto/x509util"
)

// SanitizeName converts the given domain to its ASCII form.
func SanitizeName(domain string) (string, error) {
if domain == "" {
return "", errors.New("empty server name")
}

// Note that this conversion is necessary because some server names in the handshakes
// started by some clients (such as cURL) are not converted to Punycode, which will
// prevent us from obtaining certificates for them. In addition, we should also treat
// example.com and EXAMPLE.COM as equivalent and return the same certificate for them.
// Fortunately, this conversion also helped us deal with this kind of mixedcase problems.
//
// Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use
// idna.Punycode.ToASCII (or just idna.ToASCII) here.
name, err := idna.Lookup.ToASCII(domain)
if err != nil {
return "", errors.New("server name contains invalid character")
}

return name, nil
}
var SanitizeName = x509util.SanitizeName

// SanitizeHost returns the ASCII form of the host part in a host:port address.
func SanitizeHost(host string) (string, error) {
if h, _, err := net.SplitHostPort(host); err == nil {
return SanitizeName(h)
return x509util.SanitizeName(h)
}
return SanitizeName(host)
return x509util.SanitizeName(host)
}
56 changes: 45 additions & 11 deletions x509util/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,34 @@ func NewCertificate(cr *x509.CertificateRequest, opts ...Option) (*Certificate,
// certificate.
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)

// SANs slice.
for _, san := range c.SANs {
san.Set(cert)
if c.hasExtendedSANs() && !c.hasExtension(oidExtensionSubjectAltName) {
subjectAltNameExtension, err := createSubjectAltNameExtension(c, subjectIsEmpty(cert.Subject))
if err != nil {
panic(err)
}
subjectAltNameExtension.Set(cert)
} else {
// When we have no extended SANs, use the golang x509 lib to create the
// extension instead
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)
Expand Down Expand Up @@ -122,6 +132,30 @@ func (c *Certificate) GetCertificate() *x509.Certificate {
return cert
}

// hasExtendedSANs returns true if the certificate contains any SAN types that
// are not supported by the golang x509 library (i.e. RegisteredID, OtherName,
// DirectoryName, X400Address, or EDIPartyName)
//
// See also https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.2.1.6
func (c *Certificate) hasExtendedSANs() bool {
for _, san := range c.SANs {
if !(san.Type == DNSType || san.Type == EmailType || san.Type == IPType || san.Type == URIType || san.Type == AutoType || san.Type == "") {
return true
}
}
return false
}

// hasExtension returns true if the given extension oid is in the certificate.
func (c *Certificate) hasExtension(oid ObjectIdentifier) bool {
for _, e := range c.Extensions {
if e.ID.Equal(oid) {
return true
}
}
return false
}

// CreateCertificate signs the given template using the parent private key and
// returns it.
func CreateCertificate(template, parent *x509.Certificate, pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) {
Expand Down
13 changes: 4 additions & 9 deletions x509util/certificate_request.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package x509util

import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/json"

"github.com/pkg/errors"
)

var emptyASN1Subject = []byte{0x30, 0}
var oidExtensionSubjectAltName = []int{2, 5, 29, 17}

// CertificateRequest is the JSON representation of an X.509 certificate. It is
Expand Down Expand Up @@ -175,12 +172,10 @@ func CreateCertificateRequest(commonName string, sans []string, signer crypto.Si
// fixSubjectAltName makes sure to mark the SAN extension to critical if the
// subject is empty.
func fixSubjectAltName(cr *x509.CertificateRequest) {
if asn1Subject, err := asn1.Marshal(cr.Subject.ToRDNSequence()); err == nil {
if bytes.Equal(asn1Subject, emptyASN1Subject) {
for i, ext := range cr.Extensions {
if ext.Id.Equal(oidExtensionSubjectAltName) {
cr.Extensions[i].Critical = true
}
if subjectIsEmpty(cr.Subject) {
for i, ext := range cr.Extensions {
if ext.Id.Equal(oidExtensionSubjectAltName) {
cr.Extensions[i].Critical = true
}
}
}
Expand Down
Loading

0 comments on commit 72ddf42

Please sign in to comment.