Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix: include all CA certificates in encoded pkcs12/jks stores #6806

Merged
merged 2 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 34 additions & 26 deletions pkg/controller/certificates/issuing/internal/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ package internal
import (
"bytes"
"crypto/x509"
"fmt"
"time"

jks "github.com/pavlo-v-chernykh/keystore-go/v4"
Expand Down Expand Up @@ -74,13 +75,11 @@ func encodePKCS12Keystore(profile cmapi.PKCS12Profile, password string, rawKey [
}

func encodePKCS12Truststore(profile cmapi.PKCS12Profile, password string, caPem []byte) ([]byte, error) {
ca, err := pki.DecodeX509CertificateBytes(caPem)
cas, err := pki.DecodeX509CertificateChainBytes(caPem)
inteon marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

var cas = []*x509.Certificate{ca}

switch profile {
case cmapi.Modern2023PKCS12Profile:
return pkcs12.Modern2023.EncodeTrustStore(cas, password)
Expand Down Expand Up @@ -118,25 +117,19 @@ func encodeJKSKeystore(password []byte, rawKey []byte, certPem []byte, caPem []b
}

ks := jks.New()
ks.SetPrivateKeyEntry("certificate", jks.PrivateKeyEntry{
if err = ks.SetPrivateKeyEntry("certificate", jks.PrivateKeyEntry{
CreationTime: time.Now(),
PrivateKey: keyDER,
CertificateChain: certs,
}, password)
}, password); err != nil {
return nil, err
}

// add the CA certificate, if set
if len(caPem) > 0 {
ca, err := pki.DecodeX509CertificateBytes(caPem)
if err != nil {
if err := addCAsToJKSStore(&ks, caPem); err != nil {
return nil, err
}
ks.SetTrustedCertificateEntry("ca", jks.TrustedCertificateEntry{
CreationTime: time.Now(),
Certificate: jks.Certificate{
Type: "X509",
Content: ca.Raw,
}},
)
}

buf := &bytes.Buffer{}
Expand All @@ -147,23 +140,38 @@ func encodeJKSKeystore(password []byte, rawKey []byte, certPem []byte, caPem []b
}

func encodeJKSTruststore(password []byte, caPem []byte) ([]byte, error) {
ca, err := pki.DecodeX509CertificateBytes(caPem)
if err != nil {
ks := jks.New()
if err := addCAsToJKSStore(&ks, caPem); err != nil {
return nil, err
}

ks := jks.New()
ks.SetTrustedCertificateEntry("ca", jks.TrustedCertificateEntry{
CreationTime: time.Now(),
Certificate: jks.Certificate{
Type: "X509",
Content: ca.Raw,
}},
)

buf := &bytes.Buffer{}
if err := ks.Store(buf, password); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

func addCAsToJKSStore(ks *jks.KeyStore, caPem []byte) error {
cas, err := pki.DecodeX509CertificateChainBytes(caPem)
if err != nil {
return err
}

creationTime := time.Now()
for i, ca := range cas {
alias := fmt.Sprintf("ca-%d", i)
if i == 0 {
alias = "ca"
}
if err = ks.SetTrustedCertificateEntry(alias, jks.TrustedCertificateEntry{
CreationTime: creationTime,
Certificate: jks.Certificate{
Type: "X509",
Content: ca.Raw,
}},
); err != nil {
return err
}
}
return nil
}
128 changes: 127 additions & 1 deletion pkg/controller/certificates/issuing/internal/keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ func mustSelfSignCertificate(t *testing.T, pkBytes []byte) []byte {
return certBytes
}

func mustSelfSignCertificates(t *testing.T, count int) []byte {
var buf bytes.Buffer
for i := 0; i < count; i++ {
buf.Write(mustSelfSignCertificate(t, nil))
}
return buf.Bytes()
}

type keyAndCert struct {
key crypto.Signer
keyPEM []byte
Expand Down Expand Up @@ -226,6 +234,39 @@ func TestEncodeJKSKeystore(t *testing.T) {
}
},
},
"encode a JKS bundle for a key, certificate and multiple cas": {
password: "password",
rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8),
certPEM: mustSelfSignCertificate(t, nil),
caPEM: mustSelfSignCertificates(t, 3),
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 := jks.New()
err = ks.Load(buf, []byte("password"))
if err != nil {
t.Errorf("error decoding keystore: %v", err)
return
}
if !ks.IsPrivateKeyEntry("certificate") {
t.Errorf("no certificate data found in keystore")
}
if !ks.IsTrustedCertificateEntry("ca") {
t.Errorf("no ca data found in truststore")
}
if !ks.IsTrustedCertificateEntry("ca-1") {
t.Errorf("no ca data found in truststore")
}
if !ks.IsTrustedCertificateEntry("ca-2") {
t.Errorf("no ca data found in truststore")
}
if len(ks.Aliases()) != 4 {
t.Errorf("expected 4 aliases in keystore, got %d", len(ks.Aliases()))
}
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
Expand All @@ -235,6 +276,71 @@ func TestEncodeJKSKeystore(t *testing.T) {
}
}

func TestEncodeJKSTruststore(t *testing.T) {
tests := map[string]struct {
password string
caCount int
verify func(t *testing.T, out []byte, err error)
}{
"encode a JKS truststore for a single ca": {
password: "password",
caCount: 1,
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 := jks.New()
err = ks.Load(buf, []byte("password"))
if err != nil {
t.Errorf("error decoding keystore: %v", err)
return
}
if !ks.IsTrustedCertificateEntry("ca") {
t.Errorf("no ca data found in truststore")
}
if len(ks.Aliases()) != 1 {
t.Errorf("expected 1 alias in keystore, got %d", len(ks.Aliases()))
}
},
},
"encode a JKS truststore for multiple cas": {
password: "password",
caCount: 3,
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 := jks.New()
err = ks.Load(buf, []byte("password"))
if err != nil {
t.Errorf("error decoding keystore: %v", err)
return
}
if !ks.IsTrustedCertificateEntry("ca") {
t.Errorf("no ca data found in truststore")
}
if !ks.IsTrustedCertificateEntry("ca-1") {
t.Errorf("no ca data found in truststore")
}
if !ks.IsTrustedCertificateEntry("ca-2") {
t.Errorf("no ca data found in truststore")
}
if len(ks.Aliases()) != 3 {
t.Errorf("expected 3 aliases in keystore, got %d", len(ks.Aliases()))
}
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
out, err := encodeJKSTruststore([]byte(test.password), mustSelfSignCertificates(t, test.caCount))
test.verify(t, out, err)
})
}
}

func TestEncodePKCS12Keystore(t *testing.T) {
tests := map[string]struct {
password string
Expand Down Expand Up @@ -370,7 +476,7 @@ func TestEncodePKCS12Truststore(t *testing.T) {
}{
"encode a PKCS12 bundle for a CA": {
password: "password",
caPEM: mustSelfSignCertificate(t, nil),
caPEM: mustSelfSignCertificates(t, 1),
verify: func(t *testing.T, caPEM []byte, out []byte, err error) {
if err != nil {
t.Errorf("expected no error but got: %v", err)
Expand All @@ -390,6 +496,26 @@ func TestEncodePKCS12Truststore(t *testing.T) {
}
},
},
"encode a PKCS12 bundle for multiple CAs": {
password: "password",
caPEM: mustSelfSignCertificates(t, 3),
verify: func(t *testing.T, caPEM []byte, out []byte, err error) {
if err != nil {
t.Errorf("expected no error but got: %v", err)
}
certs, err := pkcs12.DecodeTrustStore(out, "password")
if err != nil {
t.Errorf("error decoding truststore: %v", err)
return
}
if certs == nil {
t.Errorf("no certificates found in truststore")
}
if len(certs) != 3 {
t.Errorf("Trusted CA certificates should include 3 entries, got %d", len(certs))
}
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
Expand Down