blob: f5fc6b19dd31848ef26d8f4732574e6fcce741fe [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF 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 ssl
import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"strings"
corev1 "k8s.io/api/core/v1"
)
var (
// ErrUnknownSecretFormat indicates the secret does not contain supported TLS data keys.
ErrUnknownSecretFormat = errors.New("unknown secret format")
// ErrMissingCert indicates the secret is missing the certificate part.
ErrMissingCert = errors.New("missing cert field")
// ErrMissingKey indicates the secret is missing the private key part when it is required.
ErrMissingKey = errors.New("missing key field")
// ErrInvalidPEM is returned when the provided certificate is not valid PEM encoded data.
ErrInvalidPEM = errors.New("certificate is not valid PEM data")
)
// ExtractKeyPair extracts the certificate and, optionally, the private key from a Secret.
//
// Supported formats:
// 1. APISIX style: data keys `cert` and `key`
// 2. Kubernetes TLS secret: data keys `tls.crt` and `tls.key`
// 3. Kubernetes CA secret: data key `ca.crt` (without private key)
func ExtractKeyPair(secret *corev1.Secret, includePrivateKey bool) ([]byte, []byte, error) {
if secret == nil {
return nil, nil, ErrMissingCert
}
if cert, ok := secret.Data["cert"]; ok {
if includePrivateKey {
key, ok := secret.Data["key"]
if !ok {
return nil, nil, ErrMissingKey
}
return cert, key, nil
}
return cert, nil, nil
}
if cert, ok := secret.Data[corev1.TLSCertKey]; ok {
if includePrivateKey {
key, ok := secret.Data[corev1.TLSPrivateKeyKey]
if !ok {
return nil, nil, ErrMissingKey
}
return cert, key, nil
}
return cert, nil, nil
}
if cert, ok := secret.Data[corev1.ServiceAccountRootCAKey]; ok && !includePrivateKey {
return cert, nil, nil
}
return nil, nil, ErrUnknownSecretFormat
}
// ExtractCertificate extracts only the certificate data from a Secret.
func ExtractCertificate(secret *corev1.Secret) ([]byte, error) {
cert, _, err := ExtractKeyPair(secret, false)
return cert, err
}
// ExtractHostsFromCertificate parses the certificate PEM block and returns the DNS names.
func ExtractHostsFromCertificate(certPEM []byte) ([]string, error) {
block, _ := pem.Decode(certPEM)
if block == nil {
return nil, ErrInvalidPEM
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
hosts := make([]string, 0, len(cert.DNSNames))
for _, dnsName := range cert.DNSNames {
if dnsName != "*" {
hosts = append(hosts, dnsName)
}
}
return hosts, nil
}
// NormalizeHosts removes duplicate entries
func NormalizeHosts(hosts []string) []string {
if len(hosts) == 0 {
return nil
}
normalized := make([]string, 0, len(hosts))
seen := make(map[string]struct{}, len(hosts))
for _, host := range hosts {
candidate := strings.ToLower(strings.TrimSpace(host))
if _, ok := seen[candidate]; ok {
continue
}
seen[candidate] = struct{}{}
normalized = append(normalized, candidate)
}
return normalized
}
// CertificateHash returns the SHA-256 hash of the leaf certificate contained in the PEM data.
// The hash is calculated from the DER-encoded bytes so that formatting differences (whitespace,
// line endings, certificate ordering) do not affect the result.
func CertificateHash(certPEM []byte) (string, error) {
block, _ := pem.Decode(certPEM)
if block == nil {
return "", ErrInvalidPEM
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", err
}
sum := sha256.Sum256(cert.Raw)
return hex.EncodeToString(sum[:]), nil
}