Skip to content

Commit 065dd61

Browse files
committed
Revert "Use PKCS#12 encoder to encode JKS"
Signed-off-by: Erik Godding Boye <[email protected]>
1 parent 1607be3 commit 065dd61

File tree

8 files changed

+96
-24
lines changed

8 files changed

+96
-24
lines changed

LICENSES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ github.com/monochromegane/go-gitignore,https://github.com/monochromegane/go-giti
3131
github.com/munnerz/goautoneg,https://github.com/munnerz/goautoneg/blob/a7dc8b61c822/LICENSE,BSD-3-Clause
3232
github.com/onsi/ginkgo/v2,https://github.com/onsi/ginkgo/blob/v2.23.4/LICENSE,MIT
3333
github.com/onsi/gomega,https://github.com/onsi/gomega/blob/v1.37.0/LICENSE,MIT
34+
github.com/pavlo-v-chernykh/keystore-go/v4,https://github.com/pavlo-v-chernykh/keystore-go/blob/v4.5.0/LICENSE,MIT
3435
github.com/peterbourgon/diskv,https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE,MIT
3536
github.com/pkg/errors,https://github.com/pkg/errors/blob/v0.9.1/LICENSE,BSD-2-Clause
3637
github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,https://github.com/prometheus/client_golang/blob/v1.22.0/internal/github.com/golang/gddo/LICENSE,BSD-3-Clause

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/google/go-cmp v0.7.0
88
github.com/onsi/ginkgo/v2 v2.23.4
99
github.com/onsi/gomega v1.37.0
10+
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
1011
github.com/spf13/cobra v1.9.1
1112
github.com/spf13/pflag v1.0.6
1213
github.com/stretchr/testify v1.10.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus
9494
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
9595
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
9696
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
97+
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ=
98+
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ=
9799
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
98100
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
99101
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

pkg/bundle/bundle_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ func testEncodeJKS(t *testing.T, data string) []byte {
5555
t.Fatal(err)
5656
}
5757

