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

Add pemutil.UnbundleCertificate #352

Merged
merged 3 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions pemutil/pem.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,3 +740,42 @@ func BundleCertificate(bundlePEM []byte, certsPEM ...[]byte) ([]byte, bool, erro

return bundlePEM, modified, nil
}

// UnbundleCertificate removes PEM-encoded certificates from a PEM-encoded
// certificate bundle.
func UnbundleCertificate(bundlePEM []byte, certsPEM ...[]byte) ([]byte, bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optionally, return the original bundle if len(certsPEM) == 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if len(certsPEM) == 0 {
return bundlePEM, false, nil
}
drop := make(map[[sha256.Size224]byte]bool, len(certsPEM))
for i := range certsPEM {
certs, err := ParseCertificateBundle(certsPEM[i])
if err != nil {
return nil, false, fmt.Errorf("invalid certificate %d: %w", i, err)
}
for _, cert := range certs {
drop[sha256.Sum224(cert.Raw)] = true
}
}

var modified bool
var keep []byte

bundle, err := ParseCertificateBundle(bundlePEM)
if err != nil {
return nil, false, fmt.Errorf("invalid bundle: %w", err)
}
for _, cert := range bundle {
sum := sha256.Sum224(cert.Raw)
if drop[sum] {
modified = true
continue
}
keep = append(keep, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})...)
}

return keep, modified, nil
}
54 changes: 54 additions & 0 deletions pemutil/pem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1225,3 +1225,57 @@ func TestBundleCertificate(t *testing.T) {
})
}
}

func TestUnbundleCertificate(t *testing.T) {
tests := []struct {
name string
bundle string
certs []string
wantBundle string
modified bool
err error
}{
{"remove one leave one", "testdata/bundle.crt", []string{"testdata/bundle-1st.crt"}, "testdata/bundle-2nd.crt", true, nil},
{"remove two leave none", "testdata/bundle.crt", []string{"testdata/bundle-1st.crt", "testdata/bundle-2nd.crt"}, "", true, nil},
{"remove none", "testdata/bundle.crt", []string{"testdata/ca.crt"}, "testdata/bundle.crt", false, nil},
{"none to remove", "testdata/bundle.crt", []string{}, "testdata/bundle.crt", false, nil},
{"remove bundle", "testdata/bundle.crt", []string{"testdata/bundle.crt"}, "", true, nil},
{"bad cert", "testdata/bundle.crt", []string{"testdata/badca.crt"}, "", false, errors.New("invalid certificate 0")},
{"bad bundle", "testdata/badca.crt", []string{"testdata/ca.crt"}, "", false, errors.New("invalid bundle")},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
bundlePEM, err := os.ReadFile(tc.bundle)
if err != nil {
t.Fatal(err)
}
certsPEM := make([][]byte, len(tc.certs))
for i, fn := range tc.certs {
certsPEM[i], err = os.ReadFile(fn)
if err != nil {
t.Fatal(err)
}
}

got, modified, err := UnbundleCertificate(bundlePEM, certsPEM...)
if tc.err != nil {
if assert.Error(t, err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
} else {
assert.NoError(t, err)
assert.Equals(t, tc.modified, modified)
if tc.wantBundle == "" {
assert.Nil(t, got)
} else {
want, err := os.ReadFile(tc.wantBundle)
if err != nil {
t.Fatal(err)
}
assert.Equals(t, strings.TrimSpace(string(want)), strings.TrimSpace(string(got)))
}
}
})
}
}
27 changes: 27 additions & 0 deletions pemutil/testdata/bundle-1st.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEhzCCA2+gAwIBAgISA78mVnMzLbLQxw5IoWP7fRG6MA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAyMDgxMzA3NDRaFw0x
OTA1MDkxMzA3NDRaMBgxFjAUBgNVBAMTDXNtYWxsc3RlcC5jb20wWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAATtaDvEhLijnzgpf/svy2v0lA0q1KNMmKmb8kdIgFsi
Rqmzh0IPldiprW6/zIBPKC3ZWBzdw06ZuSXeuPQ0rcC1o4ICYjCCAl4wDgYDVR0P
AQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB
Af8EAjAAMB0GA1UdDgQWBBQ5p9apFolkDFuITyFnBK4BxE67dDAfBgNVHSMEGDAW
gBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYIKwYBBQUH
MAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYIKwYBBQUH
MAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBgGA1UdEQQR
MA+CDXNtYWxsc3RlcC5jb20wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC
3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcw
ggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIg
CMY3NXnmEHvMVgAAAWjNb4RTAAAEAwBGMEQCID7NdufkWtiID0FJKcXBiUnhW1OX
w2eU1ZRsitnaRqL3AiBlGOiUaaWf92NGqlEkEp2/oaED0OZYbLe1LTvPnRsQoAB3
AGPy283oO8wszwtyhCdXazOkjWF3j711pjixx2hUS9iNAAABaM1vhI4AAAQDAEgw
RgIhAJ8A7OHfNThbzUOiSk5Y+JOSvOiSJ1ferIOX4z3AbD7qAiEA3Aiw5ZfrXyEn
PsHWofgMuz8dWvv4QxFXxLZRmXH0QDIwDQYJKoZIhvcNAQELBQADggEBAFrmkLMe
OhGGuOSkY3hsUnSEUy5N1lrpGRrwyWVHTPcLJdlds5S8l5xYg2LcPfWQXkUHUYcr
Fo7jT5Up4UIXYvE6Lctm48geIExlQwcOkSo3ULSQJYz9bp1tDpv9cQgyHJtwfrbR
2rxtpasLIs8znzbBcJlQ4rlodyzUMEJh8YgT9XpynDbk5K43nfsng1uRqI9J6brt
AasWcqPaJ97ILTT3DNtk2cLBpAqtMwaxcROdZ1104fbWzYjGgv67W78CBgndhvbp
Yx8h05Bm4vY0tz7Zv0Qd3YwFKgIZQI/BR/Mdber9P+xYU51T6xu4p4JDcQsCxtYg
9zBQ7U7V9X22RGo=
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions pemutil/testdata/bundle-2nd.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----