From f6610fb744319dc5f2a483fc8aec53a8da96f46a Mon Sep 17 00:00:00 2001 From: Maartje Eyskens Date: Mon, 24 Aug 2020 20:10:01 +0200 Subject: [PATCH] Support key usages Signed-off-by: Maartje Eyskens --- pkg/util/pki/csr.go | 16 +++++++----- pkg/util/pki/csr_test.go | 19 ++++++++++---- pkg/util/pki/keyusage.go | 56 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/pkg/util/pki/csr.go b/pkg/util/pki/csr.go index 2b85ac254..7f07557aa 100644 --- a/pkg/util/pki/csr.go +++ b/pkg/util/pki/csr.go @@ -195,17 +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 { + ku, eku, err := BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA) + usage, err := buildANS1KeyUsageRequest(ku) + if err != nil { + return nil, fmt.Errorf("failed to asn1 encode usages: %w", err) + } + asn1ExtendedUsages := []asn1.ObjectIdentifier{} + for _, eku := range eku { if oid, ok := OIDFromExtKeyUsage(eku); ok { - asn1Usages = append(asn1Usages, oid) + asn1ExtendedUsages = append(asn1ExtendedUsages, oid) } } extendedUsage := pkix.Extension{ Id: oidExtensionExtendedKeyUsage, } - extendedUsage.Value, err = asn1.Marshal(asn1Usages) + extendedUsage.Value, err = asn1.Marshal(asn1ExtendedUsages) if err != nil { return nil, fmt.Errorf("failed to asn1 encode extended usages: %w", err) } @@ -229,7 +233,7 @@ func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) { IPAddresses: iPAddresses, URIs: uriNames, EmailAddresses: crt.Spec.EmailAddresses, - ExtraExtensions: []pkix.Extension{extendedUsage}, + ExtraExtensions: []pkix.Extension{usage, extendedUsage}, }, nil } diff --git a/pkg/util/pki/csr_test.go b/pkg/util/pki/csr_test.go index a3f24c556..f023684e4 100644 --- a/pkg/util/pki/csr_test.go +++ b/pkg/util/pki/csr_test.go @@ -373,25 +373,34 @@ func TestRemoveDuplicates(t *testing.T) { } func TestGenerateCSR(t *testing.T) { - asn1Value, err := asn1.Marshal([]asn1.ObjectIdentifier{}) + asn1KeyUsage, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0xa0}, BitLength: asn1BitLength([]byte{0xa0})}) + asn1ExtKeyUsage, err := asn1.Marshal([]asn1.ObjectIdentifier{}) if err != nil { t.Fatal(err) } dafaultExtraExtensions := []pkix.Extension{ + { + Id: oidExtensionKeyUsage, + Value: asn1KeyUsage, + }, { Id: oidExtensionExtendedKeyUsage, - Value: asn1Value, + Value: asn1ExtKeyUsage, }, } - asn1Value, err = asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageIPSECEndSystem}) + asn1ExtKeyUsage, err = asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageIPSECEndSystem}) if err != nil { t.Fatal(err) } ipsecExtraExtensions := []pkix.Extension{ + { + Id: oidExtensionKeyUsage, + Value: asn1KeyUsage, + }, { Id: oidExtensionExtendedKeyUsage, - Value: asn1Value, + Value: asn1ExtKeyUsage, }, } @@ -423,7 +432,7 @@ func TestGenerateCSR(t *testing.T) { }, { name: "Generate CSR from certificate with extended key usages", - crt: &v1.Certificate{Spec: v1.CertificateSpec{CommonName: "example.org", Usages: []v1.KeyUsage{v1.UsageIPsecEndSystem}}}, + crt: &v1.Certificate{Spec: v1.CertificateSpec{CommonName: "example.org", Usages: []v1.KeyUsage{v1.UsageDigitalSignature, v1.UsageKeyEncipherment, v1.UsageIPsecEndSystem}}}, want: &x509.CertificateRequest{Version: 3, SignatureAlgorithm: x509.SHA256WithRSA, PublicKeyAlgorithm: x509.RSA, diff --git a/pkg/util/pki/keyusage.go b/pkg/util/pki/keyusage.go index ada2e4277..5e3708995 100644 --- a/pkg/util/pki/keyusage.go +++ b/pkg/util/pki/keyusage.go @@ -18,11 +18,15 @@ package pki import ( "crypto/x509" + "crypto/x509/pkix" "encoding/asn1" ) // Copied from x509.go -var oidExtensionExtendedKeyUsage = []int{2, 5, 29, 37} +var ( + oidExtensionKeyUsage = []int{2, 5, 29, 15} + oidExtensionExtendedKeyUsage = []int{2, 5, 29, 37} +) // RFC 5280, 4.2.1.12 Extended Key Usage // @@ -83,3 +87,53 @@ func OIDFromExtKeyUsage(eku x509.ExtKeyUsage) (oid asn1.ObjectIdentifier, ok boo } return } + +// asn1BitLength returns the bit-length of bitString by considering the +// most-significant bit in a byte to be the "first" bit. This convention +// matches ASN.1, but differs from almost everything else. +func asn1BitLength(bitString []byte) int { + bitLen := len(bitString) * 8 + + for i := range bitString { + b := bitString[len(bitString)-i-1] + + for bit := uint(0); bit < 8; bit++ { + if (b>>bit)&1 == 1 { + return bitLen + } + bitLen-- + } + } + + return 0 +} + +func reverseBitsInAByte(in byte) byte { + b1 := in>>4 | in<<4 + b2 := b1>>2&0x33 | b1<<2&0xcc + b3 := b2>>1&0x55 | b2<<1&0xaa + return b3 +} + +func buildANS1KeyUsageRequest(usage x509.KeyUsage) (pkix.Extension, error) { + oidExtensionKeyUsage := pkix.Extension{ + Id: oidExtensionKeyUsage, + } + var a [2]byte + a[0] = reverseBitsInAByte(byte(usage)) + a[1] = reverseBitsInAByte(byte(usage >> 8)) + + l := 1 + if a[1] != 0 { + l = 2 + } + + bitString := a[:l] + var err error + oidExtensionKeyUsage.Value, err = asn1.Marshal(asn1.BitString{Bytes: bitString, BitLength: asn1BitLength(bitString)}) + if err != nil { + return pkix.Extension{}, err + } + + return oidExtensionKeyUsage, nil +}