]> Sergey Matveev's repositories - tofuproxy.git/commitdiff
Restricted CAs
authorSergey Matveev <stargrave@stargrave.org>
Wed, 15 Mar 2023 15:09:51 +0000 (18:09 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 15 Mar 2023 15:09:51 +0000 (18:09 +0300)
caches/caches.go
doc/index.texi
doc/restricted.texi [new file with mode: 0644]
fifos/ensure.do
fifos/restricted.go [new file with mode: 0644]
fifos/start.go
netrc.go
restricted.txt [new file with mode: 0644]
tls/verify.go
warc/uris.go

index 39dfd3451d51f55e3a0d07be1de5c13ef5b5e2b6..81449349fac68479c2e89db80bbc72013239bc24 100644 (file)
@@ -20,4 +20,7 @@ var (
 
        Spies  = make([]string, 0)
        SpiesM sync.RWMutex
+
+       Restricted = make(map[string][]string)
+       RestrictedM sync.RWMutex
 )
index ed3bd99d67c24f17d48d21bd6be62c7480377d7d..f5ffde92b519c8e8969a4c6b26794f2f5cc6d8b0 100644 (file)
@@ -41,6 +41,9 @@ Even if native Go's checks are failed (for example domain still does not
 use @code{SubjectAltName} extension), you can still make a decision to
 forcefully trust the domain.
 
+@item
+CAs can have restrictions on what domains they are allowed to be served.
+
 @item
 Optional @url{https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities, DANE-EE} check.
 
@@ -104,6 +107,7 @@ Web fonts downloads are forbidden.
 @include spies.texi
 @include certs.texi
 @include tlsauth.texi
+@include restricted.texi
 @include httpauth.texi
 @include warcs.texi
 @include gemini.texi
diff --git a/doc/restricted.texi b/doc/restricted.texi
new file mode 100644 (file)
index 0000000..241b71e
--- /dev/null
@@ -0,0 +1,13 @@
+@node Restricted
+@unnumbered Restricted CAs
+
+You can restrict what hosts are allowed to be served by the specified
+CA. For example you want to limit CA with SPKI's SHA256 hash of
+@code{9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124}
+to domains only in @code{stargrave.org} tree:
+
+@example
+$ tee fifos/add-restricted < restricted.txt
+9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124 stargrave.org
+[...]
+@end example
index 1a961be403e5e61d6fd45ba92712ba0cb27df357..03d50d948191d4303ed18da149806f546ba31e68 100644 (file)
@@ -1,10 +1,10 @@
 for f in cert dane err http-auth non-ok ok redir req tls tls-auth various warc ; do
     [ -p log-$f ] || mkfifo log-$f
 done
-for f in accepted http-auth rejected spies tls-auth warcs ; do
+for f in accepted http-auth rejected restricted spies tls-auth warcs ; do
     [ -p list-$f ] || mkfifo list-$f
     [ -p del-$f ] || mkfifo del-$f
 done
-for f in spies tls-auth warcs ; do
+for f in restricted spies tls-auth warcs ; do
     [ -p add-$f ] || mkfifo add-$f
 done
