2 # Unix SMB/CIFS implementation.
3 # Copyright (C) Stefan Metzmacher 2020
4 # Copyright (C) Catalyst.Net Ltd 2020
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 sys
.path
.insert(0, "bin/python")
24 os
.environ
["PYTHONUNBUFFERED"] = "1"
26 from samba
.tests
.krb5
.kdc_base_test
import KDCBaseTest
27 import samba
.tests
.krb5
.rfc4120_pyasn1
as krb5_asn1
28 from samba
.tests
.krb5
.rfc4120_constants
import (
29 AES128_CTS_HMAC_SHA1_96
,
30 AES256_CTS_HMAC_SHA1_96
,
32 KDC_ERR_PREAUTH_REQUIRED
,
43 global_asn1_print
= False
44 global_hexdump
= False
46 HIEMDAL_ENC_AS_REP_PART_TYPE_TAG
= 0x79
47 # MIT uses the EncTGSRepPart tag for the EncASRepPart
48 MIT_ENC_AS_REP_PART_TYPE_TAG
= 0x7A
50 ENC_PA_REP_FLAG
= 0x00010000
53 class SimpleKerberosTests(KDCBaseTest
):
56 super(SimpleKerberosTests
, self
).setUp()
57 self
.do_asn1_print
= global_asn1_print
58 self
.do_hexdump
= global_hexdump
60 def test_mit_EncASRepPart_tag(self
):
61 creds
= self
.get_user_creds()
62 (enc
, _
) = self
.as_req(creds
)
63 self
.assertEqual(MIT_ENC_AS_REP_PART_TYPE_TAG
, enc
[0])
65 def test_heimdal_EncASRepPart_tag(self
):
66 creds
= self
.get_user_creds()
67 (enc
, _
) = self
.as_req(creds
)
68 self
.assertEqual(HIEMDAL_ENC_AS_REP_PART_TYPE_TAG
, enc
[0])
70 def test_mit_EncryptedData_kvno(self
):
71 creds
= self
.get_user_creds()
72 (_
, enc
) = self
.as_req(creds
)
74 self
.fail("kvno present in EncryptedData")
76 def test_heimdal_EncryptedData_kvno(self
):
77 creds
= self
.get_user_creds()
78 (_
, enc
) = self
.as_req(creds
)
80 self
.fail("kvno absent in EncryptedData")
82 def test_mit_EncASRepPart_FAST_support(self
):
83 creds
= self
.get_user_creds()
84 (enc
, _
) = self
.as_req(creds
)
85 self
.assertEqual(MIT_ENC_AS_REP_PART_TYPE_TAG
, enc
[0])
86 as_rep
= self
.der_decode(enc
, asn1Spec
=krb5_asn1
.EncTGSRepPart())
87 flags
= int(as_rep
['flags'], base
=2)
88 # MIT sets enc-pa-rep, flag bit 15
89 # RFC 6806 11. Negotiation of FAST and Detecting Modified Requests
90 self
.assertTrue(ENC_PA_REP_FLAG
& flags
)
92 def test_heimdal_and_windows_EncASRepPart_FAST_support(self
):
93 creds
= self
.get_user_creds()
94 (enc
, _
) = self
.as_req(creds
)
95 self
.assertEqual(HIEMDAL_ENC_AS_REP_PART_TYPE_TAG
, enc
[0])
96 as_rep
= self
.der_decode(enc
, asn1Spec
=krb5_asn1
.EncASRepPart())
97 flags
= as_rep
['flags']
98 flags
= int(as_rep
['flags'], base
=2)
99 # Heimdal and Windows does set enc-pa-rep, flag bit 15
100 # RFC 6806 11. Negotiation of FAST and Detecting Modified Requests
101 self
.assertTrue(ENC_PA_REP_FLAG
& flags
)
103 def test_mit_arcfour_salt(self
):
104 creds
= self
.get_user_creds()
105 etypes
= (ARCFOUR_HMAC_MD5
,)
106 (rep
, *_
) = self
.as_pre_auth_req(creds
, etypes
)
107 self
.check_preauth_rep(rep
)
108 etype_info2
= self
.get_etype_info2(rep
)
109 if 'salt' not in etype_info2
[0]:
111 "(MIT) Salt not populated for ARCFOUR_HMAC_MD5 encryption")
113 def test_heimdal_arcfour_salt(self
):
114 creds
= self
.get_user_creds()
115 etypes
= (ARCFOUR_HMAC_MD5
,)
116 (rep
, *_
) = self
.as_pre_auth_req(creds
, etypes
)
117 self
.check_preauth_rep(rep
)
118 etype_info2
= self
.get_etype_info2(rep
)
119 if 'salt' in etype_info2
[0]:
121 "(Heimdal) Salt populated for ARCFOUR_HMAC_MD5 encryption")
123 # This tests also passes again Samba AD built with MIT Kerberos 1.20 which
124 # is not released yet.
126 # FIXME: Should be moved to to a new kdc_tgt_tests.py once MIT KRB5 1.20
128 def test_ticket_signature(self
):
129 # Ensure that a DC correctly issues tickets signed with its krbtgt key.
130 user_creds
= self
.get_client_creds()
131 target_creds
= self
.get_service_creds()
133 krbtgt_creds
= self
.get_krbtgt_creds()
134 key
= self
.TicketDecryptionKey_from_creds(krbtgt_creds
)
136 # Get a TGT from the DC.
137 tgt
= self
.get_tgt(user_creds
)
139 # Ensure the PAC contains the expected checksums.
140 self
.verify_ticket(tgt
, key
, service_ticket
=False)
142 # Get a service ticket from the DC.
143 service_ticket
= self
.get_service_ticket(tgt
, target_creds
)
145 # Ensure the PAC contains the expected checksums.
146 self
.verify_ticket(service_ticket
, key
, service_ticket
=True,
147 expect_ticket_checksum
=True)
149 def test_mit_pre_1_20_ticket_signature(self
):
150 # Ensure that a DC does not issue tickets signed with its krbtgt key.
151 user_creds
= self
.get_client_creds()
152 target_creds
= self
.get_service_creds()
154 krbtgt_creds
= self
.get_krbtgt_creds()
155 key
= self
.TicketDecryptionKey_from_creds(krbtgt_creds
)
157 # Get a TGT from the DC.
158 tgt
= self
.get_tgt(user_creds
)
160 # Ensure the PAC contains the expected checksums.
161 self
.verify_ticket(tgt
, key
, service_ticket
=False)
163 # Get a service ticket from the DC.
164 service_ticket
= self
.get_service_ticket(tgt
, target_creds
)
166 # Ensure the PAC does not contain the expected checksums.
167 self
.verify_ticket(service_ticket
, key
, service_ticket
=True,
168 expect_ticket_checksum
=False)
170 def as_pre_auth_req(self
, creds
, etypes
):
171 user
= creds
.get_username()
172 realm
= creds
.get_realm()
174 cname
= self
.PrincipalName_create(
175 name_type
=NT_PRINCIPAL
,
177 sname
= self
.PrincipalName_create(
178 name_type
=NT_SRV_INST
,
179 names
=["krbtgt", realm
])
181 till
= self
.get_KerberosTime(offset
=36000)
183 kdc_options
= krb5_asn1
.KDCOptions('forwardable')
186 req
= self
.AS_REQ_create(padata
=padata
,
187 kdc_options
=str(kdc_options
),
197 additional_tickets
=None)
198 rep
= self
.send_recv_transaction(req
)
200 return (rep
, cname
, sname
, realm
, till
)
202 def check_preauth_rep(self
, rep
):
203 self
.assertIsNotNone(rep
)
204 self
.assertEqual(rep
['msg-type'], KRB_ERROR
)
205 self
.assertEqual(rep
['error-code'], KDC_ERR_PREAUTH_REQUIRED
)
207 def get_etype_info2(self
, rep
):
209 rep_padata
= self
.der_decode(
211 asn1Spec
=krb5_asn1
.METHOD_DATA())
213 for pa
in rep_padata
:
214 if pa
['padata-type'] == PADATA_ETYPE_INFO2
:
215 etype_info2
= pa
['padata-value']
218 etype_info2
= self
.der_decode(
220 asn1Spec
=krb5_asn1
.ETYPE_INFO2())
223 def as_req(self
, creds
):
225 AES256_CTS_HMAC_SHA1_96
,
226 AES128_CTS_HMAC_SHA1_96
,
228 (rep
, cname
, sname
, realm
, till
) = self
.as_pre_auth_req(creds
, etypes
)
229 self
.check_preauth_rep(rep
)
231 etype_info2
= self
.get_etype_info2(rep
)
232 key
= self
.PasswordKey_from_etype_info2(creds
, etype_info2
[0])
234 (patime
, pausec
) = self
.get_KerberosTimeWithUsec()
235 pa_ts
= self
.PA_ENC_TS_ENC_create(patime
, pausec
)
236 pa_ts
= self
.der_encode(pa_ts
, asn1Spec
=krb5_asn1
.PA_ENC_TS_ENC())
238 pa_ts
= self
.EncryptedData_create(key
, KU_PA_ENC_TIMESTAMP
, pa_ts
)
239 pa_ts
= self
.der_encode(pa_ts
, asn1Spec
=krb5_asn1
.EncryptedData())
241 pa_ts
= self
.PA_DATA_create(PADATA_ENC_TIMESTAMP
, pa_ts
)
243 kdc_options
= krb5_asn1
.KDCOptions('forwardable')
246 req
= self
.AS_REQ_create(padata
=padata
,
247 kdc_options
=str(kdc_options
),
257 additional_tickets
=None)
258 rep
= self
.send_recv_transaction(req
)
259 self
.assertIsNotNone(rep
)
261 msg_type
= rep
['msg-type']
262 self
.assertEqual(msg_type
, KRB_AS_REP
)
264 enc_part
= rep
['enc-part']
265 enc_as_rep_part
= key
.decrypt(
266 KU_AS_REP_ENC_PART
, rep
['enc-part']['cipher'])
267 return (enc_as_rep_part
, enc_part
)
270 if __name__
== "__main__":
271 global_asn1_print
= False
272 global_hexdump
= False