2019-06-10 11:33:19 +03:00
package home
2019-02-27 18:53:16 +03:00
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
2019-08-13 12:32:52 +03:00
"io/ioutil"
2019-02-27 18:53:16 +03:00
"net/http"
2020-02-19 15:28:06 +03:00
"os"
2019-02-27 18:53:16 +03:00
"reflect"
"strings"
2020-02-19 15:28:06 +03:00
"sync"
2019-02-27 18:53:16 +03:00
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/joomcode/errorx"
)
2020-02-19 15:28:06 +03:00
var tlsWebHandlersRegistered = false
// TLSMod - TLS module object
type TLSMod struct {
certLastMod time . Time // last modification time of the certificate file
conf tlsConfigSettings
confLock sync . Mutex
status tlsConfigStatus
}
// Create TLS module
func tlsCreate ( conf tlsConfigSettings ) * TLSMod {
t := & TLSMod { }
t . conf = conf
if t . conf . Enabled {
if ! t . load ( ) {
return nil
}
t . setCertFileTime ( )
}
return t
}
func ( t * TLSMod ) load ( ) bool {
if ! tlsLoadConfig ( & t . conf , & t . status ) {
return false
}
// validate current TLS config and update warnings (it could have been loaded from file)
data := validateCertificates ( string ( t . conf . CertificateChainData ) , string ( t . conf . PrivateKeyData ) , t . conf . ServerName )
if ! data . ValidPair {
log . Error ( data . WarningValidation )
return false
}
t . status = data
return true
}
// Close - close module
func ( t * TLSMod ) Close ( ) {
}
// WriteDiskConfig - write config
func ( t * TLSMod ) WriteDiskConfig ( conf * tlsConfigSettings ) {
t . confLock . Lock ( )
* conf = t . conf
t . confLock . Unlock ( )
}
func ( t * TLSMod ) setCertFileTime ( ) {
if len ( t . conf . CertificatePath ) == 0 {
return
}
fi , err := os . Stat ( t . conf . CertificatePath )
if err != nil {
log . Error ( "TLS: %s" , err )
return
}
t . certLastMod = fi . ModTime ( ) . UTC ( )
}
// Start - start the module
func ( t * TLSMod ) Start ( ) {
if ! tlsWebHandlersRegistered {
tlsWebHandlersRegistered = true
t . registerWebHandlers ( )
}
t . confLock . Lock ( )
tlsConf := t . conf
t . confLock . Unlock ( )
Context . web . TLSConfigChanged ( tlsConf )
}
// Reload - reload certificate file
func ( t * TLSMod ) Reload ( ) {
t . confLock . Lock ( )
tlsConf := t . conf
t . confLock . Unlock ( )
if ! tlsConf . Enabled || len ( tlsConf . CertificatePath ) == 0 {
return
}
fi , err := os . Stat ( tlsConf . CertificatePath )
if err != nil {
log . Error ( "TLS: %s" , err )
return
}
if fi . ModTime ( ) . UTC ( ) . Equal ( t . certLastMod ) {
log . Debug ( "TLS: certificate file isn't modified" )
return
}
log . Debug ( "TLS: certificate file is modified" )
t . confLock . Lock ( )
r := t . load ( )
t . confLock . Unlock ( )
if ! r {
return
}
t . certLastMod = fi . ModTime ( ) . UTC ( )
_ = reconfigureDNSServer ( )
Context . web . TLSConfigChanged ( tlsConf )
}
2019-08-13 12:32:52 +03:00
// Set certificate and private key data
2020-02-19 15:28:06 +03:00
func tlsLoadConfig ( tls * tlsConfigSettings , status * tlsConfigStatus ) bool {
2019-08-13 12:32:52 +03:00
tls . CertificateChainData = [ ] byte ( tls . CertificateChain )
tls . PrivateKeyData = [ ] byte ( tls . PrivateKey )
var err error
if tls . CertificatePath != "" {
if tls . CertificateChain != "" {
status . WarningValidation = "certificate data and file can't be set together"
return false
}
tls . CertificateChainData , err = ioutil . ReadFile ( tls . CertificatePath )
if err != nil {
status . WarningValidation = err . Error ( )
return false
}
status . ValidCert = true
}
if tls . PrivateKeyPath != "" {
if tls . PrivateKey != "" {
status . WarningValidation = "private key data and file can't be set together"
return false
}
tls . PrivateKeyData , err = ioutil . ReadFile ( tls . PrivateKeyPath )
if err != nil {
status . WarningValidation = err . Error ( )
return false
}
status . ValidKey = true
}
return true
}
2020-02-19 15:28:06 +03:00
type tlsConfigStatus struct {
ValidCert bool ` json:"valid_cert" ` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates
ValidChain bool ` json:"valid_chain" ` // ValidChain is true if the specified certificates chain is verified and issued by a known CA
Subject string ` json:"subject,omitempty" ` // Subject is the subject of the first certificate in the chain
Issuer string ` json:"issuer,omitempty" ` // Issuer is the issuer of the first certificate in the chain
NotBefore time . Time ` json:"not_before,omitempty" ` // NotBefore is the NotBefore field of the first certificate in the chain
NotAfter time . Time ` json:"not_after,omitempty" ` // NotAfter is the NotAfter field of the first certificate in the chain
DNSNames [ ] string ` json:"dns_names" ` // DNSNames is the value of SubjectAltNames field of the first certificate in the chain
// key status
ValidKey bool ` json:"valid_key" ` // ValidKey is true if the key is a valid private key
KeyType string ` json:"key_type,omitempty" ` // KeyType is one of RSA or ECDSA
// is usable? set by validator
ValidPair bool ` json:"valid_pair" ` // ValidPair is true if both certificate and private key are correct
// warnings
WarningValidation string ` json:"warning_validation,omitempty" ` // WarningValidation is a validation warning message with the issue description
2019-02-27 18:53:16 +03:00
}
2020-02-19 15:28:06 +03:00
// field ordering is important -- yaml fields will mirror ordering from here
type tlsConfig struct {
tlsConfigSettings ` json:",inline" `
tlsConfigStatus ` json:",inline" `
2019-02-27 18:53:16 +03:00
}
2020-02-19 15:28:06 +03:00
func ( t * TLSMod ) handleTLSStatus ( w http . ResponseWriter , r * http . Request ) {
t . confLock . Lock ( )
data := tlsConfig {
tlsConfigSettings : t . conf ,
tlsConfigStatus : t . status ,
}
t . confLock . Unlock ( )
marshalTLS ( w , data )
}
func ( t * TLSMod ) handleTLSValidate ( w http . ResponseWriter , r * http . Request ) {
setts , err := unmarshalTLS ( r )
2019-02-27 18:53:16 +03:00
if err != nil {
httpError ( w , http . StatusBadRequest , "Failed to unmarshal TLS config: %s" , err )
return
}
2020-02-19 15:28:06 +03:00
if ! WebCheckPortAvailable ( setts . PortHTTPS ) {
httpError ( w , http . StatusBadRequest , "port %d is not available, cannot enable HTTPS on it" , setts . PortHTTPS )
return
2019-02-27 18:53:16 +03:00
}
2019-08-13 12:32:52 +03:00
status := tlsConfigStatus { }
2020-02-19 15:28:06 +03:00
if tlsLoadConfig ( & setts , & status ) {
status = validateCertificates ( string ( setts . CertificateChainData ) , string ( setts . PrivateKeyData ) , setts . ServerName )
2019-08-13 12:32:52 +03:00
}
2020-02-19 15:28:06 +03:00
data := tlsConfig {
tlsConfigSettings : setts ,
tlsConfigStatus : status ,
}
2019-02-27 18:53:16 +03:00
marshalTLS ( w , data )
}
2020-02-19 15:28:06 +03:00
func ( t * TLSMod ) handleTLSConfigure ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:53:16 +03:00
data , err := unmarshalTLS ( r )
if err != nil {
httpError ( w , http . StatusBadRequest , "Failed to unmarshal TLS config: %s" , err )
return
}
2020-02-19 15:28:06 +03:00
if ! WebCheckPortAvailable ( data . PortHTTPS ) {
httpError ( w , http . StatusBadRequest , "port %d is not available, cannot enable HTTPS on it" , data . PortHTTPS )
return
2019-02-27 18:53:16 +03:00
}
2019-08-13 12:32:52 +03:00
status := tlsConfigStatus { }
if ! tlsLoadConfig ( & data , & status ) {
2020-02-19 15:28:06 +03:00
data2 := tlsConfig {
tlsConfigSettings : data ,
tlsConfigStatus : t . status ,
}
marshalTLS ( w , data2 )
2019-08-13 12:32:52 +03:00
return
}
2020-02-19 15:28:06 +03:00
status = validateCertificates ( string ( data . CertificateChainData ) , string ( data . PrivateKeyData ) , data . ServerName )
2019-02-27 18:53:16 +03:00
restartHTTPS := false
2020-02-19 15:28:06 +03:00
t . confLock . Lock ( )
if ! reflect . DeepEqual ( t . conf , data ) {
2019-02-27 18:53:16 +03:00
log . Printf ( "tls config settings have changed, will restart HTTPS server" )
restartHTTPS = true
}
2020-02-19 15:28:06 +03:00
t . conf = data
t . status = status
t . confLock . Unlock ( )
t . setCertFileTime ( )
onConfigModified ( )
err = reconfigureDNSServer ( )
2019-02-27 18:53:16 +03:00
if err != nil {
2020-02-19 15:28:06 +03:00
httpError ( w , http . StatusInternalServerError , "%s" , err )
2019-02-27 18:53:16 +03:00
return
}
2020-02-19 15:28:06 +03:00
data2 := tlsConfig {
tlsConfigSettings : data ,
tlsConfigStatus : t . status ,
}
marshalTLS ( w , data2 )
2019-02-27 18:53:16 +03:00
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
if restartHTTPS {
go func ( ) {
time . Sleep ( time . Second ) // TODO: could not find a way to reliably know that data was fully sent to client by https server, so we wait a bit to let response through before closing the server
2020-02-19 15:28:06 +03:00
Context . web . TLSConfigChanged ( data )
2019-02-27 18:53:16 +03:00
} ( )
}
}
func verifyCertChain ( data * tlsConfigStatus , certChain string , serverName string ) error {
2019-10-21 16:07:55 +03:00
log . Tracef ( "TLS: got certificate: %d bytes" , len ( certChain ) )
2019-02-27 18:53:16 +03:00
// now do a more extended validation
var certs [ ] * pem . Block // PEM-encoded certificates
var skippedBytes [ ] string // skipped bytes
pemblock := [ ] byte ( certChain )
for {
var decoded * pem . Block
decoded , pemblock = pem . Decode ( pemblock )
if decoded == nil {
break
}
if decoded . Type == "CERTIFICATE" {
certs = append ( certs , decoded )
} else {
skippedBytes = append ( skippedBytes , decoded . Type )
}
}
var parsedCerts [ ] * x509 . Certificate
for _ , cert := range certs {
parsed , err := x509 . ParseCertificate ( cert . Bytes )
if err != nil {
data . WarningValidation = fmt . Sprintf ( "Failed to parse certificate: %s" , err )
2019-02-28 11:55:16 +03:00
return errors . New ( data . WarningValidation )
2019-02-27 18:53:16 +03:00
}
parsedCerts = append ( parsedCerts , parsed )
}
if len ( parsedCerts ) == 0 {
data . WarningValidation = fmt . Sprintf ( "You have specified an empty certificate" )
2019-02-28 11:55:16 +03:00
return errors . New ( data . WarningValidation )
2019-02-27 18:53:16 +03:00
}
data . ValidCert = true
// spew.Dump(parsedCerts)
opts := x509 . VerifyOptions {
DNSName : serverName ,
2020-03-04 15:11:17 +03:00
Roots : Context . tlsRoots ,
2019-02-27 18:53:16 +03:00
}
log . Printf ( "number of certs - %d" , len ( parsedCerts ) )
if len ( parsedCerts ) > 1 {
// set up an intermediate
pool := x509 . NewCertPool ( )
for _ , cert := range parsedCerts [ 1 : ] {
log . Printf ( "got an intermediate cert" )
pool . AddCert ( cert )
}
opts . Intermediates = pool
}
// TODO: save it as a warning rather than error it out -- shouldn't be a big problem
mainCert := parsedCerts [ 0 ]
_ , err := mainCert . Verify ( opts )
if err != nil {
// let self-signed certs through
data . WarningValidation = fmt . Sprintf ( "Your certificate does not verify: %s" , err )
} else {
data . ValidChain = true
}
// spew.Dump(chains)
// update status
if mainCert != nil {
notAfter := mainCert . NotAfter
data . Subject = mainCert . Subject . String ( )
data . Issuer = mainCert . Issuer . String ( )
data . NotAfter = notAfter
data . NotBefore = mainCert . NotBefore
data . DNSNames = mainCert . DNSNames
}
return nil
}
func validatePkey ( data * tlsConfigStatus , pkey string ) error {
// 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 ( pkey )
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 {
data . WarningValidation = "No valid keys were found"
2019-02-28 11:55:16 +03:00
return errors . New ( data . WarningValidation )
2019-02-27 18:53:16 +03:00
}
// parse the decoded key
_ , keytype , err := parsePrivateKey ( key . Bytes )
if err != nil {
data . WarningValidation = fmt . Sprintf ( "Failed to parse private key: %s" , err )
2019-02-28 11:55:16 +03:00
return errors . New ( data . WarningValidation )
2019-02-27 18:53:16 +03:00
}
data . ValidKey = true
data . KeyType = keytype
return nil
}
2019-02-28 15:28:38 +03:00
// Process certificate data and its private key.
// All parameters are optional.
// On error, return partially set object
// with 'WarningValidation' field containing error description.
2019-02-27 18:53:16 +03:00
func validateCertificates ( certChain , pkey , serverName string ) tlsConfigStatus {
var data tlsConfigStatus
// check only public certificate separately from the key
if certChain != "" {
if verifyCertChain ( & data , certChain , serverName ) != nil {
return data
}
}
// validate private key (right now the only validation possible is just parsing it)
if pkey != "" {
if validatePkey ( & data , pkey ) != nil {
return data
}
}
// if both are set, validate both in unison
if pkey != "" && certChain != "" {
_ , err := tls . X509KeyPair ( [ ] byte ( certChain ) , [ ] byte ( pkey ) )
if err != nil {
data . WarningValidation = fmt . Sprintf ( "Invalid certificate or key: %s" , err )
return data
}
data . ValidPair = true
}
return data
}
// 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
2020-02-19 15:28:06 +03:00
func unmarshalTLS ( r * http . Request ) ( tlsConfigSettings , error ) {
data := tlsConfigSettings { }
2019-02-27 18:53:16 +03:00
err := json . NewDecoder ( r . Body ) . Decode ( & data )
if err != nil {
return data , errorx . Decorate ( err , "Failed to parse new TLS config json" )
}
if data . CertificateChain != "" {
certPEM , err := base64 . StdEncoding . DecodeString ( data . CertificateChain )
if err != nil {
return data , errorx . Decorate ( err , "Failed to base64-decode certificate chain" )
}
data . CertificateChain = string ( certPEM )
2019-08-13 12:32:52 +03:00
if data . CertificatePath != "" {
return data , fmt . Errorf ( "certificate data and file can't be set together" )
}
2019-02-27 18:53:16 +03:00
}
if data . PrivateKey != "" {
keyPEM , err := base64 . StdEncoding . DecodeString ( data . PrivateKey )
if err != nil {
return data , errorx . Decorate ( err , "Failed to base64-decode private key" )
}
data . PrivateKey = string ( keyPEM )
2019-08-13 12:32:52 +03:00
if data . PrivateKeyPath != "" {
return data , fmt . Errorf ( "private key data and file can't be set together" )
}
2019-02-27 18:53:16 +03:00
}
return data , nil
}
func marshalTLS ( w http . ResponseWriter , data tlsConfig ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
if data . CertificateChain != "" {
encoded := base64 . StdEncoding . EncodeToString ( [ ] byte ( data . CertificateChain ) )
data . CertificateChain = encoded
}
if data . PrivateKey != "" {
encoded := base64 . StdEncoding . EncodeToString ( [ ] byte ( data . PrivateKey ) )
data . PrivateKey = encoded
}
err := json . NewEncoder ( w ) . Encode ( data )
if err != nil {
httpError ( w , http . StatusInternalServerError , "Failed to marshal json with TLS status: %s" , err )
return
}
}
2020-02-19 15:28:06 +03:00
// registerWebHandlers registers HTTP handlers for TLS configuration
func ( t * TLSMod ) registerWebHandlers ( ) {
httpRegister ( "GET" , "/control/tls/status" , t . handleTLSStatus )
httpRegister ( "POST" , "/control/tls/configure" , t . handleTLSConfigure )
httpRegister ( "POST" , "/control/tls/validate" , t . handleTLSValidate )
}