1package tlvparse
2
3import (
4	"encoding/binary"
5	"unicode"
6	"unicode/utf8"
7
8	"github.com/pires/go-proxyproto"
9)
10
11const (
12	// pp2_tlv_ssl.client  bit fields
13	PP2_BITFIELD_CLIENT_SSL       uint8 = 0x01
14	PP2_BITFIELD_CLIENT_CERT_CONN       = 0x02
15	PP2_BITFIELD_CLIENT_CERT_SESS       = 0x04
16
17	tlvSSLMinLen = 5 // len(pp2_tlv_ssl.client) + len(pp2_tlv_ssl.verify)
18)
19
20// 2.2.5. The PP2_TYPE_SSL type and subtypes
21/*
22   struct pp2_tlv_ssl {
23           uint8_t  client;
24           uint32_t verify;
25           struct pp2_tlv sub_tlv[0];
26   };
27*/
28type PP2SSL struct {
29	Client uint8 // The <client> field is made of a bit field from the following values,
30	// indicating which element is present: PP2_BITFIELD_CLIENT_SSL,
31	// PP2_BITFIELD_CLIENT_CERT_CONN, PP2_BITFIELD_CLIENT_CERT_SESS
32	Verify uint32 // Verify will be zero if the client presented a certificate
33	// and it was successfully verified, and non-zero otherwise.
34	TLV []proxyproto.TLV
35}
36
37// Verified is true if the client presented a certificate and it was successfully verified
38func (s PP2SSL) Verified() bool {
39	return s.Verify == 0
40}
41
42// ClientSSL indicates that the client connected over SSL/TLS.  When true, SSLVersion will return the version.
43func (s PP2SSL) ClientSSL() bool {
44	return s.Client&PP2_BITFIELD_CLIENT_SSL == PP2_BITFIELD_CLIENT_SSL
45}
46
47// ClientCertConn indicates that the client provided a certificate over the current connection.
48func (s PP2SSL) ClientCertConn() bool {
49	return s.Client&PP2_BITFIELD_CLIENT_CERT_CONN == PP2_BITFIELD_CLIENT_CERT_CONN
50}
51
52// ClientCertSess indicates that the client provided a certificate at least once over the TLS session this
53// connection belongs to.
54func (s PP2SSL) ClientCertSess() bool {
55	return s.Client&PP2_BITFIELD_CLIENT_CERT_SESS == PP2_BITFIELD_CLIENT_CERT_SESS
56}
57
58// SSLVersion returns the US-ASCII string representation of the TLS version and whether that extension exists.
59func (s PP2SSL) SSLVersion() (string, bool) {
60	for _, tlv := range s.TLV {
61		if tlv.Type == proxyproto.PP2_SUBTYPE_SSL_VERSION {
62			return string(tlv.Value), true
63		}
64	}
65	return "", false
66}
67
68// Marshal formats the PP2SSL structure as a TLV.
69func (s PP2SSL) Marshal() (proxyproto.TLV, error) {
70	v := make([]byte, 5)
71	v[0] = s.Client
72	binary.BigEndian.PutUint32(v[1:5], s.Verify)
73
74	tlvs, err := proxyproto.JoinTLVs(s.TLV)
75	if err != nil {
76		return proxyproto.TLV{}, err
77	}
78	v = append(v, tlvs...)
79
80	return proxyproto.TLV{
81		Type:  proxyproto.PP2_TYPE_SSL,
82		Value: v,
83	}, nil
84}
85
86// ClientCN returns the string representation (in UTF8) of the Common Name field (OID: 2.5.4.3) of the client
87// certificate's Distinguished Name and whether that extension exists.
88func (s PP2SSL) ClientCN() (string, bool) {
89	for _, tlv := range s.TLV {
90		if tlv.Type == proxyproto.PP2_SUBTYPE_SSL_CN {
91			return string(tlv.Value), true
92		}
93	}
94	return "", false
95}
96
97// SSLType is true if the TLV is type SSL
98func IsSSL(t proxyproto.TLV) bool {
99	return t.Type == proxyproto.PP2_TYPE_SSL && len(t.Value) >= tlvSSLMinLen
100}
101
102// SSL returns the pp2_tlv_ssl from section 2.2.5 or errors with ErrIncompatibleTLV or ErrMalformedTLV
103func SSL(t proxyproto.TLV) (PP2SSL, error) {
104	ssl := PP2SSL{}
105	if !IsSSL(t) {
106		return ssl, proxyproto.ErrIncompatibleTLV
107	}
108	if len(t.Value) < tlvSSLMinLen {
109		return ssl, proxyproto.ErrMalformedTLV
110	}
111	ssl.Client = t.Value[0]
112	ssl.Verify = binary.BigEndian.Uint32(t.Value[1:5])
113	var err error
114	ssl.TLV, err = proxyproto.SplitTLVs(t.Value[5:])
115	if err != nil {
116		return PP2SSL{}, err
117	}
118	versionFound := !ssl.ClientSSL()
119	for _, tlv := range ssl.TLV {
120		switch tlv.Type {
121		case proxyproto.PP2_SUBTYPE_SSL_VERSION:
122			/*
123				The PP2_CLIENT_SSL flag indicates that the client connected over SSL/TLS. When
124				this field is present, the US-ASCII string representation of the TLS version is
125				appended at the end of the field in the TLV format using the type
126				PP2_SUBTYPE_SSL_VERSION.
127			*/
128			if len(tlv.Value) == 0 || !isASCII(tlv.Value) {
129				return PP2SSL{}, proxyproto.ErrMalformedTLV
130			}
131			versionFound = true
132		case proxyproto.PP2_SUBTYPE_SSL_CN:
133			/*
134				In all cases, the string representation (in UTF8) of the Common Name field
135				(OID: 2.5.4.3) of the client certificate's Distinguished Name, is appended
136				using the TLV format and the type PP2_SUBTYPE_SSL_CN. E.g. "example.com".
137			*/
138			if len(tlv.Value) == 0 || !utf8.Valid(tlv.Value) {
139				return PP2SSL{}, proxyproto.ErrMalformedTLV
140			}
141		}
142	}
143	if !versionFound {
144		return PP2SSL{}, proxyproto.ErrMalformedTLV
145	}
146	return ssl, nil
147}
148
149// SSL returns the first PP2SSL if it exists and is well formed as well as bool indicating if it was found.
150func FindSSL(tlvs []proxyproto.TLV) (PP2SSL, bool) {
151	for _, t := range tlvs {
152		if ssl, err := SSL(t); err == nil {
153			return ssl, true
154		}
155	}
156	return PP2SSL{}, false
157}
158
159// isASCII checks whether a byte slice has all characters that fit in the ascii character set, including the null byte.
160func isASCII(b []byte) bool {
161	for _, c := range b {
162		if c > unicode.MaxASCII {
163			return false
164		}
165	}
166	return true
167}
168