34
34
import logging
35
35
import struct
36
36
from uuid import uuid4
37
+ try :
38
+ import gssapi
39
+ except :
40
+ gssapi = None
37
41
38
42
from . import errors
39
43
from .catch23 import PY2 , isstr , UNICODE_TYPES , BYTE_TYPES
40
44
from .utils import (normalize_unicode_string as norm_ustr ,
41
45
validate_normalized_unicode_string as valid_norm )
42
46
43
- _LOGGER = logging .getLogger (__name__ )
47
+ if PY2 :
48
+ from urllib import quote
49
+ else :
50
+ from urllib .parse import quote
44
51
52
+ _LOGGER = logging .getLogger (__name__ )
45
53
46
54
class BaseAuthPlugin (object ):
47
55
"""Base class for authentication plugins
@@ -280,7 +288,7 @@ class MySQLLdapSaslPasswordAuthPlugin(BaseAuthPlugin):
280
288
server, the second server respond needs to be passed to auth_finalize()
281
289
to finish the authentication process.
282
290
"""
283
- sasl_mechanisms = ['SCRAM-SHA-1' , 'SCRAM-SHA-256' ]
291
+ sasl_mechanisms = ['SCRAM-SHA-1' , 'SCRAM-SHA-256' , 'GSSAPI' ]
284
292
requires_ssl = False
285
293
plugin_name = 'authentication_ldap_sasl_client'
286
294
def_digest_mode = sha1
@@ -350,6 +358,144 @@ def _first_message(self):
350
358
cfm = cfm .encode ('utf8' )
351
359
return cfm
352
360
361
+ def _first_message_krb (self ):
362
+ """Get a TGT Authentication request and initiates security context.
363
+
364
+ This method will contact the Kerberos KDC in order of obtain a TGT.
365
+ """
366
+ _LOGGER .debug ("# user name: %s" , self ._username )
367
+ user_name = gssapi .raw .names .import_name (self ._username .encode ('utf8' ),
368
+ name_type = gssapi .NameType .user )
369
+
370
+ # Use defaults store = {'ccache': 'FILE:/tmp/krb5cc_1000'}#, 'keytab':'/etc/some.keytab' }
371
+ # Attempt to retrieve credential from default cache file.
372
+ try :
373
+ cred = gssapi .Credentials ()
374
+ _LOGGER .debug ("# Stored credentials found, if password was given it"
375
+ " will be ignored." )
376
+ try :
377
+ # validate credentials has not expired.
378
+ cred .lifetime
379
+ except gssapi .raw .exceptions .ExpiredCredentialsError as err :
380
+ _LOGGER .warning (" Credentials has expired: %s" , err )
381
+ cred .acquire (user_name )
382
+ raise errors .InterfaceError ("Credentials has expired: {}" .format (err ))
383
+ except gssapi .raw .misc .GSSError as err :
384
+ if not self ._password :
385
+ _LOGGER .error (" Unable to retrieve stored credentials: %s" , err )
386
+ raise errors .InterfaceError (
387
+ "Unable to retrieve stored credentials error: {}" .format (err ))
388
+ else :
389
+ try :
390
+ _LOGGER .debug ("# Attempt to retrieve credentials with "
391
+ "given password" )
392
+ acquire_cred_result = gssapi .raw .acquire_cred_with_password (
393
+ user_name , self ._password .encode ('utf8' ), usage = "initiate" )
394
+ cred = acquire_cred_result [0 ]
395
+ except gssapi .raw .misc .GSSError as err :
396
+ _LOGGER .error (" Unable to retrieve credentials with the given "
397
+ "password: %s" , err )
398
+ raise errors .ProgrammingError (
399
+ "Unable to retrieve credentials with the given password: "
400
+ "{}" .format (err ))
401
+
402
+ flags_l = (gssapi .RequirementFlag .mutual_authentication ,
403
+ gssapi .RequirementFlag .extended_error ,
404
+ gssapi .RequirementFlag .delegate_to_peer
405
+ )
406
+
407
+ service_principal = "ldap/ldapauth"
408
+ _LOGGER .debug ("# service principal: %s" , service_principal )
409
+ servk = gssapi .Name (service_principal , name_type = gssapi .NameType .kerberos_principal )
410
+ self .target_name = servk
411
+ self .ctx = gssapi .SecurityContext (name = servk ,
412
+ creds = cred ,
413
+ flags = sum (flags_l ),
414
+ usage = 'initiate' )
415
+
416
+ try :
417
+ initial_client_token = self .ctx .step ()
418
+ except gssapi .raw .misc .GSSError as err :
419
+ _LOGGER .error ("Unable to initiate security context: %s" , err )
420
+ raise errors .InterfaceError ("Unable to initiate security context: {}" .format (err ))
421
+
422
+ _LOGGER .debug ("# initial client token: %s" , initial_client_token )
423
+ return initial_client_token
424
+
425
+
426
+ def auth_continue_krb (self , tgt_auth_challenge ):
427
+ """Continue with the Kerberos TGT service request.
428
+
429
+ With the TGT authentication service given response generate a TGT
430
+ service request. This method must be invoked sequentially (in a loop)
431
+ until the security context is completed and an empty response needs to
432
+ be send to acknowledge the server.
433
+
434
+ Args:
435
+ tgt_auth_challenge the challenge for the negotiation.
436
+
437
+ Returns: tuple (bytearray TGS service request,
438
+ bool True if context is completed otherwise False).
439
+ """
440
+ _LOGGER .debug ("tgt_auth challenge: %s" , tgt_auth_challenge )
441
+
442
+ resp = self .ctx .step (tgt_auth_challenge )
443
+ _LOGGER .debug ("# context step response: %s" , resp )
444
+ _LOGGER .debug ("# context completed?: %s" , self .ctx .complete )
445
+
446
+ return resp , self .ctx .complete
447
+
448
+ def auth_accept_close_handshake (self , message ):
449
+ """Accept handshake and generate closing handshake message for server.
450
+
451
+ This method verifies the server authenticity from the given message
452
+ and included signature and generates the closing handshake for the
453
+ server.
454
+
455
+ When this method is invoked the security context is already established
456
+ and the client and server can send GSSAPI formated secure messages.
457
+
458
+ To finish the authentication handshake the server sends a message
459
+ with the security layer availability and the maximum buffer size.
460
+
461
+ Since the connector only uses the GSSAPI authentication mechanism to
462
+ authenticate the user with the server, the server will verify clients
463
+ message signature and terminate the GSSAPI authentication and send two
464
+ messages; an authentication acceptance b'\x01 \x00 \x00 \x08 \x01 ' and a
465
+ OK packet (that must be received after sent the returned message from
466
+ this method).
467
+
468
+ Args:
469
+ message a wrapped hssapi message from the server.
470
+
471
+ Returns: bytearray closing handshake message to be send to the server.
472
+ """
473
+ if not self .ctx .complete :
474
+ raise errors .ProgrammingError ("Security context is not completed." )
475
+ _LOGGER .debug ("# servers message: %s" , message )
476
+ _LOGGER .debug ("# GSSAPI flags in use: %s" , self .ctx .actual_flags )
477
+ try :
478
+ unwraped = self .ctx .unwrap (message )
479
+ _LOGGER .debug ("# unwraped: %s" , unwraped )
480
+ except gssapi .raw .exceptions .BadMICError as err :
481
+ _LOGGER .debug ("Unable to unwrap server message: %s" , err )
482
+ raise errors .InterfaceError ("Unable to unwrap server message: {}"
483
+ "" .format (err ))
484
+
485
+ _LOGGER .debug ("# unwrapped server message: %s" , unwraped )
486
+ # The message contents for the clients closing message:
487
+ # - security level 1 byte, must be always 1.
488
+ # - conciliated buffer size 3 bytes, without importance as no
489
+ # further GSSAPI messages will be sends.
490
+ response = bytearray (b"\x01 \x00 \x00 \00 " )
491
+ # Closing handshake must not be encrypted.
492
+ _LOGGER .debug ("# message response: %s" , response )
493
+ wraped = self .ctx .wrap (response , encrypt = False )
494
+ _LOGGER .debug ("# wrapped message response: %s, length: %d" ,
495
+ wraped [0 ], len (wraped [0 ]))
496
+
497
+ return wraped .message
498
+
353
499
def auth_response (self ):
354
500
"""This method will prepare the fist message to the server.
355
501
@@ -364,6 +510,14 @@ def auth_response(self):
364
510
auth_mechanism , '", "' .join (self .sasl_mechanisms [:- 1 ]),
365
511
self .sasl_mechanisms [- 1 ]))
366
512
513
+ if b'GSSAPI' in self ._auth_data :
514
+ if not gssapi :
515
+ raise errors .ProgrammingError (
516
+ "Module gssapi is required for GSSAPI authentication "
517
+ "mechanism but was not found. Unable to authenticate "
518
+ "with the server" )
519
+ return self ._first_message_krb ()
520
+
367
521
if self ._auth_data == b'SCRAM-SHA-256' :
368
522
self .def_digest_mode = sha256
369
523
0 commit comments