Add extended key usages into CSR

Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
This commit is contained in:
Maartje Eyskens 2020-08-24 10:22:58 +02:00
parent 4660d4828f
commit d15054e4ea
5 changed files with 174 additions and 10 deletions

View File

@ -5,6 +5,7 @@ go_library(
srcs = [
"csr.go",
"generate.go",
"keyusage.go",
"parse.go",
],
importpath = "github.com/jetstack/cert-manager/pkg/util/pki",

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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(),

69
pkg/util/pki/keyusage.go Normal file
View File

@ -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
}