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
66 cfg.ServerName = *hostname
69 crtRaw, _, err := ucspi.CertificateFromFile(*crtPath)
73 prv, err := ucspi.PrivateKeyFromFile(*prvPath)
77 cfg.Certificates = []tls.Certificate{{
78 Certificate: [][]byte{crtRaw},
84 cfg.RootCAs, err = ucspi.CertPoolFromFile(*casPath)
90 hshOur, err := hex.DecodeString(*fpr)
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")
104 conn := &ucspi.Conn{R: os.NewFile(6, "R"), W: os.NewFile(7, "W")}
106 log.Fatalln("no 6 file descriptor")
109 log.Fatalln("no 7 file descriptor")
111 tlsConn := tls.Client(conn, cfg)
112 if err := tlsConn.Handshake(); err != nil {
115 connState := tlsConn.ConnectionState()
118 os.Stderr, "Version: %04x\nCipherSuite: %s\n",
119 connState.Version, 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 copiers := make(chan struct{})
163 _, err = cmd.Process.Wait()