move utility functions to reduce fragmentation and rename functions for consistency

Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
This commit is contained in:
Tim Ramlot 2023-01-23 13:19:39 +01:00
parent 1038ca4494
commit 23de5240e9
No known key found for this signature in database
GPG Key ID: 47428728E0C2878D
27 changed files with 568 additions and 475 deletions

View File

@ -349,7 +349,7 @@ func buildCertificateSigningRequest(crt *cmapi.Certificate, pk []byte, crName, s
return nil, err
}
ku, eku, err := pki.BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA)
ku, eku, err := pki.KeyUsagesForCertificateOrCertificateRequest(crt.Spec.Usages, crt.Spec.IsCA)
if err != nil {
return nil, err
}

View File

@ -55,7 +55,7 @@ func ValidateCertificateSpec(crt *internalcmapi.CertificateSpec, fldPath *field.
el = append(el, field.Forbidden(fldPath.Child("literalSubject"), "Feature gate LiteralCertificateSubject must be enabled on both webhook and controller to use the alpha `literalSubject` field"))
}
sequence, err := pki.ParseSubjectStringToRdnSequence(crt.LiteralSubject)
sequence, err := pki.UnmarshalSubjectStringToRDNSequence(crt.LiteralSubject)
if err != nil {
el = append(el, field.Invalid(fldPath.Child("literalSubject"), crt.LiteralSubject, err.Error()))
}

View File

