// tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
//              manager, WARC/geminispace browser
// Copyright (C) 2021-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package tofuproxy

import (
	"crypto/sha256"
	"crypto/sha512"
	"crypto/x509"
	"encoding/hex"
	"fmt"
	"log"

	"github.com/miekg/dns"
)

var DNSSrv string

func DANE(addr string, cert *x509.Certificate) (bool, bool) {
	if DNSSrv == "" {
		return false, false
	}
	host, port, err := SplitHostPort(addr)
	if err != nil {
		log.Printf("can not split host+port: %s: %+v\n", addr, err)
		return false, false
	}
	if port == "" {
		port = "443"
	}
	m := new(dns.Msg)
	m.SetQuestion(dns.Fqdn(fmt.Sprintf("_%s._tcp.%s", port, host)), dns.TypeTLSA)
	msg, err := dns.Exchange(m, DNSSrv)
	if err != nil {
		log.Printf("DNS: %+v\n", err)
		return false, false
	}
	if msg.Rcode != dns.RcodeSuccess {
		return false, false
	}
	exists := false
	for _, answer := range msg.Answer {
		tlsa, ok := answer.(*dns.TLSA)
		if !ok {
			continue
		}
		if tlsa.Usage != 3 {
			// Non EE
			continue
		}
		exists = true
		var toMatch []byte
		switch tlsa.Selector {
		case 0:
			toMatch = cert.Raw
		case 1:
			toMatch = cert.RawSubjectPublicKeyInfo
		}
		var hsh []byte
		switch tlsa.MatchingType {
		case 0:
			hsh = toMatch
		case 1:
			our := sha256.Sum256(toMatch)
			hsh = our[:]
		case 2:
			our := sha512.Sum512(toMatch)
			hsh = our[:]
		}
		if tlsa.Certificate == hex.EncodeToString(hsh) {
			return true, true
		}
	}
	return exists, false
}
