Skip to content

Commit

Permalink
Add emails sans to ca [sign|certificate] and certificate create
Browse files Browse the repository at this point in the history
* x509util.SplitSANs now finds emails as well
  • Loading branch information
dopey committed Aug 23, 2019
1 parent 723e28f commit c986fb1
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 22 deletions.
6 changes: 3 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 10 additions & 10 deletions command/ca/certificate.go
Expand Up @@ -342,12 +342,7 @@ func (f *certificateFlow) CreateSignRequest(tok, subject string, sans []string)
return nil, nil, err
}

var emails []string
dnsNames, ips := splitSANs(sans, jwt.Payload.SANs)
if jwt.Payload.Email != "" {
emails = append(emails, jwt.Payload.Email)
}

dnsNames, ips, emails := splitSANs(sans, jwt.Payload.SANs)
switch jwt.Payload.Type() {
case token.AWS:
doc := jwt.Payload.Amazon.InstanceIdentityDocument
Expand All @@ -359,7 +354,7 @@ func (f *certificateFlow) CreateSignRequest(tok, subject string, sans []string)
if !sharedContext.DisableCustomSANs {
defaultSANs = append(defaultSANs, subject)
}
dnsNames, ips = splitSANs(defaultSANs)
dnsNames, ips, emails = splitSANs(defaultSANs)
}
case token.GCP:
ce := jwt.Payload.Google.ComputeEngine
Expand All @@ -371,7 +366,7 @@ func (f *certificateFlow) CreateSignRequest(tok, subject string, sans []string)
if !sharedContext.DisableCustomSANs {
defaultSANs = append(defaultSANs, subject)
}
dnsNames, ips = splitSANs(defaultSANs)
dnsNames, ips, emails = splitSANs(defaultSANs)
}
case token.Azure:
if len(ips) == 0 && len(dnsNames) == 0 {
Expand All @@ -381,8 +376,13 @@ func (f *certificateFlow) CreateSignRequest(tok, subject string, sans []string)
if !sharedContext.DisableCustomSANs {
defaultSANs = append(defaultSANs, subject)
}
dnsNames, ips = splitSANs(defaultSANs)
dnsNames, ips, emails = splitSANs(defaultSANs)
}
case token.OIDC:
if jwt.Payload.Email != "" {
emails = append(emails, jwt.Payload.Email)
}
subject = jwt.Payload.Subject
default: // Use common name in the token
subject = jwt.Payload.Subject
}
Expand Down Expand Up @@ -416,7 +416,7 @@ func (f *certificateFlow) CreateSignRequest(tok, subject string, sans []string)

