elazarl / goproxy

An HTTP proxy library for Go
BSD 3-Clause "New" or "Revised" License
6.01k stars 1.09k forks source link

feat: should copy information from server when proxy generate certificate for client #405

Open xudejian opened 4 years ago

xudejian commented 4 years ago

we know that some app might verify the SNI or DNSName, and app might also delegate the webview's http connect, so we should copy the correct cert information from server, which would lower the possibility of fail when goproxy handshake with client.

following is my code in my practice.


var certs = make(map[string]*tls.Certificate)

func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) {
    return func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) {
        ctx.Logf("signing for %s", host)

                // should add the certstore back
        cert, err := getCert(host, ca)
        if err != nil {
            ctx.Warnf("Cannot sign host certificate with provided CA: %s", err)
            return nil, err
        }

        config := &tls.Config{
            InsecureSkipVerify: true,
            Certificates:       []tls.Certificate{*cert},
        }
        return config, nil
    }
}

func getCert(hostport string, ca *tls.Certificate) (*tls.Certificate, error) {
    hostname, _, _ := net.SplitHostPort(hostport)
    if cert, ok := certs[hostname]; ok {
        return cert, nil
    }

    conn, err := tls.Dial("tcp", hostport, &tls.Config{InsecureSkipVerify: true})
    if err != nil {
        fmt.Println("failed to connect:", hostport, "Err:", err)
        return nil, err
    }
    defer conn.Close()

    err = conn.Handshake()
    if err != nil {
        return nil, err
    }
    state := conn.ConnectionState()
    cert, err := signHost(ca, state.PeerCertificates[0], []string{hostname})
    if err != nil {
        return nil, err
    }
    certs[hostname] = cert
    return cert, err
}

func signHost(ca *tls.Certificate, orig *x509.Certificate, hosts []string) (cert *tls.Certificate, err error) {
        //
    for _, h := range hosts {
        if ip := net.ParseIP(h); ip != nil {
            template.IPAddresses = append(template.IPAddresses, ip)
        } else {
            template.DNSNames = append(template.DNSNames, h)
            template.Subject.CommonName = h
        }
    }
        //... insert code here, copy information from server
        if orig != nil {
        template.Subject = orig.Subject
        template.DNSNames = orig.DNSNames
        template.EmailAddresses = orig.EmailAddresses
        template.IPAddresses = orig.IPAddresses
        template.URIs = orig.URIs

        template.NotBefore = orig.NotBefore
        template.NotAfter = orig.NotAfter
        template.KeyUsage = orig.KeyUsage
        template.ExtKeyUsage = orig.ExtKeyUsage
    }
        //...

}
vbisbest commented 3 years ago

Hello. I was looking at implementing this but the code does not match the current master. There is no "orig" in signHost for example. Do you have a fork where you implemented this?

xudejian commented 3 years ago

Hello. I was looking at implementing this but the code does not match the current master. There is no "orig" in signHost for example. Do you have a fork where you implemented this?

this code could work with the current master, according to the example: https://github.com/elazarl/goproxy/blob/master/examples/goproxy-customca/cert.go

we can use our own TLSConfigFromCA to DIY the signHost function.