From de8821acd3ada299a17d1d636df4ddb6bb89bd8b Mon Sep 17 00:00:00 2001 From: Robbie McMichael <2044464+robbiemcmichael@users.noreply.github.com> Date: Thu, 24 Dec 2020 23:41:10 +0800 Subject: [PATCH] kubeadm: support certificate chain validation Fixes an issue where some kubeadm phases fail if a certificate file contains a certificate chain with one or more intermediate CA certificates. The validation algorithm has been changed from requiring that a certificate was signed directly by the root CA to requiring that there is a valid certificate chain back to the root CA. --- cmd/kubeadm/app/cmd/phases/init/certs.go | 4 +-- cmd/kubeadm/app/phases/certs/certs.go | 27 ++++++++++---- cmd/kubeadm/app/util/pkiutil/pki_helpers.go | 39 +++++++++++++++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/cmd/kubeadm/app/cmd/phases/init/certs.go b/cmd/kubeadm/app/cmd/phases/init/certs.go index 29676b66199..f2cf429cade 100644 --- a/cmd/kubeadm/app/cmd/phases/init/certs.go +++ b/cmd/kubeadm/app/cmd/phases/init/certs.go @@ -263,7 +263,7 @@ func runCertPhase(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert) return nil } - if certData, _, err := pkiutil.TryLoadCertAndKeyFromDisk(data.CertificateDir(), cert.BaseName); err == nil { + if certData, intermediates, err := pkiutil.TryLoadCertChainFromDisk(data.CertificateDir(), cert.BaseName); err == nil { certsphase.CheckCertificatePeriodValidity(cert.BaseName, certData) caCertData, err := pkiutil.TryLoadCertFromDisk(data.CertificateDir(), caCert.BaseName) @@ -273,7 +273,7 @@ func runCertPhase(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert) certsphase.CheckCertificatePeriodValidity(caCert.BaseName, caCertData) - if err := certData.CheckSignatureFrom(caCertData); err != nil { + if err := pkiutil.VerifyCertChain(certData, intermediates, caCertData); err != nil { return errors.Wrapf(err, "[certs] certificate %s not signed by CA certificate %s", cert.BaseName, caCert.BaseName) } diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index 4e15dc9c957..a17db314447 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -229,8 +229,14 @@ func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert // Checks if the signed certificate exists in the PKI directory if pkiutil.CertOrKeyExist(pkiDir, baseName) { - // Try to load signed certificate .crt and .key from the PKI directory - signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName) + // Try to load key from the PKI directory + _, err := pkiutil.TryLoadKeyFromDisk(pkiDir, baseName) + if err != nil { + return errors.Wrapf(err, "failure loading %s key", baseName) + } + + // Try to load certificate from the PKI directory + signedCert, intermediates, err := pkiutil.TryLoadCertChainFromDisk(pkiDir, baseName) if err != nil { return errors.Wrapf(err, "failure loading %s certificate", baseName) } @@ -238,7 +244,7 @@ func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert CheckCertificatePeriodValidity(baseName, signedCert) // Check if the existing cert is signed by the given CA - if err := signedCert.CheckSignatureFrom(signingCert); err != nil { + if err := pkiutil.VerifyCertChain(signedCert, intermediates, signingCert); err != nil { return errors.Errorf("certificate %s is not signed by corresponding CA", baseName) } @@ -416,10 +422,17 @@ func validateSignedCert(l certKeyLocation) error { return validateSignedCertWithCA(l, caCert) } -// validateSignedCertWithCA tries to load a certificate and validate it with the given caCert +// validateSignedCertWithCA tries to load a certificate and private key and +// validates that the cert is signed by the given caCert func validateSignedCertWithCA(l certKeyLocation, caCert *x509.Certificate) error { - // Try to load key and signed certificate - signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(l.pkiDir, l.baseName) + // Try to load key from the PKI directory + _, err := pkiutil.TryLoadKeyFromDisk(l.pkiDir, l.baseName) + if err != nil { + return errors.Wrapf(err, "failure loading key for %s", l.baseName) + } + + // Try to load certificate from the PKI directory + signedCert, intermediates, err := pkiutil.TryLoadCertChainFromDisk(l.pkiDir, l.baseName) if err != nil { return errors.Wrapf(err, "failure loading certificate for %s", l.uxName) } @@ -427,7 +440,7 @@ func validateSignedCertWithCA(l certKeyLocation, caCert *x509.Certificate) error CheckCertificatePeriodValidity(l.uxName, signedCert) // Check if the cert is signed by the CA - if err := signedCert.CheckSignatureFrom(caCert); err != nil { + if err := pkiutil.VerifyCertChain(signedCert, intermediates, caCert); err != nil { return errors.Wrapf(err, "certificate %s is not signed by corresponding CA", l.uxName) } return nil diff --git a/cmd/kubeadm/app/util/pkiutil/pki_helpers.go b/cmd/kubeadm/app/util/pkiutil/pki_helpers.go index aa8a76da131..d988f446f38 100644 --- a/cmd/kubeadm/app/util/pkiutil/pki_helpers.go +++ b/cmd/kubeadm/app/util/pkiutil/pki_helpers.go @@ -256,6 +256,21 @@ func TryLoadCertFromDisk(pkiPath, name string) (*x509.Certificate, error) { return cert, nil } +// TryLoadCertChainFromDisk tries to load the cert chain from the disk +func TryLoadCertChainFromDisk(pkiPath, name string) (*x509.Certificate, []*x509.Certificate, error) { + certificatePath := pathForCert(pkiPath, name) + + certs, err := certutil.CertsFromFile(certificatePath) + if err != nil { + return nil, nil, errors.Wrapf(err, "couldn't load the certificate file %s", certificatePath) + } + + cert := certs[0] + intermediates := certs[1:] + + return cert, intermediates, nil +} + // TryLoadKeyFromDisk tries to load the key from the disk and validates that it is valid func TryLoadKeyFromDisk(pkiPath, name string) (crypto.Signer, error) { privateKeyPath := pathForKey(pkiPath, name) @@ -624,3 +639,27 @@ func ValidateCertPeriod(cert *x509.Certificate, offset time.Duration) error { } return nil } + +// VerifyCertChain verifies that a certificate has a valid chain of +// intermediate CAs back to the root CA +func VerifyCertChain(cert *x509.Certificate, intermediates []*x509.Certificate, root *x509.Certificate) error { + rootPool := x509.NewCertPool() + rootPool.AddCert(root) + + intermediatePool := x509.NewCertPool() + for _, c := range intermediates { + intermediatePool.AddCert(c) + } + + verifyOptions := x509.VerifyOptions{ + Roots: rootPool, + Intermediates: intermediatePool, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + } + + if _, err := cert.Verify(verifyOptions); err != nil { + return err + } + + return nil +}