// splitSANs unifies the SAN collections passed as arguments and returns a list
// of DNS names and a list of IP addresses.
func splitSANs(args ...[]string) (dnsNames []string, ipAddresses []net.IP) {
func splitSANs(args ...[]string) (dnsNames []string, ipAddresses []net.IP, email []string) {
m := make(map[string]bool)
var unique []string
for _, sans := range args {
Expand Down
6 changes: 6 additions & 0 deletions command/ca/sign.go
Expand Up @@ -139,5 +139,11 @@ func mergeSans(ctx *cli.Context, csr *x509.CertificateRequest) []string {
m[s] = true
}
}
for _, s := range csr.EmailAddresses {
if _, ok := m[s]; !ok {
uniq = append(uniq, s)
m[s] = true
}
}
return uniq
}
19 changes: 12 additions & 7 deletions command/certificate/create.go
Expand Up @@ -314,7 +314,7 @@ func createAction(ctx *cli.Context) error {
if len(sans) == 0 {
sans = []string{subject}
}
dnsNames, ips := x509util.SplitSANs(sans)
dnsNames, ips, emails := x509util.SplitSANs(sans)

var (
priv interface{}
Expand All @@ -339,8 +339,9 @@ func createAction(ctx *cli.Context) error {
Subject: pkix.Name{
CommonName: subject,
},
DNSNames: dnsNames,
IPAddresses: ips,
DNSNames: dnsNames,
IPAddresses: ips,
EmailAddresses: emails,
}
csrBytes, err := stepx509.CreateCertificateRequest(rand.Reader, _csr, priv)
if err != nil {
Expand Down Expand Up @@ -382,7 +383,8 @@ func createAction(ctx *cli.Context) error {
issIdentity.Key, x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips))
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
Expand All @@ -396,7 +398,8 @@ func createAction(ctx *cli.Context) error {
x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips))
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
Expand All @@ -406,7 +409,8 @@ func createAction(ctx *cli.Context) error {
x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips))
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
Expand All @@ -418,7 +422,8 @@ func createAction(ctx *cli.Context) error {
x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips))
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
Expand Down
7 changes: 5 additions & 2 deletions crypto/x509util/crt.go
Expand Up @@ -24,14 +24,17 @@ 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) {
func SplitSANs(sans []string) (dnsNames []string, ips []net.IP, emails []string) {
dnsNames = []string{}
ips = []net.IP{}
emails = []string{}
if sans == nil {
return
}
for _, san := range sans {
if ip := net.ParseIP(san); ip != nil {
if strings.Contains(san, "@") {
emails = append(emails, san)
} else if ip := net.ParseIP(san); ip != nil {
ips = append(ips, ip)
} else {
// If not IP then assume DNSName.
Expand Down
35 changes: 35 additions & 0 deletions crypto/x509util/crt_test.go
Expand Up @@ -4,7 +4,10 @@ import (
"crypto/x509"
"encoding/pem"
"io/ioutil"
"net"
"testing"

"github.com/smallstep/assert"
)

func TestFingerprint(t *testing.T) {
Expand Down Expand Up @@ -40,3 +43,35 @@ func mustParseCertificate(t *testing.T, filename string) *x509.Certificate {
}
return cert
}

func TestSplitSANs(t *testing.T) {
tests := []struct {
name string
sans, dns, emails []string
ips []net.IP
}{
{name: "empty"},
{name: "all-dns", sans: []string{"foo.internal", "bar.internal"}, dns: []string{"foo.internal", "bar.internal"}},
{name: "all-ip", sans: []string{"0.0.0.0", "127.0.0.1"}, ips: []net.IP{net.ParseIP("0.0.0.0"), net.ParseIP("127.0.0.1")}},
{
name: "all-email",
sans: []string{"max@smallstep.com", "mariano@smallstep.com"},
emails: []string{"max@smallstep.com", "mariano@smallstep.com"},
},
{
name: "mix",
sans: []string{"foo.internal", "max@smallstep.com", "mariano@smallstep.com", "1.1.1.1", "bar.internal"},
dns: []string{"foo.internal", "bar.internal"},
ips: []net.IP{net.ParseIP("1.1.1.1")},
emails: []string{"max@smallstep.com", "mariano@smallstep.com"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dns, ips, emails := SplitSANs(tt.sans)
assert.Equals(t, dns, tt.dns)
assert.Equals(t, ips, tt.ips)
assert.Equals(t, emails, tt.emails)
})
}
}
10 changes: 10 additions & 0 deletions crypto/x509util/profile.go
Expand Up @@ -190,6 +190,16 @@ func WithIPAddresses(ips []net.IP) WithOption {
}
}

// WithEmailAddresses returns a Profile modifier which sets the Email Addresses
// that will be bound to the subject alternative name extension of the Certificate.
func WithEmailAddresses(emails []string) WithOption {
return func(p Profile) error {
crt := p.Subject()
crt.EmailAddresses = emails
return nil
}
}

// WithHosts returns a Profile modifier which sets the DNS Names and IP Addresses
// that will be bound to the subject Certificate.
//
Expand Down

0 comments on commit c986fb1

Please sign in to comment.