Compare commits

...

3 Commits

Author SHA1 Message Date
nervuri 8cf7b02e1b improve drop root code; remove reliance on C 2023-01-31 15:28:48 +00:00
nervuri da4f262454 add go.mod and go.sum 2023-01-31 11:27:50 +00:00
nervuri a37d9a5198 clarify peek() code 2022-06-12 00:00:00 +00:00
4 changed files with 50 additions and 33 deletions

View File

@ -2,10 +2,10 @@
## Install
Start by installing Go, Git, GCC and glibc (the program is written in Go, but a tiny bit of C code is used to drop privileges when running as root). On Debian, run:
Start by installing Go and Git. On Debian, run:
```
apt install golang git gcc libc6-dev
apt install golang git
```
Then fetch and build the program:
@ -66,7 +66,7 @@ systemctl start client-hello-mirror.service
## Drop root
A standard web-facing setup involves using a CA-signed certificate and binding to privileged port 443. For security reasons, the program will drop root privileges imediately after loading the certificate and binding to the specified port. Use the `-u` option to select a user to switch to; the default is `www-data`.
A standard web-facing setup involves using a CA-signed certificate and binding to privileged port 443. For security reasons, the program will drop root privileges imediately after loading the certificate and binding to the specified port. Use the `-u` option to select a user to switch to. If you really want to run as root, set `-u root` (not recommended).
## Redirect http:// to https://

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module tildegit.org/nervuri/client-hello-mirror
go 1.19
require golang.org/x/crypto v0.5.0

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=

View File

@ -14,6 +14,7 @@ import (
"log"
"net"
"net/url"
"os"
"os/user"
"strconv"
"strings"
@ -21,12 +22,6 @@ import (
"time"
)
import (
//#include <unistd.h>
//#include <errno.h>
"C"
)
type tlsConnectionInfo struct {
TlsVersion uint16 `json:"tls_version"`
CipherSuite uint16 `json:"cipher_suite"`
@ -129,21 +124,24 @@ _____________________
=> https://tildegit.org/nervuri/client-hello-mirror Source (contributions welcome)
=> https://www.gnu.org/licenses/agpl-3.0.en.html License: AGPL-3.0-or-later`
// Copy the Client Hello message before starting the TLS handshake.
func peek(conn net.Conn, tlsConfig *tls.Config) {
// Copy the Client Hello before starting the TLS handshake.
defer conn.Close()
var buf bytes.Buffer
_, err := io.CopyN(&buf, conn, 5) // TLS record header
// Copy TLS record header.
_, err := io.CopyN(&buf, conn, 5)
if err != nil {
log.Println(err)
return
}
// Check if this is a TLS handshake record.
if buf.Bytes()[0] != 0x16 {
// Not a Client Hello message.
return
}
length := binary.BigEndian.Uint16(buf.Bytes()[3:5])
_, err = io.CopyN(&buf, conn, int64(length))
// Extract handshake message length.
handshakeMessageLength := binary.BigEndian.Uint16(buf.Bytes()[3:5])
// Copy handshake message (should be a Client Hello).
_, err = io.CopyN(&buf, conn, int64(handshakeMessageLength))
if err != nil {
log.Println(err)
return
@ -277,11 +275,11 @@ func main() {
// Parse arguments
flag.StringVar(&certFile, "c", "", "path to certificate file")
flag.StringVar(&keyFile, "k", "", "path to private key file")
flag.StringVar(&userToSwitchTo, "u", "www-data", "user to switch to, if running as root")
flag.StringVar(&userToSwitchTo, "u", "", "user to switch to, if running as root")
flag.Parse()
hostAndPort = flag.Arg(0)
if certFile == "" || keyFile == "" || hostAndPort == "" {
fmt.Println("usage: client-hello-mirror -c cert.pem -k key.pem host:port")
fmt.Println("usage: client-hello-mirror -c cert.pem -k key.pem [-u user] host:port")
return
}
@ -307,36 +305,48 @@ func main() {
defer ln.Close()
// Drop root
if syscall.Getuid() == 0 {
if syscall.Getuid() == 0 { // running as root
if userToSwitchTo == "" {
fmt.Println("Running as root. Please specify an unprivileged user to switch to, using the -u flag")
return
fmt.Println("When running as root, use the -u option to switch to an unprivileged user.")
os.Exit(1)
}
// Get user and group IDs for the user we want to switch to.
userInfo, err := user.Lookup(userToSwitchTo)
if err != nil {
fmt.Println(err)
if userToSwitchTo == "www-data" {
fmt.Println("Running as root. Please specify an unprivileged user to switch to, using the -u flag")
}
return
os.Exit(1)
}
uid, err := strconv.ParseInt(userInfo.Uid, 10, 32)
// Convert group id and user id from string to int.
gid, err := strconv.Atoi(userInfo.Gid)
if err != nil {
fmt.Println(err)
return
os.Exit(1)
}
gid, err := strconv.ParseInt(userInfo.Gid, 10, 32)
uid, err := strconv.Atoi(userInfo.Uid)
if err != nil {
fmt.Println(err)
return
os.Exit(1)
}
cerr, errno := C.setgid(C.__gid_t(gid))
if cerr != 0 {
log.Fatalln("Unable to set GID due to error:", errno)
if uid == 0 {
fmt.Println("WARNING: running as root is not recommended!")
}
cerr, errno = C.setuid(C.__uid_t(uid))
if cerr != 0 {
log.Fatalln("Unable to set UID due to error:", errno)
// Unset supplementary group IDs.
err = syscall.Setgroups([]int{})
if err != nil {
fmt.Println("Failed to unset supplementary group IDs: " + err.Error())
os.Exit(1)
}
// Set group ID.
err = syscall.Setgid(gid)
if err != nil {
fmt.Println("Failed to set group ID: " + err.Error())
os.Exit(1)
}
// Set user ID.
err = syscall.Setuid(uid)
if err != nil {
fmt.Println("Failed to set user ID: " + err.Error())
os.Exit(1)
}
}