Merge pull request #6019 from inteon/improve_pki

Cleanup CSR util/pki code
This commit is contained in:
jetstack-bot 2023-05-11 09:10:33 +01:00 committed by GitHub
commit bf5a482ab7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 512 additions and 222 deletions

View File

@ -40,7 +40,7 @@ var defaultInternalKeyUsages = []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cma
func ValidateCertificateRequest(a *admissionv1.AdmissionRequest, obj runtime.Object) (field.ErrorList, []string) {
cr := obj.(*cmapi.CertificateRequest)
allErrs := ValidateCertificateRequestSpec(&cr.Spec, field.NewPath("spec"), true)
allErrs := ValidateCertificateRequestSpec(&cr.Spec, field.NewPath("spec"))
allErrs = append(allErrs,
ValidateCertificateRequestApprovalCondition(cr.Status.Conditions, field.NewPath("status", "conditions"))...)
@ -83,7 +83,7 @@ func validateCertificateRequestAnnotations(objA, objB *cmapi.CertificateRequest,
return el
}
func ValidateCertificateRequestSpec(crSpec *cmapi.CertificateRequestSpec, fldPath *field.Path, validateCSRContent bool) field.ErrorList {
func ValidateCertificateRequestSpec(crSpec *cmapi.CertificateRequestSpec, fldPath *field.Path) field.ErrorList {
el := field.ErrorList{}
el = append(el, validateIssuerRef(crSpec.IssuerRef, fldPath)...)
@ -96,7 +96,7 @@ func ValidateCertificateRequestSpec(crSpec *cmapi.CertificateRequestSpec, fldPat
el = append(el, field.Invalid(fldPath.Child("request"), crSpec.Request, fmt.Sprintf("failed to decode csr: %s", err)))
} else {
// only compare usages if set on CR and in the CSR
if len(crSpec.Usages) > 0 && len(csr.Extensions) > 0 && validateCSRContent && !reflect.DeepEqual(crSpec.Usages, defaultInternalKeyUsages) {
if len(crSpec.Usages) > 0 && len(csr.Extensions) > 0 && !reflect.DeepEqual(crSpec.Usages, defaultInternalKeyUsages) {
if crSpec.IsCA {
crSpec.Usages = ensureCertSignIsSet(crSpec.Usages)
}

View File

@ -18,6 +18,9 @@ package validation
import (
"bytes"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"reflect"
"testing"
@ -29,6 +32,7 @@ import (
cminternal "github.com/cert-manager/cert-manager/internal/apis/certmanager"
cminternalmeta "github.com/cert-manager/cert-manager/internal/apis/meta"
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
"github.com/cert-manager/cert-manager/pkg/util/pki"
utilpki "github.com/cert-manager/cert-manager/pkg/util/pki"
"github.com/cert-manager/cert-manager/test/unit/gen"
)
@ -540,6 +544,75 @@ func TestValidateCertificateRequest(t *testing.T) {
a: someAdmissionRequest,
wantE: []*field.Error{},
},
"Test csr with default usages and isCA": {
cr: &cminternal.CertificateRequest{
Spec: cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageDigitalSignature, cmapi.UsageCertSign, cmapi.UsageKeyEncipherment), gen.SetCertificateIsCA(true))),
IssuerRef: validIssuerRef,
IsCA: true,
Usages: nil,
},
},
a: someAdmissionRequest,
wantE: []*field.Error{},
},
"Test cr with default usages": {
cr: &cminternal.CertificateRequest{
Spec: cminternal.CertificateRequestSpec{
// mustGenerateCSR will set the default usages for us
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"))),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment, cminternal.UsageDigitalSignature},
},
},
a: someAdmissionRequest,
wantE: []*field.Error{},
},
"Test cr with default usages, without any encoded in csr": {
cr: &cminternal.CertificateRequest{
Spec: cminternal.CertificateRequestSpec{
// mustGenerateCSR will set the default usages for us
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com")), func(cr *x509.CertificateRequest) {
// manually remove extensions that encode default usages
cr.Extensions = nil
cr.ExtraExtensions = nil
}),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment, cminternal.UsageDigitalSignature},
},
},
a: someAdmissionRequest,
wantE: []*field.Error{},
},
"Test cr with default usages, with empty set encoded in csr": {
cr: &cminternal.CertificateRequest{
Spec: cminternal.CertificateRequestSpec{
// mustGenerateCSR will set the default usages for us
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com")), func(cr *x509.CertificateRequest) {
// manually remove extensions that encode default usages
cr.Extensions = nil
cr.ExtraExtensions = []pkix.Extension{
{
Id: pki.OIDExtensionKeyUsage,
Critical: false,
Value: func(t *testing.T) []byte {
asn1KeyUsage, err := asn1.Marshal(asn1.BitString{Bytes: []byte{}, BitLength: 0})
if err != nil {
t.Fatal(err)
}
return asn1KeyUsage
}(t),
},
}
}),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment, cminternal.UsageDigitalSignature},
},
},
a: someAdmissionRequest,
wantE: []*field.Error{},
},
"Error on csr not having all usages": {
cr: &cminternal.CertificateRequest{
Spec: cminternal.CertificateRequestSpec{
@ -802,7 +875,7 @@ func TestValidateCertificateRequest(t *testing.T) {
}
}
func mustGenerateCSR(t *testing.T, crt *cmapi.Certificate) []byte {
func mustGenerateCSR(t *testing.T, crt *cmapi.Certificate, modifiers ...func(*x509.CertificateRequest)) []byte {
// Create a new private key
pk, err := utilpki.GenerateRSAPrivateKey(2048)
if err != nil {
@ -813,6 +886,9 @@ func mustGenerateCSR(t *testing.T, crt *cmapi.Certificate) []byte {
if err != nil {
t.Fatal(err)
}
for _, modifier := range modifiers {
modifier(x509CSR)
}
csrDER, err := utilpki.EncodeCSR(x509CSR, pk)
if err != nil {
t.Fatal(err)

View File

@ -153,7 +153,7 @@ func TestSign(t *testing.T) {
t.Fatal(err)
}
template, err := pki.GenerateTemplateFromCertificateRequest(baseCR)
template, err := pki.CertificateTemplateFromCertificateRequest(baseCR)
if err != nil {
t.Errorf("error generating template: %v", err)
}
@ -169,7 +169,12 @@ func TestSign(t *testing.T) {
if err != nil {
t.Fatal(err)
}
template2, err := pki.GenerateTemplateFromCSRPEM(generateCSR(t, sk2, "example.com", "example.com", "foo.com"), time.Hour, false)
template2, err := pki.CertificateTemplateFromCSRPEM(
generateCSR(t, sk2, "example.com", "example.com", "foo.com"),
pki.CertificateTemplateOverrideDuration(time.Hour),
pki.CertificateTemplateOverrideBasicConstraints(false, nil),
pki.CertificateTemplateOverrideKeyUsages(0, nil),
)
if err != nil {
t.Fatal(err)
}

View File

@ -69,7 +69,7 @@ func NewCA(ctx *controllerpkg.Context) certificaterequests.Issuer {
issuerOptions: ctx.IssuerOptions,
secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(),
reporter: crutil.NewReporter(ctx.Clock, ctx.Recorder),
templateGenerator: pki.GenerateTemplateFromCertificateRequest,
templateGenerator: pki.CertificateTemplateFromCertificateRequest,
signingFn: pki.SignCSRTemplate,
}
}

View File

@ -161,7 +161,7 @@ func TestSign(t *testing.T) {
badDataSecret := rsaCASecret.DeepCopy()
badDataSecret.Data[corev1.TLSPrivateKeyKey] = []byte("bad key")
template, err := pki.GenerateTemplateFromCertificateRequest(baseCR)
template, err := pki.CertificateTemplateFromCertificateRequest(baseCR)
if err != nil {
t.Fatal(err)
}
@ -360,7 +360,7 @@ func TestSign(t *testing.T) {
"a successful signing should set condition to Ready": {
certificateRequest: baseCR.DeepCopy(),
templateGenerator: func(cr *cmapi.CertificateRequest) (*x509.Certificate, error) {
_, err := pki.GenerateTemplateFromCertificateRequest(cr)
_, err := pki.CertificateTemplateFromCertificateRequest(cr)
if err != nil {
return nil, err
}
@ -586,7 +586,7 @@ func TestCA_Sign(t *testing.T) {
secretsLister: testlisters.FakeSecretListerFrom(testlisters.NewFakeSecretLister(),
testlisters.SetFakeSecretNamespaceListerGet(test.givenCASecret, nil),
),
templateGenerator: pki.GenerateTemplateFromCertificateRequest,
templateGenerator: pki.CertificateTemplateFromCertificateRequest,
signingFn: pki.SignCSRTemplate,
}

View File

@ -147,7 +147,7 @@ func (s *SelfSigned) Sign(ctx context.Context, cr *cmapi.CertificateRequest, iss
return nil, err
}
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
template, err := pki.CertificateTemplateFromCertificateRequest(cr)
if err != nil {
message := "Error generating certificate template"
s.reporter.Failed(cr, err, "ErrorGenerating", message)

View File

@ -158,7 +158,7 @@ func TestSign(t *testing.T) {
gen.SetCertificateRequestCSR(csrEmptyCertPEM),
)
templateRSA, err := pki.GenerateTemplateFromCertificateRequest(baseCR)
templateRSA, err := pki.CertificateTemplateFromCertificateRequest(baseCR)
if err != nil {
t.Error(err)
t.FailNow()
@ -169,7 +169,7 @@ func TestSign(t *testing.T) {
t.FailNow()
}
templateEC, err := pki.GenerateTemplateFromCertificateRequest(ecCR)
templateEC, err := pki.CertificateTemplateFromCertificateRequest(ecCR)
if err != nil {
t.Error(err)
t.FailNow()
@ -180,7 +180,7 @@ func TestSign(t *testing.T) {
t.FailNow()
}
templateEmptyCert, err := pki.GenerateTemplateFromCertificateRequest(emptyCR)
templateEmptyCert, err := pki.CertificateTemplateFromCertificateRequest(emptyCR)
if err != nil {
t.Error(err)
t.FailNow()

View File

@ -63,7 +63,7 @@ func generateCSR(t *testing.T, secretKey crypto.Signer) []byte {
func generateSelfSignedCert(t *testing.T, cr *cmapi.CertificateRequest, key crypto.Signer, notBefore, notAfter time.Time) []byte {
t.Helper()
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
template, err := pki.CertificateTemplateFromCertificateRequest(cr)
if err != nil {
t.Errorf("failed to generate cert template from CSR: %v", err)
t.FailNow()

View File

@ -66,7 +66,7 @@ func generateCSR(t *testing.T, secretKey crypto.Signer) []byte {
func generateSelfSignedCertFromCR(cr *cmapi.CertificateRequest, key crypto.Signer,
duration time.Duration) ([]byte, error) {
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
template, err := pki.CertificateTemplateFromCertificateRequest(cr)
if err != nil {
return nil, fmt.Errorf("error generating template: %v", err)
}

View File

@ -211,7 +211,7 @@ func TestSign(t *testing.T) {
},
}
template, err := pki.GenerateTemplateFromCertificateRequest(baseCR)
template, err := pki.CertificateTemplateFromCertificateRequest(baseCR)
if err != nil {
t.Fatal(err)
}

View File

@ -56,7 +56,7 @@ func mustSelfSignCertificate(t *testing.T, pkBytes []byte) []byte {
if err != nil {
t.Fatal(err)
}
x509Crt, err := pki.GenerateTemplate(&cmapi.Certificate{
x509Crt, err := pki.CertificateTemplateFromCertificate(&cmapi.Certificate{
Spec: cmapi.CertificateSpec{
DNSNames: []string{"example.com"},
},
@ -84,7 +84,7 @@ func mustCert(t *testing.T, commonName string, isCA bool) *keyAndCert {
keyPEM, err := pki.EncodePrivateKey(key, cmapi.PKCS8)
require.NoError(t, err)
cert, err := pki.GenerateTemplate(&cmapi.Certificate{
cert, err := pki.CertificateTemplateFromCertificate(&cmapi.Certificate{
Spec: cmapi.CertificateSpec{
CommonName: commonName,
IsCA: isCA,

View File

@ -128,7 +128,7 @@ func createCryptoBundle(originalCert *cmapi.Certificate) (*cryptoBundle, error)
},
}
unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest)
unsignedCert, err := pki.CertificateTemplateFromCertificateRequest(certificateRequest)
if err != nil {
return nil, err
}

View File

@ -225,7 +225,7 @@ func Test_ProcessItem(t *testing.T) {
}),
)
tmpl, err := pki.GenerateTemplateFromCertificateSigningRequest(baseCSR)
tmpl, err := pki.CertificateTemplateFromCertificateSigningRequest(baseCSR)
if err != nil {
t.Fatal(err)
}
@ -234,7 +234,7 @@ func Test_ProcessItem(t *testing.T) {
t.Fatal(err)
}
tmpl, err = pki.GenerateTemplateFromCertificateSigningRequest(gen.CertificateSigningRequestFrom(baseCSR,
tmpl, err = pki.CertificateTemplateFromCertificateSigningRequest(gen.CertificateSigningRequestFrom(baseCSR,
gen.SetCertificateSigningRequestRequest(csrPEMExampleNotPresent),
))
if err != nil {

View File

@ -82,7 +82,7 @@ func NewCA(ctx *controllerpkg.Context) certificatesigningrequests.Signer {
certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(),
fieldManager: ctx.FieldManager,
recorder: ctx.Recorder,
templateGenerator: pki.GenerateTemplateFromCertificateSigningRequest,
templateGenerator: pki.CertificateTemplateFromCertificateSigningRequest,
signingFn: pki.SignCSRTemplate,
}
}

View File

@ -166,7 +166,7 @@ func TestSign(t *testing.T) {
badDataSecret := ecCASecret.DeepCopy()
badDataSecret.Data[corev1.TLSPrivateKeyKey] = []byte("bad key")
template, err := pki.GenerateTemplateFromCertificateSigningRequest(baseCSR)
template, err := pki.CertificateTemplateFromCertificateSigningRequest(baseCSR)
if err != nil {
t.Fatal(err)
}
@ -465,7 +465,7 @@ func TestSign(t *testing.T) {
templateGenerator: func(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) {
// Pass the given CSR to a "real" template generator to ensure that it
// doesn't err. Return the pre-generated template.
_, err := pki.GenerateTemplateFromCertificateSigningRequest(csr)
_, err := pki.CertificateTemplateFromCertificateSigningRequest(csr)
if err != nil {
return nil, err
}
@ -743,7 +743,7 @@ func TestCA_Sign(t *testing.T) {
secretsLister: testlisters.FakeSecretListerFrom(testlisters.NewFakeSecretLister(),
testlisters.SetFakeSecretNamespaceListerGet(test.givenCASecret, nil),
),
templateGenerator: pki.GenerateTemplateFromCertificateSigningRequest,
templateGenerator: pki.CertificateTemplateFromCertificateSigningRequest,
signingFn: pki.SignCSRTemplate,
}

View File

@ -160,7 +160,7 @@ func (s *SelfSigned) Sign(ctx context.Context, csr *certificatesv1.CertificateSi
return err
}
template, err := pki.GenerateTemplateFromCertificateSigningRequest(csr)
template, err := pki.CertificateTemplateFromCertificateSigningRequest(csr)
if err != nil {
message := fmt.Sprintf("Error generating certificate template: %s", err)
log.Error(err, message)

View File

@ -320,7 +320,7 @@ func TestProcessItem(t *testing.T) {
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
KubeObjects: []runtime.Object{csrBundle.secret},
ExpectedEvents: []string{
"Warning ErrorGenerating Error generating certificate template: failed to decode csr",
"Warning ErrorGenerating Error generating certificate template: error decoding certificate request PEM block",
},
ExpectedActions: []testpkg.Action{
@ -364,7 +364,7 @@ func TestProcessItem(t *testing.T) {
Type: certificatesv1.CertificateFailed,
Status: corev1.ConditionTrue,
Reason: "ErrorGenerating",
Message: "Error generating certificate template: failed to decode csr",
Message: "Error generating certificate template: error decoding certificate request PEM block",
LastTransitionTime: metaFixedClockStart,
LastUpdateTime: metaFixedClockStart,
}),

View File

@ -69,7 +69,12 @@ func TestProcessItem(t *testing.T) {
t.Fatal(err)
}
rootTmpl, err := pki.GenerateTemplateFromCSRPEM(rootCSRPEM, time.Hour, true)
rootTmpl, err := pki.CertificateTemplateFromCSRPEM(
rootCSRPEM,
pki.CertificateTemplateOverrideDuration(time.Hour),
pki.CertificateTemplateOverrideBasicConstraints(true, nil),
pki.CertificateTemplateOverrideKeyUsages(0, nil),
)
if err != nil {
t.Fatal(err)
}
@ -84,7 +89,12 @@ func TestProcessItem(t *testing.T) {
if err != nil {
t.Fatal(err)
}
leafTmpl, err := pki.GenerateTemplateFromCSRPEM(leafCSRPEM, time.Hour, false)
leafTmpl, err := pki.CertificateTemplateFromCSRPEM(
leafCSRPEM,
pki.CertificateTemplateOverrideDuration(time.Hour),
pki.CertificateTemplateOverrideBasicConstraints(false, nil),
pki.CertificateTemplateOverrideKeyUsages(0, nil),
)
if err != nil {
t.Fatal(err)
}

View File

@ -83,7 +83,12 @@ func (v *Venafi) buildVReq(csrPEM []byte, duration time.Duration, customFields [
return nil, err
}
tmpl, err := pki.GenerateTemplateFromCSRPEM(csrPEM, duration, false)
tmpl, err := pki.CertificateTemplateFromCSRPEM(
csrPEM,
pki.CertificateTemplateOverrideDuration(duration),
pki.CertificateTemplateOverrideBasicConstraints(false, nil),
pki.CertificateTemplateOverrideKeyUsages(0, nil),
)
if err != nil {
return nil, err
}

View File

@ -19,6 +19,7 @@ package pki
import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
)
// Copied from x509.go
@ -28,14 +29,40 @@ var (
// Copied from x509.go
type basicConstraints struct {
IsCA bool
IsCA bool `asn1:"optional"`
MaxPathLen int `asn1:"optional,default:-1"`
}
// Adapted from x509.go
func MarshalBasicConstraints(isCA bool) (pkix.Extension, error) {
func MarshalBasicConstraints(isCA bool, maxPathLen *int) (pkix.Extension, error) {
ext := pkix.Extension{Id: OIDExtensionBasicConstraints}
// A value of -1 causes encoding/asn1 to omit the value as desired.
maxPathLenValue := -1
if maxPathLen != nil {
maxPathLenValue = *maxPathLen
}
var err error
ext.Value, err = asn1.Marshal(basicConstraints{isCA})
ext.Value, err = asn1.Marshal(basicConstraints{isCA, maxPathLenValue})
return ext, err
}
// Adapted from x509.go
func UnmarshalBasicConstraints(value []byte) (isCA bool, maxPathLen *int, err error) {
var constraints basicConstraints
var rest []byte
if rest, err = asn1.Unmarshal(value, &constraints); err != nil {
return isCA, maxPathLen, err
} else if len(rest) != 0 {
return isCA, maxPathLen, errors.New("x509: trailing data after X.509 BasicConstraints")
}
isCA = constraints.IsCA
if constraints.MaxPathLen >= 0 {
maxPathLen = new(int)
*maxPathLen = constraints.MaxPathLen
}
return isCA, maxPathLen, nil
}

View File

@ -0,0 +1,268 @@
/*
Copyright 2020 The cert-manager Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pki
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"time"
apiutil "github.com/cert-manager/cert-manager/pkg/api/util"
v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1"
certificatesv1 "k8s.io/api/certificates/v1"
)
type CertificateTemplateMutator func(*x509.Certificate)
// CertificateTemplateOverrideDuration returns a CertificateTemplateMutator that overrides the
// certificate duration.
func CertificateTemplateOverrideDuration(duration time.Duration) CertificateTemplateMutator {
return func(cert *x509.Certificate) {
cert.NotBefore = time.Now()
cert.NotAfter = cert.NotBefore.Add(duration)
}
}
// CertificateTemplateOverrideBasicConstraints returns a CertificateTemplateMutator that overrides
// the certificate basic constraints.
func CertificateTemplateOverrideBasicConstraints(isCA bool, maxPathLen *int) CertificateTemplateMutator {
return func(cert *x509.Certificate) {
cert.BasicConstraintsValid = true
cert.IsCA = isCA
if maxPathLen != nil {
cert.MaxPathLen = *maxPathLen
cert.MaxPathLenZero = *maxPathLen == 0
} else {
cert.MaxPathLen = 0
cert.MaxPathLenZero = false
}
}
}
// OverrideTemplateKeyUsages returns a CertificateTemplateMutator that overrides the
// certificate key usages.
func CertificateTemplateOverrideKeyUsages(keyUsage x509.KeyUsage, extKeyUsage []x509.ExtKeyUsage) CertificateTemplateMutator {
return func(cert *x509.Certificate) {
cert.KeyUsage = keyUsage
cert.ExtKeyUsage = extKeyUsage
}
}
// CertificateTemplateFromCSR will create a x509.Certificate for the
// given *x509.CertificateRequest.
// Call OverrideTemplateFromOptions to override the duration, isCA, maxPathLen, keyUsage, and extKeyUsage.
func CertificateTemplateFromCSR(csr *x509.CertificateRequest, mutators ...CertificateTemplateMutator) (*x509.Certificate, error) {
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %s", err.Error())
}
cert := &x509.Certificate{
// Version must be 2 according to RFC5280.
// A version value of 2 confusingly means version 3.
// This value isn't used by Go at the time of writing.
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1
Version: 2,
SerialNumber: serialNumber,
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
PublicKey: csr.PublicKey,
Subject: csr.Subject,
RawSubject: csr.RawSubject,
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
EmailAddresses: csr.EmailAddresses,
URIs: csr.URIs,
}
// Start by copying all extensions from the CSR
extractExtensions := func(template *x509.Certificate, val pkix.Extension) error {
// Check the CSR for the X.509 BasicConstraints (RFC 5280, 4.2.1.9)
// extension and append to template if necessary
if val.Id.Equal(OIDExtensionBasicConstraints) {
unmarshalIsCA, unmarshalMaxPathLen, err := UnmarshalBasicConstraints(val.Value)
if err != nil {
return err
}
template.BasicConstraintsValid = true
template.IsCA = unmarshalIsCA
if unmarshalMaxPathLen != nil {
template.MaxPathLen = *unmarshalMaxPathLen
template.MaxPathLenZero = *unmarshalMaxPathLen == 0
} else {
template.MaxPathLen = 0
template.MaxPathLenZero = false
}
}
// RFC 5280, 4.2.1.3
if val.Id.Equal(OIDExtensionKeyUsage) {
usage, err := UnmarshalKeyUsage(val.Value)
if err != nil {
return err
}
template.KeyUsage = usage
}
if val.Id.Equal(OIDExtensionExtendedKeyUsage) {
extUsages, unknownUsages, err := UnmarshalExtKeyUsage(val.Value)
if err != nil {
return err
}
template.ExtKeyUsage = extUsages
template.UnknownExtKeyUsage = unknownUsages
}
return nil
}
for _, val := range csr.Extensions {
if err := extractExtensions(cert, val); err != nil {
return nil, err
}
}
for _, val := range csr.ExtraExtensions {
if err := extractExtensions(cert, val); err != nil {
return nil, err
}
}
for _, mutator := range mutators {
mutator(cert)
}
return cert, nil
}
// CertificateTemplateFromCSRPEM will create a x509.Certificate for the
// given csrPEM.
// Call OverrideTemplateFromOptions to override the duration, isCA, maxPathLen, keyUsage, and extKeyUsage.
func CertificateTemplateFromCSRPEM(csrPEM []byte, mutators ...CertificateTemplateMutator) (*x509.Certificate, error) {
csr, err := DecodeX509CertificateRequestBytes(csrPEM)
if err != nil {
return nil, err
}
if err := csr.CheckSignature(); err != nil {
return nil, err
}
return CertificateTemplateFromCSR(csr, mutators...)
}
// CertificateTemplateFromCertificate will create a x509.Certificate for the given
// Certificate resource
func CertificateTemplateFromCertificate(crt *v1.Certificate) (*x509.Certificate, error) {
csr, err := GenerateCSR(crt)
if err != nil {
return nil, err
}
certDuration := apiutil.DefaultCertDuration(crt.Spec.Duration)
keyUsage, extKeyUsage, err := KeyUsagesForCertificateOrCertificateRequest(crt.Spec.Usages, crt.Spec.IsCA)
if err != nil {
return nil, err
}
return CertificateTemplateFromCSR(
csr,
CertificateTemplateOverrideDuration(certDuration),
CertificateTemplateOverrideBasicConstraints(crt.Spec.IsCA, nil),
CertificateTemplateOverrideKeyUsages(keyUsage, extKeyUsage),
)
}
// CertificateTemplateFromCertificateRequest will create a x509.Certificate for the given
// CertificateRequest resource
func CertificateTemplateFromCertificateRequest(cr *v1.CertificateRequest) (*x509.Certificate, error) {
certDuration := apiutil.DefaultCertDuration(cr.Spec.Duration)
keyUsage, extKeyUsage, err := KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA)
if err != nil {
return nil, err
}
return CertificateTemplateFromCSRPEM(
cr.Spec.Request,
CertificateTemplateOverrideDuration(certDuration),
CertificateTemplateOverrideBasicConstraints(cr.Spec.IsCA, nil),
CertificateTemplateOverrideKeyUsages(keyUsage, extKeyUsage),
)
}
// CertificateTemplateFromCertificateRequest will create a x509.Certificate for the given
// CertificateSigningRequest resource
func CertificateTemplateFromCertificateSigningRequest(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) {
duration, err := DurationFromCertificateSigningRequest(csr)
if err != nil {
return nil, err
}
ku, eku, err := BuildKeyUsagesKube(csr.Spec.Usages)
if err != nil {
return nil, err
}
isCA := csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true"
return CertificateTemplateFromCSRPEM(
csr.Spec.Request,
CertificateTemplateOverrideDuration(duration),
CertificateTemplateOverrideBasicConstraints(isCA, nil),
CertificateTemplateOverrideKeyUsages(ku, eku),
)
}
// Deprecated: use CertificateTemplateFromCertificate instead.
func GenerateTemplate(crt *v1.Certificate) (*x509.Certificate, error) {
return CertificateTemplateFromCertificate(crt)
}
// Deprecated: use CertificateTemplateFromCertificateRequest instead.
func GenerateTemplateFromCertificateRequest(cr *v1.CertificateRequest) (*x509.Certificate, error) {
return CertificateTemplateFromCertificateRequest(cr)
}
// Deprecated: use CertificateTemplateFromCertificateSigningRequest instead.
func GenerateTemplateFromCertificateSigningRequest(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) {
return CertificateTemplateFromCertificateSigningRequest(csr)
}
// Deprecated: use CertificateTemplateFromCSRPEM instead.
func GenerateTemplateFromCSRPEM(csrPEM []byte, duration time.Duration, isCA bool) (*x509.Certificate, error) {
return CertificateTemplateFromCSRPEM(
csrPEM,
CertificateTemplateOverrideDuration(duration),
CertificateTemplateOverrideBasicConstraints(isCA, nil),
CertificateTemplateOverrideKeyUsages(0, nil),
)
}
// Deprecated: use CertificateTemplateFromCSRPEM instead.
func GenerateTemplateFromCSRPEMWithUsages(csrPEM []byte, duration time.Duration, isCA bool, keyUsage x509.KeyUsage, extKeyUsage []x509.ExtKeyUsage) (*x509.Certificate, error) {
return CertificateTemplateFromCSRPEM(
csrPEM,
CertificateTemplateOverrideDuration(duration),
CertificateTemplateOverrideBasicConstraints(isCA, nil),
CertificateTemplateOverrideKeyUsages(keyUsage, extKeyUsage),
)
}

View File

@ -29,7 +29,6 @@ import (
"net"
"net/url"
"strings"
"time"
"github.com/cert-manager/cert-manager/internal/controller/feature"
apiutil "github.com/cert-manager/cert-manager/pkg/api/util"
@ -164,11 +163,33 @@ func BuildCertManagerKeyUsages(ku x509.KeyUsage, eku []x509.ExtKeyUsage) []v1.Ke
return usages
}
type generateCSROptions struct {
EncodeBasicConstraintsInRequest bool
}
type GenerateCSROption func(*generateCSROptions)
// WithEncodeBasicConstraintsInRequest determines whether the BasicConstraints
// extension should be encoded in the CSR.
// NOTE: this is a temporary option that will be removed in a future release.
func WithEncodeBasicConstraintsInRequest(encode bool) GenerateCSROption {
return func(o *generateCSROptions) {
o.EncodeBasicConstraintsInRequest = encode
}
}
// GenerateCSR will generate a new *x509.CertificateRequest template to be used
// by issuers that utilise CSRs to obtain Certificates.
// The CSR will not be signed, and should be passed to either EncodeCSR or
// to the x509.CreateCertificateRequest function.
func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) {
func GenerateCSR(crt *v1.Certificate, optFuncs ...GenerateCSROption) (*x509.CertificateRequest, error) {
opts := &generateCSROptions{
EncodeBasicConstraintsInRequest: false,
}
for _, opt := range optFuncs {
opt(opts)
}
commonName, err := extractCommonName(crt.Spec)
if err != nil {
return nil, err
@ -205,8 +226,10 @@ func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) {
}
}
if utilfeature.DefaultFeatureGate.Enabled(feature.UseCertificateRequestBasicConstraints) {
extension, err := MarshalBasicConstraints(crt.Spec.IsCA)
// NOTE(@inteon): opts.EncodeBasicConstraintsInRequest is a temporary solution and will
// be removed/ replaced in a future release.
if opts.EncodeBasicConstraintsInRequest {
extension, err := MarshalBasicConstraints(crt.Spec.IsCA, nil)
if err != nil {
return nil, err
}
@ -274,154 +297,6 @@ func buildKeyUsagesExtensionsForCertificate(crt *v1.Certificate) ([]pkix.Extensi
return []pkix.Extension{usage, extendedUsages}, nil
}
// GenerateTemplate will create a x509.Certificate for the given Certificate resource.
// This should create a Certificate template that is equivalent to the CertificateRequest
// generated by GenerateCSR.
// The PublicKey field must be populated by the caller.
func GenerateTemplate(crt *v1.Certificate) (*x509.Certificate, error) {
commonName, err := extractCommonName(crt.Spec)
if err != nil {
return nil, err
}
dnsNames := crt.Spec.DNSNames
ipAddresses := IPAddressesForCertificate(crt)
organization := OrganizationForCertificate(crt)
subject := SubjectForCertificate(crt)
uris, err := URLsFromStrings(crt.Spec.URIs)
if err != nil {
return nil, err
}
keyUsages, extKeyUsages, err := KeyUsagesForCertificateOrCertificateRequest(crt.Spec.Usages, crt.Spec.IsCA)
if err != nil {
return nil, err
}
if len(commonName) == 0 && len(dnsNames) == 0 && len(ipAddresses) == 0 && len(uris) == 0 && len(crt.Spec.EmailAddresses) == 0 {
return nil, fmt.Errorf("no common name or subject alt names requested on certificate")
}
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %s", err.Error())
}
certDuration := apiutil.DefaultCertDuration(crt.Spec.Duration)
pubKeyAlgo, _, err := SignatureAlgorithm(crt)
if err != nil {
return nil, err
}
cert := &x509.Certificate{
// Version must be 2 according to RFC5280.
// A version value of 2 confusingly means version 3.
// This value isn't used by Go at the time of writing.
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1
Version: 2,
BasicConstraintsValid: true,
SerialNumber: serialNumber,
PublicKeyAlgorithm: pubKeyAlgo,
IsCA: crt.Spec.IsCA,
NotBefore: time.Now(),
NotAfter: time.Now().Add(certDuration),
// see http://golang.org/pkg/crypto/x509/#KeyUsage
KeyUsage: keyUsages,
ExtKeyUsage: extKeyUsages,
DNSNames: dnsNames,
IPAddresses: ipAddresses,
URIs: uris,
EmailAddresses: crt.Spec.EmailAddresses,
}
if isLiteralCertificateSubjectEnabled() && len(crt.Spec.LiteralSubject) > 0 {
rawSubject, err := ParseSubjectStringToRawDERBytes(crt.Spec.LiteralSubject)
if err != nil {
return nil, err
}
cert.RawSubject = rawSubject
} else {
cert.Subject = pkix.Name{
Country: subject.Countries,
Organization: organization,
OrganizationalUnit: subject.OrganizationalUnits,
Locality: subject.Localities,
Province: subject.Provinces,
StreetAddress: subject.StreetAddresses,
PostalCode: subject.PostalCodes,
SerialNumber: subject.SerialNumber,
CommonName: commonName,
}
}
return cert, nil
}
// GenerateTemplate will create a x509.Certificate for the given
// CertificateRequest resource
func GenerateTemplateFromCertificateRequest(cr *v1.CertificateRequest) (*x509.Certificate, error) {
certDuration := apiutil.DefaultCertDuration(cr.Spec.Duration)
keyUsage, extKeyUsage, err := KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA)
if err != nil {
return nil, err
}
return GenerateTemplateFromCSRPEMWithUsages(cr.Spec.Request, certDuration, cr.Spec.IsCA, keyUsage, extKeyUsage)
}
func GenerateTemplateFromCSRPEM(csrPEM []byte, duration time.Duration, isCA bool) (*x509.Certificate, error) {
var (
ku x509.KeyUsage
eku []x509.ExtKeyUsage
)
return GenerateTemplateFromCSRPEMWithUsages(csrPEM, duration, isCA, ku, eku)
}
func GenerateTemplateFromCSRPEMWithUsages(csrPEM []byte, duration time.Duration, isCA bool, keyUsage x509.KeyUsage, extKeyUsage []x509.ExtKeyUsage) (*x509.Certificate, error) {
block, _ := pem.Decode(csrPEM)
if block == nil {
return nil, errors.New("failed to decode csr")
}
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return nil, err
}
if err := csr.CheckSignature(); err != nil {
return nil, err
}
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %s", err.Error())
}
return &x509.Certificate{
// Version must be 2 according to RFC5280.
// A version value of 2 confusingly means version 3.
// This value isn't used by Go at the time of writing.
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1
Version: 2,
BasicConstraintsValid: true,
SerialNumber: serialNumber,
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
PublicKey: csr.PublicKey,
IsCA: isCA,
Subject: csr.Subject,
RawSubject: csr.RawSubject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(duration),
// see http://golang.org/pkg/crypto/x509/#KeyUsage
KeyUsage: keyUsage,
ExtKeyUsage: extKeyUsage,
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
EmailAddresses: csr.EmailAddresses,
URIs: csr.URIs,
}, nil
}
// SignCertificate returns a signed *x509.Certificate given a template
// *x509.Certificate crt and an issuer.
// publicKey is the public key of the signee, and signerKey is the private
@ -430,7 +305,6 @@ func GenerateTemplateFromCSRPEMWithUsages(csrPEM []byte, duration time.Duration,
// which can be used for reading the encoded values.
func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, publicKey crypto.PublicKey, signerKey interface{}) ([]byte, *x509.Certificate, error) {
derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey)
if err != nil {
return nil, nil, fmt.Errorf("error creating x509 certificate: %s", err.Error())
}

View File

@ -432,7 +432,7 @@ func TestGenerateCSR(t *testing.T) {
basicConstraintsGenerator := func(isCA bool) ([]byte, error) {
return asn1.Marshal(struct {
IsCA bool
IsCA bool `asn1:"optional"`
}{
IsCA: isCA,
})
@ -615,8 +615,10 @@ func TestGenerateCSR(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.LiteralCertificateSubject, tt.literalCertificateSubjectFeatureEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.UseCertificateRequestBasicConstraints, tt.basicConstraintsFeatureEnabled)()
got, err := GenerateCSR(tt.crt)
got, err := GenerateCSR(
tt.crt,
WithEncodeBasicConstraintsInRequest(tt.basicConstraintsFeatureEnabled),
)
if (err != nil) != tt.wantErr {
t.Errorf("GenerateCSR() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@ -145,6 +145,26 @@ func MarshalKeyUsage(usage x509.KeyUsage) (pkix.Extension, error) {
return ext, err
}
func UnmarshalKeyUsage(value []byte) (usage x509.KeyUsage, err error) {
var asn1bits asn1.BitString
var rest []byte
if rest, err = asn1.Unmarshal(value, &asn1bits); err != nil {
return usage, err
} else if len(rest) != 0 {
return usage, errors.New("x509: trailing data after X.509 KeyUsage")
}
var usageInt int
for i := 0; i < 9; i++ {
if asn1bits.At(i) != 0 {
usageInt |= 1 << uint(i)
}
}
return x509.KeyUsage(usageInt), nil
}
// Adapted from x509.go
func MarshalExtKeyUsage(extUsages []x509.ExtKeyUsage, unknownUsages []asn1.ObjectIdentifier) (pkix.Extension, error) {
ext := pkix.Extension{Id: OIDExtensionExtendedKeyUsage}
@ -164,3 +184,24 @@ func MarshalExtKeyUsage(extUsages []x509.ExtKeyUsage, unknownUsages []asn1.Objec
ext.Value, err = asn1.Marshal(oids)
return ext, err
}
func UnmarshalExtKeyUsage(value []byte) (extUsages []x509.ExtKeyUsage, unknownUsages []asn1.ObjectIdentifier, err error) {
var asn1ExtendedUsages []asn1.ObjectIdentifier
var rest []byte
if rest, err = asn1.Unmarshal(value, &asn1ExtendedUsages); err != nil {
return extUsages, unknownUsages, err
} else if len(rest) != 0 {
return extUsages, unknownUsages, errors.New("x509: trailing data after X.509 ExtendedKeyUsage")
}
for _, asnExtUsage := range asn1ExtendedUsages {
if eku, ok := ExtKeyUsageFromOID(asnExtUsage); ok {
extUsages = append(extUsages, eku)
} else {
unknownUsages = append(unknownUsages, asnExtUsage)
}
}
return extUsages, unknownUsages, nil
}

View File

@ -28,24 +28,6 @@ import (
experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1"
)
// GenerateTemplateFromCertificateSigningRequest will create an
// *x509.Certificate from the given CertificateSigningRequest resource
func GenerateTemplateFromCertificateSigningRequest(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) {
duration, err := DurationFromCertificateSigningRequest(csr)
if err != nil {
return nil, err
}
ku, eku, err := BuildKeyUsagesKube(csr.Spec.Usages)
if err != nil {
return nil, err
}
isCA := csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true"
return GenerateTemplateFromCSRPEMWithUsages(csr.Spec.Request, duration, isCA, ku, eku)
}
// DurationFromCertificateSigningRequest returns the duration that the user may
// have requested using the annotation
// "experimental.cert-manager.io/request-duration" or via the CSR

View File

@ -30,7 +30,7 @@ import (
"github.com/cert-manager/cert-manager/test/unit/gen"
)
func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) {
func TestCertificateTemplateFromCertificateSigningRequest(t *testing.T) {
csr, pk, err := gen.CSR(x509.RSA, gen.SetCSRCommonName("example.com"), gen.SetCSRDNSNames("example.com", "foo.example.com"))
if err != nil {
t.Fatal(err)
@ -202,7 +202,7 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
templ, err := pki.GenerateTemplateFromCertificateSigningRequest(test.csr)
templ, err := pki.CertificateTemplateFromCertificateSigningRequest(test.csr)
assert.Equal(t, test.expErr, err != nil)
if err == nil {

View File

@ -277,7 +277,7 @@ func selfSignCertificate(t *testing.T, spec cmapi.CertificateSpec) []byte {
t.Fatal(err)
}
template, err := GenerateTemplate(&cmapi.Certificate{Spec: spec})
template, err := CertificateTemplateFromCertificate(&cmapi.Certificate{Spec: spec})
if err != nil {
t.Fatal(err)
}

View File

@ -33,7 +33,7 @@ func GenerateLocallySignedTemporaryCertificate(crt *cmapi.Certificate, pkData []
if err != nil {
return nil, err
}
caCertTemplate, err := GenerateTemplate(&cmapi.Certificate{
caCertTemplate, err := CertificateTemplateFromCertificate(&cmapi.Certificate{
Spec: cmapi.CertificateSpec{
CommonName: "cert-manager.local",
IsCA: true,
@ -48,7 +48,7 @@ func GenerateLocallySignedTemporaryCertificate(crt *cmapi.Certificate, pkData []
}
// sign a temporary certificate using the root CA
template, err := GenerateTemplate(crt)
template, err := CertificateTemplateFromCertificate(crt)
if err != nil {
return nil, err
}

View File

@ -168,7 +168,7 @@ func TestIssuingController(t *testing.T) {
})
// Sign Certificate
certTemplate, err := utilpki.GenerateTemplate(crt)
certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt)
if err != nil {
t.Fatal(err)
}
@ -391,7 +391,7 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) {
})
// Sign Certificate
certTemplate, err := utilpki.GenerateTemplate(crt)
certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt)
if err != nil {
t.Fatal(err)
}
@ -609,7 +609,7 @@ func Test_IssuingController_SecretTemplate(t *testing.T) {
})
// Sign Certificate
certTemplate, err := utilpki.GenerateTemplate(crt)
certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt)
if err != nil {
t.Fatal(err)
}
@ -858,7 +858,7 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) {
})
// Sign Certificate
certTemplate, err := utilpki.GenerateTemplate(crt)
certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt)
if err != nil {
t.Fatal(err)
}

View File

@ -393,7 +393,7 @@ func selfSignCertificateWithNotBeforeAfter(t *testing.T, pkData []byte, spec *cm
t.Fatal(err)
}
template, err := pki.GenerateTemplate(spec)
template, err := pki.CertificateTemplateFromCertificate(spec)
if err != nil {
t.Fatal(err)
}

View File

@ -135,7 +135,7 @@ func CreateCryptoBundle(originalCert *cmapi.Certificate, clock clock.Clock) (*Cr
},
}
unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest)
unsignedCert, err := pki.CertificateTemplateFromCertificateRequest(certificateRequest)
if err != nil {
return nil, err
}
@ -255,7 +255,7 @@ func MustCreateCertWithNotBeforeAfter(t *testing.T, pkData []byte, spec *cmapi.C
t.Fatal(err)
}
template, err := pki.GenerateTemplate(spec)
template, err := pki.CertificateTemplateFromCertificate(spec)
if err != nil {
t.Fatal(err)
}
@ -278,7 +278,7 @@ func MustCreateCert(t *testing.T, pkData []byte, spec *cmapi.Certificate) []byte
t.Fatal(err)
}
template, err := pki.GenerateTemplate(spec)
template, err := pki.CertificateTemplateFromCertificate(spec)
if err != nil {
t.Fatal(err)
}