@ -35,7 +35,6 @@ import (
internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates"
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
"github.com/cert-manager/cert-manager/pkg/controller/certificates"
"github.com/cert-manager/cert-manager/pkg/util/pki"
)
@ -84,7 +83,7 @@ func SecretPrivateKeyMatchesSpec(input Input) (string, string, bool) {
return SecretMismatch, fmt.Sprintf("Existing issued Secret contains invalid private key data: %v", err), true
}
violations, err := certificates.PrivateKeyMatchesSpec(pk, input.Certificate.Spec)
violations, err := pki.PrivateKeyMatchesSpec(pk, input.Certificate.Spec)
if err != nil {
return SecretMismatch, fmt.Sprintf("Failed to check private key is up to date: %v", err), true
}
@ -175,7 +174,7 @@ func CurrentCertificateRequestNotValidForSpec(input Input) (string, string, bool
return currentSecretValidForSpec(input)
}
violations, err := certificates.RequestMatchesSpec(input.CurrentRevisionRequest, input.Certificate.Spec)
violations, err := pki.RequestMatchesSpec(input.CurrentRevisionRequest, input.Certificate.Spec)
if err != nil {
// If parsing the request fails, we don't immediately trigger a re-issuance as
// the existing certificate stored in the Secret may still be valid/up to date.
@ -192,7 +191,7 @@ func CurrentCertificateRequestNotValidForSpec(input Input) (string, string, bool
// and is instead called by currentCertificateRequestValidForSpec if no there
// is no existing CertificateRequest resource.
func currentSecretValidForSpec(input Input) (string, string, bool) {
violations, err := certificates.SecretDataAltNamesMatchSpec(input.Secret, input.Certificate.Spec)
violations, err := pki.SecretDataAltNamesMatchSpec(input.Secret, input.Certificate.Spec)
if err != nil {
// This case should never be reached as we already check the certificate data can
// be parsed in an earlier policy check, but handle it anyway.
@ -228,7 +227,7 @@ func CurrentCertificateNearingExpiry(c clock.Clock) Func {
notBefore := metav1.NewTime(x509cert.NotBefore)
notAfter := metav1.NewTime(x509cert.NotAfter)
crt := input.Certificate
renewalTime := certificates.RenewalTime(notBefore.Time, notAfter.Time, crt.Spec.RenewBefore)
renewalTime := pki.RenewalTime(notBefore.Time, notAfter.Time, crt.Spec.RenewBefore)
renewIn := renewalTime.Time.Sub(c.Now())
if renewIn > 0 {

View File

@ -214,7 +214,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) {
reissue: true,
},
// we only have a basic test here for this as unit tests for the
// `certificates.RequestMatchesSpec` function cover all other cases.
// `pki.RequestMatchesSpec` function cover all other cases.
"trigger issuance when CertificateRequest does not match certificate spec": {
certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{
CommonName: "new.example.com",

View File

@ -50,6 +50,7 @@ import (
logf "github.com/cert-manager/cert-manager/pkg/logs"
utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature"
utilkube "github.com/cert-manager/cert-manager/pkg/util/kube"
"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/pkg/util/predicate"
)
@ -152,7 +153,7 @@ func NewController(
fieldManager,
),
fieldManager: fieldManager,
localTemporarySigner: certificates.GenerateLocallySignedTemporaryCertificate,
localTemporarySigner: pki.GenerateLocallySignedTemporaryCertificate,
}, queue, mustSync
}
@ -217,7 +218,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error {
logf.WithResource(log, nextPrivateKeySecret).Error(err, "failed to parse next private key, waiting for keymanager controller")
return nil
}
pkViolations, err := certificates.PrivateKeyMatchesSpec(pk, crt.Spec)
pkViolations, err := pki.PrivateKeyMatchesSpec(pk, crt.Spec)
if err != nil {
return err
}
@ -251,7 +252,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error {
// Verify the CSR options match what is requested in certificate.spec.
// If there are violations in the spec, then the requestmanager will handle this.
requestViolations, err := certificates.RequestMatchesSpec(req, crt.Spec)
requestViolations, err := pki.RequestMatchesSpec(req, crt.Spec)
if err != nil {
return err
}

View File

@ -220,7 +220,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error {
return c.deleteSecretResources(ctx, secrets)
}
violations, err := certificates.PrivateKeyMatchesSpec(pk, crt.Spec)
violations, err := pki.PrivateKeyMatchesSpec(pk, crt.Spec)
if err != nil {
log.Error(err, "Internal error verifying if private key matches spec - please open an issue.")
return nil
@ -254,7 +254,7 @@ func (c *controller) createNextPrivateKeyRotationPolicyNever(ctx context.Context
c.recorder.Eventf(crt, corev1.EventTypeWarning, reasonDecodeFailed, "Failed to decode private key stored in Secret %q - generating new key", crt.Spec.SecretName)
return c.createAndSetNextPrivateKey(ctx, crt)
}
violations, err := certificates.PrivateKeyMatchesSpec(pk, crt.Spec)
violations, err := pki.PrivateKeyMatchesSpec(pk, crt.Spec)
if err != nil {
c.recorder.Eventf(crt, corev1.EventTypeWarning, reasonDecodeFailed, "Failed to check if private key stored in Secret %q is up to date - generating new key", crt.Spec.SecretName)
return c.createAndSetNextPrivateKey(ctx, crt)

View File

@ -66,7 +66,7 @@ type controller struct {
// policyEvaluator builds Ready condition of a Certificate based on policy evaluation
policyEvaluator policyEvaluatorFunc
// renewalTimeCalculator calculates renewal time of a certificate
renewalTimeCalculator certificates.RenewalTimeFunc
renewalTimeCalculator pki.RenewalTimeFunc
// fieldManager is the string which will be used as the Field Manager on
// fields created or edited by the cert-manager Kubernetes client during
@ -84,7 +84,7 @@ func NewController(
factory informers.SharedInformerFactory,
cmFactory cminformers.SharedInformerFactory,
chain policies.Chain,
renewalTimeCalculator certificates.RenewalTimeFunc,
renewalTimeCalculator pki.RenewalTimeFunc,
policyEvaluator policyEvaluatorFunc,
fieldManager string,
) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) {
@ -259,7 +259,7 @@ func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.Rate
ctx.KubeSharedInformerFactory,
ctx.SharedInformerFactory,
policies.NewReadinessPolicyChain(ctx.Clock),
certificates.RenewalTime,
pki.RenewalTime,
BuildReadyConditionFromChain,
ctx.FieldManager,
)

View File

@ -30,8 +30,8 @@ import (
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
controllerpkg "github.com/cert-manager/cert-manager/pkg/controller"
"github.com/cert-manager/cert-manager/pkg/controller/certificates"
testpkg "github.com/cert-manager/cert-manager/pkg/controller/test"
"github.com/cert-manager/cert-manager/pkg/util/pki"
testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto"
"github.com/cert-manager/cert-manager/test/unit/gen"
)
@ -44,7 +44,7 @@ func policyEvaluatorBuilder(c cmapi.CertificateCondition) policyEvaluatorFunc {
}
// renewalTimeBuilder returns a fake renewalTimeFunc for ReadinessController.
func renewalTimeBuilder(rt *metav1.Time) certificates.RenewalTimeFunc {
func renewalTimeBuilder(rt *metav1.Time) pki.RenewalTimeFunc {
return func(notBefore, notAfter time.Time, cert *metav1.Duration) *metav1.Time {
return rt
}

View File

@ -321,7 +321,7 @@ func (c *controller) deleteRequestsNotMatchingSpec(ctx context.Context, crt *cma
var remaining []*cmapi.CertificateRequest
for _, req := range reqs {
log := logf.WithRelatedResource(log, req)
violations, err := certificates.RequestMatchesSpec(req, crt.Spec)
violations, err := pki.RequestMatchesSpec(req, crt.Spec)
if err != nil {
log.Error(err, "Failed to check if CertificateRequest matches spec, deleting CertificateRequest")
if err := c.client.CertmanagerV1().CertificateRequests(req.Namespace).Delete(ctx, req.Name, metav1.DeleteOptions{}); err != nil {

View File

@ -48,6 +48,7 @@ import (
logf "github.com/cert-manager/cert-manager/pkg/logs"
"github.com/cert-manager/cert-manager/pkg/scheduler"
utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature"
"github.com/cert-manager/cert-manager/pkg/util/pki"
"github.com/cert-manager/cert-manager/pkg/util/predicate"
)
@ -258,7 +259,7 @@ func shouldBackoffReissuingOnFailure(log logr.Logger, c clock.Clock, crt *cmapi.
if nextCR == nil {
log.V(logf.InfoLevel).Info("next CertificateRequest not available, skipping checking if Certificate matches the CertificateRequest")
} else {
mismatches, err := certificates.RequestMatchesSpec(nextCR, crt.Spec)
mismatches, err := pki.RequestMatchesSpec(nextCR, crt.Spec)
if err != nil {
log.V(logf.InfoLevel).Info("next CertificateRequest cannot be decoded, skipping checking if Certificate matches the CertificateRequest")
return false, 0

View File

@ -0,0 +1,41 @@
/*
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/x509/pkix"
"encoding/asn1"
)
// Copied from x509.go
var (
OIDExtensionBasicConstraints = []int{2, 5, 29, 19}
)
// Copied from x509.go
type basicConstraints struct {
IsCA bool
}
// Adapted from x509.go
func MarshalBasicConstraints(isCA bool) (pkix.Extension, error) {
ext := pkix.Extension{Id: OIDExtensionBasicConstraints}
var err error
ext.Value, err = asn1.Marshal(basicConstraints{isCA})
return ext, err
}

View File

@ -22,7 +22,6 @@ import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
@ -110,20 +109,6 @@ func URLsToString(uris []*url.URL) []string {
return uriStrs
}
func removeDuplicates(in []string) []string {
var found []string
Outer:
for _, i := range in {
for _, i2 := range found {
if i2 == i {
continue Outer
}
}
found = append(found, i)
}
return found
}
// OrganizationForCertificate will return the Organization to set for the
// Certificate resource.
// If an Organization is not specifically set, a default will be used.
@ -145,14 +130,18 @@ func SubjectForCertificate(crt *v1.Certificate) v1.X509Subject {
var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)
func BuildKeyUsages(usages []v1.KeyUsage, isCA bool) (ku x509.KeyUsage, eku []x509.ExtKeyUsage, err error) {
func KeyUsagesForCertificateOrCertificateRequest(usages []v1.KeyUsage, isCA bool) (ku x509.KeyUsage, eku []x509.ExtKeyUsage, err error) {
var unk []v1.KeyUsage
if isCA {
ku |= x509.KeyUsageCertSign
}
// If no usages are specified, default to the ones specified in the
// Kubernetes API.
if len(usages) == 0 {
usages = append(usages, v1.DefaultKeyUsages()...)
usages = v1.DefaultKeyUsages()
}
for _, u := range usages {
if kuse, ok := apiutil.KeyUsageType(u); ok {
ku |= kuse
@ -217,114 +206,72 @@ func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) {
}
if utilfeature.DefaultFeatureGate.Enabled(feature.UseCertificateRequestBasicConstraints) {
extension, err := buildBasicConstraintsExtensionsForCertificate(crt.Spec.IsCA)
extension, err := MarshalBasicConstraints(crt.Spec.IsCA)
if err != nil {
return nil, err
}
extraExtensions = append(extraExtensions, extension)
}
cr := &x509.CertificateRequest{
// Version 0 is the only one defined in the PKCS#10 standard, RFC2986.
// This value isn't used by Go at the time of writing.
// https://datatracker.ietf.org/doc/html/rfc2986#section-4
Version: 0,
SignatureAlgorithm: sigAlgo,
PublicKeyAlgorithm: pubKeyAlgo,
DNSNames: dnsNames,
IPAddresses: iPAddresses,
URIs: uriNames,
EmailAddresses: crt.Spec.EmailAddresses,
ExtraExtensions: extraExtensions,
}
if isLiteralCertificateSubjectEnabled() && len(crt.Spec.LiteralSubject) > 0 {
rawSubject, err := ParseSubjectStringToRawDerBytes(crt.Spec.LiteralSubject)
rawSubject, err := ParseSubjectStringToRawDERBytes(crt.Spec.LiteralSubject)
if err != nil {
return nil, err
}
return &x509.CertificateRequest{
// Version 0 is the only one defined in the PKCS#10 standard, RFC2986.
// This value isn't used by Go at the time of writing.
// https://datatracker.ietf.org/doc/html/rfc2986#section-4
Version: 0,
SignatureAlgorithm: sigAlgo,
PublicKeyAlgorithm: pubKeyAlgo,
RawSubject: rawSubject,
DNSNames: dnsNames,
IPAddresses: iPAddresses,
URIs: uriNames,
EmailAddresses: crt.Spec.EmailAddresses,
ExtraExtensions: extraExtensions,
}, nil
cr.RawSubject = rawSubject
} else {
return &x509.CertificateRequest{
// Version 0 is the only one defined in the PKCS#10 standard, RFC2986.
// This value isn't used by Go at the time of writing.
// https://datatracker.ietf.org/doc/html/rfc2986#section-4
Version: 0,
SignatureAlgorithm: sigAlgo,
PublicKeyAlgorithm: pubKeyAlgo,
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,
},
DNSNames: dnsNames,
IPAddresses: iPAddresses,
URIs: uriNames,
EmailAddresses: crt.Spec.EmailAddresses,
ExtraExtensions: extraExtensions,
}, nil
cr.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 cr, nil
}
func buildKeyUsagesExtensionsForCertificate(crt *v1.Certificate) ([]pkix.Extension, error) {
ku, ekus, err := BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA)
ku, ekus, err := KeyUsagesForCertificateOrCertificateRequest(crt.Spec.Usages, crt.Spec.IsCA)
if err != nil {
return nil, fmt.Errorf("failed to build key usages: %w", err)
}
usage, err := buildASN1KeyUsageRequest(ku)
usage, err := MarshalKeyUsage(ku)
if err != nil {
return nil, fmt.Errorf("failed to asn1 encode usages: %w", err)
}
asn1ExtendedUsages := []asn1.ObjectIdentifier{}
for _, eku := range ekus {
if oid, ok := OIDFromExtKeyUsage(eku); ok {
asn1ExtendedUsages = append(asn1ExtendedUsages, oid)
}
// if no extended usages are specified, return early
if len(ekus) == 0 {
return []pkix.Extension{usage}, nil
}
extraExtensions := []pkix.Extension{usage}
if len(ekus) > 0 {
extendedUsage := pkix.Extension{
Id: OIDExtensionExtendedKeyUsage,
}
extendedUsage.Value, err = asn1.Marshal(asn1ExtendedUsages)
if err != nil {
return nil, fmt.Errorf("failed to asn1 encode extended usages: %w", err)
}
extraExtensions = append(extraExtensions, extendedUsage)
}
return extraExtensions, nil
}
func buildBasicConstraintsExtensionsForCertificate(isCA bool) (pkix.Extension, error) {
basicConstraints := pkix.Extension{
Id: OIDExtensionBasicConstraints,
}
constraint := struct {
IsCA bool
}{
IsCA: isCA,
}
var err error
basicConstraints.Value, err = asn1.Marshal(constraint)
extendedUsages, err := MarshalExtKeyUsage(ekus, nil)
if err != nil {
return pkix.Extension{}, err
return nil, fmt.Errorf("failed to asn1 encode extended usages: %w", err)
}
return basicConstraints, nil
return []pkix.Extension{usage, extendedUsages}, nil
}
// GenerateTemplate will create a x509.Certificate for the given Certificate resource.
@ -345,7 +292,7 @@ func GenerateTemplate(crt *v1.Certificate) (*x509.Certificate, error) {
if err != nil {
return nil, err
}
keyUsages, extKeyUsages, err := BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA)
keyUsages, extKeyUsages, err := KeyUsagesForCertificateOrCertificateRequest(crt.Spec.Usages, crt.Spec.IsCA)
if err != nil {
return nil, err
}
@ -366,74 +313,56 @@ func GenerateTemplate(crt *v1.Certificate) (*x509.Certificate, error) {
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)
rawSubject, err := ParseSubjectStringToRawDERBytes(crt.Spec.LiteralSubject)
if err != nil {
return nil, err
}
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: pubKeyAlgo,
IsCA: crt.Spec.IsCA,
RawSubject: rawSubject,
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,
}, nil
cert.RawSubject = rawSubject
} else {
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: pubKeyAlgo,
IsCA: crt.Spec.IsCA,
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,
},
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,
}, nil
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 := BuildKeyUsages(cr.Spec.Usages, cr.Spec.IsCA)
keyUsage, extKeyUsage, err := KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA)
if err != nil {
return nil, err
}
@ -650,7 +579,7 @@ func extractCommonName(spec v1.CertificateSpec) (string, error) {
var commonName = spec.CommonName
if isLiteralCertificateSubjectEnabled() && len(spec.LiteralSubject) > 0 {
commonName = ""
sequence, err := ParseSubjectStringToRdnSequence(spec.LiteralSubject)
sequence, err := UnmarshalSubjectStringToRDNSequence(spec.LiteralSubject)
if err != nil {
return "", err
}

View File

@ -46,7 +46,7 @@ func buildCertificate(cn string, dnsNames ...string) *cmapi.Certificate {
}
}
func TestBuildUsages(t *testing.T) {
func TestKeyUsagesForCertificate(t *testing.T) {
type testT struct {
name string
usages []cmapi.KeyUsage
@ -101,7 +101,7 @@ func TestBuildUsages(t *testing.T) {
}
testFn := func(test testT) func(*testing.T) {
return func(t *testing.T) {
ku, eku, err := BuildKeyUsages(test.usages, test.isCa)
ku, eku, err := KeyUsagesForCertificateOrCertificateRequest(test.usages, test.isCa)
if err != nil && !test.expectedError {
t.Errorf("got unexpected error generating cert: %q", err)
return
@ -388,6 +388,20 @@ func TestRemoveDuplicates(t *testing.T) {
}
}
func removeDuplicates(in []string) []string {
var found []string
Outer:
for _, i := range in {
for _, i2 := range found {
if i2 == i {
continue Outer
}
}
found = append(found, i)
}
return found
}
func TestGenerateCSR(t *testing.T) {
// 0xa0 = DigitalSignature and Encipherment usage
asn1KeyUsage, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0xa0}, BitLength: asn1BitLength([]byte{0xa0})})
@ -441,13 +455,13 @@ func TestGenerateCSR(t *testing.T) {
}
exampleLiteralSubject := "CN=actual-cn, OU=FooLong, OU=Bar, O=example.org"
rawExampleLiteralSubject, err := ParseSubjectStringToRawDerBytes(exampleLiteralSubject)
rawExampleLiteralSubject, err := ParseSubjectStringToRawDERBytes(exampleLiteralSubject)
if err != nil {
t.Fatal(err)
}
exampleMultiValueRDNLiteralSubject := "CN=actual-cn, OU=FooLong+OU=Bar, O=example.org"
rawExampleMultiValueRDNLiteralSubject, err := ParseSubjectStringToRawDerBytes(exampleMultiValueRDNLiteralSubject)
rawExampleMultiValueRDNLiteralSubject, err := ParseSubjectStringToRawDERBytes(exampleMultiValueRDNLiteralSubject)
if err != nil {
t.Fatal(err)
}

View File

@ -116,7 +116,6 @@ func GenerateECPrivateKey(keySize int) (*ecdsa.PrivateKey, error) {
// GenerateEd25519PrivateKey will generate an Ed25519 private key
func GenerateEd25519PrivateKey() (ed25519.PrivateKey, error) {
_, prvkey, err := ed25519.GenerateKey(rand.Reader)
return prvkey, err

View File

@ -20,13 +20,13 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
)
// Copied from x509.go
var (
OIDExtensionKeyUsage = []int{2, 5, 29, 15}
OIDExtensionExtendedKeyUsage = []int{2, 5, 29, 37}
OIDExtensionBasicConstraints = []int{2, 5, 29, 19}
)
// RFC 5280, 4.2.1.12 Extended Key Usage
@ -127,10 +127,9 @@ func reverseBitsInAByte(in byte) byte {
}
// Adapted from x509.go
func buildASN1KeyUsageRequest(usage x509.KeyUsage) (pkix.Extension, error) {
OIDExtensionKeyUsage := pkix.Extension{
Id: OIDExtensionKeyUsage,
}
func MarshalKeyUsage(usage x509.KeyUsage) (pkix.Extension, error) {
ext := pkix.Extension{Id: OIDExtensionKeyUsage}
var a [2]byte
a[0] = reverseBitsInAByte(byte(usage))
a[1] = reverseBitsInAByte(byte(usage >> 8))
@ -142,10 +141,26 @@ func buildASN1KeyUsageRequest(usage x509.KeyUsage) (pkix.Extension, error) {
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
ext.Value, err = asn1.Marshal(asn1.BitString{Bytes: bitString, BitLength: asn1BitLength(bitString)})
return ext, err
}
// Adapted from x509.go
func MarshalExtKeyUsage(extUsages []x509.ExtKeyUsage, unknownUsages []asn1.ObjectIdentifier) (pkix.Extension, error) {
ext := pkix.Extension{Id: OIDExtensionExtendedKeyUsage}
oids := make([]asn1.ObjectIdentifier, len(extUsages)+len(unknownUsages))
for i, u := range extUsages {
if oid, ok := OIDFromExtKeyUsage(u); ok {
oids[i] = oid
} else {
return ext, errors.New("x509: unknown extended key usage")
}
}
return OIDExtensionKeyUsage, nil
copy(oids[len(extUsages):], unknownUsages)
var err error
ext.Value, err = asn1.Marshal(oids)
return ext, err
}

View File

@ -14,27 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package certificates
package pki
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"reflect"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
"github.com/cert-manager/cert-manager/pkg/util"
"github.com/cert-manager/cert-manager/pkg/util/pki"
)
// PrivateKeyMatchesSpec returns an error if the private key bit size
@ -68,7 +63,7 @@ func rsaPrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec)
// This requires careful handling in order to not interrupt users upgrading
// from older versions.
// The default RSA keySize is set to 2048.
keySize := pki.MinRSAKeySize
keySize := MinRSAKeySize
if spec.PrivateKey.Size > 0 {
keySize = spec.PrivateKey.Size
}
@ -89,7 +84,7 @@ func ecdsaPrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec
// This requires careful handling in order to not interrupt users upgrading
// from older versions.
// The default EC curve type is EC256
expectedKeySize := pki.ECCurve256
expectedKeySize := ECCurve256
if spec.PrivateKey.Size > 0 {
expectedKeySize = spec.PrivateKey.Size
}
@ -113,7 +108,7 @@ func ed25519PrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSp
// counterpart fields on the CertificateRequest.
// If decoding the x509 certificate request fails, an error will be returned.
func RequestMatchesSpec(req *cmapi.CertificateRequest, spec cmapi.CertificateSpec) ([]string, error) {
x509req, err := pki.DecodeX509CertificateRequestBytes(req.Spec.Request)
x509req, err := DecodeX509CertificateRequestBytes(req.Spec.Request)
if err != nil {
return nil, err
}
@ -125,22 +120,26 @@ func RequestMatchesSpec(req *cmapi.CertificateRequest, spec cmapi.CertificateSpe
}
var violations []string
if spec.LiteralSubject == "" {
if x509req.Subject.CommonName != spec.CommonName {
violations = append(violations, "spec.commonName")
}
if !util.EqualUnsorted(x509req.DNSNames, spec.DNSNames) {
violations = append(violations, "spec.dnsNames")
}
if !util.EqualUnsorted(pki.IPAddressesToString(x509req.IPAddresses), spec.IPAddresses) {
// TODO: also check these fields if LiteralSubject is set
if !util.EqualUnsorted(IPAddressesToString(x509req.IPAddresses), spec.IPAddresses) {
violations = append(violations, "spec.ipAddresses")
}
if !util.EqualUnsorted(pki.URLsToString(x509req.URIs), spec.URIs) {
if !util.EqualUnsorted(URLsToString(x509req.URIs), spec.URIs) {
violations = append(violations, "spec.uris")
}
if !util.EqualUnsorted(x509req.EmailAddresses, spec.EmailAddresses) {
violations = append(violations, "spec.emailAddresses")
}
if !util.EqualUnsorted(x509req.DNSNames, spec.DNSNames) {
violations = append(violations, "spec.dnsNames")
}
// Comparing Subject fields
if x509req.Subject.CommonName != spec.CommonName {
violations = append(violations, "spec.commonName")
}
if x509req.Subject.SerialNumber != spec.Subject.SerialNumber {
violations = append(violations, "spec.subject.serialNumber")
}
@ -165,30 +164,33 @@ func RequestMatchesSpec(req *cmapi.CertificateRequest, spec cmapi.CertificateSpe
if !util.EqualUnsorted(x509req.Subject.StreetAddress, spec.Subject.StreetAddresses) {
violations = append(violations, "spec.subject.streetAddresses")
}
// TODO: also check these fields if LiteralSubject is set
if req.Spec.IsCA != spec.IsCA {
violations = append(violations, "spec.isCA")
}
if !util.EqualKeyUsagesUnsorted(req.Spec.Usages, spec.Usages) {
violations = append(violations, "spec.usages")
}
if spec.Duration != nil && req.Spec.Duration != nil &&
spec.Duration.Duration != req.Spec.Duration.Duration {
if req.Spec.Duration != nil && spec.Duration != nil &&
req.Spec.Duration.Duration != spec.Duration.Duration {
violations = append(violations, "spec.duration")
}
if !reflect.DeepEqual(spec.IssuerRef, req.Spec.IssuerRef) {
if !reflect.DeepEqual(req.Spec.IssuerRef, spec.IssuerRef) {
violations = append(violations, "spec.issuerRef")
}
// TODO: check spec.EncodeBasicConstraintsInRequest and spec.EncodeUsagesInRequest
} else {
// we have a LiteralSubject
// parse the subject of the csr in the same way as we parse LiteralSubject and see whether the RDN Sequences match
var rdnSequenceFromCertificateRequest pkix.RDNSequence
_, err2 := asn1.Unmarshal(x509req.RawSubject, &rdnSequenceFromCertificateRequest)
if err2 != nil {
return nil, err2
rdnSequenceFromCertificateRequest, err := UnmarshalRawDerBytesToRDNSequence(x509req.RawSubject)
if err != nil {
return nil, err
}
rdnSequenceFromCertificate, err := pki.ParseSubjectStringToRdnSequence(spec.LiteralSubject)
rdnSequenceFromCertificate, err := UnmarshalSubjectStringToRDNSequence(spec.LiteralSubject)
if err != nil {
return nil, err
}
@ -207,7 +209,7 @@ func RequestMatchesSpec(req *cmapi.CertificateRequest, spec cmapi.CertificateSpe
// This is a purposely less comprehensive check than RequestMatchesSpec as some
// issuers override/force certain fields.
func SecretDataAltNamesMatchSpec(secret *corev1.Secret, spec cmapi.CertificateSpec) ([]string, error) {
x509cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey])
x509cert, err := DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey])
if err != nil {
return nil, err
}
@ -238,10 +240,10 @@ func SecretDataAltNamesMatchSpec(secret *corev1.Secret, spec cmapi.CertificateSp
}
}
if !util.EqualUnsorted(pki.IPAddressesToString(x509cert.IPAddresses), spec.IPAddresses) {
if !util.EqualUnsorted(IPAddressesToString(x509cert.IPAddresses), spec.IPAddresses) {
violations = append(violations, "spec.ipAddresses")
}
if !util.EqualUnsorted(pki.URLsToString(x509cert.URIs), spec.URIs) {
if !util.EqualUnsorted(URLsToString(x509cert.URIs), spec.URIs) {
violations = append(violations, "spec.uris")
}
if !util.EqualUnsorted(x509cert.EmailAddresses, spec.EmailAddresses) {
@ -250,90 +252,3 @@ func SecretDataAltNamesMatchSpec(secret *corev1.Secret, spec cmapi.CertificateSp
return violations, nil
}
// staticTemporarySerialNumber is a fixed serial number we use for temporary certificates
const staticTemporarySerialNumber = "1234567890"
// GenerateLocallySignedTemporaryCertificate signs a temporary certificate for
// the given certificate resource using a one-use temporary CA that is then
// discarded afterwards.
// This is to mitigate a potential attack against x509 certificates that use a
// predictable serial number and weak MD5 hashing algorithms.
// In practice, this shouldn't really be a concern anyway.
func GenerateLocallySignedTemporaryCertificate(crt *cmapi.Certificate, pkData []byte) ([]byte, error) {
// generate a throwaway self-signed root CA
caPk, err := pki.GenerateECPrivateKey(pki.ECCurve521)
if err != nil {
return nil, err
}
caCertTemplate, err := pki.GenerateTemplate(&cmapi.Certificate{
Spec: cmapi.CertificateSpec{
CommonName: "cert-manager.local",
IsCA: true,
},
})
if err != nil {
return nil, err
}
_, caCert, err := pki.SignCertificate(caCertTemplate, caCertTemplate, caPk.Public(), caPk)
if err != nil {
return nil, err
}
// sign a temporary certificate using the root CA
template, err := pki.GenerateTemplate(crt)
if err != nil {
return nil, err
}
template.Subject.SerialNumber = staticTemporarySerialNumber
signeeKey, err := pki.DecodePrivateKeyBytes(pkData)
if err != nil {
return nil, err
}
b, _, err := pki.SignCertificate(template, caCert, signeeKey.Public(), caPk)
if err != nil {
return nil, err
}
return b, nil
}
// RenewalTimeFunc is a custom function type for calculating renewal time of a certificate.
type RenewalTimeFunc func(time.Time, time.Time, *metav1.Duration) *metav1.Time
// RenewalTime calculates renewal time for a certificate. Default renewal time
// is 2/3 through certificate's lifetime. If user has configured
// spec.renewBefore, renewal time will be renewBefore period before expiry
// (unless that is after the expiry).
func RenewalTime(notBefore, notAfter time.Time, renewBeforeOverride *metav1.Duration) *metav1.Time {
// 1. Calculate how long before expiry a cert should be renewed
actualDuration := notAfter.Sub(notBefore)
renewBefore := actualDuration / 3
// If spec.renewBefore was set (and is less than duration)
// respect that. We don't want to prevent users from renewing
// longer lived certs more frequently.
if renewBeforeOverride != nil && renewBeforeOverride.Duration < actualDuration {
renewBefore = renewBeforeOverride.Duration
}
// 2. Calculate when a cert should be renewed
// Truncate the renewal time to nearest second. This is important
// because the renewal time also gets stored on Certificate's status
// where it is truncated to the nearest second. We use the renewal time
// from Certificate's status to determine when the Certificate will be
// added to the queue to be renewed, but then re-calculate whether it
// needs to be renewed _now_ using this function- so returning a
// non-truncated value here would potentially cause Certificates to be
// re-queued for renewal earlier than the calculated renewal time thus
// causing Certificates to not be automatically renewed. See
// https://github.com/cert-manager/cert-manager/pull/4399.
rt := metav1.NewTime(notAfter.Add(-1 * renewBefore).Truncate(time.Second))
return &rt
}

View File

@ -14,25 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package certificates
package pki
import (
"crypto"
"fmt"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
"github.com/cert-manager/cert-manager/pkg/util/pki"
)
func mustGenerateRSA(t *testing.T, keySize int) crypto.PrivateKey {
pk, err := pki.GenerateRSAPrivateKey(keySize)
pk, err := GenerateRSAPrivateKey(keySize)
if err != nil {
t.Fatal(err)
}
@ -40,7 +35,7 @@ func mustGenerateRSA(t *testing.T, keySize int) crypto.PrivateKey {
}
func mustGenerateECDSA(t *testing.T, keySize int) crypto.PrivateKey {
pk, err := pki.GenerateECPrivateKey(keySize)
pk, err := GenerateECPrivateKey(keySize)
if err != nil {
t.Fatal(err)
}
@ -48,7 +43,7 @@ func mustGenerateECDSA(t *testing.T, keySize int) crypto.PrivateKey {
}
func mustGenerateEd25519(t *testing.T) crypto.PrivateKey {
pk, err := pki.GenerateEd25519PrivateKey()
pk, err := GenerateEd25519PrivateKey()
if err != nil {
t.Fatal(err)
}
@ -75,18 +70,18 @@ func TestPrivateKeyMatchesSpec(t *testing.T) {
violations: []string{"spec.privateKey.size"},
},
"should match if keySize and algorithm are correct (ECDSA)": {
key: mustGenerateECDSA(t, pki.ECCurve256),
key: mustGenerateECDSA(t, ECCurve256),
expectedAlgo: cmapi.ECDSAKeyAlgorithm,
expectedSize: 256,
},
"should not match if ECDSA keySize is incorrect": {
key: mustGenerateECDSA(t, pki.ECCurve256),
key: mustGenerateECDSA(t, ECCurve256),
expectedAlgo: cmapi.ECDSAKeyAlgorithm,
expectedSize: pki.ECCurve521,
expectedSize: ECCurve521,
violations: []string{"spec.privateKey.size"},
},
"should not match if keyAlgorithm is incorrect": {
key: mustGenerateECDSA(t, pki.ECCurve256),
key: mustGenerateECDSA(t, ECCurve256),
expectedAlgo: cmapi.RSAKeyAlgorithm,
expectedSize: 2048,
violations: []string{"spec.privateKey.algorithm"},
@ -277,87 +272,20 @@ func TestSecretDataAltNamesMatchSpec(t *testing.T) {
}
func selfSignCertificate(t *testing.T, spec cmapi.CertificateSpec) []byte {
pk, err := pki.GenerateRSAPrivateKey(2048)
pk, err := GenerateRSAPrivateKey(2048)
if err != nil {
t.Fatal(err)
}
template, err := pki.GenerateTemplate(&cmapi.Certificate{Spec: spec})
template, err := GenerateTemplate(&cmapi.Certificate{Spec: spec})
if err != nil {
t.Fatal(err)
}
pemData, _, err := pki.SignCertificate(template, template, pk.Public(), pk)
pemData, _, err := SignCertificate(template, template, pk.Public(), pk)
if err != nil {
t.Fatal(err)
}
return pemData
}
func TestRenewalTime(t *testing.T) {
type scenario struct {
notBefore time.Time
notAfter time.Time
renewBeforeOverride *metav1.Duration
expectedRenewalTime *metav1.Time
}
now := time.Now().Truncate(time.Second)
tests := map[string]scenario{
"short lived cert, spec.renewBefore is not set": {
notBefore: now,
notAfter: now.Add(time.Hour * 3),
renewBeforeOverride: nil,
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2)},
},
"long lived cert, spec.renewBefore is not set": {
notBefore: now,
notAfter: now.Add(time.Hour * 4380), // 6 months
renewBeforeOverride: nil,
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2920)}, // renew in 4 months
},
"spec.renewBefore is set": {
notBefore: now,
notAfter: now.Add(time.Hour * 24),
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 20},
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 4)},
},
"long lived cert, spec.renewBefore is set to renew every day": {
notBefore: now,
notAfter: now.Add(time.Hour * 730), // 1 month
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 706}, // 1 month - 1 day
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 24)},
},
"spec.renewBefore is set, but would result in renewal time after expiry": {
notBefore: now,
notAfter: now.Add(time.Hour * 24),
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 25},
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16)},
},
// This test case is here to show the scenario where users set
// renewBefore to very slightly less than actual duration. This
// will result in cert being renewed 'continuously'.
"spec.renewBefore is set to a value slightly less than cert's duration": {
notBefore: now,
notAfter: now.Add(time.Hour*24 + time.Minute*3),
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 24},
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Minute * 3)}, // renew in 3 minutes
},
// This test case is here to guard against an earlier bug where
// a non-truncated renewal time returned from this function
// caused certs to not be renewed.
// See https://github.com/cert-manager/cert-manager/pull/4399
"certificate's duration is skewed by a second": {
notBefore: now,
notAfter: now.Add(time.Hour * 24).Add(time.Second * -1),
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16).Add(time.Second * -1)},
},
}
for n, s := range tests {
t.Run(n, func(t *testing.T) {
renewalTime := RenewalTime(s.notBefore, s.notAfter, s.renewBeforeOverride)
assert.Equal(t, s.expectedRenewalTime, renewalTime, fmt.Sprintf("Expected renewal time: %v got: %v", s.expectedRenewalTime, renewalTime))
})
}
}

View File

@ -20,12 +20,9 @@ import (
"crypto"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"github.com/cert-manager/cert-manager/pkg/util/errors"
"github.com/go-ldap/ldap/v3"
)
// DecodePrivateKeyBytes will decode a PEM encoded private key into a crypto.Signer.
@ -363,81 +360,3 @@ func (c *chainNode) root() *chainNode {
func isSelfSignedCertificate(cert *x509.Certificate) bool {
return cert.CheckSignatureFrom(cert) == nil
}
var OIDConstants = struct {
Country []int
Organization []int
OrganizationalUnit []int
CommonName []int
SerialNumber []int
Locality []int
Province []int
StreetAddress []int
DomainComponent []int
UniqueIdentifier []int
}{
Country: []int{2, 5, 4, 6},
Organization: []int{2, 5, 4, 10},
OrganizationalUnit: []int{2, 5, 4, 11},
CommonName: []int{2, 5, 4, 3},
SerialNumber: []int{2, 5, 4, 5},
Locality: []int{2, 5, 4, 7},
Province: []int{2, 5, 4, 8},
StreetAddress: []int{2, 5, 4, 9},
DomainComponent: []int{0, 9, 2342, 19200300, 100, 1, 25},
UniqueIdentifier: []int{0, 9, 2342, 19200300, 100, 1, 1},
}
// Copied from pkix.attributeTypeNames and inverted. (Sadly it is private.)
// Source: https://cs.opensource.google/go/go/+/refs/tags/go1.18.2:src/crypto/x509/pkix/pkix.go;l=26
// Added RDNs identifier to support rfc4514 LDAP certificates, cf https://github.com/cert-manager/cert-manager/issues/5582
var attributeTypeNames = map[string][]int{
"C": OIDConstants.Country,
"O": OIDConstants.Organization,
"OU": OIDConstants.OrganizationalUnit,
"CN": OIDConstants.CommonName,
"SERIALNUMBER": OIDConstants.SerialNumber,
"L": OIDConstants.Locality,
"ST": OIDConstants.Province,
"STREET": OIDConstants.StreetAddress,
"DC": OIDConstants.DomainComponent,
"UID": OIDConstants.UniqueIdentifier,
}
func ParseSubjectStringToRdnSequence(subject string) (pkix.RDNSequence, error) {
dns, err := ldap.ParseDN(subject)
if err != nil {
return nil, err
}
// Traverse the parsed RDNSequence in REVERSE order as RDNs in String format are expected to be written in reverse order.
// Meaning, a string of "CN=Foo,OU=Bar,O=Baz" actually should have "O=Baz" as the first element in the RDNSequence.
var rdns pkix.RDNSequence
for i := range dns.RDNs {
ldapRelativeDN := dns.RDNs[len(dns.RDNs)-i-1]
var atvs []pkix.AttributeTypeAndValue
for _, ldapATV := range ldapRelativeDN.Attributes {
atvs = append(atvs, pkix.AttributeTypeAndValue{
Type: attributeTypeNames[ldapATV.Type],
Value: ldapATV.Value,
})
}
rdns = append(rdns, atvs)
}
return rdns, nil
}
func ParseSubjectStringToRawDerBytes(subject string) ([]byte, error) {
rdnSequenceFromLiteralString, err := ParseSubjectStringToRdnSequence(subject)
if err != nil {
return nil, err
}
return asn1.Marshal(rdnSequenceFromLiteralString)
}

View File

@ -366,7 +366,7 @@ func TestParseSingleCertificateChain(t *testing.T) {
func TestMustParseRDN(t *testing.T) {
subject := "SERIALNUMBER=42, L=some-locality, ST=some-state-or-province, STREET=some-street, CN=foo-long.com, OU=FooLong, OU=Barq, OU=Baz, OU=Dept., O=Corp., C=US"
rdnSeq, err := ParseSubjectStringToRdnSequence(subject)
rdnSeq, err := UnmarshalSubjectStringToRDNSequence(subject)
if err != nil {
t.Fatal(err)
}
@ -413,7 +413,7 @@ func TestMustParseRDN(t *testing.T) {
func TestMustKeepOrderInRawDerBytes(t *testing.T) {
subject := "CN=foo-long.com,OU=FooLong,OU=Barq,OU=Baz,OU=Dept.,O=Corp.,C=US"
bytes, err := ParseSubjectStringToRawDerBytes(subject)
bytes, err := ParseSubjectStringToRawDERBytes(subject)
if err != nil {
t.Fatal(err)
}

View File

@ -0,0 +1,59 @@
/*
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 (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// RenewalTimeFunc is a custom function type for calculating renewal time of a certificate.
type RenewalTimeFunc func(time.Time, time.Time, *metav1.Duration) *metav1.Time
// RenewalTime calculates renewal time for a certificate. Default renewal time
// is 2/3 through certificate's lifetime. If user has configured
// spec.renewBefore, renewal time will be renewBefore period before expiry
// (unless that is after the expiry).
func RenewalTime(notBefore, notAfter time.Time, renewBeforeOverride *metav1.Duration) *metav1.Time {
// 1. Calculate how long before expiry a cert should be renewed
actualDuration := notAfter.Sub(notBefore)
renewBefore := actualDuration / 3
// If spec.renewBefore was set (and is less than duration)
// respect that. We don't want to prevent users from renewing
// longer lived certs more frequently.
if renewBeforeOverride != nil && renewBeforeOverride.Duration < actualDuration {
renewBefore = renewBeforeOverride.Duration
}
// 2. Calculate when a cert should be renewed
// Truncate the renewal time to nearest second. This is important
// because the renewal time also gets stored on Certificate's status
// where it is truncated to the nearest second. We use the renewal time
// from Certificate's status to determine when the Certificate will be
// added to the queue to be renewed, but then re-calculate whether it
// needs to be renewed _now_ using this function- so returning a
// non-truncated value here would potentially cause Certificates to be
// re-queued for renewal earlier than the calculated renewal time thus
// causing Certificates to not be automatically renewed. See
// https://github.com/cert-manager/cert-manager/pull/4399.
rt := metav1.NewTime(notAfter.Add(-1 * renewBefore).Truncate(time.Second))
return &rt
}

View File

@ -0,0 +1,93 @@
/*
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 (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestRenewalTime(t *testing.T) {
type scenario struct {
notBefore time.Time
notAfter time.Time
renewBeforeOverride *metav1.Duration
expectedRenewalTime *metav1.Time
}
now := time.Now().Truncate(time.Second)
tests := map[string]scenario{
"short lived cert, spec.renewBefore is not set": {
notBefore: now,
notAfter: now.Add(time.Hour * 3),
renewBeforeOverride: nil,
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2)},
},
"long lived cert, spec.renewBefore is not set": {
notBefore: now,
notAfter: now.Add(time.Hour * 4380), // 6 months
renewBeforeOverride: nil,
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2920)}, // renew in 4 months
},
"spec.renewBefore is set": {
notBefore: now,
notAfter: now.Add(time.Hour * 24),
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 20},
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 4)},
},
"long lived cert, spec.renewBefore is set to renew every day": {
notBefore: now,
notAfter: now.Add(time.Hour * 730), // 1 month
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 706}, // 1 month - 1 day
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 24)},
},
"spec.renewBefore is set, but would result in renewal time after expiry": {
notBefore: now,
notAfter: now.Add(time.Hour * 24),
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 25},
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16)},
},
// This test case is here to show the scenario where users set
// renewBefore to very slightly less than actual duration. This
// will result in cert being renewed 'continuously'.
"spec.renewBefore is set to a value slightly less than cert's duration": {
notBefore: now,
notAfter: now.Add(time.Hour*24 + time.Minute*3),
renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 24},
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Minute * 3)}, // renew in 3 minutes
},
// This test case is here to guard against an earlier bug where
// a non-truncated renewal time returned from this function
// caused certs to not be renewed.
// See https://github.com/cert-manager/cert-manager/pull/4399
"certificate's duration is skewed by a second": {
notBefore: now,
notAfter: now.Add(time.Hour * 24).Add(time.Second * -1),
expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16).Add(time.Second * -1)},
},
}
for n, s := range tests {
t.Run(n, func(t *testing.T) {
renewalTime := RenewalTime(s.notBefore, s.notAfter, s.renewBeforeOverride)
assert.Equal(t, s.expectedRenewalTime, renewalTime, fmt.Sprintf("Expected renewal time: %v got: %v", s.expectedRenewalTime, renewalTime))
})
}
}

114
pkg/util/pki/subject.go Normal file
View File

@ -0,0 +1,114 @@
/*
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/x509/pkix"
"encoding/asn1"
"errors"
"github.com/go-ldap/ldap/v3"
)
var OIDConstants = struct {
Country []int
Organization []int
OrganizationalUnit []int
CommonName []int
SerialNumber []int
Locality []int
Province []int
StreetAddress []int
DomainComponent []int
UniqueIdentifier []int
}{
Country: []int{2, 5, 4, 6},
Organization: []int{2, 5, 4, 10},
OrganizationalUnit: []int{2, 5, 4, 11},
CommonName: []int{2, 5, 4, 3},
SerialNumber: []int{2, 5, 4, 5},
Locality: []int{2, 5, 4, 7},
Province: []int{2, 5, 4, 8},
StreetAddress: []int{2, 5, 4, 9},
DomainComponent: []int{0, 9, 2342, 19200300, 100, 1, 25},
UniqueIdentifier: []int{0, 9, 2342, 19200300, 100, 1, 1},
}
// Copied from pkix.attributeTypeNames and inverted. (Sadly it is private.)
// Source: https://cs.opensource.google/go/go/+/refs/tags/go1.18.2:src/crypto/x509/pkix/pkix.go;l=26
// Added RDNs identifier to support rfc4514 LDAP certificates, cf https://github.com/cert-manager/cert-manager/issues/5582
var attributeTypeNames = map[string][]int{
"C": OIDConstants.Country,
"O": OIDConstants.Organization,
"OU": OIDConstants.OrganizationalUnit,
"CN": OIDConstants.CommonName,
"SERIALNUMBER": OIDConstants.SerialNumber,
"L": OIDConstants.Locality,
"ST": OIDConstants.Province,
"STREET": OIDConstants.StreetAddress,
"DC": OIDConstants.DomainComponent,
"UID": OIDConstants.UniqueIdentifier,
}
func UnmarshalSubjectStringToRDNSequence(subject string) (pkix.RDNSequence, error) {
dns, err := ldap.ParseDN(subject)
if err != nil {
return nil, err
}
// Traverse the parsed RDNSequence in REVERSE order as RDNs in String format are expected to be written in reverse order.
// Meaning, a string of "CN=Foo,OU=Bar,O=Baz" actually should have "O=Baz" as the first element in the RDNSequence.
rdns := make(pkix.RDNSequence, 0, len(dns.RDNs))
for i := range dns.RDNs {
ldapRelativeDN := dns.RDNs[len(dns.RDNs)-i-1]
atvs := make([]pkix.AttributeTypeAndValue, 0, len(ldapRelativeDN.Attributes))
for _, ldapATV := range ldapRelativeDN.Attributes {
atvs = append(atvs, pkix.AttributeTypeAndValue{
Type: attributeTypeNames[ldapATV.Type],
Value: ldapATV.Value,
})
}
rdns = append(rdns, atvs)
}
return rdns, nil
}
func MarshalRDNSequenceToRawDERBytes(rdnSequence pkix.RDNSequence) ([]byte, error) {
return asn1.Marshal(rdnSequence)
}
func UnmarshalRawDerBytesToRDNSequence(der []byte) (rdnSequence pkix.RDNSequence, err error) {
var rest []byte
if rest, err = asn1.Unmarshal(der, &rdnSequence); err != nil {
return rdnSequence, err
} else if len(rest) != 0 {
return rdnSequence, errors.New("RDNSequence: trailing data after Subject")
} else {
return rdnSequence, nil
}
}
func ParseSubjectStringToRawDERBytes(subject string) ([]byte, error) {
rdnSequence, err := UnmarshalSubjectStringToRDNSequence(subject)
if err != nil {
return nil, err
}
return MarshalRDNSequenceToRawDERBytes(rdnSequence)
}

View File

@ -0,0 +1,68 @@
/*
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 cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
// staticTemporarySerialNumber is a fixed serial number we use for temporary certificates
const staticTemporarySerialNumber = "1234567890"
// GenerateLocallySignedTemporaryCertificate signs a temporary certificate for
// the given certificate resource using a one-use temporary CA that is then
// discarded afterwards.
// This is to mitigate a potential attack against x509 certificates that use a
// predictable serial number and weak MD5 hashing algorithms.
// In practice, this shouldn't really be a concern anyway.
func GenerateLocallySignedTemporaryCertificate(crt *cmapi.Certificate, pkData []byte) ([]byte, error) {
// generate a throwaway self-signed root CA
caPk, err := GenerateECPrivateKey(ECCurve521)
if err != nil {
return nil, err
}
caCertTemplate, err := GenerateTemplate(&cmapi.Certificate{
Spec: cmapi.CertificateSpec{
CommonName: "cert-manager.local",
IsCA: true,
},
})
if err != nil {
return nil, err
}
_, caCert, err := SignCertificate(caCertTemplate, caCertTemplate, caPk.Public(), caPk)
if err != nil {
return nil, err
}
// sign a temporary certificate using the root CA
template, err := GenerateTemplate(crt)
if err != nil {
return nil, err
}
template.Subject.SerialNumber = staticTemporarySerialNumber
signeeKey, err := DecodePrivateKeyBytes(pkData)
if err != nil {
return nil, err
}
b, _, err := SignCertificate(template, caCert, signeeKey.Public(), caPk)
if err != nil {
return nil, err
}
return b, nil
}

View File

@ -139,7 +139,7 @@ func (h *Helper) ValidateIssuedCertificateRequest(cr *cmapi.CertificateRequest,
expectedDNSName = expectedDNSNames[0]
}
certificateKeyUsages, certificateExtKeyUsages, err := pki.BuildKeyUsages(cr.Spec.Usages, cr.Spec.IsCA)
certificateKeyUsages, certificateExtKeyUsages, err := pki.KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA)
if err != nil {
return nil, fmt.Errorf("failed to build key usages from certificate: %s", err)
}

View File

@ -266,7 +266,7 @@ func (s *Suite) Define() {
return err
}
rdnSeq, err2 := pki.ParseSubjectStringToRdnSequence(literalSubject)
rdnSeq, err2 := pki.UnmarshalSubjectStringToRDNSequence(literalSubject)
if err2 != nil {
return err2

View File

@ -33,7 +33,6 @@ import (
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
controllerpkg "github.com/cert-manager/cert-manager/pkg/controller"
"github.com/cert-manager/cert-manager/pkg/controller/certificates"
"github.com/cert-manager/cert-manager/pkg/controller/certificates/issuing"
"github.com/cert-manager/cert-manager/pkg/controller/certificates/keymanager"
"github.com/cert-manager/cert-manager/pkg/controller/certificates/readiness"
@ -325,7 +324,7 @@ func runAllControllers(t *testing.T, ctx context.Context, config *rest.Config) f
revCtrl, revQueue, revMustSync := revisionmanager.NewController(log, cmCl, cmFactory)
revisionManager := controllerpkg.NewController(ctx, "revisionmanager_controller", metrics, revCtrl.ProcessItem, revMustSync, nil, revQueue)
readyCtrl, readyQueue, readyMustSync := readiness.NewController(log, cmCl, factory, cmFactory, policies.NewReadinessPolicyChain(clock), certificates.RenewalTime, readiness.BuildReadyConditionFromChain, "readiness")
readyCtrl, readyQueue, readyMustSync := readiness.NewController(log, cmCl, factory, cmFactory, policies.NewReadinessPolicyChain(clock), pki.RenewalTime, readiness.BuildReadyConditionFromChain, "readiness")
readinessManager := controllerpkg.NewController(ctx, "readiness_controller", metrics, readyCtrl.ProcessItem, readyMustSync, nil, readyQueue)
issueCtrl, issueQueue, issueMustSync := issuing.NewController(log, kubeClient, cmCl, factory, cmFactory, &testpkg.FakeRecorder{}, clock, controllerpkg.CertificateOptions{}, "issuing")

View File

@ -30,7 +30,6 @@ import (
apiutil "github.com/cert-manager/cert-manager/pkg/api/util"
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
"github.com/cert-manager/cert-manager/pkg/controller/certificates"
"github.com/cert-manager/cert-manager/pkg/util/pki"
"github.com/cert-manager/cert-manager/test/unit/gen"
)
@ -179,7 +178,7 @@ func CreateCryptoBundle(originalCert *cmapi.Certificate, clock clock.Clock) (*Cr
}),
)
tempCertBytes, err := certificates.GenerateLocallySignedTemporaryCertificate(crt, privateKeyBytes)
tempCertBytes, err := pki.GenerateLocallySignedTemporaryCertificate(crt, privateKeyBytes)
if err != nil {
panic("failed to generate test fixture: " + err.Error())
}