From cb0d759766dd7183adc00f097760fc82ff2796d0 Mon Sep 17 00:00:00 2001
From: Sergey Matveev <stargrave@stargrave.org>
Date: Wed, 8 Sep 2021 12:14:58 +0300
Subject: [PATCH] Another refactor

---
 .gitignore                                    |  3 +-
 all.do                                        |  2 +-
 cert.pem.do                                   |  4 +-
 cert.tmpl                                     |  4 -
 cmd/certgen/main.go                           | 95 +++++++++++++++++++
 cmd/tofuproxy/main.go                         |  2 +-
 default.cmd.do                                |  3 +
 doc/usage.texi                                |  4 +-
 fifos/ensure.do                               |  2 +-
 fifos/fifos.go                                |  2 +
 fifos/multitail.sh                            |  1 +
 prv.pem.do                                    |  2 -
 rounds/10log.go                               | 35 -------
 rounds/{35denyFonts.go => denyFonts.go}       |  0
 rounds/{25habrImage.go => habrImage.go}       |  0
 rounds/{05noHead.go => noHead.go}             |  0
 rounds/{20reddit.go => reddit.go}             |  0
 rounds/{50redirectHTML.go => redirectHTML.go} |  0
 rounds/{15spy.go => spy.go}                   | 30 +++---
 .../{45transcodeAVIF.go => transcodeAVIF.go}  |  0
 rounds/{45transcodeJXL.go => transcodeJXL.go} |  0
 .../{40transcodeWebP.go => transcodeWebP.go}  |  0
 trip.go                                       |  2 +-
 verify.go                                     |  6 +-
 24 files changed, 133 insertions(+), 64 deletions(-)
 delete mode 100644 cert.tmpl
 create mode 100644 cmd/certgen/main.go
 create mode 100644 default.cmd.do
 delete mode 100644 prv.pem.do
 delete mode 100644 rounds/10log.go
 rename rounds/{35denyFonts.go => denyFonts.go} (100%)
 rename rounds/{25habrImage.go => habrImage.go} (100%)
 rename rounds/{05noHead.go => noHead.go} (100%)
 rename rounds/{20reddit.go => reddit.go} (100%)
 rename rounds/{50redirectHTML.go => redirectHTML.go} (100%)
 rename rounds/{15spy.go => spy.go} (82%)
 rename rounds/{45transcodeAVIF.go => transcodeAVIF.go} (100%)
 rename rounds/{45transcodeJXL.go => transcodeJXL.go} (100%)
 rename rounds/{40transcodeWebP.go => transcodeWebP.go} (100%)

diff --git a/.gitignore b/.gitignore
index 7902b80..1132f68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 /cert.pem
+/certgen.cmd
 /certs
 /prv.pem
-/tofuproxy
+/tofuproxy.cmd
diff --git a/all.do b/all.do
index 7288dc0..fbac54c 100644
--- a/all.do
+++ b/all.do
@@ -1,2 +1,2 @@
-redo-ifchange cert.pem tofuproxy fifos/ensure
+redo-ifchange cert.pem tofuproxy.cmd fifos/ensure
 mkdir -p certs
