]> Sergey Matveev's repositories - tofuproxy.git/blob - verify.go
Initial commit
[tofuproxy.git] / verify.go
1 /*
2 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, version 3 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 package main
18
19 import (
20         "bytes"
21         "crypto/x509"
22         "encoding/pem"
23         "fmt"
24         "log"
25         "os"
26         "os/exec"
27         "path/filepath"
28         "strings"
29
30         "go.cypherpunks.ru/ucspi"
31 )
32
33 func verifyCert(
34         host string,
35         dialErr error,
36         rawCerts [][]byte,
37         verifiedChains [][]*x509.Certificate,
38 ) error {
39         var certTheir *x509.Certificate
40         var err error
41         if len(verifiedChains) > 0 {
42                 certTheir = verifiedChains[0][0]
43         } else {
44                 certTheir, err = x509.ParseCertificate(rawCerts[0])
45                 if err != nil {
46                         return err
47                 }
48         }
49         certTheirHash := spkiHash(certTheir)
50         acceptedM.RLock()
51         certOurHash := accepted[host]
52         acceptedM.RUnlock()
53         if certTheirHash == certOurHash {
54                 return nil
55         }
56         rejectedM.RLock()
57         certOurHash = rejected[host]
58         rejectedM.RUnlock()
59         if certTheirHash == certOurHash {
60                 return ErrRejected{host}
61         }
62         daneExists, daneMatched := dane(host, certTheir)
63         if daneExists {
64                 if daneMatched {
65                         sinkCert <- fmt.Sprintf("DANE\t%s\tmatched", host)
66                 } else {
67                         sinkErr <- fmt.Sprintf("DANE\t%s\tnot matched", host)
68                 }
69         }
70         fn := filepath.Join(*certs, host)
71         certsOur, _, err := ucspi.CertPoolFromFile(fn)
72         if err == nil || dialErr != nil || (daneExists && !daneMatched) {
73                 if certsOur != nil && certTheirHash == spkiHash(certsOur[0]) {
74                         acceptedAdd(host, certTheirHash)
75                         if bytes.Compare(certsOur[0].Raw, rawCerts[0]) != 0 {
76                                 sinkCert <- fmt.Sprintf("Refresh\t%s\t%s", host, certTheirHash)
77                                 goto CertUpdate
78                         }
79                         return nil
80                 }
81                 cmd := exec.Command(
82                         "xmessage",
83                         "-buttons",
84                         "Accept:10,Once:11,Reject:12",
85                         "-file", "-",
86                 )
87                 var b bytes.Buffer
88                 b.WriteString("Host: " + host)
89                 if dialErr != nil {
90                         b.WriteString("\nError: " + dialErr.Error())
91                 }
92                 if daneExists {
93                         if daneMatched {
94                                 b.WriteString("\nDANE matched")
95                         } else {
96                                 b.WriteString("\nDANE no match!")
97                         }
98                 }
99                 for i, rawCert := range rawCerts {
100                         b.WriteString(fmt.Sprintf("\nTheir %d:\n", i))
101                         b.WriteString(certInfo(rawCert))
102                 }
103                 if certsOur != nil {
104                         b.WriteString(strings.Repeat("-", 80))
105                         for i, cert := range certsOur {
106                                 b.WriteString(fmt.Sprintf("\nOur %d:\n", i))
107                                 b.WriteString(certInfo(cert.Raw))
108                         }
109                 }
110                 cmd.Stdin = &b
111                 switch cmd.Run().(*exec.ExitError).ExitCode() {
112                 case 10:
113                         sinkCert <- fmt.Sprintf("ADD\t%s\t%s", host, certTheirHash)
114                         goto CertUpdate
115                 case 11:
116                         sinkCert <- fmt.Sprintf("ONCE\t%s\t%s", host, certTheirHash)
117                         acceptedAdd(host, certTheirHash)
118                         return nil
119                 case 12:
120                         rejectedAdd(host, certTheirHash)
121                         fallthrough
122                 default:
123                         sinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash)
124                         return ErrRejected{host}
125                 }
126         } else {
127                 if !os.IsNotExist(err) {
128                         return err
129                 }
130                 sinkCert <- fmt.Sprintf("TOFU\t%s\t%s", host, certTheirHash)
131         }
132 CertUpdate:
133         tmp, err := os.CreateTemp(*certs, "")
134         if err != nil {
135                 log.Fatalln(err)
136         }
137         for _, rawCert := range rawCerts {
138                 err = pem.Encode(tmp, &pem.Block{Type: "CERTIFICATE", Bytes: rawCert})
139                 if err != nil {
140                         log.Fatalln(err)
141                 }
142         }
143         tmp.Close()
144         os.Rename(tmp.Name(), fn)
145         acceptedAdd(host, certTheirHash)
146         return nil
147 }