Skip to content

Instantly share code, notes, and snippets.

@nl5887
Last active April 30, 2020 07:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nl5887/bfae16972511f5cba0ff498ab316b78d to your computer and use it in GitHub Desktop.
Save nl5887/bfae16972511f5cba0ff498ab316b78d to your computer and use it in GitHub Desktop.
LoadCertificates will load all certificates and keys in multiple directories (using glob matching) and match certificates with their private keys.
certificates, err := LoadCertificates("**.key", "**.crt")
if err != nil {
log.Fatal("Error loading x509 cert", rz.Err(err), rz.Any("cert-file", tlsCert))
}
tlsConfig := &tls.Config{
Certificates: certificates,
}
package server
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"errors"
"os"
"path/filepath"
"encoding/pem"
"fmt"
"github.com/gobwas/glob"
"io/ioutil"
"strings"
)
func LoadCertificates(paths []string) ([]tls.Certificate, error) {
certs := []tls.Certificate{}
keys := []tls.Certificate{}
for _, p := range paths {
d, f := filepath.Split(p)
g := glob.MustCompile(f, '/')
err := filepath.Walk(d, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !g.Match(info.Name()) {
return nil
}
cert, err := addBlocks(path)
if err != nil {
return err
}
if len(cert.Certificate) > 0 {
certs = append(certs, cert)
}
if cert.PrivateKey != nil {
keys = append(keys, cert)
}
return nil
})
if err != nil {
return certs, err
}
}
for i := range certs {
// We don't need to parse the public key for TLS, but we so do anyway
// to check that it looks sane and matches the private key.
x509Cert, err := x509.ParseCertificate(certs[i].Certificate[0])
if err != nil {
return certs, nil
}
switch pub := x509Cert.PublicKey.(type) {
case *rsa.PublicKey:
for _, key := range keys {
priv, ok := key.PrivateKey.(*rsa.PrivateKey)
if !ok {
continue
}
if pub.N.Cmp(priv.N) != 0 {
continue
}
certs[i].PrivateKey = priv
break
}
case *ecdsa.PublicKey:
for _, key := range keys {
priv, ok := key.PrivateKey.(*ecdsa.PrivateKey)
if !ok {
continue
}
if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 {
continue
}
certs[i].PrivateKey = priv
break
}
case ed25519.PublicKey:
for _, key := range keys {
priv, ok := key.PrivateKey.(ed25519.PrivateKey)
if !ok {
continue
}
if !bytes.Equal(priv.Public().(ed25519.PublicKey), pub) {
continue
}
certs[i].PrivateKey = priv
break
}
default:
return certs, errors.New("tls: unknown public key algorithm")
}
}
return certs, nil
}
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
return key, nil
}
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
switch key := key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
return key, nil
default:
return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
}
}
if key, err := x509.ParseECPrivateKey(der); err == nil {
return key, nil
}
return nil, errors.New("tls: failed to parse private key")
}
func addBlocks(path string) (tls.Certificate, error) {
cert := tls.Certificate{}
raw, err := ioutil.ReadFile(path)
if err != nil {
return cert, err
}
for {
block, rest := pem.Decode(raw)
if block == nil {
break
}
if block.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, block.Bytes)
} else if !(block.Type == "PRIVATE KEY" || strings.HasSuffix(block.Type, " PRIVATE KEY")) {
} else if key, err := parsePrivateKey(block.Bytes); err != nil {
return cert, fmt.Errorf("Failure reading private key from \"%s\": %s", path, err)
} else {
cert.PrivateKey = key
}
raw = rest
}
return cert, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment