package main

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"math/big"
	"strings"
	"time"
)

// tlsConfig contains the configuration for generating, serving and cleaning up Let's Encrypt certificates.
var tlsConfig = &tls.Config{
	GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
		// TODO: check DNS name & get certificate from Let's Encrypt
		return FallbackCertificate(), nil
	},
	PreferServerCipherSuites: true,

	// generated 2021-07-13, Mozilla Guideline v5.6, Go 1.14.4, intermediate configuration
	// https://ssl-config.mozilla.org/#server=go&version=1.14.4&config=intermediate&guideline=5.6
	MinVersion: tls.VersionTLS12,
	CipherSuites: []uint16{
		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
		tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
	},
}

// GetHSTSHeader returns a HSTS header with includeSubdomains & preload for MainDomainSuffix and RawDomain, or an empty
// string for custom domains.
func GetHSTSHeader(host []byte) string {
	if bytes.HasSuffix(host, MainDomainSuffix) || bytes.Equal(host, RawDomain) {
		return "max-age=63072000; includeSubdomains; preload"
	} else {
		return ""
	}
}

var fallbackCertificate *tls.Certificate
// FallbackCertificate generates a new self-signed TLS certificate on demand.
func FallbackCertificate() *tls.Certificate {
	if fallbackCertificate != nil {
		return fallbackCertificate
	}

	fallbackSerial, err := rand.Int(rand.Reader, (&big.Int{}).Lsh(big.NewInt(1), 159))
	if err != nil {
		panic(err)
	}

	fallbackCertKey, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		panic(err)
	}

	fallbackCertSpecification := &x509.Certificate{
		Subject: pkix.Name{
			CommonName: strings.TrimPrefix(string(MainDomainSuffix), "."),
		},
		SerialNumber: fallbackSerial,
		NotBefore: time.Now(),
		NotAfter: time.Now().AddDate(100, 0, 0),
	}

	fallbackCertBytes, err := x509.CreateCertificate(
		rand.Reader,
		fallbackCertSpecification,
		fallbackCertSpecification,
		fallbackCertKey.Public(),
		fallbackCertKey,
	)
	if err != nil {
		panic(err)
	}

	fallbackCert, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{
		Bytes: fallbackCertBytes,
		Type: "CERTIFICATE",
	}), pem.EncodeToMemory(&pem.Block{
		Bytes: x509.MarshalPKCS1PrivateKey(fallbackCertKey),
		Type: "RSA PRIVATE KEY",
	}))
	if err != nil {
		panic(err)
	}

	fallbackCertificate = &fallbackCert
	return fallbackCertificate
}