xref: /openbsd/regress/lib/libtls/gotls/tls.go (revision 5c389b79)
1// Package tls provides a Go interface to the libtls library.
2package tls
3
4/*
5#cgo LDFLAGS: -ltls -lssl -lcrypto
6
7#include <stdlib.h>
8
9#include <tls.h>
10
11typedef void *tls;
12*/
13import "C"
14
15import (
16	"errors"
17	"fmt"
18	"time"
19	"unsafe"
20)
21
22var (
23	errWantPollIn  = errors.New("want poll in")
24	errWantPollOut = errors.New("want poll out")
25)
26
27// ProtocolVersion represents a TLS protocol version.
28type ProtocolVersion uint32
29
30// String returns the string representation of a protocol version.
31func (pv ProtocolVersion) String() string {
32	name, ok := protocolNames[pv]
33	if !ok {
34		return fmt.Sprintf("unknown protocol version %x", uint32(pv))
35	}
36	return name
37}
38
39const (
40	ProtocolTLSv10 ProtocolVersion = C.TLS_PROTOCOL_TLSv1_0
41	ProtocolTLSv11 ProtocolVersion = C.TLS_PROTOCOL_TLSv1_1
42	ProtocolTLSv12 ProtocolVersion = C.TLS_PROTOCOL_TLSv1_2
43	ProtocolTLSv13 ProtocolVersion = C.TLS_PROTOCOL_TLSv1_3
44	ProtocolsAll   ProtocolVersion = C.TLS_PROTOCOLS_ALL
45)
46
47var protocolNames = map[ProtocolVersion]string{
48	ProtocolTLSv12: "TLSv1.2",
49	ProtocolTLSv13: "TLSv1.3",
50	ProtocolsAll:   "all",
51}
52
53// ProtocolVersionFromString returns the protocol version with the given name.
54func ProtocolVersionFromString(version string) (ProtocolVersion, error) {
55	for proto, name := range protocolNames {
56		if version == name {
57			return proto, nil
58		}
59	}
60	return 0, fmt.Errorf("unknown protocol version %q", version)
61}
62
63// TLSConfig provides configuration options for a TLS context.
64type TLSConfig struct {
65	tlsCfg *C.struct_tls_config
66}
67
68// TLS encapsulates the TLS context.
69type TLS struct {
70	cfg *TLSConfig
71	ctx *C.struct_tls
72}
73
74// Init initialises the TLS library.
75func Init() error {
76	if C.tls_init() != 0 {
77		return errors.New("initialisation failed")
78	}
79	return nil
80}
81
82// NewConfig returns a new TLS configuration.
83func NewConfig() (*TLSConfig, error) {
84	cfg := C.tls_config_new()
85	if cfg == nil {
86		return nil, errors.New("failed to allocate config")
87	}
88	return &TLSConfig{
89		tlsCfg: cfg,
90	}, nil
91}
92
93// Error returns the error message from the TLS configuration.
94func (c *TLSConfig) Error() error {
95	if msg := C.tls_config_error(c.tlsCfg); msg != nil {
96		return errors.New(C.GoString(msg))
97	}
98	return errors.New("unknown error")
99}
100
101// SetCAFile sets the CA file to be used for connections.
102func (c *TLSConfig) SetCAFile(filename string) error {
103	caFile := C.CString(filename)
104	defer C.free(unsafe.Pointer(caFile))
105	if C.tls_config_set_ca_file(c.tlsCfg, caFile) != 0 {
106		return c.Error()
107	}
108	return nil
109}
110
111// SetCiphers sets the cipher suites enabled for the connection.
112func (c *TLSConfig) SetCiphers(ciphers string) error {
113	cipherStr := C.CString(ciphers)
114	defer C.free(unsafe.Pointer(cipherStr))
115	if C.tls_config_set_ciphers(c.tlsCfg, cipherStr) != 0 {
116		return c.Error()
117	}
118	return nil
119}
120
121// SetProtocols sets the protocol versions enabled for the connection.
122func (c *TLSConfig) SetProtocols(proto ProtocolVersion) error {
123	if C.tls_config_set_protocols(c.tlsCfg, C.uint32_t(proto)) != 0 {
124		return c.Error()
125	}
126	return nil
127}
128
129// InsecureNoVerifyCert disables certificate verification for the connection.
130func (c *TLSConfig) InsecureNoVerifyCert() {
131	C.tls_config_insecure_noverifycert(c.tlsCfg)
132}
133
134// InsecureNoVerifyName disables server name verification for the connection.
135func (c *TLSConfig) InsecureNoVerifyName() {
136	C.tls_config_insecure_noverifyname(c.tlsCfg)
137}
138
139// SetSecure enables verification for the connection.
140func (c *TLSConfig) SetVerify() {
141	C.tls_config_verify(c.tlsCfg)
142}
143
144// Free frees resources associated with the TLS configuration.
145func (c *TLSConfig) Free() {
146	if c.tlsCfg == nil {
147		return
148	}
149	C.tls_config_free(c.tlsCfg)
150	c.tlsCfg = nil
151}
152
153// NewClient returns a new TLS client context, using the optional configuration.
154// If no configuration is specified the default configuration will be used.
155func NewClient(config *TLSConfig) (*TLS, error) {
156	var sslCfg *C.struct_tls_config
157	if config != nil {
158		sslCfg = config.tlsCfg
159	}
160	ctx := C.tls_client()
161	if ctx == nil {
162		return nil, errors.New("tls client failed")
163	}
164	if C.tls_configure(ctx, sslCfg) != 0 {
165		return nil, errors.New("tls configure failed")
166	}
167	return &TLS{
168		cfg: config,
169		ctx: ctx,
170	}, nil
171}
172
173// Error returns the error message from the TLS context.
174func (t *TLS) Error() error {
175	if msg := C.tls_error(t.ctx); msg != nil {
176		return errors.New(C.GoString(msg))
177	}
178	return errors.New("unknown error")
179}
180
181// PeerCertProvided returns whether the peer provided a certificate.
182func (t *TLS) PeerCertProvided() bool {
183	return C.tls_peer_cert_provided(t.ctx) == 1
184}
185
186// PeerCertContainsName checks whether the peer certificate contains
187// the specified name.
188func (t *TLS) PeerCertContainsName(name string) bool {
189	n := C.CString(name)
190	defer C.free(unsafe.Pointer(n))
191	return C.tls_peer_cert_contains_name(t.ctx, n) == 1
192}
193
194// PeerCertIssuer returns the issuer of the peer certificate.
195func (t *TLS) PeerCertIssuer() (string, error) {
196	issuer := C.tls_peer_cert_issuer(t.ctx)
197	if issuer == nil {
198		return "", errors.New("no issuer returned")
199	}
200	return C.GoString(issuer), nil
201}
202
203// PeerCertSubject returns the subject of the peer certificate.
204func (t *TLS) PeerCertSubject() (string, error) {
205	subject := C.tls_peer_cert_subject(t.ctx)
206	if subject == nil {
207		return "", errors.New("no subject returned")
208	}
209	return C.GoString(subject), nil
210}
211
212// PeerCertHash returns a hash of the peer certificate.
213func (t *TLS) PeerCertHash() (string, error) {
214	hash := C.tls_peer_cert_hash(t.ctx)
215	if hash == nil {
216		return "", errors.New("no hash returned")
217	}
218	return C.GoString(hash), nil
219}
220
221// PeerCertNotBefore returns the notBefore time from the peer
222// certificate.
223func (t *TLS) PeerCertNotBefore() (time.Time, error) {
224	notBefore := C.tls_peer_cert_notbefore(t.ctx)
225	if notBefore == -1 {
226		return time.Time{}, errors.New("no notBefore time returned")
227	}
228	return time.Unix(int64(notBefore), 0), nil
229}
230
231// PeerCertNotAfter returns the notAfter time from the peer
232// certificate.
233func (t *TLS) PeerCertNotAfter() (time.Time, error) {
234	notAfter := C.tls_peer_cert_notafter(t.ctx)
235	if notAfter == -1 {
236		return time.Time{}, errors.New("no notAfter time")
237	}
238	return time.Unix(int64(notAfter), 0), nil
239}
240
241// ConnVersion returns the protocol version of the connection.
242func (t *TLS) ConnVersion() (ProtocolVersion, error) {
243	ver := C.tls_conn_version(t.ctx)
244	if ver == nil {
245		return 0, errors.New("no connection version")
246	}
247	return ProtocolVersionFromString(C.GoString(ver))
248}
249
250// ConnCipher returns the cipher suite used for the connection.
251func (t *TLS) ConnCipher() (string, error) {
252	cipher := C.tls_conn_cipher(t.ctx)
253	if cipher == nil {
254		return "", errors.New("no connection cipher")
255	}
256	return C.GoString(cipher), nil
257}
258
259// ConnCipherStrength returns the strength in bits for the symmetric
260// cipher that is used for the connection.
261func (t *TLS) ConnCipherStrength() (int, error) {
262	strength := C.tls_conn_cipher_strength(t.ctx)
263	if strength == 0 {
264		return 0, errors.New("no connection cipher strength")
265	}
266	return int(strength), nil
267}
268
269// Connect attempts to establish an TLS connection to the specified host on
270// the given port. The host may optionally contain a colon separated port
271// value if the port string is specified as an empty string.
272func (t *TLS) Connect(host, port string) error {
273	h := C.CString(host)
274	var p *C.char
275	if port != "" {
276		p = C.CString(port)
277	}
278	defer C.free(unsafe.Pointer(h))
279	defer C.free(unsafe.Pointer(p))
280	if C.tls_connect(t.ctx, h, p) != 0 {
281		return t.Error()
282	}
283	return nil
284}
285
286// Handshake attempts to complete the TLS handshake.
287func (t *TLS) Handshake() error {
288	ret := C.tls_handshake(t.ctx)
289	switch {
290	case ret == C.TLS_WANT_POLLIN:
291		return errWantPollIn
292	case ret == C.TLS_WANT_POLLOUT:
293		return errWantPollOut
294	case ret != 0:
295		return t.Error()
296	}
297	return nil
298}
299
300// Read reads data the TLS connection into the given buffer.
301func (t *TLS) Read(buf []byte) (int, error) {
302	ret := C.tls_read(t.ctx, unsafe.Pointer(&buf[0]), C.size_t(len(buf)))
303	switch {
304	case ret == C.TLS_WANT_POLLIN:
305		return -1, errWantPollIn
306	case ret == C.TLS_WANT_POLLOUT:
307		return -1, errWantPollOut
308	case ret < 0:
309		return -1, t.Error()
310	}
311	return int(ret), nil
312}
313
314// Write writes the given data to the TLS connection.
315func (t *TLS) Write(buf []byte) (int, error) {
316	p := C.CString(string(buf))
317	defer C.free(unsafe.Pointer(p))
318	ret := C.tls_write(t.ctx, unsafe.Pointer(p), C.size_t(len(buf)))
319	switch {
320	case ret == C.TLS_WANT_POLLIN:
321		return -1, errWantPollIn
322	case ret == C.TLS_WANT_POLLOUT:
323		return -1, errWantPollOut
324	case ret < 0:
325		return -1, t.Error()
326	}
327	return int(ret), nil
328}
329
330// Close closes the TLS connection.
331func (t *TLS) Close() error {
332	ret := C.tls_close(t.ctx)
333	switch {
334	case ret == C.TLS_WANT_POLLIN:
335		return errWantPollIn
336	case ret == C.TLS_WANT_POLLOUT:
337		return errWantPollOut
338	case ret != 0:
339		return t.Error()
340	}
341	return nil
342}
343
344// Free frees resources associated with the TLS context.
345func (t *TLS) Free() {
346	if t.ctx == nil {
347		return
348	}
349	C.tls_free(t.ctx)
350	t.ctx = nil
351}
352