1 // ucspi/cmd/tlsc -- UCSPI TCP proxy client
2 // Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
33 "go.cypherpunks.ru/ucspi"
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")
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]
53 log.SetFlags(log.Lshortfile)
55 if len(flag.Args()) == 0 {
61 if *hostname == "" || *onlyShow || *insecure {
62 cfg.InsecureSkipVerify = true
65 cfg.ServerName = *hostname
68 crtRaw, _, err := ucspi.CertificateFromFile(*crtPath)
72 prv, err := ucspi.PrivateKeyFromFile(*prvPath)
76 cfg.Certificates = []tls.Certificate{{
77 Certificate: [][]byte{crtRaw},
83 _, cfg.RootCAs, err = ucspi.CertPoolFromFile(*casPath)
89 hshOur, err := hex.DecodeString(*fpr)
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")
103 conn, err := ucspi.NewConn(os.NewFile(6, "R"), os.NewFile(7, "W"))
107 tlsConn := tls.Client(conn, cfg)
108 if err := tlsConn.Handshake(); err != nil {
111 connState := tlsConn.ConnectionState()
115 "Version: %s\nCipherSuite: %s\n",
116 ucspi.TLSVersion(connState.Version),
117 tls.CipherSuiteName(connState.CipherSuite),
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)
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[:]),
131 dn := connState.PeerCertificates[0].Subject.String()
134 cmd := exec.Command(args[0], args[1:]...)
135 rr, rw, err := os.Pipe()
139 wr, ww, err := os.Pipe()
143 cmd.ExtraFiles = []*os.File{nil, nil, nil, rr, ww}
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 {
152 worker := make(chan struct{})
161 _, err = cmd.Process.Wait()