58-
// Note: Must use the PKCS12 default password here, as encoding with (non-empty) password is non-deterministic.
59-
encoded, err := truststore.NewJKSEncoder(trustapi.DefaultPKCS12Password).Encode(certPool)
58+
encoded, err := truststore.NewJKSEncoder(trustapi.DefaultJKSPassword).Encode(certPool)
6059
if err != nil {
6160
t.Error(err)
6261
}
@@ -145,8 +144,7 @@ func Test_Reconcile(t *testing.T) {
145144
KeySelector: trustapi.KeySelector{
146145
Key: "target.jks",
147146
},
148-
// Note: Must use the PKCS12 default password here, as encoding with (non-empty) password is non-deterministic.
149-
Password: ptr.To(trustapi.DefaultPKCS12Password),
147+
Password: ptr.To(trustapi.DefaultJKSPassword),
150148
},
151149
}
152150
jksDefaultAdditionalFormatsOldPassword = trustapi.AdditionalFormats{

pkg/bundle/internal/truststore/types.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ limitations under the License.
1717
package truststore
1818

1919
import (
20+
"bytes"
2021
"crypto/sha256"
2122
"encoding/hex"
23+
"fmt"
2224

25+
"github.com/pavlo-v-chernykh/keystore-go/v4"
2326
"software.sslmate.com/src/go-pkcs12"
2427

2528
"github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
@@ -31,7 +34,52 @@ type Encoder interface {
3134
}
3235

3336
func NewJKSEncoder(password string) Encoder {
34-
return NewPKCS12Encoder(password, v1alpha1.LegacyRC2PKCS12Profile)
37+
return &jksEncoder{password: password}
38+
}
39+
40+
type jksEncoder struct {
41+
password string
42+
}
43+
44+
// Encode creates a binary JKS file from the given PEM-encoded trust bundle and Password.
45+
// Note that the Password is not treated securely; JKS files generally seem to expect a Password
46+
// to exist and so we have the option for one.
47+
func (e jksEncoder) Encode(trustBundle *util.CertPool) ([]byte, error) {
48+
// WithOrderedAliases ensures that trusted certs are added to the JKS file in order,
49+
// which makes the files appear to be reliably deterministic.
50+
ks := keystore.New(keystore.WithOrderedAliases())
51+
52+
for _, c := range trustBundle.Certificates() {
53+
alias := certAlias(c.Raw, c.Subject.String())
54+
55+
// Note on CreationTime:
56+
// Debian's JKS trust store sets the creation time to match the time that certs are added to the
57+
// trust store (i.e., it's effectively time.Now() at the instant the file is generated).
58+
// Using that method would make our JKS files in trust-manager non-deterministic, leaving us with
59+
// two options if we want to maintain determinism:
60+
// - Using something from the cert being added (e.g. NotBefore / NotAfter)
61+
// - Using a fixed time (i.e. unix epoch)
62+
// We use NotBefore here, arbitrarily.
63+
64+
if err := ks.SetTrustedCertificateEntry(alias, keystore.TrustedCertificateEntry{
65+
CreationTime: c.NotBefore,
66+
Certificate: keystore.Certificate{
67+
Type: "X509",
68+
Content: c.Raw,
69+
},
70+
}); err != nil {
71+
// this error should never happen if we set jks.Certificate correctly
72+
return nil, fmt.Errorf("failed to add cert with alias %q to trust store: %w", alias, err)
73+
}
74+
}
75+
76+
buf := &bytes.Buffer{}
77+
78+
if err := ks.Store(buf, []byte(e.password)); err != nil {
79+
return nil, fmt.Errorf("failed to create JKS file: %w", err)
80+
}
81+
82+
return buf.Bytes(), nil
3583
}
3684

3785
func NewPKCS12Encoder(password string, profile v1alpha1.PKCS12Profile) Encoder {

pkg/bundle/internal/truststore/types_test.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ limitations under the License.
1717
package truststore
1818

1919
import (
20+
"bytes"
2021
"crypto/x509"
2122
"encoding/pem"
2223
"testing"
2324

25+
"github.com/pavlo-v-chernykh/keystore-go/v4"
2426
"github.com/stretchr/testify/assert"
25-
"software.sslmate.com/src/go-pkcs12"
2627

2728
"github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
2829
"github.com/cert-manager/trust-manager/pkg/util"
@@ -35,12 +36,10 @@ func Test_Encoder_Deterministic(t *testing.T) {
3536
expNonDeterministic bool
3637
}{
3738
"JKS default password": {
38-
encoder: NewJKSEncoder(v1alpha1.DefaultJKSPassword),
39-
expNonDeterministic: true,
39+
encoder: NewJKSEncoder(v1alpha1.DefaultJKSPassword),
4040
},
4141
"JKS custom password": {
42-
encoder: NewJKSEncoder("my-password"),
43-
expNonDeterministic: true,
42+
encoder: NewJKSEncoder("my-password"),
4443
},
4544
"PKCS#12 default password": {
4645
encoder: NewPKCS12Encoder(v1alpha1.DefaultPKCS12Password, ""),
@@ -105,13 +104,19 @@ func Test_encodeJKSAliases(t *testing.T) {
105104
t.Fatalf("didn't expect an error but got: %s", err)
106105
}
107106

108-
certs, err := pkcs12.DecodeTrustStore(jksFile, v1alpha1.DefaultJKSPassword)
107+
reader := bytes.NewReader(jksFile)
108+
109+
ks := keystore.New()
110+
111+
err = ks.Load(reader, []byte(v1alpha1.DefaultJKSPassword))
109112
if err != nil {
110113
t.Fatalf("failed to parse generated JKS file: %s", err)
111114
}
112115

113-
if len(certs) != 2 {
114-
t.Fatalf("expected two certs in JKS file but got %d", len(certs))
116+
entryNames := ks.Aliases()
117+
118+
if len(entryNames) != 2 {
119+
t.Fatalf("expected two certs in JKS file but got %d", len(entryNames))
115120
}
116121
}
117122

pkg/bundle/source_test.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ limitations under the License.
1717
package bundle
1818

1919
import (
20+
"bytes"
2021
"encoding/pem"
2122
"errors"
2223
"strings"
2324
"testing"
2425

26+
jks "github.com/pavlo-v-chernykh/keystore-go/v4"
2527
"github.com/stretchr/testify/assert"
2628
corev1 "k8s.io/api/core/v1"
2729
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -488,14 +490,24 @@ func Test_buildSourceBundle(t *testing.T) {
488490
assert.Equal(t, test.expJKS, jksExists)
489491

490492
if test.expJKS {
491-
certs, err := pkcs12.DecodeTrustStore(binData, password)
493+
reader := bytes.NewReader(binData)
494+
495+
ks := jks.New()
496+
497+
err := ks.Load(reader, []byte(password))
492498
assert.Nil(t, err)
493499

494-
assert.Len(t, certs, 1)
500+
entryNames := ks.Aliases()
501+
502+
assert.Len(t, entryNames, 1)
503+
assert.True(t, ks.IsTrustedCertificateEntry(entryNames[0]))
504+
505+
// Safe to ignore errors here, we've tested that it's present and a TrustedCertificateEntry
506+
cert, _ := ks.GetTrustedCertificateEntry(entryNames[0])
495507

496508
// Only one certificate block for this test, so we can safely ignore the `remaining` byte array
497509
p, _ := pem.Decode([]byte(data))
498-
assert.Equal(t, p.Bytes, certs[0].Raw)
510+
assert.Equal(t, p.Bytes, cert.Certificate.Content)
499511
}
500512

501513
binData, pkcs12Exists := resolvedBundle.Data.BinaryData[pkcs12Key]

test/env/data.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import (
2323
"fmt"
2424
"strings"
2525

26+
jks "github.com/pavlo-v-chernykh/keystore-go/v4"
2627
corev1 "k8s.io/api/core/v1"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2930
"sigs.k8s.io/controller-runtime/pkg/client"
30-
"software.sslmate.com/src/go-pkcs12"
3131

3232
trustapi "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
3333
bundlectrl "github.com/cert-manager/trust-manager/pkg/bundle"
@@ -345,24 +345,29 @@ func EventuallyBundleHasSyncedAllNamespacesContains(ctx context.Context, cl clie
345345

346346
// CheckJKSFileSynced ensures that the given JKS data
347347
func CheckJKSFileSynced(jksData []byte, expectedPassword string, expectedCertPEMData string) error {
348+
reader := bytes.NewReader(jksData)
348349
certPool := util.NewCertPool(util.WithFilteredExpiredCerts(false))
349-
if err := certPool.AddCertsFromPEM([]byte(expectedCertPEMData)); err != nil {
350-
return fmt.Errorf("invalid PEM data passed to CheckJKSFileSynced: %s", err)
351-
}
352350

353-
certs, err := pkcs12.DecodeTrustStore(jksData, expectedPassword)
351+
ks := jks.New()
352+
353+
err := ks.Load(reader, []byte(expectedPassword))
354354
if err != nil {
355355
return err
356356
}
357357

358+
err = certPool.AddCertsFromPEM([]byte(expectedCertPEMData))
359+
if err != nil {
360+
return fmt.Errorf("invalid PEM data passed to CheckJKSFileSynced: %s", err)
361+
}
362+
358363
// TODO: check that the cert content matches expectedCertPEMData exactly, not just
359364
// that the count is the same
360365

361-
certCount := len(certs)
366+
aliasCount := len(ks.Aliases())
362367
expectedPEMCount := certPool.Size()
363368

364-
if certCount != expectedPEMCount {
365-
return fmt.Errorf("expected %d certificates in JKS but found %d", expectedPEMCount, certCount)
369+
if aliasCount != expectedPEMCount {
370+
return fmt.Errorf("expected %d certificates in JKS but found %d", expectedPEMCount, aliasCount)
366371
}
367372

368373
return nil

0 commit comments

Comments
 (0)