pomerium/pkg/cryptutil/pem_test.go
Caleb Doxsey 9631d9ff1c
cryptutil: add a function to normalize PEM files so that leaf certificates appear first (#5642)
## Summary
Go requires that the first certificate in a bundle be the one associated
with a private key:

> LoadX509KeyPair reads and parses a public/private key pair from a pair
of files. The files must contain PEM encoded data. The certificate file
may contain intermediate certificates following the leaf certificate to
form a certificate chain. On successful return, Certificate.Leaf will be
populated.

I don't think Go is unusual in this regard, but to make the code more
tolerant, add a new `NormalizePEM` function which will take raw PEM data
and rewrite it so that leaf certificates appear first. This will be used
in zero and the enterprise console.

## Related issues
-
[ENG-2433](https://linear.app/pomerium/issue/ENG-2423/enterprise-console-updatekeypair-check-is-too-restrictive)

## Checklist
- [x] reference any related issues
- [x] updated unit tests
- [x] add appropriate label (`enhancement`, `bug`, `breaking`,
`dependencies`, `ci`)
- [x] ready for review
2025-06-06 12:37:02 -06:00

55 lines
1.7 KiB
Go

package cryptutil_test
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/internal/testutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
)
func TestNormalizePEM(t *testing.T) {
t.Parallel()
rootCA, intermediateCA, cert := testutil.GenerateCertificateChain(t)
for _, tc := range []struct {
input []byte
expect []byte
}{
{
input: slices.Concat(rootCA.PublicPEM, intermediateCA.PublicPEM, cert.PublicPEM, cert.PrivateKeyPEM),
expect: slices.Concat(cert.PublicPEM, cert.PrivateKeyPEM, intermediateCA.PublicPEM, rootCA.PublicPEM),
},
{
input: slices.Concat(cert.PublicPEM, cert.PrivateKeyPEM, intermediateCA.PublicPEM, rootCA.PublicPEM),
expect: slices.Concat(cert.PublicPEM, cert.PrivateKeyPEM, intermediateCA.PublicPEM, rootCA.PublicPEM),
},
{
input: nil,
expect: nil,
},
{
input: []byte("\n\n\nNON PEM DATA\n\n\n"),
expect: []byte("\n\n\nNON PEM DATA\n\n\n"),
},
{
input: rootCA.PublicPEM,
expect: rootCA.PublicPEM,
},
{
input: slices.Concat(rootCA.PublicPEM, intermediateCA.PublicPEM, cert.PublicPEM, cert.PrivateKeyPEM),
expect: slices.Concat(cert.PublicPEM, cert.PrivateKeyPEM, intermediateCA.PublicPEM, rootCA.PublicPEM),
},
{
// looks a bit weird, but the text before a block gets moved with it
input: slices.Concat([]byte("BEFORE\n"), intermediateCA.PublicPEM, []byte("BETWEEN\n"), cert.PublicPEM, []byte("AFTER\n")),
expect: slices.Concat([]byte("BETWEEN\n"), cert.PublicPEM, []byte("AFTER\n"), []byte("BEFORE\n"), intermediateCA.PublicPEM),
},
} {
actual := cryptutil.NormalizePEM(tc.input)
assert.Equal(t, string(tc.expect), string(actual))
}
}