Repository: smallstep/cli
Pull request #305 (2020-06-23 18:21:38)

Add URI capability to SplitSANs method




Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 855aa640..00000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: 
-labels: 
-assignees: 
-
----
-
-### Subject of the issue
-Describe your issue here.
-
-### Your environment
-* OS - 
-* Version - 
-
-### Steps to reproduce
-Tell us how to reproduce this issue. Please provide a working demo, you can use [this template](https://plnkr.co/edit/XorWgI?p=preview) as a base.
-
-### Expected behaviour
-Tell us what should happen
-
-### Actual behaviour
-Tell us what happens instead
-
-### Additional context
-Add any other context about the problem here.
diff --git a/command/certificate/create.go b/command/certificate/create.go
index 25448e00..89aab38b 100644
--- a/command/certificate/create.go
+++ b/command/certificate/create.go
@@ -293,7 +293,7 @@ func createAction(ctx *cli.Context) error {
 		if len(sans) == 0 {
 			sans = []string{subject}
 		}
-		dnsNames, ips, emails := x509util.SplitSANs(sans)
+		dnsNames, ips, emails, uris := x509util.SplitSANs(sans)
 
 		csr := &x509.CertificateRequest{
 			Subject: pkix.Name{
@@ -302,6 +302,7 @@ func createAction(ctx *cli.Context) error {
 			DNSNames:       dnsNames,
 			IPAddresses:    ips,
 			EmailAddresses: emails,
+			URIs:           uris,
 		}
 		csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csr, priv)
 		if err != nil {
@@ -328,8 +329,6 @@ func createAction(ctx *cli.Context) error {
 		if (len(sans) == 0) && ((prof == "leaf") || (prof == "self-signed")) {
 			sans = []string{subject}
 		}
-		dnsNames, ips, emails := x509util.SplitSANs(sans)
-
 		if bundle && prof != "leaf" {
 			return errs.IncompatibleFlagValue(ctx, "bundle", "profile", prof)
 		}
@@ -351,9 +350,7 @@ func createAction(ctx *cli.Context) error {
 				profile, err = x509util.NewLeafProfile(subject, issIdentity.Crt,
 					issIdentity.Key, x509util.GenerateKeyPair(kty, crv, size),
 					x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
-					x509util.WithDNSNames(dnsNames),
-					x509util.WithIPAddresses(ips),
-					x509util.WithEmailAddresses(emails))
+					x509util.WithSANs(sans))
 				if err != nil {
 					return errors.WithStack(err)
 				}
@@ -367,9 +364,7 @@ func createAction(ctx *cli.Context) error {
 					issIdentity.Crt, issIdentity.Key,
 					x509util.GenerateKeyPair(kty, crv, size),
 					x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
-					x509util.WithDNSNames(dnsNames),
-					x509util.WithIPAddresses(ips),
-					x509util.WithEmailAddresses(emails))
+					x509util.WithSANs(sans))
 				if err != nil {
 					return errors.WithStack(err)
 				}
@@ -378,9 +373,7 @@ func createAction(ctx *cli.Context) error {
 			profile, err = x509util.NewRootProfile(subject,
 				x509util.GenerateKeyPair(kty, crv, size),
 				x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
-				x509util.WithDNSNames(dnsNames),
-				x509util.WithIPAddresses(ips),
-				x509util.WithEmailAddresses(emails))
+				x509util.WithSANs(sans))
 			if err != nil {
 				return errors.WithStack(err)
 			}
@@ -391,9 +384,7 @@ func createAction(ctx *cli.Context) error {
 			profile, err = x509util.NewSelfSignedLeafProfile(subject,
 				x509util.GenerateKeyPair(kty, crv, size),
 				x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
-				x509util.WithDNSNames(dnsNames),
-				x509util.WithIPAddresses(ips),
-				x509util.WithEmailAddresses(emails))
+				x509util.WithSANs(sans))
 			if err != nil {
 				return errors.WithStack(err)
 			}
diff --git a/command/ssh/certificate.go b/command/ssh/certificate.go
index d9852c8d..97147760 100644
--- a/command/ssh/certificate.go
+++ b/command/ssh/certificate.go
@@ -309,15 +309,14 @@ func certificateAction(ctx *cli.Context) error {
 				return errs.Wrap(err, "failed parsing uuid urn")
 			}
 
-			// Hack to add the URI.
-			// TODO: the proper solution would be to add support in
-			// x509util.SplitSANs.
 			template := &x509.CertificateRequest{
 				Subject:        csr.Subject,
 				DNSNames:       csr.DNSNames,
 				IPAddresses:    csr.IPAddresses,
 				EmailAddresses: csr.EmailAddresses,
-				URIs:           []*url.URL{uri},
+				// Prepend the generated uri. There is code that expects the
+				// uuid URI to be the first one.
+				URIs: append([]*url.URL{uri}, csr.URIs...),
 			}
 			csrBytes, err := x509.CreateCertificateRequest(rand.Reader, template, key)
 			if err != nil {
diff --git a/crypto/x509util/crt.go b/crypto/x509util/crt.go
index a8571738..0e0870d0 100644
--- a/crypto/x509util/crt.go
+++ b/crypto/x509util/crt.go
@@ -7,6 +7,7 @@ import (
 	"encoding/pem"
 	"io/ioutil"
 	"net"
+	"net/url"
 	"os"
 	"path/filepath"
 	"strings"
@@ -24,10 +25,11 @@ func Fingerprint(cert *x509.Certificate) string {
 // SplitSANs splits a slice of Subject Alternative Names into slices of
 // IP Addresses and DNS Names. If an element is not an IP address, then it
 // is bucketed as a DNS Name.
-func SplitSANs(sans []string) (dnsNames []string, ips []net.IP, emails []string) {
+func SplitSANs(sans []string) (dnsNames []string, ips []net.IP, emails []string, uris []*url.URL) {
 	dnsNames = []string{}
 	ips = []net.IP{}
 	emails = []string{}
+	uris = []*url.URL{}
 	if sans == nil {
 		return
 	}
@@ -37,8 +39,14 @@ func SplitSANs(sans []string) (dnsNames []string, ips []net.IP, emails []string)
 		} else if ip := net.ParseIP(san); ip != nil {
 			ips = append(ips, ip)
 		} else {
-			// If not IP then assume DNSName.
-			dnsNames = append(dnsNames, san)
+			// This is a hacky way to check if a SAN is a URL. If / when
+			// someone complains we can see about making it more "robust".
+			if u, err := url.Parse(san); err != nil || u.Scheme == "" {
+				// If not email, ip, or url, then dns name.
+				dnsNames = append(dnsNames, san)
+			} else {
+				uris = append(uris, u)
+			}
 		}
 	}
 	return
diff --git a/crypto/x509util/profile.go b/crypto/x509util/profile.go
index a3288b3d..7867acd8 100644
--- a/crypto/x509util/profile.go
+++ b/crypto/x509util/profile.go
@@ -10,6 +10,7 @@ import (
 	"encoding/pem"
 	"math/big"
 	"net"
+	"net/url"
 	"strings"
 	"time"
 
@@ -207,6 +208,30 @@ func WithEmailAddresses(emails []string) WithOption {
 	}
 }
 
+// WithURIs returns a Profile modifier which sets the URIs
+// that will be bound to the subject alternative name extension of the Certificate.
+func WithURIs(uris []*url.URL) WithOption {
+	return func(p Profile) error {
+		crt := p.Subject()
+		crt.URIs = uris
+		return nil
+	}
+}
+
+// WithSANs returns a profile modifier which set the dnsNames, emailAddresses,
+// ipAddresses, and URIs attributes of the Certificate.
+func WithSANs(sans []string) WithOption {
+	return func(p Profile) error {
+		dnsNames, ips, emails, uris := SplitSANs(sans)
+		cert := p.Subject()
+		cert.DNSNames = dnsNames
+		cert.IPAddresses = ips
+		cert.EmailAddresses = emails
+		cert.URIs = uris
+		return nil
+	}
+}
+
 // WithHosts returns a Profile modifier which sets the DNS Names and IP Addresses
 // that will be bound to the subject Certificate.
 //
diff --git a/go.mod b/go.mod
index 641bf592..2e42fb85 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 	github.com/lunixbochs/vtclean v1.0.0 // indirect
 	github.com/manifoldco/promptui v0.3.1
-	github.com/pkg/errors v0.8.1
+	github.com/pkg/errors v0.9.1
 	github.com/pquerna/otp v1.0.0
 	github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189
 	github.com/shurcooL/sanitized_anchor_name v1.0.0
@@ -24,14 +24,14 @@ require (
 	github.com/smallstep/truststore v0.9.3
 	github.com/smallstep/zcrypto v0.0.0-20200203191936-fbc32cf76bce
 	github.com/smallstep/zlint v0.0.0-20180727184541-d84eaafe274f
-	github.com/stretchr/testify v1.4.0
+	github.com/stretchr/testify v1.5.1
 	github.com/urfave/cli v1.22.2
 	golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
-	golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
+	golang.org/x/net v0.0.0-20200202094626-16171245cfb2
 	golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e
 	gopkg.in/square/go-jose.v2 v2.4.0
 	howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 // indirect
 )
 
-// replace github.com/smallstep/certificates => ../certificates
-// replace github.com/smallstep/certinfo => ../certinfo
+//replace github.com/smallstep/certificates => ../certificates
+//replace github.com/smallstep/certinfo => ../certinfo
diff --git a/go.sum b/go.sum
index cdcd061c..730965ac 100644
--- a/go.sum
+++ b/go.sum
@@ -51,6 +51,7 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -144,8 +145,11 @@ github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTD
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
+github.com/go-piv/piv-go v1.5.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
 github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
@@ -309,6 +313,7 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
 github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
@@ -438,6 +443,8 @@ github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pquerna/otp v1.0.0 h1:TBZrpfnzVbgmpYhiYBK+bJ4Ig0+ye+GGNMe2pTrvxCo=
@@ -570,6 +577,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec h1:AmoEvWAO3nDx1MEcMzPh+GzOOIA5Znpv6++c7bePPY0=
@@ -697,6 +706,8 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
diff --git a/utils/cautils/acmeutils.go b/utils/cautils/acmeutils.go
index 6dad10df..a330eb11 100644
--- a/utils/cautils/acmeutils.go
+++ b/utils/cautils/acmeutils.go
@@ -267,9 +267,9 @@ func finalizeOrder(ac *ca.ACMEClient, o *acme.Order, csr *x509.CertificateReques
 }
 
 func validateSANsForACME(sans []string) ([]string, error) {
-	dnsNames, ips, emails := splitSANs(sans)
-	if len(ips) > 0 || len(emails) > 0 {
-		return nil, errors.New("IP Address and Email Address SANs are not supported for ACME flow")
+	dnsNames, ips, emails, uris := splitSANs(sans)
+	if len(ips) > 0 || len(emails) > 0 || len(uris) > 0 {
+		return nil, errors.New("IP Address, Email Address, and URI SANs are not supported for ACME flow")
 	}
 	for _, dns := range dnsNames {
 		if strings.Contains(dns, "*") {
diff --git a/utils/cautils/certificate_flow.go b/utils/cautils/certificate_flow.go
index fcdfa1dd..ee1774e8 100644
--- a/utils/cautils/certificate_flow.go
+++ b/utils/cautils/certificate_flow.go
@@ -8,6 +8,7 @@ import (
 	"encoding/pem"
 	"fmt"
 	"net"
+	"net/url"
 	"os"
 	"strings"
 	"time"
@@ -235,7 +236,7 @@ func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject strin
 		return nil, nil, err
 	}
 
-	dnsNames, ips, emails := splitSANs(sans, jwt.Payload.SANs)
+	dnsNames, ips, emails, uris := splitSANs(sans, jwt.Payload.SANs)
 	switch jwt.Payload.Type() {
 	case token.AWS:
 		doc := jwt.Payload.Amazon.InstanceIdentityDocument
@@ -247,7 +248,7 @@ func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject strin
 			if !sharedContext.DisableCustomSANs {
 				defaultSANs = append(defaultSANs, subject)
 			}
-			dnsNames, ips, emails = splitSANs(defaultSANs)
+			dnsNames, ips, emails, uris = splitSANs(defaultSANs)
 		}
 	case token.GCP:
 		ce := jwt.Payload.Google.ComputeEngine
@@ -259,7 +260,7 @@ func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject strin
 			if !sharedContext.DisableCustomSANs {
 				defaultSANs = append(defaultSANs, subject)
 			}
-			dnsNames, ips, emails = splitSANs(defaultSANs)
+			dnsNames, ips, emails, uris = splitSANs(defaultSANs)
 		}
 	case token.Azure:
 		if len(ips) == 0 && len(dnsNames) == 0 {
@@ -269,7 +270,7 @@ func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject strin
 			if !sharedContext.DisableCustomSANs {
 				defaultSANs = append(defaultSANs, subject)
 			}
-			dnsNames, ips, emails = splitSANs(defaultSANs)
+			dnsNames, ips, emails, uris = splitSANs(defaultSANs)
 		}
 	case token.OIDC:
 		if jwt.Payload.Email != "" {
@@ -287,6 +288,7 @@ func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject strin
 		DNSNames:       dnsNames,
 		IPAddresses:    ips,
 		EmailAddresses: emails,
+		URIs:           uris,
 	}
 
 	csr, err := x509.CreateCertificateRequest(rand.Reader, template, pk)
@@ -308,7 +310,7 @@ func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject strin
 
 // splitSANs unifies the SAN collections passed as arguments and returns a list
 // of DNS names, a list of IP addresses, and a list of emails.
-func splitSANs(args ...[]string) (dnsNames []string, ipAddresses []net.IP, email []string) {
+func splitSANs(args ...[]string) (dnsNames []string, ipAddresses []net.IP, email []string, uris []*url.URL) {
 	m := make(map[string]bool)
 	var unique []string
 	for _, sans := range args {


Test patch:
diff --git a/crypto/x509util/crt_test.go b/crypto/x509util/crt_test.go
index afef7f93..fb764ac8 100644
--- a/crypto/x509util/crt_test.go
+++ b/crypto/x509util/crt_test.go
@@ -5,6 +5,7 @@ import (
 	"encoding/pem"
 	"io/ioutil"
 	"net"
+	"net/url"
 	"testing"
 
 	"github.com/smallstep/assert"
@@ -45,18 +46,26 @@ func mustParseCertificate(t *testing.T, filename string) *x509.Certificate {
 }
 
 func TestSplitSANs(t *testing.T) {
+	u1, err := url.Parse("https://ca.smallstep.com")
+	assert.FatalError(t, err)
+	u2, err := url.Parse("https://google.com/index.html")
+	assert.FatalError(t, err)
+	u3, err := url.Parse("urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959")
+	assert.FatalError(t, err)
 	tests := []struct {
 		name              string
 		sans, dns, emails []string
 		ips               []net.IP
+		uris              []*url.URL
 	}{
-		{name: "empty", sans: []string{}, dns: []string{}, ips: []net.IP{}, emails: []string{}},
+		{name: "empty", sans: []string{}, dns: []string{}, ips: []net.IP{}, emails: []string{}, uris: []*url.URL{}},
 		{
 			name:   "all-dns",
 			sans:   []string{"foo.internal", "bar.internal"},
 			dns:    []string{"foo.internal", "bar.internal"},
 			ips:    []net.IP{},
 			emails: []string{},
+			uris:   []*url.URL{},
 		},
 		{
 			name:   "all-ip",
@@ -64,6 +73,7 @@ func TestSplitSANs(t *testing.T) {
 			dns:    []string{},
 			ips:    []net.IP{net.ParseIP("0.0.0.0"), net.ParseIP("127.0.0.1")},
 			emails: []string{},
+			uris:   []*url.URL{},
 		},
 		{
 			name:   "all-email",
@@ -71,21 +81,32 @@ func TestSplitSANs(t *testing.T) {
 			dns:    []string{},
 			ips:    []net.IP{},
 			emails: []string{"max@smallstep.com", "mariano@smallstep.com"},
+			uris:   []*url.URL{},
+		},
+		{
+			name:   "all-uri",
+			sans:   []string{"https://ca.smallstep.com", "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959", "https://google.com/index.html"},
+			dns:    []string{},
+			ips:    []net.IP{},
+			emails: []string{},
+			uris:   []*url.URL{u1, u3, u2},
 		},
 		{
 			name:   "mix",
-			sans:   []string{"foo.internal", "max@smallstep.com", "mariano@smallstep.com", "1.1.1.1", "bar.internal"},
+			sans:   []string{"foo.internal", "https://ca.smallstep.com", "max@smallstep.com", "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959", "mariano@smallstep.com", "1.1.1.1", "bar.internal", "https://google.com/index.html"},
 			dns:    []string{"foo.internal", "bar.internal"},
 			ips:    []net.IP{net.ParseIP("1.1.1.1")},
 			emails: []string{"max@smallstep.com", "mariano@smallstep.com"},
+			uris:   []*url.URL{u1, u3, u2},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			dns, ips, emails := SplitSANs(tt.sans)
+			dns, ips, emails, uris := SplitSANs(tt.sans)
 			assert.Equals(t, dns, tt.dns)
 			assert.Equals(t, ips, tt.ips)
 			assert.Equals(t, emails, tt.emails)
+			assert.Equals(t, uris, tt.uris)
 		})
 	}
 }