diff --git a/fifos/restricted.go b/fifos/restricted.go
new file mode 100644 (file)
index 0000000..76c5f0b
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
+             manager, WARC/geminispace browser
+Copyright (C) 2021-2023 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 fifos
+
+import (
+       "fmt"
+       "log"
+       "os"
+       "strings"
+
+       "go.stargrave.org/tofuproxy/caches"
+)
+
+func listRestricted(p string) {
+       for {
+               fd, err := os.OpenFile(p, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               caches.RestrictedM.RLock()
+               for spki, hosts := range caches.Restricted {
+                       for _, host := range hosts {
+                               if _, err = fmt.Fprintf(fd, "%s\t%s\n", spki, host); err != nil {
+                                       break
+                               }
+                       }
+               }
+               caches.RestrictedM.RUnlock()
+               fd.Close()
+       }
+}
+
+func addRestricted(p string) {
+       for {
+               neu := make(map[string][]string)
+               for _, line := range readLinesFromFIFO(p) {
+                       cols := strings.Fields(line)
+                       if len(cols) != 2 {
+                               continue
+                       }
+                       spki, host := cols[0], cols[1]
+                       log.Printf("%s: adding %s: %s\n", p, spki, host)
+                       neu[spki] = append(neu[spki], host)
+               }
+               caches.RestrictedM.Lock()
+               for spki, hosts := range neu {
+                       caches.Restricted[spki] = append(caches.Restricted[spki], hosts...)
+               }
+               caches.RestrictedM.Unlock()
+       }
+}
index 14acead5c1a6a32a883af1e9537375d1ad01497d..2dad65a00d04a8a80c5e8b70190cf20935682652 100644 (file)
@@ -42,6 +42,7 @@ func Start(fifos string) {
        go listAccepted(filepath.Join(fifos, "list-accepted"))
        go listHTTPAuth(filepath.Join(fifos, "list-http-auth"))
        go listRejected(filepath.Join(fifos, "list-rejected"))
+       go listRestricted(filepath.Join(fifos, "list-restricted"))
        go listSpies(filepath.Join(fifos, "list-spies"))
        go listTLSAuth(filepath.Join(fifos, "list-tls-auth"))
        go listWARCs(filepath.Join(fifos, "list-warcs"))
@@ -59,6 +60,8 @@ func Start(fifos string) {
                filepath.Join(fifos, "del-rejected"),
        )
 
+       go addRestricted(filepath.Join(fifos, "add-restricted"))
+
        go addSpy(filepath.Join(fifos, "add-spies"))
        go del(
                &caches.SpiesM, func(host string) {
index d8877b4b9f41005f443f30937ed4ebdcb1cbe719..bdc61bb46fca4ed7d720495e00ba82c79ea29820 100644 (file)
--- a/netrc.go
+++ b/netrc.go
@@ -3,6 +3,8 @@
 package tofuproxy
 
 import (
+       "errors"
+       "io/fs"
        "log"
        "os"
        "path/filepath"
@@ -20,7 +22,7 @@ func findInNetrc(host string) (string, string) {
        }
        data, err := os.ReadFile(netrcPath)
        if err != nil {
-               if os.IsNotExist(err) {
+               if errors.Is(err, fs.ErrNotExist) {
                        return "", ""
                }
                log.Fatalln(err)
diff --git a/restricted.txt b/restricted.txt
new file mode 100644 (file)
index 0000000..4cbb700
--- /dev/null
@@ -0,0 +1,21 @@
+02b8220c070728db771d9ac59e54521c4eddad21a783bb26cfdf19c1db0fae37 sber.ru
+444b8b16828a1a8a1c8207e50d768bcad5d41f7ba0bac95ffbcee0524e3ad4fc tlscc.ru
+
+508992ef00482435975b688b0ea730fbe6df6f64453ca363c67a4fa81daa1fff cryptoanarchy.ru
+508992ef00482435975b688b0ea730fbe6df6f64453ca363c67a4fa81daa1fff cryptoparty.ru
+508992ef00482435975b688b0ea730fbe6df6f64453ca363c67a4fa81daa1fff cypherpunks.ru
+508992ef00482435975b688b0ea730fbe6df6f64453ca363c67a4fa81daa1fff govpn.org
+508992ef00482435975b688b0ea730fbe6df6f64453ca363c67a4fa81daa1fff nncpgo.org
+508992ef00482435975b688b0ea730fbe6df6f64453ca363c67a4fa81daa1fff stargrave.org
+9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124 cryptoanarchy.ru
+9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124 cryptoparty.ru
+9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124 cypherpunks.ru
+9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124 govpn.org
+9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124 nncpgo.org
+9215d9eeddeb403b0ffebb228cfc13104da825117d3640a0dfbfc0c08a012124 stargrave.org
+e8345915b3e01d8f9788349619cea61a9ef22ead08b5a6f7f75f17aec000dcde cryptoanarchy.ru
+e8345915b3e01d8f9788349619cea61a9ef22ead08b5a6f7f75f17aec000dcde cryptoparty.ru
+e8345915b3e01d8f9788349619cea61a9ef22ead08b5a6f7f75f17aec000dcde cypherpunks.ru
+e8345915b3e01d8f9788349619cea61a9ef22ead08b5a6f7f75f17aec000dcde govpn.org
+e8345915b3e01d8f9788349619cea61a9ef22ead08b5a6f7f75f17aec000dcde nncpgo.org
+e8345915b3e01d8f9788349619cea61a9ef22ead08b5a6f7f75f17aec000dcde stargrave.org
index 7bff1c185f7c825d976d46fe9852dd4bae50c5ec..b7a317d6942eca635f08f9878c3c2ecb324650ab 100644 (file)
@@ -24,7 +24,9 @@ import (
        "crypto/x509"
        "encoding/hex"
        "encoding/pem"
+       "errors"
        "fmt"
+       "io/fs"
        "log"
        "os"
        "os/exec"
@@ -206,6 +208,30 @@ func verifyCert(
                        fifos.LogDANE <- fmt.Sprintf("%s\tNAK", host)
                }
        }
+       if len(verifiedChains) > 0 {
+               caHashes := make(map[string]struct{})
+               for _, certs := range verifiedChains {
+                       for _, cert := range certs {
+                               caHashes[spkiHash(cert)] = struct{}{}
+                       }
+               }
+               var restrictedHosts []string
+               caches.RestrictedM.RLock()
+               for h := range caHashes {
+                       restrictedHosts = append(restrictedHosts, caches.Restricted[h]...)
+               }
+               caches.RestrictedM.RUnlock()
+               if len(restrictedHosts) > 0 {
+                       for _, h := range restrictedHosts {
+                               if host == h || strings.HasSuffix(host, "."+h) {
+                                       goto HostIsNotRestricted
+                               }
+                       }
+                       fifos.LogCert <- fmt.Sprintf("Restricted\t%s", host)
+                       return ErrRejected{host}
+               }
+       }
+HostIsNotRestricted:
        fn := filepath.Join(Certs, host)
        certsOur, _, err := ucspi.CertPoolFromFile(fn)
        if err == nil || dialErr != nil || (daneExists && !daneMatched) {
@@ -279,7 +305,7 @@ func verifyCert(
                        return ErrRejected{host}
                }
        } else {
-               if !os.IsNotExist(err) {
+               if !errors.Is(err, fs.ErrNotExist) {
                        return err
                }
                fifos.LogCert <- fmt.Sprintf("TOFU\t%s\t%s", host, certTheirHash)
index 36ff15fb85d17c993b6b5d50c1591b40f9df52d6..2c40dcfbb7f60cde38240edd8309c07749fbd3c1 100644 (file)
@@ -20,8 +20,10 @@ package warc
 
 import (
        "encoding/gob"
+       "errors"
        "fmt"
        "io"
+       "io/fs"
        "log"
        "os"
        "strconv"
@@ -59,7 +61,7 @@ func Add(warcPath string) error {
                log.Println("loaded marshalled index:", warcPath+IndexExt)
                return nil
        }
-       if err != nil && !os.IsNotExist(err) {
+       if err != nil && !errors.Is(err, fs.ErrNotExist) {
                return err
        }
        r, err := NewReader(warcPath)