diff --git a/pkg/util/pki/BUILD.bazel b/pkg/util/pki/BUILD.bazel index 155d3efc8..bccad9f45 100644 --- a/pkg/util/pki/BUILD.bazel +++ b/pkg/util/pki/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "csr.go", "generate.go", + "keyusage.go", "parse.go", ], importpath = "github.com/jetstack/cert-manager/pkg/util/pki", diff --git a/pkg/util/pki/csr.go b/pkg/util/pki/csr.go index dc653cbec..2b85ac254 100644 --- a/pkg/util/pki/csr.go +++ b/pkg/util/pki/csr.go @@ -22,6 +22,7 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" "encoding/pem" "errors" "fmt" @@ -121,8 +122,6 @@ Outer: return found } -const defaultOrganization = "cert-manager" - // OrganizationForCertificate will return the Organization to set for the // Certificate resource. // If an Organization is not specifically set, a default will be used. @@ -196,6 +195,21 @@ func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) { return nil, err } + _, eka, err := BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA) + asn1Usages := []asn1.ObjectIdentifier{} + for _, eku := range eka { + if oid, ok := OIDFromExtKeyUsage(eku); ok { + asn1Usages = append(asn1Usages, oid) + } + } + extendedUsage := pkix.Extension{ + Id: oidExtensionExtendedKeyUsage, + } + extendedUsage.Value, err = asn1.Marshal(asn1Usages) + if err != nil { + return nil, fmt.Errorf("failed to asn1 encode extended usages: %w", err) + } + return &x509.CertificateRequest{ Version: 3, SignatureAlgorithm: sigAlgo, @@ -211,12 +225,11 @@ func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) { SerialNumber: subject.SerialNumber, CommonName: commonName, }, - DNSNames: dnsNames, - IPAddresses: iPAddresses, - URIs: uriNames, - EmailAddresses: crt.Spec.EmailAddresses, - // TODO: work out how best to handle extensions/key usages here - ExtraExtensions: []pkix.Extension{}, + DNSNames: dnsNames, + IPAddresses: iPAddresses, + URIs: uriNames, + EmailAddresses: crt.Spec.EmailAddresses, + ExtraExtensions: []pkix.Extension{extendedUsage}, }, nil } diff --git a/pkg/util/pki/csr_test.go b/pkg/util/pki/csr_test.go index 6809d6d6a..a3f24c556 100644 --- a/pkg/util/pki/csr_test.go +++ b/pkg/util/pki/csr_test.go @@ -18,10 +18,12 @@ package pki import ( "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "reflect" "testing" - "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + v1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" "github.com/jetstack/cert-manager/pkg/util" ) @@ -369,3 +371,82 @@ func TestRemoveDuplicates(t *testing.T) { } } } + +func TestGenerateCSR(t *testing.T) { + asn1Value, err := asn1.Marshal([]asn1.ObjectIdentifier{}) + if err != nil { + t.Fatal(err) + } + dafaultExtraExtensions := []pkix.Extension{ + { + Id: oidExtensionExtendedKeyUsage, + Value: asn1Value, + }, + } + + asn1Value, err = asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageIPSECEndSystem}) + if err != nil { + t.Fatal(err) + } + ipsecExtraExtensions := []pkix.Extension{ + { + Id: oidExtensionExtendedKeyUsage, + Value: asn1Value, + }, + } + + tests := []struct { + name string + crt *v1.Certificate + want *x509.CertificateRequest + wantErr bool + }{ + { + name: "Generate CSR from certificate with only DNS", + crt: &v1.Certificate{Spec: v1.CertificateSpec{DNSNames: []string{"example.org"}}}, + want: &x509.CertificateRequest{Version: 3, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + DNSNames: []string{"example.org"}, + ExtraExtensions: dafaultExtraExtensions, + }, + }, + { + name: "Generate CSR from certificate with only CN", + crt: &v1.Certificate{Spec: v1.CertificateSpec{CommonName: "example.org"}}, + want: &x509.CertificateRequest{Version: 3, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + Subject: pkix.Name{CommonName: "example.org"}, + ExtraExtensions: dafaultExtraExtensions, + }, + }, + { + name: "Generate CSR from certificate with extended key usages", + crt: &v1.Certificate{Spec: v1.CertificateSpec{CommonName: "example.org", Usages: []v1.KeyUsage{v1.UsageIPsecEndSystem}}}, + want: &x509.CertificateRequest{Version: 3, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + Subject: pkix.Name{CommonName: "example.org"}, + ExtraExtensions: ipsecExtraExtensions, + }, + }, + { + name: "Error on generating CSR from certificate with no subject", + crt: &v1.Certificate{Spec: v1.CertificateSpec{}}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateCSR(tt.crt) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateCSR() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GenerateCSR() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/util/pki/generate_test.go b/pkg/util/pki/generate_test.go index 8522e2a7e..90e0ca74f 100644 --- a/pkg/util/pki/generate_test.go +++ b/pkg/util/pki/generate_test.go @@ -239,7 +239,7 @@ func signTestCert(key crypto.Signer) *x509.Certificate { SerialNumber: serialNumber, SignatureAlgorithm: x509.SHA256WithRSA, Subject: pkix.Name{ - Organization: []string{defaultOrganization}, + Organization: []string{"cert-manager"}, CommonName: commonName, }, NotBefore: time.Now(), diff --git a/pkg/util/pki/keyusage.go b/pkg/util/pki/keyusage.go new file mode 100644 index 000000000..ec42c7061 --- /dev/null +++ b/pkg/util/pki/keyusage.go @@ -0,0 +1,69 @@ +package pki + +import ( + "crypto/x509" + "encoding/asn1" +) + +// Copied from x509.go +var oidExtensionExtendedKeyUsage = []int{2, 5, 29, 37} + +// RFC 5280, 4.2.1.12 Extended Key Usage +// +// anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 } +// +// id-kp OBJECT IDENTIFIER ::= { id-pkix 3 } +// +// id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } +// id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 } +// id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 } +// id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 } +// id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 } +// id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 } +var ( + oidExtKeyUsageAny = asn1.ObjectIdentifier{2, 5, 29, 37, 0} + oidExtKeyUsageServerAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1} + oidExtKeyUsageClientAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2} + oidExtKeyUsageCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3} + oidExtKeyUsageEmailProtection = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4} + oidExtKeyUsageIPSECEndSystem = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5} + oidExtKeyUsageIPSECTunnel = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6} + oidExtKeyUsageIPSECUser = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7} + oidExtKeyUsageTimeStamping = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8} + oidExtKeyUsageOCSPSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9} + oidExtKeyUsageMicrosoftServerGatedCrypto = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 10, 3, 3} + oidExtKeyUsageNetscapeServerGatedCrypto = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 4, 1} + oidExtKeyUsageMicrosoftCommercialCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 1, 22} + oidExtKeyUsageMicrosoftKernelCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 61, 1, 1} +) + +// extKeyUsageOIDs contains the mapping between an ExtKeyUsage and its OID. +var extKeyUsageOIDs = []struct { + extKeyUsage x509.ExtKeyUsage + oid asn1.ObjectIdentifier +}{ + {x509.ExtKeyUsageAny, oidExtKeyUsageAny}, + {x509.ExtKeyUsageServerAuth, oidExtKeyUsageServerAuth}, + {x509.ExtKeyUsageClientAuth, oidExtKeyUsageClientAuth}, + {x509.ExtKeyUsageCodeSigning, oidExtKeyUsageCodeSigning}, + {x509.ExtKeyUsageEmailProtection, oidExtKeyUsageEmailProtection}, + {x509.ExtKeyUsageIPSECEndSystem, oidExtKeyUsageIPSECEndSystem}, + {x509.ExtKeyUsageIPSECTunnel, oidExtKeyUsageIPSECTunnel}, + {x509.ExtKeyUsageIPSECUser, oidExtKeyUsageIPSECUser}, + {x509.ExtKeyUsageTimeStamping, oidExtKeyUsageTimeStamping}, + {x509.ExtKeyUsageOCSPSigning, oidExtKeyUsageOCSPSigning}, + {x509.ExtKeyUsageMicrosoftServerGatedCrypto, oidExtKeyUsageMicrosoftServerGatedCrypto}, + {x509.ExtKeyUsageNetscapeServerGatedCrypto, oidExtKeyUsageNetscapeServerGatedCrypto}, + {x509.ExtKeyUsageMicrosoftCommercialCodeSigning, oidExtKeyUsageMicrosoftCommercialCodeSigning}, + {x509.ExtKeyUsageMicrosoftKernelCodeSigning, oidExtKeyUsageMicrosoftKernelCodeSigning}, +} + +// OIDFromExtKeyUsage returns the ASN1 Identifier for a x509.ExtKeyUsage +func OIDFromExtKeyUsage(eku x509.ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool) { + for _, pair := range extKeyUsageOIDs { + if eku == pair.extKeyUsage { + return pair.oid, true + } + } + return +}