]> Cypherpunks.ru repositories - ucspi.git/blob - cmd/tlsc/main.go
Unify copyright comment format
[ucspi.git] / cmd / tlsc / main.go
1 // ucspi/cmd/tlsc -- UCSPI TCP proxy client
2 // Copyright (C) 2021-2024 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 package main
17
18 import (
19         "bytes"
20         "crypto/sha256"
21         "crypto/tls"
22         "crypto/x509"
23         "encoding/hex"
24         "encoding/pem"
25         "errors"
26         "flag"
27         "fmt"
28         "io"
29         "log"
30         "os"
31         "os/exec"
32
33         "go.cypherpunks.ru/ucspi"
34 )
35
36 func main() {
37         onlyShow := flag.Bool("show", false, "Show server's certificate and exit")
38         crtPath := flag.String("cert", "", "Path to client X.509 certificate")
39         prvPath := flag.String("key", "", "Path to client PKCS#8 private key")
40         casPath := flag.String("ca", "", "Path to CA certificates file")
41         hostname := flag.String("name", "example.com", "Expected server's hostname")
42         insecure := flag.Bool("insecure", false, "Insecure mode")
43         fpr := flag.String("fpr", "", "Expected SHA256 hash of server certificate's SPKI")
44         flag.Usage = func() {
45                 fmt.Fprintf(os.Stderr, `Usage: tcpclient host port tlsc -name expected.name
46         [-cert cert.pem -key prv.pem] [-ca CAs.pem] [-fpr DEADBABE] [-show]
47         program [args...]
48
49 `)
50                 flag.PrintDefaults()
51         }
52         flag.Parse()
53         log.SetFlags(log.Lshortfile)
54
55         if len(flag.Args()) == 0 {
56                 flag.Usage()
57                 os.Exit(1)
58         }
59
60         cfg := &tls.Config{}
61         if *hostname == "" || *onlyShow || *insecure {
62                 cfg.InsecureSkipVerify = true
63         }
64         if *hostname != "" {
65                 cfg.ServerName = *hostname
66         }
67         if *crtPath != "" {
68                 crtRaw, _, err := ucspi.CertificateFromFile(*crtPath)
69                 if err != nil {
70                         log.Fatalln(err)
71                 }
72                 prv, err := ucspi.PrivateKeyFromFile(*prvPath)
73                 if err != nil {
74                         log.Fatalln(err)
75                 }
76                 cfg.Certificates = []tls.Certificate{{
77                         Certificate: [][]byte{crtRaw},
78                         PrivateKey:  prv,
79                 }}
80         }
81         if *casPath != "" {
82                 var err error
83                 _, cfg.RootCAs, err = ucspi.CertPoolFromFile(*casPath)
84                 if err != nil {
85                         log.Fatalln(err)
86                 }
87         }
88         if *fpr != "" {
89                 hshOur, err := hex.DecodeString(*fpr)
90                 if err != nil {
91                         log.Fatalln(err)
92                 }
93                 cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
94                         spki := verifiedChains[0][0].RawSubjectPublicKeyInfo
95                         hshTheir := sha256.Sum256(spki)
96                         if !bytes.Equal(hshOur, hshTheir[:]) {
97                                 return errors.New("server certificate's SPKI hash mismatch")
98                         }
99                         return nil
100                 }
101         }
102
103         conn, err := ucspi.NewConn(os.NewFile(6, "R"), os.NewFile(7, "W"))
104         if err != nil {
105                 log.Fatalln(err)
106         }
107         tlsConn := tls.Client(conn, cfg)
108         if err := tlsConn.Handshake(); err != nil {
109                 log.Fatalln(err)
110         }
111         connState := tlsConn.ConnectionState()
112         if *onlyShow {
113                 fmt.Fprintf(
114                         os.Stderr,
115                         "Version: %s\nCipherSuite: %s\n",
116                         ucspi.TLSVersion(connState.Version),
117                         tls.CipherSuiteName(connState.CipherSuite),
118                 )
119                 for _, cert := range connState.PeerCertificates {
120                         os.Stderr.WriteString("\n")
121                         pem.Encode(os.Stderr, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
122                         hsh := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
123                         fmt.Fprintf(
124                                 os.Stderr, "Issuer: %s\nSubject: %s\nValidity: %s / %s\nSPKI SHA256: %s\n",
125                                 cert.Issuer, cert.Subject, cert.NotBefore, cert.NotAfter,
126                                 hex.EncodeToString(hsh[:]),
127                         )
128                 }
129                 return
130         }
131         dn := connState.PeerCertificates[0].Subject.String()
132
133         args := flag.Args()
134         cmd := exec.Command(args[0], args[1:]...)
135         rr, rw, err := os.Pipe()
136         if err != nil {
137                 log.Fatalln(err)
138         }
139         wr, ww, err := os.Pipe()
140         if err != nil {
141                 log.Fatalln(err)
142         }
143         cmd.ExtraFiles = []*os.File{nil, nil, nil, rr, ww}
144         cmd.Stdin = os.Stdin
145         cmd.Stdout = os.Stdout
146         cmd.Stderr = os.Stderr
147         cmd.Env = append(os.Environ(), "PROTO=TLS")
148         cmd.Env = append(cmd.Env, "TLSREMOTEDN="+dn)
149         if err = cmd.Start(); err != nil {
150                 log.Fatalln(err)
151         }
152         worker := make(chan struct{})
153         go func() {
154                 io.Copy(rw, tlsConn)
155                 rw.Close()
156                 close(worker)
157         }()
158         go func() {
159                 io.Copy(tlsConn, wr)
160         }()
161         _, err = cmd.Process.Wait()
162         <-worker
163         tlsConn.Close()
164         if err != nil {
165                 log.Fatalln(err)
166         }
167 }