From 55820ef1e8b93efd207cced1f9d0e3006fed1d95 Mon Sep 17 00:00:00 2001 From: Manuel Alejandro de Brito Fontes Date: Fri, 13 Sep 2019 09:22:24 -0300 Subject: [PATCH] Allow multiple CA Certificates (#4556) --- .../ingress/controller/store/backend_ssl.go | 14 ++-- internal/ingress/sslcert.go | 2 + internal/net/ssl/ssl.go | 73 ++++++++++--------- internal/net/ssl/ssl_test.go | 4 +- 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/internal/ingress/controller/store/backend_ssl.go b/internal/ingress/controller/store/backend_ssl.go index 1ee1f521c..4638343cd 100644 --- a/internal/ingress/controller/store/backend_ssl.go +++ b/internal/ingress/controller/store/backend_ssl.go @@ -17,8 +17,6 @@ limitations under the License. package store import ( - "crypto/sha1" - "encoding/hex" "fmt" "strings" @@ -107,11 +105,17 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error } if len(ca) > 0 { + caCert, err := ssl.CheckCACert(ca) + if err != nil { + return nil, fmt.Errorf("parsing CA certificate: %v", err) + } + path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert) if err != nil { return nil, fmt.Errorf("error while storing certificate and key: %v", err) } + sslCert.CACertificate = caCert sslCert.CAFileName = path sslCert.CASHA = file.SHA1(path) @@ -125,7 +129,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error if err != nil { return nil, fmt.Errorf("error configuring CRL certificate: %v", err) } - } } @@ -170,11 +173,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error sslCert.Name = secret.Name sslCert.Namespace = secret.Namespace - hasher := sha1.New() - hasher.Write(sslCert.Certificate.Raw) - - sslCert.PemSHA = hex.EncodeToString(hasher.Sum(nil)) - // the default SSL certificate needs to be present on disk if secretName == s.defaultSSLCertificate { path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert) diff --git a/internal/ingress/sslcert.go b/internal/ingress/sslcert.go index c59fdb93e..82cdcbf68 100644 --- a/internal/ingress/sslcert.go +++ b/internal/ingress/sslcert.go @@ -30,6 +30,8 @@ type SSLCert struct { Certificate *x509.Certificate `json:"-"` + CACertificate []*x509.Certificate `json:"-"` + // CAFileName contains the path to the file with the root certificate CAFileName string `json:"caFileName"` diff --git a/internal/net/ssl/ssl.go b/internal/net/ssl/ssl.go index d1d3002ff..723bbbc5d 100644 --- a/internal/net/ssl/ssl.go +++ b/internal/net/ssl/ssl.go @@ -20,10 +20,12 @@ import ( "bytes" "crypto/rand" "crypto/rsa" + "crypto/sha1" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "encoding/hex" "encoding/pem" "errors" "fmt" @@ -63,21 +65,6 @@ func getPemFileName(fullSecretName string) (string, string) { return fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, pemName), pemName } -func verifyPemCertAgainstRootCA(pemCert *x509.Certificate, ca []byte) error { - bundle := x509.NewCertPool() - bundle.AppendCertsFromPEM(ca) - opts := x509.VerifyOptions{ - Roots: bundle, - } - - _, err := pemCert.Verify(opts) - if err != nil { - return err - } - - return nil -} - // CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) { var pemCertBuffer bytes.Buffer @@ -138,8 +125,12 @@ func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) { } } + hasher := sha1.New() + hasher.Write(pemCert.Raw) + return &ingress.SSLCert{ Certificate: pemCert, + PemSHA: hex.EncodeToString(hasher.Sum(nil)), CN: cn.List(), ExpireTime: pemCert.NotAfter, PemCertKey: pemCertBuffer.String(), @@ -150,25 +141,41 @@ func CreateSSLCert(cert, key []byte, uid string) (*ingress.SSLCert, error) { // CreateCACert is similar to CreateSSLCert but it creates instance of SSLCert only based on given ca after // parsing and validating it func CreateCACert(ca []byte) (*ingress.SSLCert, error) { - pemCABlock, _ := pem.Decode(ca) - if pemCABlock == nil { - return nil, fmt.Errorf("no valid PEM formatted block found") - } - // If the first certificate does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used. - if pemCABlock.Type != "CERTIFICATE" { - return nil, fmt.Errorf("no certificate PEM data found, make sure certificate content starts with 'BEGIN CERTIFICATE'") - } - - pemCert, err := x509.ParseCertificate(pemCABlock.Bytes) + caCert, err := CheckCACert(ca) if err != nil { return nil, err } return &ingress.SSLCert{ - Certificate: pemCert, + CACertificate: caCert, }, nil } +// CheckCACert validates a byte array containing one or more CA certificate/s +func CheckCACert(caBytes []byte) ([]*x509.Certificate, error) { + certs := []*x509.Certificate{} + + var block *pem.Block + for { + block, caBytes = pem.Decode(caBytes) + if block == nil { + break + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + certs = append(certs, cert) + } + + if len(certs) == 0 { + return nil, fmt.Errorf("error decoding CA certificate/s") + } + + return certs, nil +} + // StoreSSLCertOnDisk creates a .pem file with content PemCertKey from the given sslCert // and sets relevant remaining fields of sslCert object func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) { @@ -185,27 +192,21 @@ func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) { // ConfigureCACertWithCertAndKey appends ca into existing PEM file consisting of cert and key // and sets relevant fields in sslCert object func ConfigureCACertWithCertAndKey(name string, ca []byte, sslCert *ingress.SSLCert) error { - err := verifyPemCertAgainstRootCA(sslCert.Certificate, ca) - if err != nil { - oe := fmt.Sprintf("failed to verify certificate chain: \n\t%s\n", err) - return errors.New(oe) - } - var buffer bytes.Buffer - _, err = buffer.Write([]byte(sslCert.PemCertKey)) + _, err := buffer.Write([]byte(sslCert.PemCertKey)) if err != nil { - return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err) + return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.CAFileName, err) } _, err = buffer.Write([]byte("\n")) if err != nil { - return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err) + return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.CAFileName, err) } _, err = buffer.Write(ca) if err != nil { - return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.PemFileName, err) + return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.CAFileName, err) } return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644) diff --git a/internal/net/ssl/ssl_test.go b/internal/net/ssl/ssl_test.go index eb4434e42..317391158 100644 --- a/internal/net/ssl/ssl_test.go +++ b/internal/net/ssl/ssl_test.go @@ -176,7 +176,7 @@ func TestConfigureCACert(t *testing.T) { if sslCert.CAFileName != "" { t.Fatalf("expected CAFileName to be empty") } - if sslCert.Certificate == nil { + if sslCert.CACertificate == nil { t.Fatalf("expected Certificate to be set") } @@ -221,7 +221,7 @@ fUNCdMGmr8FVF6IzTNYGmCuk/C4= if sslCert.CRLFileName != "" { t.Fatalf("expected CRLFileName to be empty") } - if sslCert.Certificate == nil { + if sslCert.CACertificate == nil { t.Fatalf("expected Certificate to be set") }