package iroh

import (
	
	
	
	
	

	tls 
	
)

// alpn values and TLS parameters shared by iroh peers. iroh authenticates peers
// with TLS 1.3 raw public keys (RFC 7250): each endpoint presents its ed25519
// public key as a bare SubjectPublicKeyInfo, and verifies the peer's key rather
// than any X.509 chain. See [github.com/tmc/go-iroh/internal/itls/tls] for the
// raw-public-key TLS implementation.

// base32DNSSEC is data_encoding::BASE32_DNSSEC: RFC 4648 base32hex, lowercase,
// unpadded. iroh encodes an EndpointID into the TLS server name with it (see
// [ServerName]); it differs from the z-base-32 used for human-facing key
// strings.
var base32DNSSEC = base32.NewEncoding("0123456789abcdefghijklmnopqrstuv").WithPadding(base32.NoPadding)

// tlsNameSuffix is appended to the base32 endpoint id to form the TLS server
// name. The .invalid TLD (RFC 2606) never resolves; the iroh label is a
// protocol marker.
const tlsNameSuffix = ".iroh.invalid"

// KeyExchangePolicy selects the TLS key-exchange groups offered by an
// endpoint. The zero value uses the package default.
type KeyExchangePolicy uint8

const (
	// KeyExchangeDefault uses the package default key-exchange groups.
	KeyExchangeDefault KeyExchangePolicy = iota
	// KeyExchangeClassical disables post-quantum key exchange.
	KeyExchangeClassical
	// KeyExchangePreferPQ prefers X25519MLKEM768 and retains classical fallback.
	KeyExchangePreferPQ
	// KeyExchangePQOnly requires X25519MLKEM768.
	KeyExchangePQOnly
)

func ( KeyExchangePolicy) () bool { return  <= KeyExchangePQOnly }

func ( KeyExchangePolicy) () []tls.CurveID {
	switch  {
	case KeyExchangeClassical:
		return []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521}
	case KeyExchangePreferPQ:
		return []tls.CurveID{tls.X25519MLKEM768, tls.X25519, tls.CurveP256, tls.CurveP384}
	case KeyExchangePQOnly:
		return []tls.CurveID{tls.X25519MLKEM768}
	default:
		return nil
	}
}

// ServerName returns the TLS server name (SNI) iroh uses to address id:
// BASE32_DNSSEC(id) + ".iroh.invalid". A dialing endpoint puts this in its
// ClientHello; the accepting endpoint proves it holds id by presenting id as
// its raw public key. Deriving the name from the id (rather than a constant)
// also keeps per-peer 0-RTT session tickets in separate cache buckets.
func ( key.EndpointID) string {
	 := .Bytes()
	return base32DNSSEC.EncodeToString([:]) + tlsNameSuffix
}

// endpointIDFromServerName is the inverse of [ServerName]. It reports whether
// name is a well-formed iroh server name and, if so, the encoded endpoint id.
func endpointIDFromServerName( string) (key.EndpointID, bool) {
	,  := strings.CutSuffix(, tlsNameSuffix)
	if ! || strings.Contains(, ".") {
		return key.EndpointID{}, false
	}
	,  := base32DNSSEC.DecodeString()
	if  != nil || len() != key.PublicKeySize {
		return key.EndpointID{}, false
	}
	,  := key.EndpointIDFromSlice()
	if  != nil {
		return key.EndpointID{}, false
	}
	return , true
}

// rawKeyCertificate builds the RFC 7250 certificate for sk: the leaf is sk's
// ed25519 public key as a SubjectPublicKeyInfo, signed under sk during the
// handshake.
func rawKeyCertificate( key.SecretKey) (tls.Certificate, error) {
	 := .Ed25519()
	 := .Public().(ed25519.PublicKey)
	return tls.MarshalRawPublicKeyCertificate(, )
}

