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