Files
certigo/jceks/encoder_test.go
Nik 3053a19816 Add JCEKS integration tests (#350)
This commit adds tests for the `jceks` package against the
installed JDK's `keytool` CLI. The tests ensure that JCEKS files
created by `keytool` can be read by the package, and vice versa.
These tests are disabled by default and are enabled with the
`-jceks.keytool-tests=true` flag. This commit configures GitHub
Actions to test the package against a wide variety of JDKs.

This commit also restricts GitHub Actions permissions.
2025-08-07 17:33:08 -04:00

307 lines
7.8 KiB
Go

// Copyright 2025 Block, Inc.
// SPDX-License-Identifier: Apache-2.0
//
// 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 jceks
import (
"bytes"
"crypto/sha1"
"crypto/x509"
"errors"
"flag"
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
)
var writeReencoded = flag.Bool("jceks.write-reencoded", false, "write expected re-encoded JCEKS files")
type discardErrWriter struct {
err error
writeAfterErr int
}
func (dew *discardErrWriter) Write(p []byte) (int, error) {
if dew.err != nil {
dew.writeAfterErr++
return 1, dew.err
}
return len(p), nil
}
func TestCounterWriter(t *testing.T) {
t.Parallel()
var dew discardErrWriter
cw := &counterWriter{w: &dew}
nn, err := cw.Write([]byte("Hello "))
require.NoError(t, err)
require.Equal(t, 6, nn)
nn, err = cw.Write([]byte("World"))
require.NoError(t, err)
require.Equal(t, 5, nn)
expectErr := errors.New("expected error")
dew.err = expectErr
nn, err = cw.Write([]byte("discarded"))
require.NoError(t, err)
require.Equal(t, 9, nn)
nn, err = cw.Write([]byte("more"))
require.NoError(t, err)
require.Equal(t, 4, nn)
n, err := cw.Results()
require.Equal(t, expectErr, err)
require.Equal(t, int64(6+5+1), n)
require.Equal(t, 1, dew.writeAfterErr)
}
func TestJCEKSDigester(t *testing.T) {
t.Parallel()
jd := newJCEKSDigester([]byte("CHANGE"))
_, err := jd.Write([]byte("Hello, "))
require.NoError(t, err)
_, err = jd.Write([]byte("World!"))
require.NoError(t, err)
var buf bytes.Buffer
err = jd.WriteDigest(&buf)
require.NoError(t, err)
expected := sha1.Sum([]byte("CHANGEMighty AphroditeHello, World!"))
require.Equal(t, expected[:], buf.Bytes())
}
func TestSetIntegrityPassword(t *testing.T) {
t.Parallel()
var enc Encoder
require.False(t, enc.setIntegrityPassword)
err := enc.SetIntegrityPassword("")
require.Error(t, err)
require.ErrorIs(t, err, ErrInvalidPassword)
require.False(t, enc.setIntegrityPassword)
err = enc.SetIntegrityPassword("123456")
require.NoError(t, err)
require.True(t, enc.setIntegrityPassword)
require.Equal(t, []byte{0, '1', 0, '2', 0, '3', 0, '4', 0, '5', 0, '6'}, enc.integrityPassword)
}
func TestEncodePrivateKeyStore(t *testing.T) {
t.Parallel()
rnd := sha3.NewShake128()
now := time.Date(2025, 4, 24, 0, 0, 0, 0, time.UTC)
rsaPriv, err := LoadPEMKey("testdata/private-key.key")
require.NoError(t, err)
pkcs1 := x509.MarshalPKCS1PrivateKey(rsaPriv)
leafCert, err := LoadPEMCert("testdata/private-key.crt")
require.NoError(t, err)
caCert, err := LoadPEMCert("testdata/private-key-ca.crt")
require.NoError(t, err)
var enc Encoder
cipher, err := PBEWithMD5AndDES3CBC([]byte("changeit"), rnd, 20)
require.NoError(t, err)
err = enc.AddPrivateKeyPKCS1("example.test", now, pkcs1, [][]byte{leafCert.Raw, caCert.Raw}, cipher)
require.NoError(t, err)
err = enc.SetIntegrityPassword("changeit")
require.NoError(t, err)
var buf bytes.Buffer
n, err := enc.WriteTo(&buf)
require.NoError(t, err)
require.Equal(t, int64(buf.Len()), n)
if *writeReencoded {
err = os.WriteFile("testdata/encoder-private-store.jceks", buf.Bytes(), 0666)
require.NoError(t, err)
}
expected, err := os.ReadFile("testdata/encoder-private-store.jceks")
require.NoError(t, err)
require.Equal(t, expected, buf.Bytes())
}
func TestEncodeTrustedCertificateStore(t *testing.T) {
t.Parallel()
now := time.Date(2025, 4, 24, 0, 0, 0, 0, time.UTC)
cert, err := LoadPEMCert("testdata/trusted-cert.crt")
require.NoError(t, err)
var enc Encoder
err = enc.AddTrustedCertificate("ca1", now, cert.Raw)
require.NoError(t, err)
err = enc.SetIntegrityPassword("changeit")
require.NoError(t, err)
var buf bytes.Buffer
n, err := enc.WriteTo(&buf)
require.NoError(t, err)
require.Equal(t, int64(buf.Len()), n)
if *writeReencoded {
err = os.WriteFile("testdata/encoder-cert-store.jceks", buf.Bytes(), 0666)
require.NoError(t, err)
}
expected, err := os.ReadFile("testdata/encoder-cert-store.jceks")
require.NoError(t, err)
require.Equal(t, expected, buf.Bytes())
}
func TestEncodeExistingPrivateKeyStore(t *testing.T) {
t.Parallel()
ks, err := LoadFromFile("testdata/private-key.jceks", []byte("store-password"))
require.NoError(t, err)
var enc Encoder
err = enc.AddKeyStore(ks)
require.NoError(t, err)
err = enc.SetIntegrityPassword("changeit")
require.NoError(t, err)
var buf bytes.Buffer
n, err := enc.WriteTo(&buf)
require.NoError(t, err)
require.Equal(t, int64(buf.Len()), n)
if *writeReencoded {
err = os.WriteFile("testdata/encoder-re-encode-private-key.jceks", buf.Bytes(), 0666)
require.NoError(t, err)
}
expected, err := os.ReadFile("testdata/encoder-re-encode-private-key.jceks")
require.NoError(t, err)
require.Equal(t, expected, buf.Bytes())
}
func TestEncodeExistingTrustedCertificateStore(t *testing.T) {
t.Parallel()
ks, err := LoadFromFile("testdata/trusted-cert.jceks", []byte("store-password"))
require.NoError(t, err)
var enc Encoder
err = enc.AddKeyStore(ks)
require.NoError(t, err)
err = enc.SetIntegrityPassword("changeit")
require.NoError(t, err)
var buf bytes.Buffer
n, err := enc.WriteTo(&buf)
require.NoError(t, err)
require.Equal(t, int64(buf.Len()), n)
if *writeReencoded {
err = os.WriteFile("testdata/encoder-re-encode-trusted-cert.jceks", buf.Bytes(), 0666)
require.NoError(t, err)
}
expected, err := os.ReadFile("testdata/encoder-re-encode-trusted-cert.jceks")
require.NoError(t, err)
require.Equal(t, expected, buf.Bytes())
}
func TestEncodeDuplicateAliases(t *testing.T) {
t.Parallel()
rnd := sha3.NewShake128()
now := time.Date(2025, 4, 24, 0, 0, 0, 0, time.UTC)
rsaPriv, err := LoadPEMKey("testdata/private-key.key")
require.NoError(t, err)
pkcs1 := x509.MarshalPKCS1PrivateKey(rsaPriv)
privCert, err := LoadPEMCert("testdata/private-key.crt")
require.NoError(t, err)
otherCert, err := LoadPEMCert("testdata/trusted-cert.crt")
require.NoError(t, err)
var enc Encoder
cipher, err := PBEWithMD5AndDES3CBC([]byte("confusion"), rnd, 20)
require.NoError(t, err)
// Write private key entry as "foo" and cert as "bar"
err = enc.AddPrivateKeyPKCS1("foo", now, pkcs1, [][]byte{privCert.Raw}, cipher)
require.NoError(t, err)
err = enc.AddTrustedCertificate("bar", now, otherCert.Raw)
require.NoError(t, err)
// Now write them again with opposite aliases, which should overwrite the previous ones
err = enc.AddPrivateKeyPKCS1("bar", now, pkcs1, [][]byte{privCert.Raw}, cipher)
require.NoError(t, err)
err = enc.AddTrustedCertificate("foo", now, otherCert.Raw)
require.NoError(t, err)
err = enc.SetIntegrityPassword("confusion")
require.NoError(t, err)
var buf bytes.Buffer
n, err := enc.WriteTo(&buf)
require.NoError(t, err)
require.Equal(t, int64(buf.Len()), n)
reloaded, err := LoadFromReader(&buf, []byte("confusion"))
require.NoError(t, err)
require.Equal(t, []string{"foo"}, reloaded.ListCerts())
require.Equal(t, []string{"bar"}, reloaded.ListPrivateKeys())
}
func TestProhibitDuplicateAliases(t *testing.T) {
t.Parallel()
now := time.Date(2025, 4, 24, 0, 0, 0, 0, time.UTC)
cert, err := LoadPEMCert("testdata/trusted-cert.crt")
require.NoError(t, err)
var enc Encoder
enc.SetProhibitDuplicateAliases(true)
err = enc.AddTrustedCertificate("alias", now, cert.Raw)
require.NoError(t, err)
err = enc.AddTrustedCertificate("alias", now, cert.Raw)
require.Error(t, err)
require.ErrorIs(t, err, ErrDuplicateAlias)
}