// peerEndpointID extracts the peer's endpoint id from a completed raw-public-key
// TLS handshake. It is the public key carried in the single peer certificate.
func peerEndpointID( tls.ConnectionState) (key.EndpointID, error) {
	if len(.PeerCertificates) != 1 {
		return key.EndpointID{}, fmt.Errorf("iroh: expected exactly one peer certificate, got %d", len(.PeerCertificates))
	}
	,  := .PeerCertificates[0].PublicKey.(ed25519.PublicKey)
	if ! {
		return key.EndpointID{}, errors.New("iroh: peer key is not ed25519")
	}
	,  := key.EndpointIDFromSlice()
	if  != nil {
		return key.EndpointID{}, fmt.Errorf("iroh: peer key: %w", )
	}
	return , nil
}

// clientTLSConfig builds the TLS configuration a dialing endpoint uses to
// connect to want. It presents sk as a raw public key and verifies that the
// peer's key equals want — the same check iroh's ServerCertificateVerifier
// performs by decoding the dialed server name (RFC 7250 server auth).
//
// Session tickets are enabled and stored in cache so a repeat dial to want can
// resume with 0-RTT early data. iroh derives a unique [ServerName] per peer, so
// the cache keys tickets by identity automatically. cache may be nil to opt out
// of resumption (the connection then always performs a full handshake). This
// mirrors the Rust client config, which enables early data and stores tickets
// in a ClientSessionMemoryCache (iroh/src/tls.rs:86-87).
func clientTLSConfig( key.SecretKey,  key.EndpointID,  []string,  tls.ClientSessionCache) (*tls.Config, error) {
	return clientTLSConfigWithCurves(, , , , nil)
}

func clientTLSConfigWithCurves( key.SecretKey,  key.EndpointID,  []string,  tls.ClientSessionCache,  []tls.CurveID) (*tls.Config, error) {
	,  := rawKeyCertificate()
	if  != nil {
		return nil, 
	}
	return &tls.Config{
		Certificates:           []tls.Certificate{},
		RawPublicKeys:          true,
		MinVersion:             tls.VersionTLS13,
		MaxVersion:             tls.VersionTLS13,
		SessionTicketsDisabled:  == nil,
		ClientSessionCache:     ,
		CurvePreferences:       ,
		NextProtos:             ,
		ServerName:             ServerName(),
		InsecureSkipVerify:     true, // chain verification is replaced by VerifyConnection
		VerifyConnection: func( tls.ConnectionState) error {
			,  := peerEndpointID()
			if  != nil {
				return 
			}
			if !.Equal() {
				return fmt.Errorf("iroh: server identity mismatch: dialed %s, got %s", , )
			}
			return nil
		},
	}, nil
}

// serverTLSConfig builds the TLS configuration an accepting endpoint uses. It
// presents sk as a raw public key and requires the client to do the same; the
// client's identity is learned from its certificate after the handshake (iroh's
// server does not check the client key against anything, mirroring
// ClientCertificateVerifier).
//
// Session tickets are enabled so the server issues a NewSessionTicket the client
// can later resume with for 0-RTT. The QUIC layer advertises max_early_data_size
// = u32::MAX on those tickets when 0-RTT acceptance is enabled (RFC 9001 §4.6.1,
// iroh/src/tls.rs:118); the iroh server opts in via [quic.Config.Allow0RTT].
func serverTLSConfig( key.SecretKey,  []string) (*tls.Config, error) {
	return serverTLSConfigWithCurves(, , nil)
}

func serverTLSConfigWithCurves( key.SecretKey,  []string,  []tls.CurveID) (*tls.Config, error) {
	,  := rawKeyCertificate()
	if  != nil {
		return nil, 
	}
	return &tls.Config{
		Certificates:           []tls.Certificate{},
		RawPublicKeys:          true,
		MinVersion:             tls.VersionTLS13,
		MaxVersion:             tls.VersionTLS13,
		SessionTicketsDisabled: false,
		CurvePreferences:       ,
		NextProtos:             ,
		ClientAuth:             tls.RequireAnyClientCert,
		InsecureSkipVerify:     true,
		VerifyConnection: func( tls.ConnectionState) error {
			// Authenticate that a client key is present and parseable; the
			// signature over the handshake transcript proves possession. The
			// concrete identity is surfaced to the application post-handshake.
			,  := peerEndpointID()
			return 
		},
	}, nil
}