diff --git a/cert.pem.do b/cert.pem.do
index 09c82a3..f50920a 100644
--- a/cert.pem.do
+++ b/cert.pem.do
@@ -1,2 +1,2 @@
-redo-ifchange prv.pem cert.tmpl
-certtool --generate-self-signed --load-privkey prv.pem --template cert.tmpl
+[ -e certgen.cmd ] || redo certgen.cmd
+./certgen.cmd -cert $3
diff --git a/cert.tmpl b/cert.tmpl
deleted file mode 100644
index 3b4a452..0000000
--- a/cert.tmpl
+++ /dev/null
@@ -1,4 +0,0 @@
-dn = "cn=tofu.localhost"
-expiration_days = 365
-ca
-cert_signing_key
diff --git a/cmd/certgen/main.go b/cmd/certgen/main.go
new file mode 100644
index 0000000..f4a0d97
--- /dev/null
+++ b/cmd/certgen/main.go
@@ -0,0 +1,95 @@
+/*
+tofuproxy -- HTTP proxy with TLS certificates management
+Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"flag"
+	"io"
+	"log"
+	"math/big"
+	"os"
+	"time"
+)
+
+func main() {
+	cn := flag.String("cn", "tofuproxy.localhost", "CommonName")
+	crtPath := flag.String("cert", "cert.pem", "Path to server X.509 certificate")
+	prvPath := flag.String("key", "prv.pem", "Path to server PKCS#8 private key")
+	flag.Parse()
+	log.SetFlags(log.Lshortfile)
+
+	prv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	pub := prv.Public()
+	notBefore := time.Now()
+	notAfter := notBefore.Add(365 * 24 * time.Hour)
+
+	serialRaw := make([]byte, 16)
+	if _, err = io.ReadFull(rand.Reader, serialRaw); err != nil {
+		log.Fatalln(err)
+	}
+	serial := big.NewInt(0)
+	serial = serial.SetBytes(serialRaw)
+
+	template := x509.Certificate{
+		SerialNumber:          serial,
+		Subject:               pkix.Name{CommonName: *cn},
+		DNSNames:              []string{*cn},
+		NotBefore:             notBefore,
+		NotAfter:              notAfter,
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+	}
+	certRaw, err := x509.CreateCertificate(
+		rand.Reader, &template, &template, pub, prv,
+	)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	if _, err = x509.ParseCertificate(certRaw); err != nil {
+		log.Fatalln(err)
+	}
+	pkcs8, err := x509.MarshalPKCS8PrivateKey(prv)
+	if err != nil {
+		log.Fatalln(err)
+	}
+
+	fd, err := os.OpenFile(*prvPath, os.O_WRONLY|os.O_CREATE, 0600)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	err = pem.Encode(fd, &pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8})
+	if err != nil {
+		log.Fatalln(err)
+	}
+	fd.Close()
+
+	fd, err = os.OpenFile(*crtPath, os.O_WRONLY|os.O_CREATE, 0600)
+	err = pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: certRaw})
+	if err != nil {
+		log.Fatalln(err)
+	}
+}
diff --git a/cmd/tofuproxy/main.go b/cmd/tofuproxy/main.go
index 5ad6e62..5335ffb 100644
--- a/cmd/tofuproxy/main.go
+++ b/cmd/tofuproxy/main.go
@@ -32,7 +32,7 @@ func main() {
 	crtPath := flag.String("cert", "cert.pem", "Path to server X.509 certificate")
 	prvPath := flag.String("key", "prv.pem", "Path to server PKCS#8 private key")
 	bind := flag.String("bind", "[::1]:8080", "Bind address")
-	certs := flag.String("certs", "certs", "Directory with pinned certificates")
+	certs := flag.String("certs", "./certs", "Directory with pinned certificates")
 	dnsSrv := flag.String("dns", "[::1]:53", "DNS server")
 	fifosDir := flag.String("fifos", "fifos", "Directory with FIFOs")
 	notai := flag.Bool("notai", false, "Do not prepend TAI64N to logs")
diff --git a/default.cmd.do b/default.cmd.do
new file mode 100644
index 0000000..d15196b
--- /dev/null
+++ b/default.cmd.do
@@ -0,0 +1,3 @@
+redo-ifchange *.go cmd/*/*.go fifos/*.go rounds/*.go
+GO_LDFLAGS="${GO_LDFLAGS:--ldflags=-s}"
+${GO:-go} build -o $3 $GO_LDFLAGS ./cmd/${1%.cmd}
diff --git a/doc/usage.texi b/doc/usage.texi
index 6965994..8cfbb06 100644
--- a/doc/usage.texi
+++ b/doc/usage.texi
@@ -36,8 +36,8 @@ Run @command{tofuproxy} itself. By default it will bind to
 (set to an empty string to disable DANE lookups):
 
 @example
-$ ./tofuproxy
-main.go:316: listening: [::1]:8080
+$ ./tofuproxy.cmd
+main.go:316: listening: [::1]:8080 certs: ./certs
 @end example
 
 @item Trust your newly generated CA:
diff --git a/fifos/ensure.do b/fifos/ensure.do
index 682d687..7aa937b 100644
--- a/fifos/ensure.do
+++ b/fifos/ensure.do
@@ -1,3 +1,3 @@
-for f in cert err ok other redir req tls ; do
+for f in cert dane err ok other redir req tls ; do
     [ -p $f ] || mkfifo $f
 done
diff --git a/fifos/fifos.go b/fifos/fifos.go
index 6e78096..61f0f63 100644
--- a/fifos/fifos.go
+++ b/fifos/fifos.go
@@ -30,6 +30,7 @@ var (
 	NoTAI     bool
 	FIFOs string
 	SinkCert  = make(chan string)
+	SinkDANE  = make(chan string)
 	SinkErr   = make(chan string)
 	SinkOK    = make(chan string)
 	SinkOther = make(chan string)
@@ -63,6 +64,7 @@ func sinker(c chan string, p string) {
 
 func Init() {
 	go sinker(SinkCert, filepath.Join(FIFOs, "cert"))
+	go sinker(SinkDANE, filepath.Join(FIFOs, "dane"))
 	go sinker(SinkErr, filepath.Join(FIFOs, "err"))
 	go sinker(SinkOK, filepath.Join(FIFOs, "ok"))
 	go sinker(SinkOther, filepath.Join(FIFOs, "other"))
diff --git a/fifos/multitail.sh b/fifos/multitail.sh
index e6f29fb..8a8ccee 100755
--- a/fifos/multitail.sh
+++ b/fifos/multitail.sh
@@ -3,6 +3,7 @@
 multitail \
     -wh 10 \
     -t "Certificates" -ci magenta -l "while :; do tai64nlocal < cert ; done" \
+    -t "DANE" --label "DANE " -L "while :; do tai64nlocal < dane ; done" \
     -t "Errors" -ci red -L "while :; do tai64nlocal < err ; done" \
     -t "Responses" -ci green --label "< " -l "while :; do tai64nlocal < ok ; done" \
     -t "Others" -ci white -L "while :; do tai64nlocal < other ; done" \
diff --git a/prv.pem.do b/prv.pem.do
deleted file mode 100644
index c14af89..0000000
--- a/prv.pem.do
+++ /dev/null
@@ -1,2 +0,0 @@
-umask 077
-certtool --generate-privkey --bits 256 --ecc > $3
diff --git a/rounds/10log.go b/rounds/10log.go
deleted file mode 100644
index 135394d..0000000
--- a/rounds/10log.go
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-tofuproxy -- HTTP proxy with TLS certificates management
-Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-package rounds
-
-import (
-	"fmt"
-	"net/http"
-
-	"go.stargrave.org/tofuproxy/fifos"
-)
-
-func RoundLog(
-	host string,
-	resp *http.Response,
-	w http.ResponseWriter,
-	req *http.Request,
-) (bool, error) {
-	fifos.SinkReq <- fmt.Sprintf("%s %s", req.Method, req.URL.String())
-	return true, nil
-}
diff --git a/rounds/35denyFonts.go b/rounds/denyFonts.go
similarity index 100%
rename from rounds/35denyFonts.go
rename to rounds/denyFonts.go
diff --git a/rounds/25habrImage.go b/rounds/habrImage.go
similarity index 100%
rename from rounds/25habrImage.go
rename to rounds/habrImage.go
diff --git a/rounds/05noHead.go b/rounds/noHead.go
similarity index 100%
rename from rounds/05noHead.go
rename to rounds/noHead.go
diff --git a/rounds/20reddit.go b/rounds/reddit.go
similarity index 100%
rename from rounds/20reddit.go
rename to rounds/reddit.go
diff --git a/rounds/50redirectHTML.go b/rounds/redirectHTML.go
similarity index 100%
rename from rounds/50redirectHTML.go
rename to rounds/redirectHTML.go
diff --git a/rounds/15spy.go b/rounds/spy.go
similarity index 82%
rename from rounds/15spy.go
rename to rounds/spy.go
index 42ebd9e..3b242f8 100644
--- a/rounds/15spy.go
+++ b/rounds/spy.go
@@ -29,6 +29,7 @@ var spyDomains = []string{
 	"google-analytics.com",
 	"goo.gl",
 	"ads.google.com",
+	"googletagmanager.com",
 	"facebook.com",
 	"facebook.net",
 	"fbcdn.com",
@@ -44,23 +45,30 @@ var spyDomains = []string{
 	"tns-counter.ru",
 }
 
+func IsSpy(host string) bool {
+	for _, spy := range spyDomains {
+		if strings.HasSuffix(host, spy) {
+			return true
+		}
+	}
+	return false
+}
+
 func RoundDenySpy(
 	host string,
 	resp *http.Response,
 	w http.ResponseWriter,
 	req *http.Request,
 ) (bool, error) {
-	for _, spy := range spyDomains {
-		if strings.HasSuffix(host, spy) {
-			http.NotFound(w, req)
-			fifos.SinkOther <- fmt.Sprintf(
-				"%s %s\t%d\tdeny spy",
-				req.Method,
-				req.URL.String(),
-				http.StatusNotFound,
-			)
-			return false, nil
-		}
+	if IsSpy(host) {
+		http.NotFound(w, req)
+		fifos.SinkOther <- fmt.Sprintf(
+			"%s %s\t%d\tdeny spy",
+			req.Method,
+			req.URL.String(),
+			http.StatusNotFound,
+		)
+		return false, nil
 	}
 	return true, nil
 }
diff --git a/rounds/45transcodeAVIF.go b/rounds/transcodeAVIF.go
similarity index 100%
rename from rounds/45transcodeAVIF.go
rename to rounds/transcodeAVIF.go
diff --git a/rounds/45transcodeJXL.go b/rounds/transcodeJXL.go
similarity index 100%
rename from rounds/45transcodeJXL.go
rename to rounds/transcodeJXL.go
diff --git a/rounds/40transcodeWebP.go b/rounds/transcodeWebP.go
similarity index 100%
rename from rounds/40transcodeWebP.go
rename to rounds/transcodeWebP.go
diff --git a/trip.go b/trip.go
index 27d4e9e..3ad6eb1 100644
--- a/trip.go
+++ b/trip.go
@@ -58,10 +58,10 @@ type Round func(
 ) (bool, error)
 
 func roundTrip(w http.ResponseWriter, req *http.Request) {
+	fifos.SinkReq <- fmt.Sprintf("%s %s", req.Method, req.URL.String())
 	host := strings.TrimSuffix(req.URL.Host, ":443")
 	for _, round := range []Round{
 		rounds.RoundNoHead,
-		rounds.RoundLog,
 		rounds.RoundDenySpy,
 		rounds.RoundRedditOld,
 		rounds.RoundHabrImage,
diff --git a/verify.go b/verify.go
index 4c20c0e..b2eb606 100644
--- a/verify.go
+++ b/verify.go
@@ -123,9 +123,9 @@ func verifyCert(
 	daneExists, daneMatched := dane(host, certTheir)
 	if daneExists {
 		if daneMatched {
-			fifos.SinkCert <- fmt.Sprintf("DANE\t%s\tmatched", host)
+			fifos.SinkDANE <- fmt.Sprintf("%s\tmatched", host)
 		} else {
-			fifos.SinkErr <- fmt.Sprintf("DANE\t%s\tnot matched", host)
+			fifos.SinkDANE <- fmt.Sprintf("%s\tNOT matched", host)
 		}
 	}
 	fn := filepath.Join(Certs, host)
@@ -154,7 +154,7 @@ $tErr configure -wrap word -height 5
 			if daneMatched {
 				b.WriteString("label .lDANE -bg green -text \"DANE matched\"\n")
 			} else {
-				b.WriteString("label .lDANE -bg red -text \"DANE not matched!\"\n")
+				b.WriteString("label .lDANE -bg red -text \"DANE NOT matched\"\n")
 			}
 			b.WriteString("grid .lDANE\n")
 		}
-- 
2.51.0