Fix JKS keystore functionality and add additional tests
Signed-off-by: James Munnelly <james@munnelly.eu>
This commit is contained in:
parent
6d689cefd6
commit
acff2b12bb
@ -231,7 +231,11 @@ func buildControllerContext(ctx context.Context, stopCh <-chan struct{}, opts *o
|
||||
DefaultAutoCertificateAnnotations: opts.DefaultAutoCertificateAnnotations,
|
||||
},
|
||||
CertificateOptions: controller.CertificateOptions{
|
||||
EnableOwnerRef: opts.EnableCertificateOwnerRef,
|
||||
EnableOwnerRef: opts.EnableCertificateOwnerRef,
|
||||
ExperimentalIssuePKCS12: opts.ExperimentalIssuePKCS12,
|
||||
ExperimentalPKCS12KeystorePassword: opts.ExperimentalPKCS12KeystorePassword,
|
||||
ExperimentalIssueJKS: opts.ExperimentalIssueJKS,
|
||||
ExperimentalJKSPassword: opts.ExperimentalJKSPassword,
|
||||
},
|
||||
SchedulerOptions: controller.SchedulerOptions{
|
||||
MaxConcurrentChallenges: opts.MaxConcurrentChallenges,
|
||||
|
||||
@ -378,7 +378,7 @@ func TestSync(t *testing.T) {
|
||||
gen.SetCertificateRequestCertificate([]byte("a bad certificate")),
|
||||
)},
|
||||
ExpectedEvents: []string{
|
||||
"Warning DecodeError Failed to decode returned certificate: error decoding cert PEM block",
|
||||
"Warning DecodeError Failed to decode returned certificate: error decoding certificate PEM block",
|
||||
},
|
||||
ExpectedActions: []testpkg.Action{
|
||||
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
||||
@ -391,7 +391,7 @@ func TestSync(t *testing.T) {
|
||||
Type: cmapi.CertificateRequestConditionReady,
|
||||
Status: cmmeta.ConditionFalse,
|
||||
Reason: "Failed",
|
||||
Message: "Failed to decode returned certificate: error decoding cert PEM block",
|
||||
Message: "Failed to decode returned certificate: error decoding certificate PEM block",
|
||||
LastTransitionTime: &nowMetaTime,
|
||||
}),
|
||||
gen.SetCertificateRequestFailureTime(nowMetaTime),
|
||||
|
||||
@ -46,6 +46,7 @@ go_library(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"keystore_test.go",
|
||||
"sync_test.go",
|
||||
"util_test.go",
|
||||
],
|
||||
@ -59,6 +60,8 @@ go_test(
|
||||
"//pkg/util:go_default_library",
|
||||
"//pkg/util/pki:go_default_library",
|
||||
"//test/unit/gen:go_default_library",
|
||||
"@com_github_pavel_v_chernykh_keystore_go//:go_default_library",
|
||||
"@com_sslmate_software_src_go_pkcs12//:go_default_library",
|
||||
"@io_k8s_api//core/v1:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/runtime:go_default_library",
|
||||
|
||||
@ -173,7 +173,7 @@ func (c *certificateRequestManager) Register(ctx *controllerpkg.Context) (workqu
|
||||
c.experimentalIssueJKS = ctx.CertificateOptions.ExperimentalIssueJKS
|
||||
c.experimentalJKSPassword = ctx.CertificateOptions.ExperimentalJKSPassword
|
||||
if c.experimentalIssueJKS && len(c.experimentalJKSPassword) == 0 {
|
||||
return nil, nil, nil, fmt.Errorf("if experimental pkcs12 issuance is enabled, a keystore password must be provided")
|
||||
return nil, nil, nil, fmt.Errorf("if experimental jks issuance is enabled, a keystore password must be provided")
|
||||
}
|
||||
|
||||
c.cmClient = ctx.CMClient
|
||||
|
||||
@ -102,7 +102,7 @@ func encodeJKSKeystore(password string, rawKey []byte, certPem []byte, caPem []b
|
||||
}
|
||||
|
||||
ks := jks.KeyStore{
|
||||
"certificate": jks.PrivateKeyEntry{
|
||||
"certificate": &jks.PrivateKeyEntry{
|
||||
Entry: jks.Entry{
|
||||
CreationDate: time.Now(),
|
||||
},
|
||||
@ -117,7 +117,7 @@ func encodeJKSKeystore(password string, rawKey []byte, certPem []byte, caPem []b
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ks["ca"] = jks.TrustedCertificateEntry{
|
||||
ks["ca"] = &jks.TrustedCertificateEntry{
|
||||
Entry: jks.Entry{
|
||||
CreationDate: time.Now(),
|
||||
},
|
||||
|
||||
225
pkg/controller/certificates/keystore_test.go
Normal file
225
pkg/controller/certificates/keystore_test.go
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
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 certificates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
jks "github.com/pavel-v-chernykh/keystore-go"
|
||||
"software.sslmate.com/src/go-pkcs12"
|
||||
|
||||
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
|
||||
"github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
)
|
||||
|
||||
func mustGeneratePrivateKey(t *testing.T, encoding cmapi.KeyEncoding) []byte {
|
||||
pk, err := pki.GenerateRSAPrivateKey(2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkBytes, err := pki.EncodePrivateKey(pk, encoding)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return pkBytes
|
||||
}
|
||||
|
||||
func mustSelfSignCertificate(t *testing.T, pkBytes []byte) []byte {
|
||||
if pkBytes == nil {
|
||||
pkBytes = mustGeneratePrivateKey(t, cmapi.PKCS8)
|
||||
}
|
||||
pk, err := pki.DecodePrivateKeyBytes(pkBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
x509Crt, err := pki.GenerateTemplate(&cmapi.Certificate{
|
||||
Spec: cmapi.CertificateSpec{
|
||||
DNSNames: []string{"example.com"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certBytes, _, err := pki.SignCertificate(x509Crt, x509Crt, pk.Public(), pk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return certBytes
|
||||
}
|
||||
|
||||
func TestEncodeJKSKeystore(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
password string
|
||||
rawKey, certPEM, caPEM []byte
|
||||
verify func(t *testing.T, out []byte, err error)
|
||||
}{
|
||||
"encode a JKS bundle for a PKCS1 key and certificate only": {
|
||||
password: "password",
|
||||
rawKey: mustGeneratePrivateKey(t, cmapi.PKCS1),
|
||||
certPEM: mustSelfSignCertificate(t, nil),
|
||||
verify: func(t *testing.T, out []byte, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
return
|
||||
}
|
||||
buf := bytes.NewBuffer(out)
|
||||
ks, err := jks.Decode(buf, []byte("password"))
|
||||
if err != nil {
|
||||
t.Errorf("error decoding keystore: %v", err)
|
||||
return
|
||||
}
|
||||
if ks["certificate"] == nil {
|
||||
t.Errorf("no certificate data found in keystore")
|
||||
}
|
||||
if ks["ca"] != nil {
|
||||
t.Errorf("unexpected ca data found in keystore")
|
||||
}
|
||||
},
|
||||
},
|
||||
"encode a JKS bundle for a PKCS8 key and certificate only": {
|
||||
password: "password",
|
||||
rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8),
|
||||
certPEM: mustSelfSignCertificate(t, nil),
|
||||
verify: func(t *testing.T, out []byte, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
buf := bytes.NewBuffer(out)
|
||||
ks, err := jks.Decode(buf, []byte("password"))
|
||||
if err != nil {
|
||||
t.Errorf("error decoding keystore: %v", err)
|
||||
return
|
||||
}
|
||||
if ks["certificate"] == nil {
|
||||
t.Errorf("no certificate data found in keystore")
|
||||
}
|
||||
if ks["ca"] != nil {
|
||||
t.Errorf("unexpected ca data found in keystore")
|
||||
}
|
||||
},
|
||||
},
|
||||
"encode a JKS bundle for a key, certificate and ca": {
|
||||
password: "password",
|
||||
rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8),
|
||||
certPEM: mustSelfSignCertificate(t, nil),
|
||||
caPEM: mustSelfSignCertificate(t, nil),
|
||||
verify: func(t *testing.T, out []byte, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
buf := bytes.NewBuffer(out)
|
||||
ks, err := jks.Decode(buf, []byte("password"))
|
||||
if err != nil {
|
||||
t.Errorf("error decoding keystore: %v", err)
|
||||
return
|
||||
}
|
||||
if ks["certificate"] == nil {
|
||||
t.Errorf("no certificate data found in keystore")
|
||||
}
|
||||
if ks["ca"] == nil {
|
||||
t.Errorf("no ca data found in keystore")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
out, err := encodeJKSKeystore(test.password, test.rawKey, test.certPEM, test.caPEM)
|
||||
test.verify(t, out, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodePKCS12Keystore(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
password string
|
||||
rawKey, certPEM, caPEM []byte
|
||||
verify func(t *testing.T, out []byte, err error)
|
||||
}{
|
||||
"encode a JKS bundle for a PKCS1 key and certificate only": {
|
||||
password: "password",
|
||||
rawKey: mustGeneratePrivateKey(t, cmapi.PKCS1),
|
||||
certPEM: mustSelfSignCertificate(t, nil),
|
||||
verify: func(t *testing.T, out []byte, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
pk, cert, err := pkcs12.Decode(out, "password")
|
||||
if err != nil {
|
||||
t.Errorf("error decoding keystore: %v", err)
|
||||
return
|
||||
}
|
||||
if cert == nil {
|
||||
t.Errorf("no certificate data found in keystore")
|
||||
}
|
||||
if pk == nil {
|
||||
t.Errorf("no ca data found in keystore")
|
||||
}
|
||||
},
|
||||
},
|
||||
"encode a JKS bundle for a PKCS8 key and certificate only": {
|
||||
password: "password",
|
||||
rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8),
|
||||
certPEM: mustSelfSignCertificate(t, nil),
|
||||
verify: func(t *testing.T, out []byte, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
pk, cert, err := pkcs12.Decode(out, "password")
|
||||
if err != nil {
|
||||
t.Errorf("error decoding keystore: %v", err)
|
||||
return
|
||||
}
|
||||
if cert == nil {
|
||||
t.Errorf("no certificate data found in keystore")
|
||||
}
|
||||
if pk == nil {
|
||||
t.Errorf("no ca data found in keystore")
|
||||
}
|
||||
},
|
||||
},
|
||||
"encode a JKS bundle for a key, certificate and ca": {
|
||||
password: "password",
|
||||
rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8),
|
||||
certPEM: mustSelfSignCertificate(t, nil),
|
||||
caPEM: mustSelfSignCertificate(t, nil),
|
||||
verify: func(t *testing.T, out []byte, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
// The pkcs12 package does not expose a way to decode the CA
|
||||
// data that has been written.
|
||||
// It will return an error when attempting to decode a file
|
||||
// with more than one 'certbag', so we just ensure the error
|
||||
// returned is the expected error and don't inspect the keystore
|
||||
// contents.
|
||||
_, _, err = pkcs12.Decode(out, "password")
|
||||
if err == nil || err.Error() != "pkcs12: expected exactly two safe bags in the PFX PDU" {
|
||||
t.Errorf("unexpected error string, exp=%q, got=%v", "pkcs12: expected exactly two safe bags in the PFX PDU", err)
|
||||
return
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
out, err := encodePKCS12Keystore(test.password, test.rawKey, test.certPEM, test.caPEM)
|
||||
test.verify(t, out, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ func DecodeX509CertificateChainBytes(certBytes []byte) ([]*x509.Certificate, err
|
||||
}
|
||||
|
||||
if len(certs) == 0 {
|
||||
return nil, errors.NewInvalidData("error decoding cert PEM block")
|
||||
return nil, errors.NewInvalidData("error decoding certificate PEM block")
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
|
||||
Loading…
Reference in New Issue
Block a user