Skip to content

Commit

Permalink
Merge pull request #3306 from jetstack-bot/cherry-pick-3279-to-releas…
Browse files Browse the repository at this point in the history
…e-1.0

[release-1.0] Fix double "signing" KU validation
  • Loading branch information
jetstack-bot committed Sep 22, 2020
2 parents cafefa0 + 772d3cc commit 219b793
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 4 deletions.
2 changes: 2 additions & 0 deletions pkg/internal/apis/certmanager/validation/BUILD.bazel
Expand Up @@ -34,6 +34,7 @@ go_test(
srcs = [
"certificate_for_issuer_test.go",
"certificate_test.go",
"certificaterequest_test.go",
"issuer_test.go",
],
embed = [":go_default_library"],
Expand All @@ -42,6 +43,7 @@ go_test(
"//pkg/internal/apis/acme:go_default_library",
"//pkg/internal/apis/certmanager:go_default_library",
"//pkg/internal/apis/meta:go_default_library",
"//pkg/util/pki:go_default_library",
"//test/unit/gen:go_default_library",
"@io_k8s_api//core/v1:go_default_library",
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
Expand Down
32 changes: 28 additions & 4 deletions pkg/internal/apis/certmanager/validation/certificaterequest.go
Expand Up @@ -63,6 +63,9 @@ func ValidateCertificateRequestSpec(crSpec *cmapi.CertificateRequestSpec, fldPat
} else {
// only compare usages if set on CR and in the CSR
if len(crSpec.Usages) > 0 && len(csr.Extensions) > 0 && validateCSRContent && !reflect.DeepEqual(crSpec.Usages, defaultInternalKeyUsages) {
if crSpec.IsCA {
crSpec.Usages = ensureCertSignIsSet(crSpec.Usages)
}
csrUsages, err := getCSRKeyUsage(crSpec, fldPath, csr, el)
if len(err) > 0 {
el = append(el, err...)
Expand Down Expand Up @@ -123,13 +126,21 @@ func getCSRKeyUsage(crSpec *cmapi.CertificateRequestSpec, fldPath *field.Path, c

func patchDuplicateKeyUsage(usages []cmapi.KeyUsage) []cmapi.KeyUsage {
// usage signing and digital signature are the same key use in x509
for i, usage := range usages {
if usage == cmapi.UsageSigning {
usages[i] = cmapi.UsageDigitalSignature
// we should patch this for proper validation

newUsages := []cmapi.KeyUsage(nil)
hasUsageSigning := false
for _, usage := range usages {
if (usage == cmapi.UsageSigning || usage == cmapi.UsageDigitalSignature) && !hasUsageSigning {
newUsages = append(newUsages, cmapi.UsageDigitalSignature)
// prevent having 2 UsageDigitalSignature in the slice
hasUsageSigning = true
} else {
newUsages = append(newUsages, usage)
}
}

return usages
return newUsages
}

func isUsageEqual(a, b []cmapi.KeyUsage) bool {
Expand All @@ -148,3 +159,16 @@ func isUsageEqual(a, b []cmapi.KeyUsage) bool {

return util.EqualUnsorted(aStrings, bStrings)
}

// ensureCertSignIsSet adds UsageCertSign in case it is not set
// TODO: add a mutating webhook to make sure this is always set
// when isCA is true.
func ensureCertSignIsSet(list []cmapi.KeyUsage) []cmapi.KeyUsage {
for _, usage := range list {
if usage == cmapi.UsageCertSign {
return list
}
}

return append(list, cmapi.UsageCertSign)
}
158 changes: 158 additions & 0 deletions pkg/internal/apis/certmanager/validation/certificaterequest_test.go
@@ -0,0 +1,158 @@
/*
Copyright 2020 The Jetstack cert-manager contributors.
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 validation

import (
"bytes"
"encoding/pem"
"reflect"
"testing"

"k8s.io/apimachinery/pkg/util/validation/field"

cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
cminternal "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager"
"github.com/jetstack/cert-manager/pkg/util/pki"
utilpki "github.com/jetstack/cert-manager/pkg/util/pki"
"github.com/jetstack/cert-manager/test/unit/gen"
)

func TestValidateCertificateRequestSpec(t *testing.T) {
fldPath := field.NewPath("test")

tests := []struct {
name string
crSpec *cminternal.CertificateRequestSpec
want field.ErrorList
}{
{
name: "Test csr with no usages",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"))),
IssuerRef: validIssuerRef,
Usages: nil,
},
want: []*field.Error{},
},
{
name: "Test csr with double signature usages",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageSigning, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment))),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageSigning, cminternal.UsageKeyEncipherment},
},
want: []*field.Error{},
},
{
name: "Test csr with double extended usages",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageServerAuth, cmapi.UsageClientAuth))),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageSigning, cminternal.UsageKeyEncipherment, cminternal.UsageServerAuth, cminternal.UsageClientAuth},
},
want: []*field.Error{},
},
{
name: "Test csr with reordered usages",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageServerAuth, cmapi.UsageClientAuth))),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageServerAuth, cminternal.UsageClientAuth, cminternal.UsageKeyEncipherment, cminternal.UsageDigitalSignature},
},
want: []*field.Error{},
},
{
name: "Test csr that is CA with usages set",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageAny, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageCertSign), gen.SetCertificateIsCA(true))),
IssuerRef: validIssuerRef,
IsCA: true,
Usages: []cminternal.KeyUsage{cminternal.UsageAny, cminternal.UsageDigitalSignature, cminternal.UsageKeyEncipherment, cminternal.UsageCertSign},
},
want: []*field.Error{},
},
{
name: "Test csr that is CA but no cert sign in usages",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageAny, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageClientAuth, cmapi.UsageServerAuth), gen.SetCertificateIsCA(true))),
IssuerRef: validIssuerRef,
IsCA: true,
Usages: []cminternal.KeyUsage{cminternal.UsageAny, cminternal.UsageDigitalSignature, cminternal.UsageKeyEncipherment, cminternal.UsageClientAuth, cminternal.UsageServerAuth},
},
want: []*field.Error{},
},
{
name: "Error on csr not having all usages",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageServerAuth))),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageSigning, cminternal.UsageKeyEncipherment, cminternal.UsageServerAuth, cminternal.UsageClientAuth},
},
want: []*field.Error{
field.Invalid(fldPath.Child("request"), nil, "csr key usages do not match specified usages, these should match if both are set: [[]certmanager.KeyUsage[3] != []certmanager.KeyUsage[4]]"),
},
},
{
name: "Error on cr not having all usages",
crSpec: &cminternal.CertificateRequestSpec{
Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageServerAuth, cmapi.UsageClientAuth))),
IssuerRef: validIssuerRef,
Usages: []cminternal.KeyUsage{cminternal.UsageSigning, cminternal.UsageKeyEncipherment},
},
want: []*field.Error{
field.Invalid(fldPath.Child("request"), nil, "csr key usages do not match specified usages, these should match if both are set: [[]certmanager.KeyUsage[4] != []certmanager.KeyUsage[2]]"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ValidateCertificateRequestSpec(tt.crSpec, field.NewPath("test"), true)
for i := range got {
// filter out the value so it does not print the full CSR in tests
got[i].BadValue = nil
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ValidateCertificateRequestSpec() = %v, want %v", got, tt.want)
}
})
}
}

func mustGenerateCSR(t *testing.T, crt *cmapi.Certificate) []byte {
// Create a new private key
pk, err := utilpki.GenerateRSAPrivateKey(2048)
if err != nil {
t.Fatal(err)
}

x509CSR, err := pki.GenerateCSR(crt)
if err != nil {
t.Fatal(err)
}
csrDER, err := pki.EncodeCSR(x509CSR, pk)
if err != nil {
t.Fatal(err)
}

csrPEM := bytes.NewBuffer([]byte{})
err = pem.Encode(csrPEM, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER})
if err != nil {
t.Fatal(err)
}

return csrPEM.Bytes()
}
10 changes: 10 additions & 0 deletions pkg/util/pki/csr_test.go
Expand Up @@ -437,6 +437,16 @@ func TestGenerateCSR(t *testing.T) {
ExtraExtensions: ipsecExtraExtensions,
},
},
{
name: "Generate CSR from certificate with double signing key usages",
crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.org", Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageSigning}}},
want: &x509.CertificateRequest{Version: 3,
SignatureAlgorithm: x509.SHA256WithRSA,
PublicKeyAlgorithm: x509.RSA,
Subject: pkix.Name{CommonName: "example.org"},
ExtraExtensions: defaultExtraExtensions,
},
},
{
name: "Error on generating CSR from certificate with no subject",
crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{}},
Expand Down

0 comments on commit 219b793

Please sign in to comment.