]> Cypherpunks.ru repositories - ucspi.git/blob - cmd/tlsc/main.go
8d32b827275c3805628f61dd3f00a836207c9c74
[ucspi.git] / cmd / tlsc / main.go
1 /*
2 ucspi/cmd/tlsc -- UCSPI TLS client
3 Copyright (C) 2021 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         fpr := flag.String("fpr", "", "Expected SHA256 hash of server certificate's SPKI")
45         flag.Usage = func() {
46                 fmt.Fprintf(os.Stderr, `Usage: tcpclient host port tlsc -name expected.name
47         [-cert cert.pem -key prv.pem] [-ca CAs.pem] [-fpr DEADBABE] [-show]
48         program [args...]
49
50 `)
51                 flag.PrintDefaults()
52         }
53         flag.Parse()
54         log.SetFlags(log.Lshortfile)
55
56         if len(flag.Args()) == 0 {
57                 flag.Usage()
58                 os.Exit(1)
59         }
60
61         cfg := &tls.Config{}
62         if *hostname == "" || *onlyShow {
63                 cfg.InsecureSkipVerify = true
64         } else {
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.Compare(hshOur, hshTheir[:]) != 0 {
97                                 return errors.New("server certificate's SPKI hash mismatch")
98                         }
99                         return nil
100                 }
101         }
102
103         conn := &ucspi.Conn{R: os.NewFile(6, "R"), W: os.NewFile(7, "W")}
104         if conn.R == nil {
105                 log.Fatalln("no 6 file descriptor")
106         }
107         if conn.W == nil {
108                 log.Fatalln("no 7 file descriptor")
109         }
110         tlsConn := tls.Client(conn, cfg)
111         if err := tlsConn.Handshake(); err != nil {
112                 log.Fatalln(err)
113         }
114         connState := tlsConn.ConnectionState()
115         if *onlyShow {
116                 fmt.Fprintf(
117                         os.Stderr, "Version: %04x\nCipherSuite: %s\n",
118                         connState.Version, tls.CipherSuiteName(connState.CipherSuite),
119                 )
120                 for _, cert := range connState.PeerCertificates {
121                         os.Stderr.WriteString("\n")
122                         pem.Encode(os.Stderr, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
123                         hsh := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
124                         fmt.Fprintf(
125                                 os.Stderr, "Issuer: %s\nSubject: %s\nValidity: %s / %s\nSPKI SHA256: %s\n",
126                                 cert.Issuer, cert.Subject, cert.NotBefore, cert.NotAfter,
127                                 hex.EncodeToString(hsh[:]),
128                         )
129                 }
130                 return
131         }
132         dn := connState.PeerCertificates[0].Subject.String()
133
134         args := flag.Args()
135         cmd := exec.Command(args[0], args[1:]...)
136         rr, rw, err := os.Pipe()
137         if err != nil {
138                 log.Fatalln(err)
139         }
140         wr, ww, err := os.Pipe()
141         if err != nil {
142                 log.Fatalln(err)
143         }
144         cmd.ExtraFiles = []*os.File{nil, nil, nil, rr, ww}
145         cmd.Stdin = os.Stdin
146         cmd.Stdout = os.Stdout
147         cmd.Stderr = os.Stderr
148         cmd.Env = append(os.Environ(), "PROTO=TLS")
149         cmd.Env = append(cmd.Env, "TLSREMOTEDN="+dn)
150         if err = cmd.Start(); err != nil {
151                 log.Fatalln(err)
152         }
153         copiers := make(chan struct{})
154         go func() {
155                 io.Copy(rw, tlsConn)
156                 rw.Close()
157                 close(copiers)
158         }()
159         go func() {
160                 io.Copy(tlsConn, wr)
161         }()
162         _, err = cmd.Process.Wait()
163         <-copiers
164         if err != nil {
165                 log.Fatalln(err)
166         }
167 }