EDU BLOG

Feb 22, 2025

Implement a simple HTTPS proxy using Go

This is a simple implementation of an HTTPS forward proxy using Go, which supports Basic Authentication. It includes handling both HTTP and HTTPS requests, and runs over TLS (SSL).

Functionality:

  • Supports HTTPS CONNECT method (tunneling).
  • Supports regular HTTP requests.
  • Implements Basic Authentication for access control.
  • Runs on port 8443 (configurable).
  • Uses goroutines for bidirectional data transfer.

Code Implementation

Go Code for HTTPS Forward Proxy with Basic Authentication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main

import (
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"log"
"net"
"net/http"
"strings"
)

// ProxyAuth configuration
type ProxyAuth struct {
Username string
Password string
}

// Check Basic authentication
func (p *ProxyAuth) checkAuth(r *http.Request) bool {
auth := r.Header.Get("Proxy-Authorization")
if auth == "" {
return false
}

const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
return false
}

encoded := strings.TrimPrefix(auth, prefix)
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return false
}

credentials := strings.SplitN(string(decoded), ":", 2)
if len(credentials) != 2 {
return false
}

return credentials[0] == p.Username && credentials[1] == p.Password
}

// Handle CONNECT requests (HTTPS)
func handleTunneling(w http.ResponseWriter, r *http.Request, auth *ProxyAuth) {
if !auth.checkAuth(r) {
w.Header().Set("Proxy-Authenticate", `Basic realm="Proxy"`)
w.WriteHeader(http.StatusProxyAuthRequired)
return
}

destConn, err := net.Dial("tcp", r.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer destConn.Close()

w.WriteHeader(http.StatusOK)

hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
return
}

clientConn, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer clientConn.Close()

// Bidirectional data transfer
go func() {
_, _ = io.Copy(destConn, clientConn)
}()
_, _ = io.Copy(clientConn, destConn)
}

// Handle regular HTTP requests
func handleHTTP(w http.ResponseWriter, r *http.Request, auth *ProxyAuth) {
if !auth.checkAuth(r) {
w.Header().Set("Proxy-Authenticate", `Basic realm="Proxy"`)
w.WriteHeader(http.StatusProxyAuthRequired)
return
}

client := &http.Client{}
r.RequestURI = ""

resp, err := client.Do(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()

// Copy response headers
for k, vv := range resp.Header {
for _, v := range vv {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}

func main() {
// Configure proxy authentication
proxyAuth := &ProxyAuth{
Username: "admin",
Password: "password123",
}

// Configure TLS
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatalf("Failed to load certificate: %v", err)
}

tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
}

server := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
handleTunneling(w, r, proxyAuth)
} else {
handleHTTP(w, r, proxyAuth)
}
}),
}

fmt.Println("HTTPS Proxy Server starting on :8443")
log.Fatal(server.ListenAndServeTLS("", ""))
}

Usage Instructions

1. Generate SSL Certificate:

Before running the server, generate a self-signed certificate using OpenSSL:

1
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes

This will generate:

  • server.crt (SSL certificate)
  • server.key (SSL private key)

2. How to Use:

  • Set the proxy address to https://admin:password123@localhost:8443 in your browser or client.
  • Modify the authentication details in the code if needed.

3. Modify Authentication:

Update the Username and Password in the Go code:

1
2
3
4
proxyAuth := &ProxyAuth{
Username: "your_username",
Password: "your_password",
}

4. Compile and Run:

Run the proxy server with the following command:

1
go run proxy.go

The server will start and listen on https://localhost:8443.


Notes:

  • For production, consider using a valid SSL certificate rather than a self-signed certificate.
  • Add logging to improve debugging.
  • Enhance error handling as needed.
  • Consider adding concurrency limits to protect the server.

Features:

  • Basic Authentication: Ensures that only authorized users can access the proxy.
  • HTTPS CONNECT Method (Tunneling): Supports secure connections by tunneling HTTPS requests.
  • Regular HTTP Requests: Proxies regular HTTP requests as well.
  • TLS Configuration: Secures the proxy server with SSL/TLS encryption.

Code Structure:

  • ProxyAuth struct stores authentication info.
  • checkAuth method verifies Basic Authentication.
  • handleTunneling handles HTTPS CONNECT method (tunneling).
  • handleHTTP handles regular HTTP proxy requests.
  • main function configures and starts the HTTPS proxy server.

This is a simple, extendable HTTPS forward proxy that supports Basic Authentication. You can enhance it further with features like logging, access control, rate limiting, or caching based on your needs.

OLDER > < NEWER