File: ssl.go

package info (click to toggle)
golang-github-pires-go-proxyproto 0.7.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 332 kB
  • sloc: makefile: 3
file content (185 lines) | stat: -rw-r--r-- 5,628 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package tlvparse

import (
	"encoding/binary"
	"unicode"
	"unicode/utf8"

	"github.com/pires/go-proxyproto"
)

const (
	// pp2_tlv_ssl.client  bit fields
	PP2_BITFIELD_CLIENT_SSL       uint8 = 0x01
	PP2_BITFIELD_CLIENT_CERT_CONN uint8 = 0x02
	PP2_BITFIELD_CLIENT_CERT_SESS uint8 = 0x04

	tlvSSLMinLen = 5 // len(pp2_tlv_ssl.client) + len(pp2_tlv_ssl.verify)
)

// 2.2.5. The PP2_TYPE_SSL type and subtypes
/*
   struct pp2_tlv_ssl {
           uint8_t  client;
           uint32_t verify;
           struct pp2_tlv sub_tlv[0];
   };
*/
type PP2SSL struct {
	Client uint8 // The <client> field is made of a bit field from the following values,
	// indicating which element is present: PP2_BITFIELD_CLIENT_SSL,
	// PP2_BITFIELD_CLIENT_CERT_CONN, PP2_BITFIELD_CLIENT_CERT_SESS
	Verify uint32 // Verify will be zero if the client presented a certificate
	// and it was successfully verified, and non-zero otherwise.
	TLV []proxyproto.TLV
}

// Verified is true if the client presented a certificate and it was successfully verified
func (s PP2SSL) Verified() bool {
	return s.Verify == 0
}

// ClientSSL indicates that the client connected over SSL/TLS.  When true, SSLVersion will return the version.
func (s PP2SSL) ClientSSL() bool {
	return s.Client&PP2_BITFIELD_CLIENT_SSL == PP2_BITFIELD_CLIENT_SSL
}

// ClientCertConn indicates that the client provided a certificate over the current connection.
func (s PP2SSL) ClientCertConn() bool {
	return s.Client&PP2_BITFIELD_CLIENT_CERT_CONN == PP2_BITFIELD_CLIENT_CERT_CONN
}

// ClientCertSess indicates that the client provided a certificate at least once over the TLS session this
// connection belongs to.
func (s PP2SSL) ClientCertSess() bool {
	return s.Client&PP2_BITFIELD_CLIENT_CERT_SESS == PP2_BITFIELD_CLIENT_CERT_SESS
}

// SSLVersion returns the US-ASCII string representation of the TLS version and whether that extension exists.
func (s PP2SSL) SSLVersion() (string, bool) {
	for _, tlv := range s.TLV {
		if tlv.Type == proxyproto.PP2_SUBTYPE_SSL_VERSION {
			return string(tlv.Value), true
		}
	}
	return "", false
}

// SSLCipher returns the US-ASCII string representation of the used TLS cipher and whether that extension exists.
func (s PP2SSL) SSLCipher() (string, bool) {
	for _, tlv := range s.TLV {
		if tlv.Type == proxyproto.PP2_SUBTYPE_SSL_CIPHER {
			return string(tlv.Value), true
		}
	}
	return "", false
}

// Marshal formats the PP2SSL structure as a TLV.
func (s PP2SSL) Marshal() (proxyproto.TLV, error) {
	v := make([]byte, 5)
	v[0] = s.Client
	binary.BigEndian.PutUint32(v[1:5], s.Verify)

	tlvs, err := proxyproto.JoinTLVs(s.TLV)
	if err != nil {
		return proxyproto.TLV{}, err
	}
	v = append(v, tlvs...)

	return proxyproto.TLV{
		Type:  proxyproto.PP2_TYPE_SSL,
		Value: v,
	}, nil
}

// ClientCN returns the string representation (in UTF8) of the Common Name field (OID: 2.5.4.3) of the client
// certificate's Distinguished Name and whether that extension exists.
func (s PP2SSL) ClientCN() (string, bool) {
	for _, tlv := range s.TLV {
		if tlv.Type == proxyproto.PP2_SUBTYPE_SSL_CN {
			return string(tlv.Value), true
		}
	}
	return "", false
}

// SSLType is true if the TLV is type SSL
func IsSSL(t proxyproto.TLV) bool {
	return t.Type == proxyproto.PP2_TYPE_SSL && len(t.Value) >= tlvSSLMinLen
}

// SSL returns the pp2_tlv_ssl from section 2.2.5 or errors with ErrIncompatibleTLV or ErrMalformedTLV
func SSL(t proxyproto.TLV) (PP2SSL, error) {
	ssl := PP2SSL{}
	if !IsSSL(t) {
		return ssl, proxyproto.ErrIncompatibleTLV
	}
	if len(t.Value) < tlvSSLMinLen {
		return ssl, proxyproto.ErrMalformedTLV
	}
	ssl.Client = t.Value[0]
	ssl.Verify = binary.BigEndian.Uint32(t.Value[1:5])
	var err error
	ssl.TLV, err = proxyproto.SplitTLVs(t.Value[5:])
	if err != nil {
		return PP2SSL{}, err
	}
	versionFound := !ssl.ClientSSL()
	for _, tlv := range ssl.TLV {
		switch tlv.Type {
		case proxyproto.PP2_SUBTYPE_SSL_VERSION:
			/*
				The PP2_CLIENT_SSL flag indicates that the client connected over SSL/TLS. When
				this field is present, the US-ASCII string representation of the TLS version is
				appended at the end of the field in the TLV format using the type
				PP2_SUBTYPE_SSL_VERSION.
			*/
			if len(tlv.Value) == 0 || !isASCII(tlv.Value) {
				return PP2SSL{}, proxyproto.ErrMalformedTLV
			}
			versionFound = true
		case proxyproto.PP2_SUBTYPE_SSL_CN:
			/*
				In all cases, the string representation (in UTF8) of the Common Name field
				(OID: 2.5.4.3) of the client certificate's Distinguished Name, is appended
				using the TLV format and the type PP2_SUBTYPE_SSL_CN. E.g. "example.com".
			*/
			if len(tlv.Value) == 0 || !utf8.Valid(tlv.Value) {
				return PP2SSL{}, proxyproto.ErrMalformedTLV
			}
		case proxyproto.PP2_SUBTYPE_SSL_CIPHER:
			/*
				The second level TLV PP2_SUBTYPE_SSL_CIPHER provides the US-ASCII string name
				of the used cipher, for example "ECDHE-RSA-AES128-GCM-SHA256".
			*/
			if len(tlv.Value) == 0 || !isASCII(tlv.Value) {
				return PP2SSL{}, proxyproto.ErrMalformedTLV
			}
		}
	}
	if !versionFound {
		return PP2SSL{}, proxyproto.ErrMalformedTLV
	}
	return ssl, nil
}

// SSL returns the first PP2SSL if it exists and is well formed as well as bool indicating if it was found.
func FindSSL(tlvs []proxyproto.TLV) (PP2SSL, bool) {
	for _, t := range tlvs {
		if ssl, err := SSL(t); err == nil {
			return ssl, true
		}
	}
	return PP2SSL{}, false
}

// isASCII checks whether a byte slice has all characters that fit in the ascii character set, including the null byte.
func isASCII(b []byte) bool {
	for _, c := range b {
		if c > unicode.MaxASCII {
			return false
		}
	}
	return true
}