2 ucspi/cmd/tlsc -- UCSPI TLS client
3 Copyright (C) 2021 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 fpr := flag.String("fpr", "", "Expected SHA256 hash of server certificate's SPKI")
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]
54 log.SetFlags(log.Lshortfile)
56 if len(flag.Args()) == 0 {
62 if *hostname == "" || *onlyShow {
63 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.Compare(hshOur, hshTheir[:]) != 0 {
97 return errors.New("server certificate's SPKI hash mismatch")
103 conn := &ucspi.Conn{R: os.NewFile(6, "R"), W: os.NewFile(7, "W")}
105 log.Fatalln("no 6 file descriptor")
108 log.Fatalln("no 7 file descriptor")
110 tlsConn := tls.Client(conn, cfg)
111 if err := tlsConn.Handshake(); err != nil {
114 connState := tlsConn.ConnectionState()
117 os.Stderr, "Version: %04x\nCipherSuite: %s\n",
118 connState.Version, tls.CipherSuiteName(connState.CipherSuite),
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)
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[:]),
132 dn := connState.PeerCertificates[0].Subject.String()
135 cmd := exec.Command(args[0], args[1:]...)
136 rr, rw, err := os.Pipe()
140 wr, ww, err := os.Pipe()
144 cmd.ExtraFiles = []*os.File{nil, nil, nil, rr, ww}
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 {
153 copiers := make(chan struct{})
162 _, err = cmd.Process.Wait()