# Copyright 2020 Istvan Ruzman # SPDX-License-Identifier: MIT OR Apache-2.0 import struct from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network import pytest from pyrad3 import dictionary, utils # @pytest.mark.parametrize("header", [ # b""]) # def test_valid_header(header): # utils.parse_header(header) SECRET = b"secret" def num_tlv(num_type, num, length, expected=None): exp = num if expected is None else expected return (num_type + num.to_bytes(length, "big"), exp) TEST_ATTRIBUTES = [ (b"\x01\x07ABCDE", "ABCDE"), # rfc string (b"\x02\x07ABCDE", b"ABCDE"), # rfc octets (b"\x03\x06\0\0\0\0", 0), # rfc date # TODO: ABINARY (b"\x05\x03\x00", 0), # rfc byte num_tlv(b"\x06\x04", 0, 2), # rfc short num_tlv(b"\x06\x04", 0xFF, 2), # rfc short num_tlv(b"\x06\x04", 0x100, 2), # rfc short num_tlv(b"\x06\x04", 0xFFFF, 2), # rfc short num_tlv(b"\x07\x06", 0, 4), # rfc integer num_tlv(b"\x07\x06", 0xFF, 4), # rfc integer num_tlv(b"\x07\x06", 0x100, 4), # rfc integer num_tlv(b"\x07\x06", 0xFFFF, 4), # rfc integer num_tlv(b"\x07\x06", 0x10000, 4), # rfc integer num_tlv(b"\x07\x06", 0xFFFFFFFF, 4), # rfc integer num_tlv(b"\x08\x06", 0, 4), # rfc signed num_tlv(b"\x08\x06", 0xFF, 4), # rfc signed num_tlv(b"\x08\x06", 0x1000, 4), # rfc signed num_tlv(b"\x08\x06", 0xFFFF, 4), # rfc signed num_tlv(b"\x08\x06", 0x10000, 4), # rfc signed num_tlv(b"\x08\x06", 0xFFFFFFFF, 4, -1), # rfc signed num_tlv(b"\x08\x06", 0x80000000, 4, -2147483648), # rfc signed num_tlv(b"\x08\x06", 0x7FFFFFFF, 4, 2147483647), # rfc signed num_tlv(b"\x09\x0A", 0, 8), # rfc integer64 num_tlv(b"\x09\x0A", 0xFF, 8), # rfc integer64 num_tlv(b"\x09\x0A", 0x100, 8), # rfc integer64 num_tlv(b"\x09\x0A", 0xFFFF, 8), # rfc integer64 num_tlv(b"\x09\x0A", 0x10000, 8), # rfc integer64 num_tlv(b"\x09\x0A", 0xFFFFFFFF, 8), # rfc integer64 num_tlv(b"\x09\x0A", 0x100000000, 8), # rfc integer64 num_tlv(b"\x09\x0A", 0xFFFFFFFFFFFFFFFF, 8), # rfc integer64 (b"\x0a\x06\xc0\xa8\x01\x08", IPv4Address("192.168.1.8")), (b"\x0b\x07\x10\xc0\xa8\x00\x00", IPv4Network("192.168.0.0/16")), ( b"\x0c\x12\x20\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", IPv6Address("2003::1"), ), ( b"\x0d\x14\x00\x40\x20\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", IPv6Network("2003::0/64"), ), (b"\x0c\x04\x20\x03", IPv6Address("2003::0")), (b"\x0d\x06\x00\x40\x20\x03", IPv6Network("2003::0/64")), ] TAGGED_ATTRIBUTES = [ (b"\x65\x08\x01ABCDE", "ABCDE"), # rfc string (b"\x66\x08\x01ABCDE", b"ABCDE"), # rfc octets (b"\x67\x07\x01\0\0\0\0", 0), # rfc date # TODO: ABINARY (b"\x69\x04\x01\x00", 0), # rfc byte num_tlv(b"\x6a\x05\x01", 0, 2), # rfc short num_tlv(b"\x6a\x05\x01", 0xFF, 2), # rfc short num_tlv(b"\x6a\x05\x01", 0x100, 2), # rfc short num_tlv(b"\x6a\x05\x01", 0xFFFF, 2), # rfc short num_tlv(b"\x6b\x06\x01", 0, 3), # rfc integer num_tlv(b"\x6b\x06\x01", 0xFF, 3), # rfc integer num_tlv(b"\x6b\x06\x01", 0x100, 3), # rfc integer num_tlv(b"\x6b\x06\x01", 0xFFFF, 3), # rfc integer num_tlv(b"\x6b\x06\x01", 0x10000, 3), # rfc integer # integer with tag is only 3 bytes # num_tlv(b"\x70\x06\x01", 0xFFFFFFFF, 4), # rfc integer num_tlv(b"\x6c\x07\x01", 0, 4), # rfc signed num_tlv(b"\x6c\x07\x01", 0xFF, 4), # rfc signed num_tlv(b"\x6c\x07\x01", 0x1000, 4), # rfc signed num_tlv(b"\x6c\x07\x01", 0xFFFF, 4), # rfc signed num_tlv(b"\x6c\x07\x01", 0x10000, 4), # rfc signed num_tlv(b"\x6c\x07\x01", 0xFFFFFFFF, 4, -1), # rfc signed num_tlv(b"\x6c\x07\x01", 0x80000000, 4, -2147483648), # rfc signed num_tlv(b"\x6c\x07\x01", 0x7FFFFFFF, 4, 2147483647), # rfc signed num_tlv(b"\x6d\x0B\x01", 0, 8), # rfc integer64 num_tlv(b"\x6d\x0B\x01", 0xFF, 8), # rfc integer64 num_tlv(b"\x6d\x0B\x01", 0x100, 8), # rfc integer64 num_tlv(b"\x6d\x0B\x01", 0xFFFF, 8), # rfc integer64 num_tlv(b"\x6d\x0B\x01", 0x10000, 8), # rfc integer64 num_tlv(b"\x6d\x0B\x01", 0xFFFFFFFF, 8), # rfc integer64 num_tlv(b"\x6d\x0B\x01", 0x100000000, 8), # rfc integer64 num_tlv(b"\x6d\x0B\x01", 0xFFFFFFFFFFFFFFFF, 8), # rfc integer64 (b"\x6e\x07\x01\xc0\xa8\x01\x08", IPv4Address("192.168.1.8")), (b"\x6f\x08\x01\x10\xc0\xa8\x00\x00", IPv4Network("192.168.0.0/16")), ( b"\x70\x13\x01\x20\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", IPv6Address("2003::1"), ), ( b"\x71\x15\x01\x00\x40\x20\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", IPv6Network("2003::0/64"), ), (b"\x70\x05\x01\x20\x03", IPv6Address("2003::0")), (b"\x71\x07\x01\x00\x40\x20\x03", IPv6Network("2003::0/64")), ] @pytest.fixture def radius_dictionary(): return dictionary.Dictionary("tests/dictionaries/dict") @pytest.mark.parametrize( "header", [ b"\1\0" + struct.pack("!H", 5000) + 4996 * b"\0", b"\1\0" + struct.pack("!H", 100), b"\0\0" + struct.pack("!H", 20) + 16 * b"\0", b"", ], ) def test_invalid_header(header): with pytest.raises(utils.PacketError): utils.decode_header(header) @pytest.mark.parametrize("attr_bytes, expected", TEST_ATTRIBUTES) def test_decode_attribute_rfc(radius_dictionary, attr_bytes, expected): raw_packet = bytes(20) + attr_bytes attrs = utils.decode_attributes(radius_dictionary, raw_packet) assert len(attrs) == 1 assert attrs[0].value == expected assert attrs[0].tag == 0 @pytest.mark.parametrize("attr_bytes, expected", TEST_ATTRIBUTES) def test_decode_attribute_vsa(radius_dictionary, attr_bytes, expected): vsa_length = (6 + len(attr_bytes)).to_bytes(1, "big") raw_packet = ( bytes(20) + b"\x1a" + vsa_length + b"\x00\x00\x04\xd2" + attr_bytes ) attrs = utils.decode_attributes(radius_dictionary, raw_packet) assert len(attrs) == 1 assert attrs[0].value == expected assert attrs[0].tag == 0 @pytest.mark.parametrize("attr_bytes, expected", TAGGED_ATTRIBUTES) def test_decode_attribute_rfc_tagged(radius_dictionary, attr_bytes, expected): raw_packet = bytes(20) + attr_bytes attrs = utils.decode_attributes(radius_dictionary, raw_packet) assert len(attrs) == 1 assert attrs[0].value == expected assert attrs[0].tag == 1 @pytest.mark.parametrize("attr_bytes, expected", TAGGED_ATTRIBUTES) def test_decode_attribute_vsa_tagged(radius_dictionary, attr_bytes, expected): vsa_length = (6 + len(attr_bytes)).to_bytes(1, "big") raw_packet = ( bytes(20) + b"\x1a" + vsa_length + b"\x00\x00\x04\xd2" + attr_bytes ) attrs = utils.decode_attributes(radius_dictionary, raw_packet) assert len(attrs) == 1 assert attrs[0].value == expected assert attrs[0].tag == 1 @pytest.mark.parametrize( "plaintext, obfuscated, authenticator", [ ( b"short_password", "ed9b49281f9de8edefae1b09b04beb86", "7b19486d8372b8c136ccf2444d0a5b2c", ), ( b"superlongpassword_exeeding_16_bytes", "1f123e277869997fdfb93f6df037024463918d29064c9fcd5831c57dccd9308ac6b835e6d8f70995d1498a6c5a2a5b71", "12441ce350ce269c04f650f7923058e1", ), ], ) def test_password(plaintext, obfuscated, authenticator): obfuscated = bytes.fromhex(obfuscated) authenticator = bytes.fromhex(authenticator) encoded = utils.password_encode(SECRET, authenticator, plaintext) assert len(encoded) == len(obfuscated) assert encoded == obfuscated decoded = utils.password_decode(SECRET, authenticator, encoded) assert len(decoded) == len(plaintext) assert decoded == plaintext assert utils.validate_pap_password( SECRET, authenticator, encoded, plaintext ) @pytest.mark.parametrize( "plaintext, chap, challenge", [ ( b"short_password", bytes.fromhex("2302a92821f675a52df8e5a3b10e49b0ab"), b"1234567890ABCDEF", ), ], ) def test_chap_password(plaintext, chap, challenge): chapid = chap[:1] encoded = utils.create_chap_password(chapid, challenge, plaintext) assert len(encoded) == len(chap) assert encoded == chap assert utils.validate_chap_password(chapid, challenge, chap, plaintext) def test_salt_crypt(): plaintext = (13).to_bytes(4, "big") authenticator = bytes.fromhex("18e3657cf849d5e677d8752486ceaad7") radius_value = bytes.fromhex("8472d1f6511f389ea42d572fed0f52a77159") salt = int.from_bytes(radius_value[:2], "big") encrypted = utils.salt_encrypt(SECRET, authenticator, plaintext, salt) decrypted = utils.salt_decrypt(SECRET, authenticator, encrypted[2:], salt) assert len(radius_value) == len(encrypted) # assert radius_value == encrypted assert len(plaintext) == len(decrypted) assert plaintext == decrypted # @pytest.mark.parametrize( # "plaintext, encrypted", [("some-password", # bytes.fromhex('7a9528106b80e4aa05b143708400d37e'), # bytes.fromhex('62c3dcbdc8d7239fc782a300f8b7707c'))]), # def test_ascend_password(plaintext, encrypted, authenticator): # salt = # enc = utils.salt_encrypt("secret", authenticator, salt, plaintext) # dec = utils.salt_decrypt("secret", authenticator, salt, enc) # assert enc == encrypted # assert plaintext = dec