mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-30 10:56:28 +02:00
* envoy: support autocert * envoy: fallback to http host routing if sni fails to match * update comment * envoy: renew certs when necessary * fix tests
199 lines
5.3 KiB
Go
199 lines
5.3 KiB
Go
package cryptutil
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"net"
|
|
"time"
|
|
)
|
|
|
|
// CertificateFromBase64 returns an X509 pair from a base64 encoded blob.
|
|
func CertificateFromBase64(cert, key string) (*tls.Certificate, error) {
|
|
decodedCert, err := base64.StdEncoding.DecodeString(cert)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode certificate cert %v: %w", decodedCert, err)
|
|
}
|
|
decodedKey, err := base64.StdEncoding.DecodeString(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode certificate key %v: %w", decodedKey, err)
|
|
}
|
|
x509, err := tls.X509KeyPair(decodedCert, decodedKey)
|
|
return &x509, err
|
|
}
|
|
|
|
// CertificateFromFile given a certificate, and key file path, returns a X509
|
|
// keypair.
|
|
func CertificateFromFile(certFile, keyFile string) (*tls.Certificate, error) {
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
return &cert, err
|
|
}
|
|
|
|
// CertPoolFromBase64 takes a base64 encoded string and returns a new
|
|
// X509 certificate pool.
|
|
func CertPoolFromBase64(encPemCerts string) (*x509.CertPool, error) {
|
|
b, err := base64.StdEncoding.DecodeString(encPemCerts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't decode pem %v: %w", b, err)
|
|
}
|
|
return bytesToCertPool(b)
|
|
}
|
|
|
|
// CertPoolFromFile reads a file and returns an X509 certificate pool.
|
|
func CertPoolFromFile(pemFile string) (*x509.CertPool, error) {
|
|
b, err := ioutil.ReadFile(pemFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bytesToCertPool(b)
|
|
}
|
|
|
|
func bytesToCertPool(b []byte) (*x509.CertPool, error) {
|
|
certPool := x509.NewCertPool()
|
|
if ok := certPool.AppendCertsFromPEM(b); !ok {
|
|
return nil, fmt.Errorf("could append certs from PEM")
|
|
}
|
|
return certPool, nil
|
|
}
|
|
|
|
// DecodePublicKey decodes a PEM-encoded ECDSA public key.
|
|
func DecodePublicKey(encodedKey []byte) (*ecdsa.PublicKey, error) {
|
|
block, _ := pem.Decode(encodedKey)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("cryptutil: decoded nil PEM block")
|
|
}
|
|
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
|
|
if !ok {
|
|
return nil, errors.New("cryptutil: data was not an ECDSA public key")
|
|
}
|
|
|
|
return ecdsaPub, nil
|
|
}
|
|
|
|
// EncodePublicKey encodes an ECDSA public key to PEM format.
|
|
func EncodePublicKey(key *ecdsa.PublicKey) ([]byte, error) {
|
|
derBytes, err := x509.MarshalPKIXPublicKey(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block := &pem.Block{
|
|
Type: "PUBLIC KEY",
|
|
Bytes: derBytes,
|
|
}
|
|
|
|
return pem.EncodeToMemory(block), nil
|
|
}
|
|
|
|
// DecodePrivateKey decodes a PEM-encoded ECDSA private key.
|
|
func DecodePrivateKey(encodedKey []byte) (*ecdsa.PrivateKey, error) {
|
|
var skippedTypes []string
|
|
var block *pem.Block
|
|
|
|
for {
|
|
block, encodedKey = pem.Decode(encodedKey)
|
|
|
|
if block == nil {
|
|
return nil, fmt.Errorf("cryptutil: failed to find EC PRIVATE KEY in PEM data after skipping types %v", skippedTypes)
|
|
}
|
|
|
|
if block.Type == "EC PRIVATE KEY" {
|
|
break
|
|
} else {
|
|
skippedTypes = append(skippedTypes, block.Type)
|
|
continue
|
|
}
|
|
}
|
|
|
|
privKey, err := x509.ParseECPrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return privKey, nil
|
|
}
|
|
|
|
// EncodePrivateKey encodes an ECDSA private key to PEM format.
|
|
func EncodePrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
|
|
derKey, err := x509.MarshalECPrivateKey(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keyBlock := &pem.Block{
|
|
Type: "EC PRIVATE KEY",
|
|
Bytes: derKey,
|
|
}
|
|
|
|
return pem.EncodeToMemory(keyBlock), nil
|
|
}
|
|
|
|
// GenerateSelfSignedCertificate generates a self-signed TLS certificate.
|
|
//
|
|
// mostly copied from https://golang.org/src/crypto/tls/generate_cert.go
|
|
func GenerateSelfSignedCertificate(domain string) (*tls.Certificate, error) {
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to geneate private key: %w", err)
|
|
}
|
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate serial number: %w", err)
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
Organization: []string{"Pomerium"},
|
|
},
|
|
NotBefore: time.Now().Add(-time.Minute * 10),
|
|
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
}
|
|
if ip := net.ParseIP(domain); ip != nil {
|
|
template.IPAddresses = append(template.IPAddresses, ip)
|
|
} else {
|
|
template.DNSNames = append(template.DNSNames, domain)
|
|
}
|
|
|
|
publicKeyBytes, err := x509.CreateCertificate(rand.Reader,
|
|
&template, &template,
|
|
privateKey.Public(), privateKey,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create certificate: %w", err)
|
|
}
|
|
|
|
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal private key: %w", err)
|
|
}
|
|
|
|
cert, err := tls.X509KeyPair(
|
|
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicKeyBytes}),
|
|
pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert x509 bytes into tls certificate: %w", err)
|
|
}
|
|
|
|
return &cert, nil
|
|
}
|