]> Cypherpunks.ru repositories - ucspi.git/blob - cmd/tlsc/main.go
SNI friendly tlsc -show
[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         }
65         if *hostname != "" {
66                 cfg.ServerName = *hostname
67         }
68         if *crtPath != "" {
69                 crtRaw, _, err := ucspi.CertificateFromFile(*crtPath)
70                 if err != nil {
71                         log.Fatalln(err)
72                 }
73                 prv, err := ucspi.PrivateKeyFromFile(*prvPath)
74                 if err != nil {
75                         log.Fatalln(err)
76                 }
77                 cfg.Certificates = []tls.Certificate{{
78                         Certificate: [][]byte{crtRaw},
79                         PrivateKey:  prv,
80                 }}
81         }
82         if *casPath != "" {
83                 var err error
84                 cfg.RootCAs, err = ucspi.CertPoolFromFile(*casPath)
85                 if err != nil {
86                         log.Fatalln(err)
87                 }
88         }
89         if *fpr != "" {
90                 hshOur, err := hex.DecodeString(*fpr)
91                 if err != nil {
92                         log.Fatalln(err)
93                 }
94                 cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
95                         spki := verifiedChains[0][0].RawSubjectPublicKeyInfo
96                         hshTheir := sha256.Sum256(spki)
97                         if bytes.Compare(hshOur, hshTheir[:]) != 0 {
98                                 return errors.New("server certificate's SPKI hash mismatch")
99                         }
100                         return nil
101                 }
102         }
103
104         conn := &ucspi.Conn{R: os.NewFile(6, "R"), W: os.NewFile(7, "W")}
105         if conn.R == nil {
106                 log.Fatalln("no 6 file descriptor")
107         }
108         if conn.W == nil {
109                 log.Fatalln("no 7 file descriptor")
110         }
111         tlsConn := tls.Client(conn, cfg)
112         if err := tlsConn.Handshake(); err != nil {
113                 log.Fatalln(err)
114         }
115         connState := tlsConn.ConnectionState()
116         if *onlyShow {
117                 fmt.Fprintf(
118                         os.Stderr, "Version: %04x\nCipherSuite: %s\n",
119                         connState.Version, 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         copiers := make(chan struct{})
155         go func() {
156                 io.Copy(rw, tlsConn)
157                 rw.Close()
158                 close(copiers)
159         }()
160         go func() {
161                 io.Copy(tlsConn, wr)
162         }()
163         _, err = cmd.Process.Wait()
164         <-copiers
165         if err != nil {
166                 log.Fatalln(err)
167         }
168 }