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:
parent
1038ca4494
commit
23de5240e9
@ -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
|
||||
}
|
||||
|
||||
@ -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()))
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
41
pkg/util/pki/basicconstraints.go
Normal file
41
pkg/util/pki/basicconstraints.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
59
pkg/util/pki/renewaltime.go
Normal file
59
pkg/util/pki/renewaltime.go
Normal 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
|
||||
}
|
||||
93
pkg/util/pki/renewaltime_test.go
Normal file
93
pkg/util/pki/renewaltime_test.go
Normal 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
114
pkg/util/pki/subject.go
Normal 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)
|
||||
}
|
||||
68
pkg/util/pki/temporarycertificate.go
Normal file
68
pkg/util/pki/temporarycertificate.go
Normal 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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user