/tls/validate and /tls/configure -- do checks on private key, add more fields to certificate status, do keypair check last.

This commit is contained in:
Eugene Bujak 2019-02-15 15:16:25 +03:00 committed by Eugene Bujak
parent 81bb4aea78
commit cb97c221fd
2 changed files with 99 additions and 11 deletions

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"time"
"github.com/AdguardTeam/AdGuardHome/dhcpd" "github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/AdguardTeam/AdGuardHome/dnsfilter"
@ -73,9 +74,20 @@ type tlsConfigSettings struct {
// field ordering is not important -- these are for API and are recalculated on each run // field ordering is not important -- these are for API and are recalculated on each run
type tlsConfigStatus struct { type tlsConfigStatus struct {
NotAfter string `yaml:"-" json:"not_after,omitempty"` // certificate status
ValidChain bool `yaml:"-" json:"valid_chain"`
Subject string `yaml:"-" json:"subject,omitempty"`
Issuer string `yaml:"-" json:"issuer,omitempty"`
NotBefore time.Time `yaml:"-" json:"not_before,omitempty"`
NotAfter time.Time `yaml:"-" json:"not_after,omitempty"`
DNSNames []string `yaml:"-" json:"dns_names"`
StatusCertificate string `yaml:"-" json:"status_cert,omitempty"` StatusCertificate string `yaml:"-" json:"status_cert,omitempty"`
StatusKey string `yaml:"-" json:"status_key,omitempty"`
// key status
ValidKey bool `yaml:"-" json:"valid_key"`
KeyType string `yaml:"-" json:"key_type,omitempty"`
// warnings
Warning string `yaml:"-" json:"warning,omitempty"` Warning string `yaml:"-" json:"warning,omitempty"`
WarningValidation string `yaml:"-" json:"warning_validation,omitempty"` WarningValidation string `yaml:"-" json:"warning_validation,omitempty"`
} }

View File

@ -3,11 +3,15 @@ package main
import ( import (
"bytes" "bytes"
"context" "context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
@ -1097,16 +1101,13 @@ func handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
func validateCertificates(data tlsConfig) (tlsConfig, error) { func validateCertificates(data tlsConfig) (tlsConfig, error) {
var err error var err error
// clear out status for certificates
data.tlsConfigStatus = tlsConfigStatus{}
// check only public certificate separetely from the key
if data.CertificateChain != "" { if data.CertificateChain != "" {
log.Printf("got certificate: %s", data.CertificateChain) log.Printf("got certificate: %s", data.CertificateChain)
if data.PrivateKey != "" {
_, err = tls.X509KeyPair([]byte(data.CertificateChain), []byte(data.PrivateKey))
if err != nil {
return data, errorx.Decorate(err, "Invalid certificate or key")
}
}
// now do a more extended validation // now do a more extended validation
var certs []*pem.Block // PEM-encoded certificates var certs []*pem.Block // PEM-encoded certificates
var skippedBytes []string // skipped bytes var skippedBytes []string // skipped bytes
@ -1165,13 +1166,20 @@ func validateCertificates(data tlsConfig) (tlsConfig, error) {
if err != nil { if err != nil {
// let self-signed certs through // let self-signed certs through
data.WarningValidation = fmt.Sprintf("Your certificate does not verify: %s", err) data.WarningValidation = fmt.Sprintf("Your certificate does not verify: %s", err)
} else {
data.ValidChain = true
} }
// spew.Dump(chains) // spew.Dump(chains)
// update status // update status
if mainCert != nil { if mainCert != nil {
notAfter := mainCert.NotAfter notAfter := mainCert.NotAfter
data.NotAfter = notAfter.Format(time.RFC3339) data.Subject = mainCert.Subject.String()
data.Issuer = mainCert.Issuer.String()
data.NotAfter = notAfter
data.NotBefore = mainCert.NotBefore
data.DNSNames = mainCert.DNSNames
data.StatusCertificate = fmt.Sprintf("Certificate expires on %s", notAfter) //, valid for hostname %s", mainCert.NotAfter, mainCert.Subject.CommonName) data.StatusCertificate = fmt.Sprintf("Certificate expires on %s", notAfter) //, valid for hostname %s", mainCert.NotAfter, mainCert.Subject.CommonName)
if len(mainCert.DNSNames) == 1 { if len(mainCert.DNSNames) == 1 {
data.StatusCertificate += fmt.Sprintf(", valid for hostname %s", mainCert.DNSNames[0]) data.StatusCertificate += fmt.Sprintf(", valid for hostname %s", mainCert.DNSNames[0])
@ -1192,9 +1200,77 @@ func validateCertificates(data tlsConfig) (tlsConfig, error) {
} }
} }
// validate private key (right now the only validation possible is just parsing it)
if data.PrivateKey != "" {
// now do a more extended validation
var key *pem.Block // PEM-encoded certificates
var skippedBytes []string // skipped bytes
// go through all pem blocks, but take first valid pem block and drop the rest
pemblock := []byte(data.PrivateKey)
for {
var decoded *pem.Block
decoded, pemblock = pem.Decode(pemblock)
if decoded == nil {
break
}
if decoded.Type == "PRIVATE KEY" || strings.HasSuffix(decoded.Type, " PRIVATE KEY") {
key = decoded
break
} else {
skippedBytes = append(skippedBytes, decoded.Type)
}
}
if key == nil {
return data, fmt.Errorf("No valid keys were found")
}
// parse the decoded key
_, keytype, err := parsePrivateKey(key.Bytes)
if err != nil {
return data, errorx.Decorate(err, "Failed to parse private key")
}
data.ValidKey = true
data.KeyType = keytype
}
// if both are set, validate both in unison
if data.PrivateKey != "" && data.CertificateChain != "" {
_, err = tls.X509KeyPair([]byte(data.CertificateChain), []byte(data.PrivateKey))
if err != nil {
return data, errorx.Decorate(err, "Invalid certificate or key")
}
}
return data, nil return data, 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, string, error) {
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
return key, "RSA", nil
}
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
switch key := key.(type) {
case *rsa.PrivateKey:
return key, "RSA", nil
case *ecdsa.PrivateKey:
return key, "ECDSA", 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, "ECDSA", nil
}
return nil, "", errors.New("tls: failed to parse private key")
}
// unmarshalTLS handles base64-encoded certificates transparently // unmarshalTLS handles base64-encoded certificates transparently
func unmarshalTLS(r *http.Request) (tlsConfig, error) { func unmarshalTLS(r *http.Request) (tlsConfig, error) {
data := tlsConfig{} data := tlsConfig{}