transport/tlscommon/diag.go (142 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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
//
// http://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 tlscommon
import (
"bytes"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"log"
"time"
)
// DiagCerts returns a diagnostics hook callback that will validate if the certifiactes (cert + key, and CAs) present in the config are valid.
func (c *Config) DiagCerts() func() []byte {
if c == nil {
return func() []byte {
return []byte("error: nil tlscommon.Config\n")
}
}
return func() []byte {
var b bytes.Buffer
logger := log.New(&b, "tlscommon.Config: ", 0)
logger.Printf("Start diagnostics %s", time.Now().UTC())
logger.Printf("verification_mode=%s", c.VerificationMode)
logger.Printf("ca_trusted_fingerprint=%s", c.CATrustedFingerprint)
logger.Printf("ca_sha256=%v", c.CASha256)
diagCertificate(logger, &c.Certificate)
diagCAs(logger, c.CAs)
return b.Bytes()
}
}
// DiagCerts returns a diagnostics hook callback that will validate if the certifiactes (cert + key, and CAs) present in the config are valid.
//
// Implementation is mostly a copy of Config.DiagCerts
func (c *ServerConfig) DiagCerts() func() []byte {
if c == nil {
return func() []byte {
return []byte("error: nil tlscommon.ServerConfig\n")
}
}
return func() []byte {
var b bytes.Buffer
logger := log.New(&b, "tlscommon.ServerConfig: ", 0)
logger.Printf("Start diagnostics %s", time.Now().UTC())
logger.Printf("verification_mode=%s", c.VerificationMode)
logger.Printf("client_auth=%s", c.ClientAuth)
logger.Printf("ca_sha256=%v", c.CASha256)
diagCertificate(logger, &c.Certificate)
diagCAs(logger, c.CAs)
return b.Bytes()
}
}
// diagCertificate will write diagnostics information about the cert/key to the passed logger.
func diagCertificate(logger *log.Logger, cfg *CertificateConfig) {
prefix := logger.Prefix()
defer logger.SetPrefix(prefix)
logger.SetPrefix(prefix + "CertificateSettings: ")
logger.Print("checking certificate keypair")
if cfg == nil {
logger.Print("certificate keypair is nil.")
return
}
crt, err := LoadCertificate(cfg)
if err != nil {
logger.Printf("certificate keypair error: %v", err)
return
}
if crt == nil {
logger.Print("certificate keypair is nil.")
return
}
logger.Print("certificate keypair OK.")
for i, p := range crt.Certificate {
cert, err := x509.ParseCertificate(p)
if err != nil {
logger.Printf("cert %d - error loading cert: %v", i, err)
continue
}
logger.Printf("cert %d %s", i, CertDiagString(cert))
}
}
// diagCAs will write diagnostics information about the passed CAs into logger.
func diagCAs(logger *log.Logger, cas []string) {
prefix := logger.Prefix()
defer logger.SetPrefix(prefix)
logger.SetPrefix(prefix + "CertificateAuthorities: ")
if len(cas) == 0 {
logger.Print("certificate_authorities not provided, using system certificates.")
return
}
logger.Print("certificate_authorities provided.")
i := 0
for _, ca := range cas {
certs, err := getCACerts(ca)
if err != nil {
logger.Printf("Error handling CA: %v", err)
continue
}
for _, cert := range certs {
logger.Printf("- cert %d %s", i, CertDiagString(cert))
i++
}
}
}
// CertDiagString returns a diagnostics string describing the passed certificate
func CertDiagString(cert *x509.Certificate) string {
if cert == nil {
return ""
}
return fmt.Sprintf("\n\tSubject=%s\n\tIssuer=%s\n\tIsCA=%v\n\tBasicConstraintsValid=%v\n\tNotBefore=%s\n\tNotAfter=%s\n\tFingerprint=%s\n\tSAN IP=%v\n\tSAN DNS=%v\n\tSAN URI=%v",
cert.Subject,
cert.Issuer,
cert.IsCA,
cert.BasicConstraintsValid,
cert.NotBefore,
cert.NotAfter,
Fingerprint(cert),
cert.IPAddresses,
cert.DNSNames,
cert.URIs,
)
}
func getCACerts(ca string) ([]*x509.Certificate, error) {
r, err := NewPEMReader(ca)
if err != nil {
return nil, fmt.Errorf("unable to read CA: %w", err)
}
defer r.Close()
p, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("unable to read ca %s: %w", r, err)
}
certs := make([]*x509.Certificate, 0)
// Loop below copied from go stdlib crypto/x509.CertPool.AppendCertsFromPem
for len(p) > 0 {
var block *pem.Block
block, p = pem.Decode(p)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
certBytes := block.Bytes
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
continue
}
certs = append(certs, cert)
}
return certs, nil
}