EVS decoding should be mostly working now. We do not have any tests yet, because it is unclear to me how EVS is defined in dictionaries.
352 lines
12 KiB
Python
352 lines
12 KiB
Python
# Copyright 2020 Istvan Ruzman
|
|
# SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
import struct
|
|
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
|
from itertools import product
|
|
|
|
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")),
|
|
(b"\x0e\x06\xc0\xa8\x01\x08", IPv4Address("192.168.1.8")),
|
|
(
|
|
b"\x0e\x12\x20\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
|
|
IPv6Address("2003::1"),
|
|
),
|
|
]
|
|
|
|
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")),
|
|
# combo-ip (v4)
|
|
(b"\x72\x07\x01\xc0\xa8\x01\x08", IPv4Address("192.168.1.8")),
|
|
# combo-ip (v6)
|
|
(
|
|
b"\x72\x13\x01\x20\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
|
|
IPv6Address("2003::1"),
|
|
),
|
|
]
|
|
|
|
VENDOR_FORMAT_COMBINATIONS = [
|
|
(1234, 1, 0),
|
|
(1235, 1, 1),
|
|
(1236, 1, 2),
|
|
(1237, 2, 0),
|
|
(1238, 2, 1),
|
|
(1239, 2, 2),
|
|
(1240, 4, 0),
|
|
(1241, 4, 1),
|
|
(1242, 4, 2),
|
|
]
|
|
|
|
|
|
def flattened_product(l1, l2):
|
|
result = []
|
|
for combination in product(l1, l2):
|
|
left, right = combination
|
|
result.append(list(left) + list(right))
|
|
return result
|
|
|
|
|
|
def transform_attr_to_extended_evs(attributes):
|
|
vendor_id = (1234).to_bytes(4, "big")
|
|
new_attrs = []
|
|
for attr, result in attributes:
|
|
length = (attr[1] + 6).to_bytes(1, "big")
|
|
orig_type = attr[0].to_bytes(1, "big")
|
|
attr = b"\x13" + length + b"\x15" + vendor_id + orig_type + attr[2:]
|
|
new_attrs.append((attr, result))
|
|
return new_attrs
|
|
|
|
|
|
def transform_attr_to_longextended_evs(attributes):
|
|
vendor_id = (1234).to_bytes(4, "big")
|
|
new_attrs = []
|
|
for attr, result in attributes:
|
|
length = (attr[1] + 7).to_bytes(1, "big")
|
|
orig_type = attr[0].to_bytes(1, "big")
|
|
attr = b"\x14" + length + b"\x15" + b"\x00" + vendor_id + orig_type + attr[2:]
|
|
new_attrs.append((attr, result))
|
|
return new_attrs
|
|
|
|
|
|
VENDOR_TEST_ATTRIBUTES = flattened_product(VENDOR_FORMAT_COMBINATIONS, TEST_ATTRIBUTES)
|
|
VENDOR_TAGGED_ATTRIBUTES = flattened_product(
|
|
VENDOR_FORMAT_COMBINATIONS, TAGGED_ATTRIBUTES
|
|
)
|
|
VENDOR_EXTENDED_EVS_ATTRIBUTES = transform_attr_to_extended_evs(TEST_ATTRIBUTES)
|
|
VENDOR_LONGEXTENDED_EVS_ATTRIBUTES = transform_attr_to_longextended_evs(TEST_ATTRIBUTES)
|
|
|
|
|
|
@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", 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", VENDOR_EXTENDED_EVS_ATTRIBUTES)
|
|
def test_decode_extended_evs(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", VENDOR_LONGEXTENDED_EVS_ATTRIBUTES)
|
|
def test_decode_longextended_evs(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
|
|
|
|
|
|
def generate_vendor_attribute(vendor_id, tlen, llen, attr_bytes):
|
|
vendor_id = vendor_id.to_bytes(4, "big")
|
|
vsa_length = (4 + tlen + llen + len(attr_bytes)).to_bytes(1, "big")
|
|
attr_type = attr_bytes[0].to_bytes(tlen, "big")
|
|
|
|
attr_len = attr_bytes[1] + tlen
|
|
if llen == 0:
|
|
attr_len = b""
|
|
else:
|
|
attr_len = attr_len.to_bytes(llen, "big")
|
|
|
|
attr_bytes = attr_bytes[2:]
|
|
packet = (
|
|
bytes(20) + b"\x1a" + vsa_length + vendor_id + attr_type + attr_len + attr_bytes
|
|
)
|
|
return packet
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"vendor_id, tlen, llen, attr_bytes, expected", VENDOR_TEST_ATTRIBUTES
|
|
)
|
|
def test_decode_attribute_vsa(
|
|
radius_dictionary, vendor_id, tlen, llen, attr_bytes, expected
|
|
):
|
|
raw_packet = generate_vendor_attribute(vendor_id, tlen, llen, 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(
|
|
"vendor_id, tlen, llen, attr_bytes, expected", VENDOR_TAGGED_ATTRIBUTES
|
|
)
|
|
def test_decode_attribute_vsa_tagged(
|
|
radius_dictionary, vendor_id, tlen, llen, attr_bytes, expected
|
|
):
|
|
raw_packet = generate_vendor_attribute(vendor_id, tlen, llen, 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
|