Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flatbot PROTOCOL File Updates #1

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/user/include_jinja_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
| mac_type | netutils.mac.mac_type |
| compare_type5 | netutils.password.compare_type5 |
| compare_type7 | netutils.password.compare_type7 |
| decrypt_juniper | netutils.password.decrypt_juniper |
| decrypt_type7 | netutils.password.decrypt_type7 |
| encrypt_juniper | netutils.password.encrypt_juniper |
| encrypt_type5 | netutils.password.encrypt_type5 |
| encrypt_type7 | netutils.password.encrypt_type7 |
| get_hash_salt | netutils.password.get_hash_salt |
Expand Down
3 changes: 3 additions & 0 deletions netutils/data_files/protocol_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,8 @@
"xact-backup": {"port_number": 911, "protocols": ["tcp", "udp"]},
"apex-mesh": {"port_number": 912, "protocols": ["tcp", "udp"]},
"apex-edge": {"port_number": 913, "protocols": ["tcp", "udp"]},
"rift-lies": {"port_number": 914, "protocols": ["udp"]},
"rift-ties": {"port_number": 915, "protocols": ["udp"]},
"rndc": {"port_number": 953, "protocols": ["tcp"]},
"ftps-data": {"port_number": 989, "protocols": ["tcp", "udp"]},
"ftps": {"port_number": 990, "protocols": ["tcp", "udp"]},
Expand Down Expand Up @@ -2005,6 +2007,7 @@
"pcc-mfp": {"port_number": 2256, "protocols": ["tcp", "udp"]},
"simple-tx-rx": {"port_number": 2257, "protocols": ["tcp", "udp"]},
"rcts": {"port_number": 2258, "protocols": ["tcp", "udp"]},
"bid-serv": {"port_number": 2259, "protocols": ["tcp", "udp"]},
"apc-2260": {"port_number": 2260, "protocols": ["tcp", "udp"]},
"comotionmaster": {"port_number": 2261, "protocols": ["tcp", "udp"]},
"comotionback": {"port_number": 2262, "protocols": ["tcp", "udp"]},
Expand Down
110 changes: 110 additions & 0 deletions netutils/password.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@
"0x37",
]

JUNIPER_ENCODING = [
[1, 4, 32],
[1, 16, 32],
[1, 8, 32],
[1, 64],
[1, 32],
[1, 4, 16, 128],
[1, 32, 64],
]

JUNIPER_KEYS = ["QzF3n6/9CAtpu0O", "B1IREhcSyrleKvMW8LXx", "7N-dVbwsY2g4oaJZGUDj", "iHkq.mPf5T"]
JUNIPER_KEYS_STRING = "".join(JUNIPER_KEYS)
JUNIPER_KEYS_LENGTH = len(JUNIPER_KEYS_STRING)
JUNIPER_CHARACTER_KEYS = dict()
for idx, key in enumerate(JUNIPER_KEYS):
for character in key:
JUNIPER_CHARACTER_KEYS[character] = 3 - idx


