Skip to content

Commit

Permalink
introduce certitiface pool sctructure
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com>

add a test for duplicate certs

Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com>

add comment for functions

Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com>
  • Loading branch information
arsenalzp committed Jun 30, 2024
1 parent dd39f97 commit 8f9488a
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 109 deletions.
4 changes: 4 additions & 0 deletions cmd/trust-manager/app/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ func (o *Options) addBundleFlags(fs *pflag.FlagSet) {
fs.BoolVar(&o.Bundle.FilterExpiredCerts,
"filter-expired-certificates", false,
"Filter expired certificates from the bundle.")

fs.BoolVar(&o.Bundle.FilterDuplicateCerts,
"filter-duplicate-certificates", true,
"Filter duplicate certificates from the bundle.")
}

func (o *Options) addLoggingFlags(fs *pflag.FlagSet) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ type Options struct {

// FilterExpiredCerts controls if expired certificates are filtered from the bundle.
FilterExpiredCerts bool

// FilterExpiredCerts controls if duplicate certificates are filtered from the bundle.
FilterDuplicateCerts bool
}

// bundle is a controller-runtime controller. Implements the actual controller
Expand Down
16 changes: 5 additions & 11 deletions pkg/bundle/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type bundleData struct {
// is each bundle is concatenated together with a new line character.
func (b *bundle) buildSourceBundle(ctx context.Context, bundle *trustapi.Bundle) (bundleData, error) {
var resolvedBundle bundleData
var bundles []string
var certPool = util.NewCertPool(b.FilterExpiredCerts, b.FilterDuplicateCerts)

for _, source := range bundle.Spec.Sources {
var (
Expand Down Expand Up @@ -99,27 +99,21 @@ func (b *bundle) buildSourceBundle(ctx context.Context, bundle *trustapi.Bundle)
return bundleData{}, fmt.Errorf("failed to retrieve bundle from source: %w", err)
}

opts := util.ValidateAndSanitizeOptions{FilterExpired: b.Options.FilterExpiredCerts}
sanitizedBundle, err := util.ValidateAndSanitizePEMBundleWithOptions([]byte(sourceData), opts)
err = util.ValidateAndSanitizePEMBundleWithOptions(certPool, []byte(sourceData))
if err != nil {
return bundleData{}, fmt.Errorf("invalid PEM data in source: %w", err)
}

bundles = append(bundles, string(sanitizedBundle))
//bundles = append(bundles, string(sanitizedBundle))
}

// NB: empty bundles are not valid so check and return an error if one somehow snuck through.

if len(bundles) == 0 {
if certPool.GetCertsQuantity() == 0 {
return bundleData{}, fmt.Errorf("couldn't find any valid certificates in bundle")
}

deduplicatedBundles, err := deduplicateBundles(bundles)
if err != nil {
return bundleData{}, err
}

if err := resolvedBundle.populateData(deduplicatedBundles, bundle.Spec.Target); err != nil {
if err := resolvedBundle.populateData(util.GetSplitPEMBundleStrings(certPool), bundle.Spec.Target); err != nil {
return bundleData{}, err
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/bundle/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ func Test_buildSourceBundle(t *testing.T) {
Version: "123",
Bundle: dummy.TestCertificate5,
},
Options: Options{
FilterDuplicateCerts: true,
},
}

// for corresponding store if arbitrary password is expected then set it instead of default one
Expand Down
5 changes: 4 additions & 1 deletion pkg/fspkg/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ func (p *Package) Clone() *Package {
func (p *Package) Validate() error {
// Ignore the sanitized bundle here and preserve the bundle as-is.
// We'll sanitize later, when building a bundle on a reconcile.
_, err := util.ValidateAndSanitizePEMBundle([]byte(p.Bundle))
var certPool = util.NewCertPool(false, true)

//_, err := util.ValidateAndSanitizePEMBundle([]byte(p.Bundle))
err := util.ValidateAndSanitizePEMBundleWithOptions(certPool, []byte(p.Bundle))
if err != nil {
return fmt.Errorf("package bundle failed validation: %w", err)
}
Expand Down
50 changes: 41 additions & 9 deletions pkg/util/cert_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,33 @@ limitations under the License.
package util

import (
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"time"
)

// CertPool is a set of certificates.
type certPool struct {
certificates []*x509.Certificate
filterExpired bool
type CertPool struct {
certificatesHashes map[[32]byte]struct{}
certificates []*x509.Certificate
filterExpired bool
filterDuplicates bool
}

// newCertPool returns a new, empty CertPool.
func newCertPool(filterExpired bool) *certPool {
return &certPool{
certificates: make([]*x509.Certificate, 0),
filterExpired: filterExpired,
func NewCertPool(filterExpired bool, filterDuplicates bool) *CertPool {
return &CertPool{
certificates: make([]*x509.Certificate, 0),
filterExpired: filterExpired,
filterDuplicates: filterDuplicates,
certificatesHashes: make(map[[32]byte]struct{}),
}
}

// Append certificate to a pool
func (cp *certPool) appendCertFromPEM(pemData []byte) error {
func (cp *CertPool) appendCertFromPEM(pemData []byte) error {
if pemData == nil {
return fmt.Errorf("certificate data can't be nil")
}
Expand Down Expand Up @@ -75,14 +80,20 @@ func (cp *certPool) appendCertFromPEM(pemData []byte) error {
continue
}

if cp.filterDuplicates {
if cp.isDuplicate(certificate) {
continue
}
}

cp.certificates = append(cp.certificates, certificate)
}

return nil
}

// Get PEM certificates from pool
func (cp *certPool) getCertsPEM() [][]byte {
func (cp *CertPool) getCertsPEM() [][]byte {
var certsData [][]byte = make([][]byte, len(cp.certificates))

for i, cert := range cp.certificates {
Expand All @@ -91,3 +102,24 @@ func (cp *certPool) getCertsPEM() [][]byte {

return certsData
}

// Get the certificates quantity in the certificates pool
func (cp *CertPool) GetCertsQuantity() int {
return len(cp.certificates)
}

// Check deplicate of certificates in the certificates pool
func (cp *CertPool) isDuplicate(cert *x509.Certificate) bool {
hash := sha256.Sum256(cert.Raw)
// check existence of the hash
if _, ok := cp.certificatesHashes[hash]; !ok {
cp.certificatesHashes[hash] = struct{}{}
return false
}

return true
}

func (cp *CertPool) getCertsList() []*x509.Certificate {
return cp.certificates
}
4 changes: 2 additions & 2 deletions pkg/util/cert_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

func TestNewCertPool(t *testing.T) {
certPool := newCertPool(false)
certPool := NewCertPool(false, true)

if certPool == nil {
t.Fatal("pool is nil")
Expand Down Expand Up @@ -76,7 +76,7 @@ func TestAppendCertFromPEM(t *testing.T) {

// populate certificates bundle
for _, crt := range certificateList {
certPool := newCertPool(false)
certPool := NewCertPool(false, true)

if err := certPool.appendCertFromPEM([]byte(crt.certificate)); err != nil {
t.Fatalf("error adding PEM certificate into pool %s", err)
Expand Down
62 changes: 35 additions & 27 deletions pkg/util/pem.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
)

// ValidateAndSanitizePEMBundle strictly validates a given input PEM bundle to confirm it contains
Expand All @@ -46,35 +47,19 @@ type ValidateAndSanitizeOptions struct {
FilterExpired bool // If true, expired certificates will be filtered out
}

// ValidateAndSanitizePEMBundle keeps the original function signature for backward compatibility
func ValidateAndSanitizePEMBundle(data []byte) ([]byte, error) {
opts := ValidateAndSanitizeOptions{
FilterExpired: false,
}
return ValidateAndSanitizePEMBundleWithOptions(data, opts)
}

// ValidateAndSplitPEMBundle keeps the original function signature for backward compatibility
func ValidateAndSplitPEMBundle(data []byte) ([][]byte, error) {
opts := ValidateAndSanitizeOptions{
FilterExpired: false,
}
return ValidateAndSplitPEMBundleWithOptions(data, opts)
}

// See also https://github.com/golang/go/blob/5d5ed57b134b7a02259ff070864f753c9e601a18/src/crypto/x509/cert_pool.go#L201-L239
// An option to enable filtering of expired certificates is available.
func ValidateAndSanitizePEMBundleWithOptions(data []byte, opts ValidateAndSanitizeOptions) ([]byte, error) {
certificates, err := ValidateAndSplitPEMBundleWithOptions(data, opts)
func ValidateAndSanitizePEMBundleWithOptions(certPool *CertPool, data []byte) error {
err := certPool.appendCertFromPEM(data)
if err != nil {
return nil, err
return err
}

if len(certificates) == 0 {
return nil, fmt.Errorf("bundle contains no PEM certificates")
if certPool.GetCertsQuantity() == 0 {
return fmt.Errorf("bundle contains no PEM certificates")
}

return bytes.TrimSpace(bytes.Join(certificates, nil)), nil
return nil
}

// ValidateAndSplitPEMBundleWithOptions takes a PEM bundle as input, validates it and
Expand All @@ -83,15 +68,13 @@ func ValidateAndSanitizePEMBundleWithOptions(data []byte, opts ValidateAndSaniti
// no duplicated certificates in the bundle.
// For details of the validation performed, see the comment for ValidateAndSanitizePEMBundle
// An option to enable filtering of expired certificates is available.
func ValidateAndSplitPEMBundleWithOptions(data []byte, opts ValidateAndSanitizeOptions) ([][]byte, error) {
var certPool *certPool = newCertPool(opts.FilterExpired) // put PEM encoded certificate into a pool

func ValidateAndSplitPEMBundleWithOptions(certPool *CertPool, data []byte, opts ValidateAndSanitizeOptions) error {
err := certPool.appendCertFromPEM(data)
if err != nil {
return nil, fmt.Errorf("invalid PEM block in bundle; invalid PEM certificate: %w", err)
return fmt.Errorf("invalid PEM block in bundle; invalid PEM certificate: %w", err)
}

return certPool.getCertsPEM(), nil
return nil
}

// DecodeX509CertificateChainBytes will decode a PEM encoded x509 Certificate chain.
Expand Down Expand Up @@ -121,3 +104,28 @@ func DecodeX509CertificateChainBytes(certBytes []byte) ([]*x509.Certificate, err

return certs, nil
}

func GetSplitPEMBundle(certPool *CertPool) [][]byte {
return certPool.getCertsPEM()
}

// Get the split bundle of all certificates in the certificates pool as representation of []byte
func GetSplitPEMBundleBytes(certPool *CertPool) []byte {
return bytes.TrimSpace(bytes.Join(certPool.getCertsPEM(), nil))
}

// Get the split bundle of all certificates in the certificates pool as representation of []string
func GetSplitPEMBundleStrings(certPool *CertPool) []string {
var certList = make([]string, 0)

for _, cert := range certPool.getCertsPEM() {
certList = append(certList, strings.TrimSpace(string(cert)))
}

return certList
}

// Get the list of all Certificates in the certificates pool
func GetCertsList(certPool *CertPool) []*x509.Certificate {
return certPool.getCertsList()
}
Loading

0 comments on commit 8f9488a

Please sign in to comment.