1
0
mirror of https://github.com/coalaura/up.git synced 2025-07-18 21:53:23 +00:00

http3 wip

This commit is contained in:
Laura
2025-06-21 02:15:41 +02:00
parent 28443050cf
commit 1604b702fd
7 changed files with 237 additions and 50 deletions

View File

@ -8,13 +8,11 @@ import (
"encoding/hex"
"errors"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
type PinnedCertificate struct {
@ -135,31 +133,31 @@ func CertificateFingerprint(certificate *x509.Certificate) string {
return fmt.Sprintf("%s-%s", algo, hex.EncodeToString(sum[:]))
}
func PreFetchServerCertificate(store *CertificateStore, addr string) error {
conn, err := tls.DialWithDialer(&net.Dialer{
Timeout: 5 * time.Second,
}, "tcp", addr, &tls.Config{
InsecureSkipVerify: true,
})
func PreFetchServerCertificate(store *CertificateStore, hostname string, useHttp3 bool) error {
addr, err := EnsurePort(hostname)
if err != nil {
return err
}
var (
name string
certificate *x509.Certificate
)
if useHttp3 {
certificate, name, err = ResolveTLSCertificateHttp3(addr)
} else {
certificate, name, err = ResolveTLSCertificateHttp2(addr)
}
if err != nil {
return err
}
defer conn.Close()
state := conn.ConnectionState()
if len(state.PeerCertificates) == 0 {
return fmt.Errorf("no peer certificates")
}
certificate := state.PeerCertificates[0]
if certificate.Subject.CommonName != "up" {
return errors.New("invalid certificate subject")
}
name := state.ServerName
fingerprint := CertificateFingerprint(certificate)
if store.IsPinned(name, fingerprint) {
@ -180,12 +178,8 @@ func PreFetchServerCertificate(store *CertificateStore, addr string) error {
return store.Pin(name, fingerprint)
}
func NewPinnedClient(store *CertificateStore) *http.Client {
config := &tls.Config{
InsecureSkipVerify: true,
}
config.VerifyConnection = func(cs tls.ConnectionState) error {
func NewPinnedClient(store *CertificateStore, useHttp3 bool) *http.Client {
return NewHttpClient(func(cs tls.ConnectionState) error {
if len(cs.PeerCertificates) == 0 {
return errors.New("missing certificate")
}
@ -204,16 +198,5 @@ func NewPinnedClient(store *CertificateStore) *http.Client {
}
return nil
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: config,
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
IdleConnTimeout: 10 * time.Second,
},
}
}, useHttp3)
}

View File

@ -75,10 +75,9 @@ func Run(_ context.Context, cmd *cli.Command) error {
}
if found, _ := cfg.Get(hostname, "HostName"); found != "" {
hostname = found
if index := strings.Index(hostname, ":"); index != -1 {
hostname = hostname[:index]
hostname, err = StripPort(found)
if err != nil {
return err
}
}
@ -119,11 +118,19 @@ func Run(_ context.Context, cmd *cli.Command) error {
return fmt.Errorf("failed to load certificate store: %v", err)
}
if err = PreFetchServerCertificate(store, hostname); err != nil {
useHttp3 := cmd.Bool("http3")
if useHttp3 {
log.Println("Using http3 over udp")
} else {
log.Println("Using http2 over tcp")
}
if err = PreFetchServerCertificate(store, hostname, useHttp3); err != nil {
return err
}
client := NewPinnedClient(store)
client := NewPinnedClient(store, useHttp3)
log.Println("Requesting challenge...")

118
client/http.go Normal file
View File

@ -0,0 +1,118 @@
package client
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)
func GetHttp2Transport(verify func(tls.ConnectionState) error) *http.Transport {
return &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS13,
NextProtos: []string{"h2"},
InsecureSkipVerify: true,
VerifyConnection: verify,
},
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
IdleConnTimeout: 10 * time.Second,
ForceAttemptHTTP2: true,
}
}
func GetHttp3Transport(verify func(tls.ConnectionState) error) *http3.Transport {
return &http3.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS13,
NextProtos: []string{http3.NextProtoH3},
InsecureSkipVerify: true,
VerifyConnection: verify,
},
QUICConfig: &quic.Config{
HandshakeIdleTimeout: 5 * time.Second,
MaxIdleTimeout: 10 * time.Second,
},
}
}
func NewHttpClient(verify func(tls.ConnectionState) error, useHttp3 bool) *http.Client {
var transport http.RoundTripper
if useHttp3 {
transport = GetHttp2Transport(verify)
} else {
transport = GetHttp3Transport(verify)
}
return &http.Client{
Transport: transport,
}
}
func ResolveTLSCertificateHttp2(addr string) (*x509.Certificate, string, error) {
transport := GetHttp2Transport(nil)
conn, err := tls.DialWithDialer(&net.Dialer{
Timeout: 5 * time.Second,
}, "tcp", addr, transport.TLSClientConfig)
if err != nil {
return nil, "", err
}
defer conn.Close()
state := conn.ConnectionState()
if len(state.PeerCertificates) == 0 {
return nil, "", errors.New("no peer certificates")
}
return state.PeerCertificates[0], state.ServerName, nil
}
func ResolveTLSCertificateHttp3(addr string) (*x509.Certificate, string, error) {
transport := GetHttp3Transport(nil)
conn, err := quic.DialAddr(context.Background(), addr, transport.TLSClientConfig, transport.QUICConfig)
if err != nil {
return nil, "", err
}
defer conn.CloseWithError(quic.ApplicationErrorCode(0), "")
state := conn.ConnectionState().TLS
if len(state.PeerCertificates) == 0 {
return nil, "", errors.New("no peer certificates")
}
return state.PeerCertificates[0], state.ServerName, nil
}
func StripPort(hostname string) (string, error) {
host, _, err := net.SplitHostPort(hostname)
return host, err
}
func EnsurePort(hostname string) (string, error) {
host, port, err := net.SplitHostPort(hostname)
if err != nil {
return "", err
}
if port == "" {
port = "443"
}
return fmt.Sprintf("%s:%s", host, port), err
}