From d31bb2515a8d6b7345e0373ff1d5b7691d873584 Mon Sep 17 00:00:00 2001 From: Reid Rankin Date: Tue, 31 Jul 2018 10:50:54 -0400 Subject: [PATCH 1/2] Add support for EKUs --- cmd/request_cert.go | 59 ++++++++++++++++++++++++++++++++++++++++++++- pkix/cert_auth.go | 2 ++ pkix/cert_host.go | 2 ++ pkix/csr.go | 5 +++- pkix/csr_test.go | 2 +- 5 files changed, 67 insertions(+), 3 deletions(-) diff --git a/cmd/request_cert.go b/cmd/request_cert.go index 5bab3ca..8ad4bac 100644 --- a/cmd/request_cert.go +++ b/cmd/request_cert.go @@ -26,6 +26,10 @@ import ( "github.com/square/certstrap/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/square/certstrap/depot" "github.com/square/certstrap/pkix" + + "strconv" + "encoding/asn1" + x509pkix "crypto/x509/pkix" ) // NewCertRequestCommand sets up a "request-cert" command to create a request for a new certificate (CSR) @@ -47,6 +51,8 @@ func NewCertRequestCommand() cli.Command { cli.StringFlag{"domain", "", "DNS entries for subject alt name (comma separated)", ""}, cli.StringFlag{"key", "", "Path to private key PEM file. If blank, will generate new keypair.", ""}, cli.BoolFlag{"stdout", "Print signing request to stdout in addition to saving file", ""}, + + cli.StringFlag{"eku", "", "Comma-separated list of EKU OIDs. If the anyExtendedKeyUsage OID (2.5.29.37.0) is not in this list, the extension will be marked critical.", ""}, }, Action: newCertAction, } @@ -121,7 +127,58 @@ func newCertAction(c *cli.Context) { } } - csr, err := pkix.CreateCertificateSigningRequest(key, c.String("organizational-unit"), ips, domains, c.String("organization"), c.String("country"), c.String("province"), c.String("locality"), name) + var ekuExtension *x509pkix.Extension + if c.IsSet("eku") { + var anyEKUOid asn1.ObjectIdentifier = asn1.ObjectIdentifier{2, 5, 29, 37, 0} + var sawAnyEKUOid bool = false + var oids []asn1.ObjectIdentifier + for _, oidString := range strings.Split(c.String("eku"), ",") { + var thisOid asn1.ObjectIdentifier + var isAnyEKUOid bool = true + for i, oidComponent := range strings.Split(oidString, ".") { + var thisOidComponent int + if val, err := strconv.Atoi(oidComponent); err == nil { + thisOidComponent = val + } else { + fmt.Fprintln(os.Stderr, "OID component parsing error:", err) + os.Exit(1) + } + thisOid = append(thisOid, thisOidComponent) + if i < len(anyEKUOid) { + isAnyEKUOid = isAnyEKUOid && (thisOidComponent == anyEKUOid[i]) + } else { + isAnyEKUOid = false + } + } + if len(thisOid) > 0 { + oids = append(oids, thisOid) + sawAnyEKUOid = sawAnyEKUOid || isAnyEKUOid + } + } + if len(oids) > 0 { + if val, err := asn1.Marshal(oids); err == nil { + ekuExtension = &x509pkix.Extension{ + Id: asn1.ObjectIdentifier{2, 5, 29, 37}, + Critical: !sawAnyEKUOid, + Value: val, + } + } else { + fmt.Fprintln(os.Stderr, "Error marshalling EKU extension:", err) + os.Exit(1) + } + } + } + + var extensionsList []x509pkix.Extension + var extensions *[]x509pkix.Extension = nil + if ekuExtension != nil { + extensionsList = append(extensionsList, *ekuExtension) + } + if len(extensionsList) > 0 { + extensions = &extensionsList + } + + csr, err := pkix.CreateCertificateSigningRequest(key, c.String("organizational-unit"), ips, domains, c.String("organization"), c.String("country"), c.String("province"), c.String("locality"), name, extensions) if err != nil { fmt.Fprintln(os.Stderr, "Create certificate request error:", err) os.Exit(1) diff --git a/pkix/cert_auth.go b/pkix/cert_auth.go index d363aca..eb43675 100644 --- a/pkix/cert_auth.go +++ b/pkix/cert_auth.go @@ -145,6 +145,8 @@ func CreateIntermediateCertificateAuthority(crtAuth *Certificate, keyAuth *Key, authTemplate.IPAddresses = rawCsr.IPAddresses authTemplate.DNSNames = rawCsr.DNSNames + authTemplate.ExtraExtensions = rawCsr.Extensions + rawCrtAuth, err := crtAuth.GetRawCertificate() if err != nil { return nil, err diff --git a/pkix/cert_host.go b/pkix/cert_host.go index 7d59e7c..8dbe84c 100644 --- a/pkix/cert_host.go +++ b/pkix/cert_host.go @@ -96,6 +96,8 @@ func CreateCertificateHost(crtAuth *Certificate, keyAuth *Key, csr *CertificateS hostTemplate.IPAddresses = rawCsr.IPAddresses hostTemplate.DNSNames = rawCsr.DNSNames + hostTemplate.ExtraExtensions = rawCsr.Extensions + rawCrtAuth, err := crtAuth.GetRawCertificate() if err != nil { return nil, err diff --git a/pkix/csr.go b/pkix/csr.go index 4e153b1..0ed1bc2 100644 --- a/pkix/csr.go +++ b/pkix/csr.go @@ -70,7 +70,7 @@ func ParseAndValidateIPs(ipList string) (res []net.IP, err error) { } // CreateCertificateSigningRequest sets up a request to create a csr file with the given parameters -func CreateCertificateSigningRequest(key *Key, organizationalUnit string, ipList []net.IP, domainList []string, organization string, country string, province string, locality string, commonName string) (*CertificateSigningRequest, error) { +func CreateCertificateSigningRequest(key *Key, organizationalUnit string, ipList []net.IP, domainList []string, organization string, country string, province string, locality string, commonName string, extensions *[]pkix.Extension) (*CertificateSigningRequest, error) { csrPkixName.CommonName = commonName @@ -94,6 +94,9 @@ func CreateCertificateSigningRequest(key *Key, organizationalUnit string, ipList IPAddresses: ipList, DNSNames: domainList, } + if extensions != nil { + (*csrTemplate).ExtraExtensions = *extensions + } csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key.Private) if err != nil { diff --git a/pkix/csr_test.go b/pkix/csr_test.go index f19db37..22d85a2 100644 --- a/pkix/csr_test.go +++ b/pkix/csr_test.go @@ -79,7 +79,7 @@ func TestCreateCertificateSigningRequest(t *testing.T) { t.Fatal("Failed creating rsa key:", err) } - csr, err := CreateCertificateSigningRequest(key, csrHostname, nil, nil, "example", "US", "California", "San Francisco", csrCN) + csr, err := CreateCertificateSigningRequest(key, csrHostname, nil, nil, "example", "US", "California", "San Francisco", csrCN, nil) if err != nil { t.Fatal("Failed creating certificate request:", err) } From c71646f4c81dcbabc801c4ba95415514b591da07 Mon Sep 17 00:00:00 2001 From: Reid Rankin Date: Tue, 31 Jul 2018 11:12:57 -0400 Subject: [PATCH 2/2] Render 'years' using %d instead of %s --- cmd/init.go | 2 +- cmd/sign.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index 2050525..626ca52 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -69,7 +69,7 @@ func initAction(c *cli.Context) { var err error expires := c.String("expires") if years := c.Int("years"); years != 0 { - expires = fmt.Sprintf("%s %s years", expires, years) + expires = fmt.Sprintf("%s %d years", expires, years) } // Expiry parsing is a naive regex implementation diff --git a/cmd/sign.go b/cmd/sign.go index 42effea..63cef94 100644 --- a/cmd/sign.go +++ b/cmd/sign.go @@ -61,7 +61,7 @@ func newSignAction(c *cli.Context) { expires := c.String("expires") if years := c.Int("years"); years != 0 { - expires = fmt.Sprintf("%s %s years", expires, years) + expires = fmt.Sprintf("%s %d years", expires, years) } // Expiry parsing is a naive regex implementation