Skip to content

Commit 235b66a

Browse files
simo5Gemini
andcommitted
Support GSSAPI channel bindings
This change adds support for GSSAPI channel bindings, which helps to protect against man-in-the-middle relay attacks by tying the authentication to the underlying secure channel. A `channel_bindings` parameter is added to `HTTPSPNEGOAuth`. When set to 'tls- server-end-point', the server's TLS certificate is retrieved from the socket, hashed, and used to create the GSSAPI channel bindings. This feature requires the `cryptography` library as an optional dependency. If it's not available, channel bindings cannot be used and a warning is logged. Co-authored-by: Gemini <[email protected]> Signed-off-by: Simo Sorce <[email protected]>
1 parent 93660c3 commit 235b66a

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

src/requests_gssapi/gssapi_.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ class HTTPSPNEGOAuth(AuthBase):
107107
`sanitize_mutual_error_response` controls whether we should clean up
108108
server responses. See the `SanitizedResponse` class.
109109
110+
`channel_bindings` can be used to pass channel bindings to GSSAPI.
111+
The only accepted value is 'tls-server-end-point' which uses the TLS
112+
server's certificate for the channel bindings. Default is `None`.
113+
110114
"""
111115

112116
def __init__(
@@ -118,6 +122,7 @@ def __init__(
118122
creds=None,
119123
mech=SPNEGO,
120124
sanitize_mutual_error_response=True,
125+
channel_bindings=None,
121126
):
122127
self.context = {}
123128
self.pos = None
@@ -128,6 +133,11 @@ def __init__(
128133
self.creds = creds
129134
self.mech = mech if mech else SPNEGO
130135
self.sanitize_mutual_error_response = sanitize_mutual_error_response
136+
if channel_bindings not in (None, "tls-server-end-point"):
137+
raise ValueError(
138+
"channel_bindings must be None or 'tls-server-end-point'"
139+
)
140+
self.channel_bindings = channel_bindings
131141

132142
def generate_request_header(self, response, host, is_preemptive=False):
133143
"""
@@ -144,6 +154,51 @@ def generate_request_header(self, response, host, is_preemptive=False):
144154
if self.mutual_authentication != DISABLED:
145155
gssflags.append(gssapi.RequirementFlag.mutual_authentication)
146156

157+
gss_cb = None
158+
if self.channel_bindings == "tls-server-end-point":
159+
if is_preemptive:
160+
log.warning(
161+
"channel_bindings were requested, but are unavailable for "
162+
"opportunistic authentication"
163+
)
164+
# The 'connection' attribute on raw is a public urllib3 API
165+
# and can be None if the connection has been released.
166+
elif getattr(response.raw, "connection", None) and getattr(
167+
response.raw.connection, "sock", None
168+
):
169+
try:
170+
# Defer import so it's not a hard dependency.
171+
from cryptography import x509
172+
173+
sock = response.raw.connection.sock
174+
175+
der_cert = sock.getpeercert(binary_form=True)
176+
cert = x509.load_der_x509_certificate(der_cert)
177+
hash = cert.signature_hash_algorithm
178+
cert_hash = cert.fingerprint(hash)
179+
180+
app_data = b"tls-server-end-point:" + cert_hash
181+
gss_cb = gssapi.ChannelBindings(application_data=app_data)
182+
log.debug(
183+
"generate_request_header(): "
184+
"Successfully retrieved channel bindings"
185+
)
186+
except ImportError:
187+
log.warning(
188+
"Could not import cryptography, "
189+
"python-cryptography is required for this feature."
190+
)
191+
except Exception:
192+
log.warning(
193+
"Failed to get channel bindings from socket",
194+
exc_info=True,
195+
)
196+
else:
197+
log.warning(
198+
"channel_bindings were requested, but a socket could "
199+
"not be retrieved from the response"
200+
)
201+
147202
try:
148203
gss_stage = "initiating context"
149204
name = self.target_name
@@ -153,7 +208,12 @@ def generate_request_header(self, response, host, is_preemptive=False):
153208

154209
name = gssapi.Name(name, gssapi.NameType.hostbased_service)
155210
self.context[host] = gssapi.SecurityContext(
156-
usage="initiate", flags=gssflags, name=name, creds=self.creds, mech=self.mech
211+
usage="initiate",
212+
flags=gssflags,
213+
name=name,
214+
creds=self.creds,
215+
mech=self.mech,
216+
channel_bindings=gss_cb,
157217
)
158218

159219
gss_stage = "stepping context"

0 commit comments

Comments
 (0)