Skip to content
This repository has been archived by the owner on Feb 29, 2024. It is now read-only.

Commit

Permalink
feat: auto generate cert
Browse files Browse the repository at this point in the history
  • Loading branch information
CoolSpring8 committed Jun 23, 2020
1 parent b4fa514 commit d753a26
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 120 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@
*.code-workspace

# Local History for Visual Studio Code
.history/
.history/

# Other Runtime-Generated Files
*.crt
*.key
52 changes: 17 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,37 @@

RVPN Web Portal Proxy Adapter (based on MITM)

尝试将[浙江大学 RVPN 网页版](https://rvpn.zju.edu.cn)模拟成一个 HTTP 代理。
因为不想在电脑上安装 EasyConnect,所以做了一个访问 ZJU 校内网站的轻量级方案——将 [浙江大学 RVPN 网页版](https://rvpn.zju.edu.cn) 模拟为本地 HTTP 代理。

提出这个想法的讨论:[有没有将 CGI Proxy 转化为普通 HTTP Proxy 的工具呢? - V2EX](https://www.v2ex.com/t/670356)
## 使用

1. 前往 [Releases](https://github.com/CoolSpring8/rwppa/releases/) 下载并放于合适位置;
2. 运行,输入上网服务账号、密码(即用于校内拨号 VPN 的账号)以及监听端口(如“127.0.0.1:8080”代表监听 8080 端口,仅接受本机流量)。账号密码仅会被发送至 rvpn.zju.edu.cn,用于登录认证。
3. 点击登录后别着急,先将程序同目录下生成的 rootCA.crt 安装至“受信任的根证书颁发机构”存储区。(之后运行不需要这一步)
4. 用 SwitchyOmega 等设置好代理,start browsing!

注意不要删除 rootCA.crt 和 rootCA.key,否则需要重新添加信任。

## 特性及局限

### 特性:

- 配置完毕并登录后,可访问校内校外 HTTP / HTTPS 网站(例如 CC98,正版软件服务与管理平台等),原理基于模拟 RVPN 网页版。
- 支持 HTTP 的 GET,POST,PUT,DELETE,HEAD 方法,一定程度上支持 OPTIONS 方法(模拟)。
- 配置完毕并登录后,可访问校内校外 HTTP / HTTPS 网站(例如 CC98,正版软件服务与管理平台等)。
- 支持 HTTP 的 GET,POST,PUT,DELETE,HEAD 方法,一定程度上支持 OPTIONS 方法(仅为模拟)。
- 正确处理大部分网页及网页资源。如果碰到问题,欢迎提 Issue/PR !

### 局限:

- **不支持 WebSocket,FTP,SSH等非 HTTP 协议。**
- **不支持 WebSocket,FTP,SSH等非 HTTP 协议。**你可能会想了解 [Hagb/docker-easyconnect](https://github.com/Hagb/docker-easyconnect) 项目。
- 不支持 HTTP 的 PATCH 等方法。
- OPTIONS 方法由于 RVPN 网页版不支持,所以实际不会向服务器发送请求,而是由 rwppa 直接返回一个针对 CORS 预检而配置的较为宽松的结果,安全性有所降低。
- 不支持 TLS 1.3 Early Data(0-RTT)。若原网站启用过 TLS 1.3 Early Data且 session ticket 还没过期则会[无法访问](https://golang.org/src/crypto/tls/handshake_server_tls13.go)(如 V2EX)。(但是短期内大概不会有开启这个特性的校内网站吧。)

## 使用

1. ```bash
openssl genrsa -out rootCA.key 4096
```

2. ```bash
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt
```

3. 把 rootCA.crt 安装到受信任证书存储区;

4. 将 rootCA.key 和 rootCA.crt 的内容填入 cert.go;

5. ```
go build
```

6. 在浏览器登录 rvpn.zju.edu.cn,从 Cookies 中找到 TWFID 字段的值;

7. ```
./rwppa
```

1~4 是可选的。你也可以信任并使用 cert.go 自带的证书,但出于安全性考虑不推荐这样做。

由于使用了 Fyne GUI,现在编译还需要 GCC 等额外依赖。
- 不支持 TLS 1.3 Early Data(0-RTT)。若原网站启用过 TLS 1.3 Early Data 且 session ticket 还没过期则会[无法访问](https://golang.org/src/crypto/tls/handshake_server_tls13.go)(如 V2EX)。(但是短期内大概不会有开启这个特性的校内网站吧。)

## 其他

欢迎 PR 改进!
提出这个想法的最初讨论:[有没有将 CGI Proxy 转化为普通 HTTP Proxy 的工具呢? - V2EX](https://www.v2ex.com/t/670356)

欢迎提出 Issue 或 PR!

## LICENSE

Expand Down
169 changes: 86 additions & 83 deletions cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,95 +31,84 @@
package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"io/ioutil"
"math/big"
"os"
"time"

"github.com/elazarl/goproxy"
)

var caCert = []byte(`-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIUdWndr4j2nJH3C+DfjL0sqG8PIMQwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA2MTgwNzQ1NDBaFw0yMzA0
MDgwNzQ1NDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDHiQ7JbOCvmEw+VYQY+4iLwtTHe9GyH3zXYujeeqC5
rcBU5MBD8NyqBjSDi/JPXEiCpBkYzmDtmqiCEmuoi8r6LADoJUdo2QUzu+S9UVt7
FqgXRNzZC5Enh1F9m/pkyz7+GtuQB4PIQGf74FNlza5Bd+9NmJrDYKxj6ydXoXTS
hTQrMEx3Wy8PzqpJw1006/So8Nh4jeu41DJr43bcO2J09/MpOd9pzB2GiDgrHu5M
5EAUJONWut62oWT42PYXzP2MRZPRmcd70/u5IX3LsV5Pi6XVqsFjjmIYnvKOa9rw
XICiCVdv5374CLwlTh4AnsstIjKpGOfWNx2cPXEDHtUQUXW+aaoNTR8gZOEs68qr
EpKz87xbl4pLo33nOFOSs0JU24nRGfLwOF3vN/usPqFEUyqquSWSOWfm3XPgKZlm
JrpmI/MgJ6YZunEujdvogT479RZ/IGq/Z/MhZaJj4iOcdq0iZqPUaKtV7v1lrl8S
1Z2O5fiVdoc/95n1D2ZQW7M5/+d9RiGuGSfVD95VA5WmQdGhRlV+MdeoGvvC+/F3
1pMl61YBvGc7jpPM6GA535Cmb9yXZFiKsutTHuDWEMngjfvlFV2qrxNy9PX+AQ+W
XbidvCZKQqxE6fgnFIQxsI82ZCkdddI9d9ifMt4qkFNeJDbyeNxPuea0qb4752er
5wIDAQABo1MwUTAdBgNVHQ4EFgQUYB+DRN17B+CMNJCRMRlvrR26HbAwHwYDVR0j
BBgwFoAUYB+DRN17B+CMNJCRMRlvrR26HbAwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEAuWn+Vea4esUk5HCZH5KVd43jhoN5qZ0EOVB0jNQ+JayW
/n3FO+o6N+CTqT1gU11uUp1H54N24s6oj+a/VuFzy38Delu34qKA8Y+rSnM/IgxW
Uo6M1exgPQ0KqSZvbiJ0JamdIaO14OL5hS7ZzNWo3ZnvDaG04tfOAUPPAIqRt1cq
ilRemHc4WmJ/U/7Vf1HaJ+QWEMQUVg0WbDHfsVP2EY+L1oQD7ZEoPKkCLcd/pGeP
1eoGxV6vKJL7JD8Sdl1UoHPksp/1+LIB4s6JE68NrksY3TqF5vC8jKHiG67h0XJ8
WoWriNK/T6tSLj/ApAL1qeyz1nG1iccGJ4gaBOFH0kDl1vkU2qTbMQJv59cLUHWv
boevsBp356BiCBMYYkTvLAS4LDA27tvFcuzHtnaxQi22BnwZap9dvQJuxxtvfgCj
zeqmrCxwuuQpPvhHO4DJQxMxxeado6IyQmuVU6VJAnobBI2KIiGRDjldRqGe+aFe
6sTqW+NTs5ehkf0O12JTE9V2/yxALBd8BWEFhfaZfjU4g22TSF+1oPgd2r8qvmg6
fICwA9tna6N36NyDnic1V+xF+93wBxIfhl+ZtzMISzxYlh+zyfipvz4qJb0Hhk0S
u8FUK+ksijxQTFP/ZFXNw/r8ufC1mHsWbD0bCqRhb3eVblU1f9e2XLmW4LUgtII=
-----END CERTIFICATE-----`)

var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`)
func getCA() ([]byte, []byte, error) {
if fileExists("rootCA.crt") && fileExists("rootCA.key") {
caCert, err := ioutil.ReadFile("rootCA.crt")
if err != nil {
return nil, nil, err
}
caKey, err := ioutil.ReadFile("rootCA.key")
if err != nil {
return nil, nil, err
}
return caCert, caKey, err
}

// files do not exist, make new ones instead
// source: https://golang.org/src/crypto/tls/generate_cert.go
priv, err := rsa.GenerateKey(rand.Reader, 2048)
panicOnErr(err)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
notBefore := time.Now()
notAfter := notBefore.Add(3 * 365 * 24 * time.Hour) // 3 years
panicOnErr(err)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"github.com/coolspring8/rwppa"},
},
NotBefore: notBefore,
NotAfter: notAfter,

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
panicOnErr(err)
certOut, err := os.Create("rootCA.crt")
panicOnErr(err)
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
panicOnErr(err)
err = certOut.Close()
panicOnErr(err)

keyOut, err := os.OpenFile("rootCA.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
panicOnErr(err)
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
panicOnErr(err)
err = pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
panicOnErr(err)
err = keyOut.Close()
panicOnErr(err)

caCert, err := ioutil.ReadFile("rootCA.crt")
if err != nil {
return nil, nil, err
}
caKey, err := ioutil.ReadFile("rootCA.key")
if err != nil {
return nil, nil, err
}
return caCert, caKey, err
}

func setCA(caCert, caKey []byte) error {
goproxyCa, err := tls.X509KeyPair(caCert, caKey)
Expand All @@ -136,3 +125,17 @@ func setCA(caCert, caKey []byte) error {
goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
return nil
}

func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}

func panicOnErr(err error) {
if err != nil {
panic(err)
}
}
9 changes: 8 additions & 1 deletion proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,14 @@ type reqData struct {
}

func startProxyServer(listenAddr string, twfid string) {
setCA(caCert, caKey)
caCert, caKey, err := getCA()
if err != nil {
panic(err)
}
err = setCA(caCert, caKey)
if err != nil {
panic(err)
}

proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
Expand Down

0 comments on commit d753a26

Please sign in to comment.