#!/usr/bin/env python3 # # rdfeed-fetch: NTLM against the test vectors of MS-NLMP, the feed, the .rdp # files, the icons, and whole subscriptions against tests/fake_rdweb.py. # # Copyright (C) 2026 Felitendo # SPDX-License-Identifier: GPL-3.0-or-later import hmac import os import struct import subprocess import sys import tempfile import unittest import zlib HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) import fake_rdweb # noqa: E402 fetch = fake_rdweb.fetch FETCH = os.path.join(HERE, "..", "src", "fetch", "rdfeed-fetch") def png_info(png): """Width, height and the RGBA bytes of a PNG written by png_encode.""" assert png[:8] == fetch.PNG_SIGNATURE width, height = struct.unpack(">II", png[16:24]) pos, idat = 8, b"" while pos < len(png): length = struct.unpack(">I", png[pos:pos + 4])[0] kind = png[pos + 4:pos + 8] if kind == b"IDAT": idat += png[pos + 8:pos + 8 + length] pos += 12 + length raw = zlib.decompress(idat) return width, height, raw class Ntlm(unittest.TestCase): def test_md4(self): self.assertEqual(fetch.md4(b"").hex(), "31d6cfe0d16ae931b73c59d7e0c089c0") self.assertEqual(fetch.md4(b"abc").hex(), "a448017aaf21d8525fc10ae87aa6729d") self.assertEqual(fetch.md4(b"1234567890" * 8).hex(), "e33b4ddc9c38f2199c3e7b164fcc0536") # MS-NLMP 4.2.4: User, Domain, Password, server challenge 0123456789abcdef, # client challenge aa..aa, time 0, target info Domain and Server. def test_ntlmv2_vectors(self): key = fetch.ntowfv2("User", "Domain", "Password") self.assertEqual(key.hex(), "0c868a403bfd7a93a3001ef22ef02e3f") server, client = bytes.fromhex("0123456789abcdef"), b"\xaa" * 8 lm = hmac.new(key, server + client, "md5").digest() + client self.assertEqual(lm.hex(), "86c35097ac9cec102554764a57cccc19aaaaaaaaaaaaaaaa") info = (struct.pack("login", b"", b""): with self.assertRaises(fetch.FeedError): fetch.parse_feed(data) def test_slugs(self): taken = set() self.assertEqual(fetch.make_slug("Passwort ändern", taken), "passwort-andern") self.assertEqual(fetch.make_slug("Passwort ändern", taken), "passwort-andern-2") self.assertEqual(fetch.make_slug("计算器", taken), "app") self.assertEqual(fetch.make_slug("!!!", taken), "app-2") self.assertEqual(fetch.common_suffix(["a-X-Y", "bb-X-Y"]), "-X-Y") self.assertEqual(fetch.common_suffix(["word", "sword"]), "") self.assertEqual(fetch.common_suffix(["only-one"]), "") class Rdp(unittest.TestCase): def test_sanitize(self): text = fetch.decode_rdp(fake_rdweb.rdp_file("winword-x", "Word", "RemoteApp")) out = fetch.sanitize_rdp(text) self.assertIn("promptcredentialonce:i:1\r\n", out) self.assertIn("prompt for credentials on client:i:0\r\n", out) for gone in ("drivestoredirect", "devicestoredirect", "camerastoredirect"): self.assertNotIn(gone, out) self.assertIn("remoteapplicationprogram:s:||winword\r\n", out) def test_encodings(self): line = "full address:s:host\r\n" for data in (line.encode("utf-16"), line.encode("utf-16-le"), line.encode("utf-8-sig"), line.encode()): self.assertEqual(fetch.decode_rdp(data), line) def test_append(self): self.assertEqual(fetch.rdp_set("a:s:b\r\n", "promptcredentialonce", "i", "1"), "a:s:b\r\npromptcredentialonce:i:1\r\n") class Icons(unittest.TestCase): def test_32bpp(self): width, height, raw = png_info(fetch.ico_to_png(fake_rdweb.ico_32bpp(48, (30, 110, 220, 128)))) self.assertEqual((width, height), (48, 48)) self.assertEqual(raw[1:5], bytes((30, 110, 220, 128))) def test_24bpp_with_mask(self): size = 16 header = struct.pack("