def _fail_on_mac(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
"""There is an issue with Macintosh for encryption."""
Expand Down Expand Up @@ -252,3 +270,95 @@ def get_hash_salt(encrypted_password: str) -> str:
if len(split_password) != 4:
raise ValueError(f"Could not parse salt out password correctly from {encrypted_password}")
return split_password[2]


def decrypt_juniper(encrypted_password: str) -> str:
"""Given an encrypted Junos $9$ type password, decrypt it.

Args:
encrypted_password: A password that has been encrypted, and will be decrypted.

Returns:
The unencrypted_password password.

Examples:
>>> from netutils.password import decrypt_juniper
>>> decrypt_juniper("$9$7YdwgGDkTz6oJz69A1INdb")
'juniper'
>>>
"""
# Strip $9$ from start of string
password_characters = encrypted_password.split("$9$", 1)[1]

# Get first character and toss extra characters
first_character = password_characters[0]
stripped_password_characters = password_characters[JUNIPER_CHARACTER_KEYS[first_character] + 1 :]

previous_char = first_character
decrypted_password = "" # nosec
while stripped_password_characters:
# Get encoding modulus
decode = JUNIPER_ENCODING[len(decrypted_password) % len(JUNIPER_ENCODING)]

# Get nibble we will decode
nibble = stripped_password_characters[0 : len(decode)]
stripped_password_characters = stripped_password_characters[len(decode) :]

# Decode value for nibble and convert to character, append to decryped password
value = 0
for idx, char in enumerate(nibble):
gap = (
(JUNIPER_KEYS_STRING.index(char) - JUNIPER_KEYS_STRING.index(previous_char)) % JUNIPER_KEYS_LENGTH
) - 1
value += gap * decode[idx]
previous_char = char
decrypted_password += chr(value)

return decrypted_password


def encrypt_juniper(unencrypted_password: str, salt: t.Optional[int] = None) -> str:
"""Given an unencrypted password, encrypt to Juniper $9$ type password.

Args:
unencrypted_password: A password that has not been encrypted, and will be compared against.
salt: A integer that can be set by the operator. Defaults to random generated one.

Returns:
The encrypted password.

Examples:
>>> from netutils.password import encrypt_juniper
>>> encrypt_juniper("juniper", 35) # doctest: +SKIP
'$9$7YdwgGDkTz6oJz69A1INdb'
>>>
"""
if not salt:
salt = random.randint(0, JUNIPER_KEYS_LENGTH) - 1 # nosec

# Use salt to generate start of encrypted password
first_character = JUNIPER_KEYS_STRING[salt]
random_chars = "".join(
[
JUNIPER_KEYS_STRING[random.randint(0, JUNIPER_KEYS_LENGTH) - 1] # nosec
for x in range(0, JUNIPER_CHARACTER_KEYS[first_character])
]
)
encrypted_password = "$9$" + first_character + random_chars

previous_character = first_character
for idx, char in enumerate(unencrypted_password):
encode = JUNIPER_ENCODING[idx % len(JUNIPER_ENCODING)][::-1] # Get encoding modulus in reverse order
char_ord = ord(char)
gaps: t.List[int] = []
for modulus in encode:
gaps = [int(char_ord / modulus)] + gaps
char_ord %= modulus

for gap in gaps:
gap += JUNIPER_KEYS_STRING.index(previous_character) + 1
new_character = JUNIPER_KEYS_STRING[gap % JUNIPER_KEYS_LENGTH]
previous_character = new_character
encrypted_password += new_character

return encrypted_password
2 changes: 2 additions & 0 deletions netutils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"encrypt_type5": "password.encrypt_type5",
"encrypt_type7": "password.encrypt_type7",
"get_hash_salt": "password.get_hash_salt",
"encrypt_juniper": "password.encrypt_juniper",
"decrypt_juniper": "password.decrypt_juniper",
"tcp_ping": "ping.tcp_ping",
"longest_prefix_match": "route.longest_prefix_match",
"vlanlist_to_config": "vlan.vlanlist_to_config",
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/test_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@
},
]

ENCRYPT_JUNIPER = [
{
"sent": {"unencrypted_password": "juniper", "salt": 35},
"received_one": "$9$7",
"received_two": "gGDkTz6oJz69A1INdb",
},
]

DECRYPT_JUNIPER = [
{
"sent": {"encrypted_password": "$9$7YdwgGDkTz6oJz69A1INdb"},
"received": "juniper",
}
]


@pytest.mark.parametrize("data", COMPARE_TYPE5)
def test_compare_type5(data):
Expand Down Expand Up @@ -98,3 +113,16 @@ def test_encrypt_type7(data):
@pytest.mark.parametrize("data", GET_HASH_SALT)
def test_get_hash_salt(data):
assert password.get_hash_salt(**data["sent"]) == data["received"]


@pytest.mark.parametrize("data", ENCRYPT_JUNIPER)
def test_encrypt_juniper(data):
# Passwords include random padding, check only the non random sections
decrypted_password = password.encrypt_juniper(**data["sent"])
assert decrypted_password[0:4] == data["received_one"]
assert decrypted_password[7:] == data["received_two"]


@pytest.mark.parametrize("data", DECRYPT_JUNIPER)
def test_decrypt_juniper(data):
assert password.decrypt_juniper(**data["sent"]) == data["received"]