/* Copyright © The ESO Authors 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 https://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 template import ( "os" "slices" "testing" ) const ( certData = `-----BEGIN CERTIFICATE----- MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4 MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE 1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0 mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d 9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u -----END CERTIFICATE----- ` otherCert = `-----BEGIN CERTIFICATE----- MIIBqjCCAU+gAwIBAgIRAPnGGsBUMbZhmh5QdnYdBmUwCgYIKoZIzj0EAwIwGjEY MBYGA1UEAxMPaW50ZXJtZWRpYXRlLWNhMB4XDTIyMDIwOTEwMjUzMVoXDTIyMDIx MDEwMjUzMVowDjEMMAoGA1UEAxMDZm9vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD QgAEqnxdeInykx8JZsLi13rZLekoG2cosQ3F+2InVNy7hCQ7soMqdaJsGQ6LFtov ogUFtOOTRWrunblqNWGZsowHbKOBgTB/MA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUE FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLtundVbuKd73OWzo6SY by0Ajeb2MB8GA1UdIwQYMBaAFCLg80J/bZBbOd+Y8+V94l5xM2zEMA4GA1UdEQQH MAWCA2ZvbzAKBggqhkjOPQQDAgNJADBGAiEA4K4SbVNqrEtl7RfwBfJFMnWI+X8D zMPMc4Xqzp2qTxcCIQDsySgtiakypZfWakpB49zJph0kLwGK8xhWvGMUw1N1/w== -----END CERTIFICATE----- ` keyData = `-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3o6/JdZEqNbqN RkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4NzjaG15owr92/11W0pxPUliRLti 3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvWY8jh8A0LQALZiV/9QsrJdXZd S47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE1gEDqnKfRxXI8DEQKXr+CKPU wCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4eugHe52vXHdh/HJ9VjNp0xOH1 waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJaYOOonQSEswveSv6PcG9AHvpN Pot2Xs6hAgMBAAECggEACTGPrmVNZDCWa1Y2hkJ0J7SoNcw+9O4M/jwMp4l/PD6P I98S78LYLCZhPLK17SmjUcnFO1AXKW1JeFS2D/fjfP256guvcqQNjLFoioxcOhVb ZGyd1Mi8JPqP5wfOj16gBeYDwTkjz9wqldcfiZaL9XoXetkZecbzR2JwC2FtIVuC 0njTjMNYpaBKnoLb8OTR0EQz7lYEo2MkQiWryz8wseONnFmdfh18p+p10YgCbuCH qesrWfDLLxaxZelNtDhDngg9LoCLmarYy7BgShacmUEgJTZ/x3xFC75thK3ln0OY +ktTgvVotYYaZi7qAjQiEsTvkTAPg5RMpQLd2UIWsQKBgQDCBp+1vURbwGzmTNUg HMipD6WDFdLc9DCacx6+ZqsEPTMWQbCpVZrDKiY0Rjt5F+xOCyMr00J5RDJXRC0G +L7NcJdywOFutT7vB+cmETg7l/6PHweNYBnE66706eTL/KVYZMi4tEinarPWhHmL jasfdLANtpDjdWkRt299TkPRbQKBgQDyS8Rr7KZdv04Csqkf+ASmiJpT5R6Y72kc 3XYpKETyB2FyPZkuh/zInMut9SkkSI9O/jA3zf956jj6sF1DHvp7T8KkIp5OAQeD J9AF65m2MnZfHFUeJ6ZQsggwMWqrD0ycIWP7YWtiBHH+D1wGkjYrssq+bvG/yNpA LtqdKq9lhQKBgQCZA2hIhy61vRckuEsLvCdzTGeW7UsR/XGnHEqOlaEhArKbRsrv gBdA+qiOaSTV5svw8E+YbE7sG6AnuhhYeyreEYEeeoZOLJmpIG5mUwYp2UBj1nC6 SaOI7OVZOGu7g09SWokBQQxbG4cgEfFY4Sym7fs5lVTGTP3Dfwppo6NQMQKBgQCo J5NDP3Lafwk58BpV+H/pv8YzUUDh7M2rXbtCpxLqUdr8OOnVlEUISWFF8m5CIyVq MhjuscWLK9Wtjba7/YTjDaDM3sW05xv6lyfU5ATCoNTr/zLHgcb4HAZ4w+L+otiN RtMnxB2NYf5mzuwUF2cG/secUEzwyAlIH/xStSwTLQKBgQCRvqF+rqxnegoOgwVW qrWPv06wXD8dW2FlPpY5GXqA0l6erSK3YsQQToRmbem9ibPD7bd5P4gNbWfxwK4C Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd BixHvI/EJ8YK3ta5WdJWKC6hnA== -----END PRIVATE KEY----- ` ) const ( filterPrivateKey = "private key" filterCert = "certificate" ) func TestFilterPEM(t *testing.T) { type args struct { input string pemType string } tests := []struct { name string args args want string wantErr bool }{ { name: "extract cert / cert first", args: args{ input: certData + keyData, pemType: filterCert, }, want: certData, }, { name: "extract cert / key first", args: args{ input: keyData + certData, pemType: filterCert, }, want: certData, }, { name: "extract multiple certs", args: args{ input: keyData + certData + keyData + otherCert, pemType: filterCert, }, want: certData + otherCert, }, { name: "extract key", args: args{ input: keyData + certData, pemType: filterPrivateKey, }, want: keyData, }, { name: "key with junk", args: args{ input: certData + keyData + "some ---junk---", pemType: filterPrivateKey, }, want: keyData, }, { name: "begin/end with junk", args: args{ // pem.Decode trims junk from the beginning of the input // so we are able to decode both cert & key input: "some junk" + certData + keyData + "some ---junk---", pemType: filterPrivateKey, }, want: keyData, }, { name: "interleaved junk", args: args{ // can parse cert but not key due to junk input: certData + "some junk" + keyData, pemType: filterPrivateKey, }, wantErr: true, }, { name: "err when junk", args: args{ input: "---junk---", pemType: filterPrivateKey, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := filterPEM(tt.args.pemType, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("filterPEM() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("filterPEM() = %v, want %v", got, tt.want) } }) } } type filterCertChainTestArgs struct { input []string certType string } type filterCertChainTest struct { name string args filterCertChainTestArgs want string wantErr bool } func TestFilterCertChain(t *testing.T) { const ( leafCertPath = "_testdata/foo.crt" intermediateCertPath = "_testdata/intermediate-ca.crt" rootCertPath = "_testdata/root-ca.crt" rootKeyPath = "_testdata/root-ca.key" ) tests := []filterCertChainTest{ { name: "extract leaf cert / empty cert chain", args: filterCertChainTestArgs{ input: []string{}, certType: certTypeLeaf, }, wantErr: true, }, { name: "extract leaf cert / cert chain with pkey", args: filterCertChainTestArgs{ input: []string{ leafCertPath, rootKeyPath, }, certType: certTypeLeaf, }, wantErr: true, }, { name: "extract leaf cert / leaf cert only", args: filterCertChainTestArgs{ input: []string{ leafCertPath, }, certType: certTypeLeaf, }, want: leafCertPath, }, { name: "extract leaf cert / cert chain without root", args: filterCertChainTestArgs{ input: []string{ leafCertPath, intermediateCertPath, }, certType: certTypeLeaf, }, want: leafCertPath, }, { name: "extract leaf cert / root cert only", args: filterCertChainTestArgs{ input: []string{ rootCertPath, }, certType: certTypeLeaf, }, want: "", }, { name: "extract leaf cert / full cert chain", args: filterCertChainTestArgs{ input: []string{ leafCertPath, intermediateCertPath, rootCertPath, }, certType: certTypeLeaf, }, want: leafCertPath, }, { name: "extract intermediate cert / leaf cert only", args: filterCertChainTestArgs{ input: []string{ leafCertPath, }, certType: certTypeIntermediate, }, want: "", }, { name: "extract intermediate cert / cert chain without root", args: filterCertChainTestArgs{ input: []string{ leafCertPath, intermediateCertPath, }, certType: certTypeIntermediate, }, want: intermediateCertPath, }, { name: "extract intermediate cert / full cert chain", args: filterCertChainTestArgs{ input: []string{ leafCertPath, intermediateCertPath, rootCertPath, }, certType: certTypeIntermediate, }, want: intermediateCertPath, }, { name: "extract root cert / leaf cert only", args: filterCertChainTestArgs{ input: []string{ leafCertPath, }, certType: certTypeRoot, }, want: "", }, { name: "extract root cert / root cert only", args: filterCertChainTestArgs{ input: []string{ rootCertPath, }, certType: certTypeRoot, }, want: rootCertPath, }, { name: "extract root cert / full cert chain", args: filterCertChainTestArgs{ input: []string{ leafCertPath, intermediateCertPath, rootCertPath, }, certType: certTypeRoot, }, want: rootCertPath, }, } for _, tt := range tests { runFilterCertChainTest(t, tt) } } func runFilterCertChainTest(t *testing.T, tt filterCertChainTest) { t.Run(tt.name, func(t *testing.T) { chainIn, err := readCertificates(tt.args.input) if err != nil { t.Error(err) } var expOut []byte if tt.want != "" { var err error expOut, err = os.ReadFile(tt.want) if err != nil { t.Error(err) } } got, err := filterCertChain(tt.args.certType, string(chainIn)) if (err != nil) != tt.wantErr { t.Errorf("filterCertChain() error = %v, wantErr %v", err, tt.wantErr) return } if got != string(expOut) { t.Errorf("filterCertChain() = %v, want %v", got, string(expOut)) } }) } func readCertificates(certFiles []string) ([]byte, error) { var certificates []byte for _, f := range certFiles { c, err := os.ReadFile(f) if err != nil { return nil, err } certificates = append(certificates, c...) } return certificates, nil } func TestCertSANs(t *testing.T) { tests := []struct { name string input string want []string wantErr bool }{ { name: "extract DNS SANs from cert", input: certData, want: []string{"gooble.com"}, }, { name: "invalid PEM input", input: "not a pem", wantErr: true, }, { name: "empty input", input: "", wantErr: true, }, { name: "cert with junk before PEM", input: "some junk\n" + certData, want: []string{"gooble.com"}, }, { name: "cert from file with all types of SANs", input: func() string { b, err := os.ReadFile("_testdata/sans.crt") if err != nil { panic("test setup failed: " + err.Error()) } return string(b) }(), want: []string{"example.com", "www.example.com", "192.168.1.10", "10.0.0.1", "admin@example.com", "https://example.com"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := certSANs(tt.input) if (err != nil) != tt.wantErr { t.Errorf("certSANs() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr && !slices.Equal(got, tt.want) { t.Errorf("certSANs() = %v, want %v", got, tt.want) } }) } }