253 lines
9.4 KiB
Python
253 lines
9.4 KiB
Python
# 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
|