141141from scapy .layers .inet import TCP , UDP
142142from scapy .layers .smb import _NV_VERSION
143143from scapy .layers .smb2 import STATUS_ERREF
144+ from scapy .layers .tls .cert import Cert , PrivKey
144145from scapy .layers .x509 import X509_AlgorithmIdentifier
145146
146147# Redirect exports from RFC3961
@@ -2714,30 +2715,50 @@ def select(sockets, remain=None):
27142715
27152716class KerberosClient (Automaton ):
27162717 """
2718+ Implementation of a Kerberos client.
2719+
2720+ Prefer to use the ``krb_as_req`` and ``krb_tgs_req`` functions which
2721+ wrap this client.
2722+
2723+ Common parameters:
2724+
27172725 :param mode: the mode to use for the client (default: AS_REQ).
27182726 :param ip: the IP of the DC (default: discovered by dclocator)
27192727 :param upn: the UPN of the client.
27202728 :param password: the password of the client.
27212729 :param key: the Key of the client (instead of the password)
27222730 :param realm: the realm of the domain. (default: from the UPN)
2723- :param spn: the SPN to request in a TGS-REQ
2724- :param ticket: the existing ticket to use in a TGS-REQ
27252731 :param host: the name of the host doing the request
2726- :param renew: sets the Renew flag in a TGS-REQ
2727- :param additional_tickets: in U2U or S4U2Proxy, the additional tickets
2728- :param u2u: sets the U2U flag
2729- :param for_user: the UPN of another user in TGS-REQ, to do a S4U2Self
2730- :param s4u2proxy: sets the S4U2Proxy flag
2731- :param dmsa: sets the 'unconditional delegation' mode for DMSA TGT retrieval
2732+ :param port: the Kerberos port (default 88)
2733+ :param timeout: timeout of each request (default 5)
2734+
2735+ Advanced common parameters:
2736+
27322737 :param kdc_proxy: specify a KDC proxy url
27332738 :param kdc_proxy_no_check_certificate: do not check the KDC proxy certificate
27342739 :param fast: use FAST armoring
27352740 :param armor_ticket: an external ticket to use for armoring
27362741 :param armor_ticket_upn: the UPN of the client of the armoring ticket
27372742 :param armor_ticket_skey: the session Key object of the armoring ticket
27382743 :param etypes: specify the list of encryption types to support
2739- :param port: the Kerberos port (default 88)
2740- :param timeout: timeout of each request (default 5)
2744+
2745+ AS-REQ only:
2746+
2747+ :param x509: a X509 certificate to use for PKINIT AS_REQ or S4U2Proxy
2748+ :param x509key: the private key of the X509 certificate (in an AS_REQ)
2749+ :param p12: (optional) use a pfx/p12 instead of x509 and x509key. In this case,
2750+ 'password' is the password of the p12.
2751+
2752+ TGS-REQ only:
2753+
2754+ :param spn: the SPN to request in a TGS-REQ
2755+ :param ticket: the existing ticket to use in a TGS-REQ
2756+ :param renew: sets the Renew flag in a TGS-REQ
2757+ :param additional_tickets: in U2U or S4U2Proxy, the additional tickets
2758+ :param u2u: sets the U2U flag
2759+ :param for_user: the UPN of another user in TGS-REQ, to do a S4U2Self
2760+ :param s4u2proxy: sets the S4U2Proxy flag
2761+ :param dmsa: sets the 'unconditional delegation' mode for DMSA TGT retrieval
27412762 """
27422763
27432764 RES_AS_MODE = namedtuple ("AS_Result" , ["asrep" , "sessionkey" , "kdcrep" ])
@@ -2756,6 +2777,9 @@ def __init__(
27562777 password = None ,
27572778 key = None ,
27582779 realm = None ,
2780+ x509 = None ,
2781+ x509key = None ,
2782+ p12 = None ,
27592783 spn = None ,
27602784 ticket = None ,
27612785 host = None ,
@@ -2796,9 +2820,31 @@ def __init__(
27962820 else :
27972821 raise ValueError ("Invalid realm" )
27982822
2823+ # PKINIT checks
2824+ if p12 is not None :
2825+ from cryptography .hazmat .primitives .serialization import pkcs12
2826+
2827+ # password should be None or bytes
2828+ if isinstance (password , str ):
2829+ password = password .encode ()
2830+
2831+ # Read p12/pfx
2832+ with open (p12 , "rb" ) as fd :
2833+ x509key , x509 , _ = pkcs12 .load_key_and_certificates (
2834+ fd .read (),
2835+ password = password ,
2836+ )
2837+ x509 = Cert (cryptography_obj = x509 )
2838+ x509key = PrivKey (cryptography_obj = x509key )
2839+ elif x509 and x509key :
2840+ x509 = Cert (x509 )
2841+ x509key = PrivKey (x509key )
2842+
27992843 if mode in [self .MODE .AS_REQ , self .MODE .GET_SALT ]:
28002844 if not host :
28012845 raise ValueError ("Invalid host" )
2846+ if (x509 is None ) ^ (x509key is None ):
2847+ raise ValueError ("Must provide both 'x509' and 'x509key' !" )
28022848 elif mode == self .MODE .TGS_REQ :
28032849 if not ticket :
28042850 raise ValueError ("Invalid ticket" )
@@ -2815,6 +2861,7 @@ def __init__(
28152861 debug = kwargs .get ("debug" , 0 ),
28162862 ).ip
28172863
2864+ # Armoring checks
28182865 if fast :
28192866 if mode == self .MODE .AS_REQ :
28202867 # Requires an external ticket
@@ -2867,6 +2914,8 @@ def __init__(
28672914 self .spn = spn
28682915 self .upn = upn
28692916 self .realm = realm .upper ()
2917+ self .x509 = x509
2918+ self .x509key = x509key
28702919 self .ticket = ticket
28712920 self .fast = fast
28722921 self .armor_ticket = armor_ticket
@@ -2902,6 +2951,10 @@ def __init__(
29022951 )
29032952
29042953 def _connect (self ):
2954+ """
2955+ Internal function to bind a socket to the DC.
2956+ This also takes care of an eventual KDC proxy.
2957+ """
29052958 if self .kdc_proxy :
29062959 # If we are using a KDC Proxy, wrap the socket with the KdcProxySocket,
29072960 # that takes our messages and transport them over HTTP.
@@ -2918,9 +2971,15 @@ def _connect(self):
29182971 return sock
29192972
29202973 def send (self , pkt ):
2974+ """
2975+ Sends a wrapped Kerberos packet
2976+ """
29212977 super (KerberosClient , self ).send (KerberosTCPHeader () / pkt )
29222978
29232979 def _base_kdc_req (self , now_time ):
2980+ """
2981+ Return the KRB_KDC_REQ_BODY used in both AS-REQ and TGS-REQ
2982+ """
29242983 kdcreq = KRB_KDC_REQ_BODY (
29252984 etype = [ASN1_INTEGER (x ) for x in self .etypes ],
29262985 additionalTickets = None ,
@@ -3112,33 +3171,46 @@ def as_req(self):
31123171
31133172 # Pre-auth is requested
31143173 if self .pre_auth :
3115- if self .fast :
3116- # Special FAST factor
3117- # RFC6113 sect 5.4.6
3118- from scapy .libs .rfc3961 import KRB_FX_CF2
3119-
3120- # Calculate the 'challenge key'
3121- ts_key = KRB_FX_CF2 (
3122- self .fast_armorkey ,
3123- self .key ,
3124- b"clientchallengearmor" ,
3125- b"challengelongterm" ,
3126- )
3174+ if self .x509 :
3175+ # Special PKINIT (RFC4556) factor
31273176 pafactor = PADATA (
3128- padataType = 138 , # PA-ENCRYPTED-CHALLENGE
3129- padataValue = EncryptedData (),
3177+ padataType = 16 , # PA-PK-AS-REQ
3178+ padataValue = PA_PK_AS_REQ (
3179+
3180+ )
31303181 )
31313182 else :
3132- # Usual 'timestamp' factor
3133- ts_key = self .key
3134- pafactor = PADATA (
3135- padataType = 2 , # PA-ENC-TIMESTAMP
3136- padataValue = EncryptedData (),
3183+ # Key-based factor
3184+
3185+ if self .fast :
3186+ # Special FAST factor
3187+ # RFC6113 sect 5.4.6
3188+ from scapy .libs .rfc3961 import KRB_FX_CF2
3189+
3190+ # Calculate the 'challenge key'
3191+ ts_key = KRB_FX_CF2 (
3192+ self .fast_armorkey ,
3193+ self .key ,
3194+ b"clientchallengearmor" ,
3195+ b"challengelongterm" ,
3196+ )
3197+ pafactor = PADATA (
3198+ padataType = 138 , # PA-ENCRYPTED-CHALLENGE
3199+ padataValue = EncryptedData (),
3200+ )
3201+ else :
3202+ # Usual 'timestamp' factor
3203+ ts_key = self .key
3204+ pafactor = PADATA (
3205+ padataType = 2 , # PA-ENC-TIMESTAMP
3206+ padataValue = EncryptedData (),
3207+ )
3208+ pafactor .padataValue .encrypt (
3209+ ts_key ,
3210+ PA_ENC_TS_ENC (patimestamp = ASN1_GENERALIZED_TIME (now_time )),
31373211 )
3138- pafactor .padataValue .encrypt (
3139- ts_key ,
3140- PA_ENC_TS_ENC (patimestamp = ASN1_GENERALIZED_TIME (now_time )),
3141- )
3212+
3213+ # Insert Pre-Authentication data
31423214 padata .insert (
31433215 0 ,
31443216 pafactor ,
@@ -3664,7 +3736,17 @@ def _spn_are_equal(spn1, spn2):
36643736
36653737
36663738def krb_as_req (
3667- upn , spn = None , ip = None , key = None , password = None , realm = None , host = "WIN10" , ** kwargs
3739+ upn ,
3740+ spn = None ,
3741+ ip = None ,
3742+ key = None ,
3743+ password = None ,
3744+ realm = None ,
3745+ host = "WIN10" ,
3746+ p12 = None ,
3747+ x509 = None ,
3748+ x509key = None ,
3749+ ** kwargs ,
36683750):
36693751 r"""
36703752 Kerberos AS-Req
@@ -3677,26 +3759,34 @@ def krb_as_req(
36773759 _kerberos._tcp.dc._msdcs.domain.local).
36783760 :param key: (optional) pass the Key object.
36793761 :param password: (optional) otherwise, pass the user's password
3762+ :param x509: (optional) pass a x509 certificate for PKINIT.
3763+ :param x509key: (optional) pass the key of the x509 certificate for PKINIT.
3764+ :param p12: (optional) use a pfx/p12 instead of x509 and x509key. In this case,
3765+ 'password' is the password of the p12.
36803766 :param realm: (optional) the realm to use. Otherwise use the one from UPN.
36813767 :param host: (optional) the host performing the AS-Req. WIN10 by default.
36823768
36833769 :return: returns a named tuple (asrep=<...>, sessionkey=<...>)
36843770
36853771 Example::
36863772
3687- >>> # The KDC is on 192.168.122.17 , we ask a TGT for user1
3688- >>> krb_as_req("user1@DOMAIN.LOCAL", "192.168.122.17", password="Password1")
3773+ >>> # The KDC is found via DC Locator , we ask a TGT for user1
3774+ >>> krb_as_req("user1@DOMAIN.LOCAL", password="Password1")
36893775
36903776 Equivalent::
36913777
36923778 >>> from scapy.libs.rfc3961 import Key, EncryptionType
36933779 >>> key = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=hex_bytes("6d0748c546
36943780 ...: f4e99205e78f8da7681d4ec5520ae4815543720c2a647c1ae814c9"))
3695- >>> krb_as_req("user1@DOMAIN.LOCAL", "192.168.122.17", key=key)
3781+ >>> krb_as_req("user1@DOMAIN.LOCAL", ip="192.168.122.17", key=key)
3782+
3783+ Example using PKINIT with a p12::
3784+
3785+ >>> krb_as_req("user1@DOMAIN.LOCAL", p12="./store.p12", password="password")
36963786 """
36973787 if realm is None :
36983788 _ , realm = _parse_upn (upn )
3699- if key is None :
3789+ if key is None and p12 is None and x509 is None :
37003790 if password is None :
37013791 try :
37023792 from prompt_toolkit import prompt
@@ -3713,6 +3803,9 @@ def krb_as_req(
37133803 upn = upn ,
37143804 password = password ,
37153805 key = key ,
3806+ p12 = p12 ,
3807+ x509 = x509 ,
3808+ x509key = x509key ,
37163809 ** kwargs ,
37173810 )
37183811 cli .run ()
0 commit comments