]> Cypherpunks.ru repositories - ucspi.git/blob - cmd/tlsc/main.go
Raised copyright years
[ucspi.git] / cmd / tlsc / main.go
1 /*
2 ucspi/cmd/tlsc -- UCSPI TLS client
3 Copyright (C) 2021-2022 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package main
19
20 import (
21         "bytes"
22         "crypto/sha256"
23         "crypto/tls"
24         "crypto/x509"
25         "encoding/hex"
26         "encoding/pem"
27         "errors"
28         "flag"
29         "fmt"
30         "io"
31         "log"
32         "os"
33         "os/exec"
34
35         "go.cypherpunks.ru/ucspi"
36 )
37
38 func main() {
39         onlyShow := flag.Bool("show", false, "Show server's certificate and exit")
40         crtPath := flag.String("cert", "", "Path to client X.509 certificate")
41         prvPath := flag.String("key", "", "Path to client PKCS#8 private key")
42         casPath := flag.String("ca", "", "Path to CA certificates file")
43         hostname := flag.String("name", "example.com", "Expected server's hostname")
44         insecure := flag.Bool("insecure", false, "Insecure mode")
45         fpr := flag.String("fpr", "", "Expected SHA256 hash of server certificate's SPKI")
46         flag.Usage = func() {
47                 fmt.Fprintf(os.Stderr, `Usage: tcpclient host port tlsc -name expected.name
48         [-cert cert.pem -key prv.pem] [-ca CAs.pem] [-fpr DEADBABE] [-show]
49         program [args...]
50
51 `)
52                 flag.PrintDefaults()
53         }
54         flag.Parse()
55         log.SetFlags(log.Lshortfile)
56
57         if len(flag.Args()) == 0 {
58                 flag.Usage()
59                 os.Exit(1)
60         }
61
62         cfg := &tls.Config{}
63         if *hostname == "" || *onlyShow || *insecure {
64                 cfg.InsecureSkipVerify = true
65         }
66         if *hostname != "" {
67                 cfg.ServerName = *hostname
68         }
69         if *crtPath != "" {
70                 crtRaw, _, err := ucspi.CertificateFromFile(*crtPath)
71                 if err != nil {
72                         log.Fatalln(err)
73                 }
74                 prv, err := ucspi.PrivateKeyFromFile(*prvPath)
75                 if err != nil {
76                         log.Fatalln(err)
77                 }
78                 cfg.Certificates = []tls.Certificate{{
79                         Certificate: [][]byte{crtRaw},
80                         PrivateKey:  prv,
81                 }}
82         }
83         if *casPath != "" {
84                 var err error
85                 _, cfg.RootCAs, err = ucspi.CertPoolFromFile(*casPath)
86                 if err != nil {
87                         log.Fatalln(err)
88                 }
89         }
90         if *fpr != "" {
91                 hshOur, err := hex.DecodeString(*fpr)
92                 if err != nil {
93                         log.Fatalln(err)
94                 }
95                 cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
96                         spki := verifiedChains[0][0].RawSubjectPublicKeyInfo
97                         hshTheir := sha256.Sum256(spki)
98                         if bytes.Compare(hshOur, hshTheir[:]) != 0 {
99                                 return errors.New("server certificate's SPKI hash mismatch")
100                         }
101                         return nil
102                 }
103         }
104
105         conn, err := ucspi.NewConn(os.NewFile(6, "R"), os.NewFile(7, "W"))
106         if err != nil {
107                 log.Fatalln(err)
108         }
109         tlsConn := tls.Client(conn, cfg)
110         if err := tlsConn.Handshake(); err != nil {
111                 log.Fatalln(err)
112         }
113         connState := tlsConn.ConnectionState()
114         if *onlyShow {
115                 fmt.Fprintf(
116                         os.Stderr,
117                         "Version: %s\nCipherSuite: %s\n",
118                         ucspi.TLSVersion(connState.Version),
119                         tls.CipherSuiteName(connState.CipherSuite),
120                 )
121                 for _, cert := range connState.PeerCertificates {
122                         os.Stderr.WriteString("\n")
123                         pem.Encode(os.Stderr, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
124                         hsh := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
125                         fmt.Fprintf(
126                                 os.Stderr, "Issuer: %s\nSubject: %s\nValidity: %s / %s\nSPKI SHA256: %s\n",
127                                 cert.Issuer, cert.Subject, cert.NotBefore, cert.NotAfter,
128                                 hex.EncodeToString(hsh[:]),
129                         )
130                 }
131                 return
132         }
133         dn := connState.PeerCertificates[0].Subject.String()
134
135         args := flag.Args()
136         cmd := exec.Command(args[0], args[1:]...)
137         rr, rw, err := os.Pipe()
138         if err != nil {
139                 log.Fatalln(err)
140         }
141         wr, ww, err := os.Pipe()
142         if err != nil {
143                 log.Fatalln(err)
144         }
145         cmd.ExtraFiles = []*os.File{nil, nil, nil, rr, ww}
146         cmd.Stdin = os.Stdin
147         cmd.Stdout = os.Stdout
148         cmd.Stderr = os.Stderr
149         cmd.Env = append(os.Environ(), "PROTO=TLS")
150         cmd.Env = append(cmd.Env, "TLSREMOTEDN="+dn)
151         if err = cmd.Start(); err != nil {
152                 log.Fatalln(err)
153         }
154         worker := make(chan struct{})
155         go func() {
156                 io.Copy(rw, tlsConn)
157                 rw.Close()
158                 close(worker)
159         }()
160         go func() {
161                 io.Copy(tlsConn, wr)
162         }()
163         _, err = cmd.Process.Wait()
164         <-worker
165         tlsConn.Close()
166         if err != nil {
167                 log.Fatalln(err)
168         }
169 }