Repository: pires/go-proxyproto
Pull request #91 (2022-04-28 00:57:04)

Add method to get SSL cipher from the SSL TLV
Tests are using a header captured from haproxy with "proxy-v2-options
ssl-cipher" set.

To use this with haproxy you also need to set the ssl-cipher option,
something like:

    backend test
      default-server send-proxy-v2-ssl proxy-v2-options ssl-cipher
      server a [::1]:8080


Patch:
diff --git a/tlvparse/ssl.go b/tlvparse/ssl.go
index 5f62e2a..d6b80be 100644
--- a/tlvparse/ssl.go
+++ b/tlvparse/ssl.go
@@ -65,6 +65,16 @@ func (s PP2SSL) SSLVersion() (string, bool) {
 	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)
@@ -138,6 +148,14 @@ func SSL(t proxyproto.TLV) (PP2SSL, error) {
 			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 {


Test patch:
diff --git a/tlvparse/ssl_test.go b/tlvparse/ssl_test.go
index e98f87b..a198cbe 100644
--- a/tlvparse/ssl_test.go
+++ b/tlvparse/ssl_test.go
@@ -74,11 +74,85 @@ var testCases = []struct {
 				t.Fatalf("TestParseV2TLV %s: Unexpected SSLVersion expected %#v, actual %#v", name, esslVer, asslVer)
 			}
 
+			if _, ok := ssl.SSLCipher(); ok {
+				t.Fatalf("TestParseV2TLV %s: Unexpected SSLCipher", name)
+			}
+
 			if !ssl.Verified() {
 				t.Fatalf("TestParseV2TLV %s: Expected Verified to be true", name)
 			}
 		},
 	},
+	{
+		name: "SSL haproxy cipher",
+		raw: []byte{
+			0x0d, 0x0a, 0x0d, 0x0a,
+			0x00, 0x0d, 0x0a, 0x51,
+			0x55, 0x49, 0x54, 0x0a,
+			0x21, 0x21, 0x00, 0x4f,
+			0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0xff, 0xff,
+			0x0a, 0x01, 0x5b, 0x0e,
+			0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0xff, 0xff,
+			0x0a, 0x01, 0x01, 0x9f,
+			0xf4, 0x7c, 0x01, 0xbb,
+			0x20, 0x00, 0x28, 0x01,
+			0x00, 0x00, 0x00, 0x00,
+			0x21, 0x00, 0x07, 0x54,
+			0x4c, 0x53, 0x76, 0x31,
+			0x2e, 0x33, 0x23, 0x00,
+			0x16, 0x54, 0x4c, 0x53,
+			0x5f, 0x41, 0x45, 0x53,
+			0x5f, 0x32, 0x35, 0x36,
+			0x5f, 0x47, 0x43, 0x4d,
+			0x5f, 0x53, 0x48, 0x41,
+			0x33, 0x38, 0x34,
+		},
+		types: []proxyproto.PP2Type{proxyproto.PP2_TYPE_SSL},
+		valid: func(t *testing.T, name string, tlvs []proxyproto.TLV) {
+			if !IsSSL(tlvs[0]) {
+				t.Fatalf("TestParseV2TLV %s: Expected tlvs[0] to be the SSL type", name)
+			}
+
+			ssl, err := SSL(tlvs[0])
+			if err != nil {
+				t.Fatalf("TestParseV2TLV %s: Unexpected error when parsing SSL %#v", name, err)
+			}
+
+			if !ssl.ClientSSL() {
+				t.Fatalf("TestParseV2TLV %s: Expected ClientSSL() to be true", name)
+			}
+
+			if ssl.ClientCertConn() {
+				t.Fatalf("TestParseV2TLV %s: Expected ClientCertConn() to be false", name)
+			}
+
+			if ssl.ClientCertSess() {
+				t.Fatalf("TestParseV2TLV %s: Expected ClientCertSess() to be false", name)
+			}
+
+			if _, ok := ssl.ClientCN(); ok {
+				t.Fatalf("TestParseV2TLV %s: Expected ClientCN to not exist", name)
+			}
+
+			esslVer := "TLSv1.3"
+			if asslVer, ok := ssl.SSLVersion(); !ok {
+				t.Fatalf("TestParseV2TLV %s: Expected SSLVersion to exist", name)
+			} else if asslVer != esslVer {
+				t.Fatalf("TestParseV2TLV %s: Unexpected SSLVersion expected %#v, actual %#v", name, esslVer, asslVer)
+			}
+
+			esslCipher := "TLS_AES_256_GCM_SHA384"
+			if asslCipher, ok := ssl.SSLCipher(); !ok {
+				t.Fatalf("TestParseV2TLV %s: Expected SSLCipher to exist", name)
+			} else if asslCipher != esslCipher {
+				t.Fatalf("TestParseV2TLV %s: Unexpected SSLCipher expected %#v, actual %#v", name, esslCipher, asslCipher)
+			}
+		},
+	},
 }
 
 func TestParseV2TLV(t *testing.T) {