2 ucspi/cmd/tlsc -- UCSPI TLS client
3 Copyright (C) 2021-2022 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
35 "go.cypherpunks.ru/ucspi"
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")
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]
55 log.SetFlags(log.Lshortfile)
57 if len(flag.Args()) == 0 {
63 if *hostname == "" || *onlyShow || *insecure {
64 cfg.InsecureSkipVerify = true
67 cfg.ServerName = *hostname
70 crtRaw, _, err := ucspi.CertificateFromFile(*crtPath)
74 prv, err := ucspi.PrivateKeyFromFile(*prvPath)
78 cfg.Certificates = []tls.Certificate{{
79 Certificate: [][]byte{crtRaw},
85 _, cfg.RootCAs, err = ucspi.CertPoolFromFile(*casPath)
91 hshOur, err := hex.DecodeString(*fpr)
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")
105 conn, err := ucspi.NewConn(os.NewFile(6, "R"), os.NewFile(7, "W"))
109 tlsConn := tls.Client(conn, cfg)
110 if err := tlsConn.Handshake(); err != nil {
113 connState := tlsConn.ConnectionState()
117 "Version: %s\nCipherSuite: %s\n",
118 ucspi.TLSVersion(connState.Version),
119 tls.CipherSuiteName(connState.CipherSuite),
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)
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[:]),
133 dn := connState.PeerCertificates[0].Subject.String()
136 cmd := exec.Command(args[0], args[1:]...)
137 rr, rw, err := os.Pipe()
141 wr, ww, err := os.Pipe()
145 cmd.ExtraFiles = []*os.File{nil, nil, nil, rr, ww}
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 {
154 worker := make(chan struct{})
163 _, err = cmd.Process.Wait()