Improve tests some more

This commit is contained in:
Florian Bach 2021-12-19 22:13:19 +01:00
parent 2fae9b83a2
commit 619b7bab22
9 changed files with 174 additions and 61 deletions

View File

@ -32,7 +32,8 @@ jobs:
- name: Install dependencies
run: |
pip3 install freezegun lxml pycryptodome rsa oscrypto
# Require cryptography >= 3.1 because in 3.0 and below, the backend param in load_key_and_certificates was still required.
pip3 install freezegun lxml pycryptodome rsa oscrypto "cryptography>=3.1"
- name: Run tests
run: |

View File

@ -11,6 +11,9 @@ make
popd
# Delete cache
rm -r __pycache__
# Set module ID. This needs to be changed if any of the module ZIPs change.
echo -n "2021-12-19-03" > module_id.txt

View File

@ -35,7 +35,8 @@
# update python-cryptography from 3.4.8 to 36.0.1, update python-rsa from 4.7.2 to 4.8.
# Currently in development:
# Ignore fatal HTTP errors during optional fulfillment notifications,
# allow authorizing an eReader through USB.
# allow authorizing an eReader through USB, add a ton of testing code,
# drop dependencies python-cryptography, python-rsa and python-pyasn1.
PLUGIN_NAME = "DeACSM"
PLUGIN_VERSION_TUPLE = (0, 0, 15)

View File

@ -8,8 +8,23 @@ The original code used blinding and this one doesn't,
but we don't really care about side-channel attacks ...
'''
try:
from Cryptodome.PublicKey import RSA
except ImportError:
# Some distros still ship this as Crypto
from Crypto.PublicKey import RSA
class CustomRSA:
def encrypt_for_adobe_signature(signing_key, message):
key = RSA.importKey(signing_key)
keylen = CustomRSA.byte_size(key.n)
padded = CustomRSA.pad_message(message, keylen)
payload = CustomRSA.transform_bytes2int(padded)
encrypted = CustomRSA.normal_encrypt(key, payload)
block = CustomRSA.transform_int2bytes(encrypted, keylen)
return block
def byte_size(number: int):
return (number.bit_length() + 7) // 8

View File

@ -13,18 +13,16 @@ from datetime import datetime, timedelta
from lxml import etree
try:
from Crypto import Random
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA
except ImportError:
# Debian (and Ubuntu) ship pycryptodome, but not in its compatible mode with pycrypto
# If `Crypto` can't be found, try under pycryptodome's own namespace
from Cryptodome import Random
from Cryptodome.Cipher import AES
from Cryptodome.PublicKey import RSA
from Cryptodome.Hash import SHA
except ImportError:
# Some distros still ship Crypto
from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Hash import SHA
try:
from customRSA import CustomRSA
except:
@ -176,6 +174,8 @@ def makeFingerprint(serial: str):
# base64(sha1(serial + privateKey))
# Fingerprint must be 20 bytes or less.
global devkey_bytes
if devkey_bytes is None:
f = open(FILE_DEVICEKEY, "rb")
devkey_bytes = f.read()
f.close()
@ -416,6 +416,8 @@ def sign_node(node):
devkey_bytes = f.read()
f.close()
# Get private key
try:
activationxml = etree.parse(FILE_ACTIVATIONXML)
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
@ -424,17 +426,12 @@ def sign_node(node):
return None
my_pkcs12 = base64.b64decode(pkcs12)
my_priv_key, _, _ = keys.parse_pkcs12(my_pkcs12, base64.b64encode(devkey_bytes))
my_priv_key = dump_private_key(my_priv_key, None, "der")
# textbook RSA with that private key
key = RSA.importKey(my_priv_key)
keylen = CustomRSA.byte_size(key.n)
padded = CustomRSA.pad_message(sha_hash, keylen)
payload = CustomRSA.transform_bytes2int(padded)
encrypted = CustomRSA.normal_encrypt(key, payload)
block = CustomRSA.transform_int2bytes(encrypted, keylen)
block = CustomRSA.encrypt_for_adobe_signature(my_priv_key, sha_hash)
signature = base64.b64encode(block).decode()
# Debug

View File

@ -3,15 +3,15 @@ import base64
import locale, platform
try:
from Crypto.PublicKey import RSA
from Crypto.Util.asn1 import DerSequence
from Crypto.Cipher import PKCS1_v1_5
except ImportError:
# Debian (and Ubuntu) ship pycryptodome, but not in its compatible mode with pycrypto
# If `Crypto` can't be found, try under pycryptodome's own namespace
from Cryptodome.PublicKey import RSA
from Cryptodome.Util.asn1 import DerSequence
from Cryptodome.Cipher import PKCS1_v1_5
except ImportError:
# Some distros ship this as Crypto still.
from Crypto.PublicKey import RSA
from Crypto.Util.asn1 import DerSequence
from Crypto.Cipher import PKCS1_v1_5
try:
from libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest

View File

@ -3,11 +3,10 @@ import base64
import os, locale, platform
try:
from Crypto.Cipher import AES as _AES
except ImportError:
# Debian (and Ubuntu) ship pycryptodome, but not in its compatible mode with pycrypto
# If `Crypto` can't be found, try under pycryptodome's own namespace
from Cryptodome.Cipher import AES as _AES
except ImportError:
# Some distros still ship this as Crypto
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key, iv):

View File

@ -66,7 +66,7 @@ def main():
print("Login unsuccessful: " + resp)
exit(1)
success, resp = activateDevice(VAR_VER)
success, resp = activateDevice(VAR_VER, None)
if (success is False):
print("Couldn't activate device: " + resp)
exit(1)

View File

@ -1,6 +1,6 @@
#!/usr/bin/python3 -W ignore::DeprecationWarning
#!/usr/bin/python3
import sys
import sys, os
sys.path.append("../calibre-plugin")
@ -38,23 +38,43 @@ class TestAdobe(unittest.TestCase):
def test_checkIfVersionListsAreValid(self):
'''These lists must have the same amount of elements, otherwise stuff will break.'''
'''
Check if version lists are sane
These four lists must all have the same amount of elements.
Also, the default build ID must be valid, and all the IDs
available for authorization or switching must be valid, too.
'''
self.assertIn(libadobe.VAR_VER_DEFAULT_BUILD_ID, libadobe.VAR_VER_BUILD_IDS, "Default build ID invalid")
self.assertEqual(len(libadobe.VAR_VER_SUPP_CONFIG_NAMES), len(libadobe.VAR_VER_SUPP_VERSIONS), "Version lists seem to be invalid")
self.assertEqual(len(libadobe.VAR_VER_SUPP_CONFIG_NAMES), len(libadobe.VAR_VER_HOBBES_VERSIONS), "Version lists seem to be invalid")
self.assertEqual(len(libadobe.VAR_VER_SUPP_CONFIG_NAMES), len(libadobe.VAR_VER_OS_IDENTIFIERS), "Version lists seem to be invalid")
self.assertEqual(len(libadobe.VAR_VER_SUPP_CONFIG_NAMES), len(libadobe.VAR_VER_BUILD_IDS), "Version lists seem to be invalid")
for version in libadobe.VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE:
self.assertIn(version, libadobe.VAR_VER_BUILD_IDS, "Invalid buildID for authorization")
for version in libadobe.VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO:
self.assertIn(version, libadobe.VAR_VER_BUILD_IDS, "Invalid buildID for switching")
def test_checkSystemIdentifiers(self):
'''Check if we can figure out necessary system IDs.'''
'''Check if we can figure out necessary system IDs'''
self.assertEqual((len(libadobe.get_mac_address())), 6, "MAC address invalid")
self.assertEqual(libadobe.get_mac_address(), libadobe.get_mac_address(), "MAC address not constant")
self.assertEqual((len(libadobe.makeSerial(False))), 40, "SHA1 hash for device serial invalid (device-based)")
self.assertEqual((len(libadobe.makeSerial(True))), 40, "SHA1 hash for device serial invalid (random)")
def test_fingerprintGeneration(self):
'''Check if fingerprint generation works'''
libadobe.devkey_bytes = bytes([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51])
self.assertEqual(libadobe.makeFingerprint("f0081bce3f771bdeeb26fcb4b2011fed77edff7b"), b"FgLMNXxv1BZPqMOM6IUnfaG4Qj8=", "Wrong fingerprint")
self.assertEqual(libadobe.makeFingerprint("HelloWorld123"), b"hpp223C1kfLDOoyxo8WR7KhcXB8=", "Wrong fingerprint")
@patch("libadobe.Random")
def test_deviceKeyEncryption(self, random):
'''Check if encryption with the device key works properly.'''
'''Check if encryption with the device key works'''
# Overwrite the get_random_bytes function that's used to get a random IV
# Forcing hard-coded IV ...
@ -72,7 +92,7 @@ class TestAdobe(unittest.TestCase):
self.assertEqual(mock_result, expected_result, "devkey encryption returned invalid result")
def test_deviceKeyDecryption(self):
'''Check if decryption with the device key works properly.'''
'''Check if decryption with the device key works'''
libadobe.devkey_bytes = bytes([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51])
@ -87,7 +107,7 @@ class TestAdobe(unittest.TestCase):
@freeze_time("2021-12-18 22:07:15.988961")
def test_verifyNonceCalculation(self):
'''Check if the nonce calculation is correct, at a given date/time.'''
'''Check if the nonce calculation is correct, at a given date/time'''
nonce_return = libadobe.addNonce()
expected_return = "<adept:nonce>8B2aPgg6AAAAAAAA</adept:nonce><adept:expiration>2021-12-18T22:17:15Z</adept:expiration>"
@ -96,7 +116,7 @@ class TestAdobe(unittest.TestCase):
@freeze_time("2031-07-19 22:15:22.074860")
def test_verifyNonceCalculationFuture(self):
'''Check if the nonce calculation is still correct in the future.'''
'''Check if the nonce calculation is still correct in the future'''
nonce_return = libadobe.addNonce()
expected_return = "<adept:nonce>JFUTp046AAAAAAAA</adept:nonce><adept:expiration>2031-07-19T22:25:22Z</adept:expiration>"
@ -105,7 +125,7 @@ class TestAdobe(unittest.TestCase):
def test_hash_node(self):
'''Check if the XML hash (needed for the signature) is correct.'''
'''Check if the XML hash (needed for the signature) is correct'''
# This XML is an anonymized (all IDs replaced with random UUIDs) ACSM file.
@ -143,12 +163,12 @@ class TestAdobe(unittest.TestCase):
mock_xml_obj = etree.fromstring(mock_xml_str)
sha_hash = libadobe.hash_node(mock_xml_obj).hexdigest().lower()
self.assertEqual(sha_hash, "3452e3d11cdd70eb90323f291c06afafe10e098a", "Invalid SHA hash after node signing")
self.assertEqual(sha_hash, "3452e3d11cdd70eb90323f291c06afafe10e098a", "Invalid SHA hash for node signing")
def test_sign_node_old(self):
'''Tests the "old" signing method with the external RSA library. '''
'''Check if the external RSA library (unused) signs correctly'''
# This is no longer in use by the plugin, but I still want to leave this test in,
# in case we need to go back to the old method.
@ -168,35 +188,90 @@ class TestAdobe(unittest.TestCase):
expected_signature = "RO/JmWrustzT50GB9bSb4VdRZCP77y0ZuFFmn8gk/p0E6EbQnqP10QkB5HM8JSV9lKaKJuZpDBJ8cp+FxZmMSPe6odTUiL134Y6tXOCllBtKwVamQjsYbIFLv/HOX68rUadSHpr4QKMle2jeQinIT0viS5kwO7XofKHaSLM2XjE="
self.assertEqual(signature, expected_signature, "Old (external RSA) signing method broken")
self.assertEqual(signature, expected_signature, "Old (external RSA) node hash signing method broken")
def test_sign_node_new(self):
'''Tests the "new" signing method with CustomRSA. '''
'''Check if the builtin CustomRSA library signs correctly'''
mock_signing_key = "MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBALluuPvdDpr4L0j3eIGy3VxhgRcEKU3++qwbdvLXI99/izW9kfELFFJtq5d4ktIIUIvHsWkW0jblGi+bQ4sQXCeIvtOgqVHMSvRpW78lnGEkdD4Y1qhbcVGw7OGpWlhp8qCJKVCGbrkML7BSwFvQqqvg4vMU8O1uALfJvicKN3YfAgMBAAECf3uEg+Hr+DrstHhZF40zJPHKG3FkFd3HerXbOawMH5Q6CKTuKDGmOYQD+StFIlMArQJh8fxTVM3gSqgPkyyiesw0OuECU985FaLbUWxuCQzBcitnhl+VSv19oEPHTJWu0nYabasfT4oPjf8eiWR/ymJ9DZrjMWWy4Xf/S+/nFYUCQQDIZ1pc9nZsCB4QiBl5agTXoMcKavxFHPKxI/mHfRCHYjNyirziBJ+Dc/N40zKvldNBjO43KjLhUZs/BxdAJo09AkEA7OAdsg6SmviVV8xk0vuTmgLxhD7aZ9vpV4KF5+TH2DbximFoOP3YRObXV862wAjCpa84v43ok7Imtsu3NKQ+iwJAc0mx3GUU/1U0JoKFVSm+m2Ws27tsYT4kB/AQLvetuJSv0CcsPkI2meLsoAev0v84Ry+SIz4tgx31V672mzsSaQJBAJET1rw2Vq5Zr8Y9ZkceVFGQmfGAOW5A71Jsm6zin0+anyc874NwXaQdqiiab61/8A9gGSahOKA1DacJcCTqr28CQGm4mn3rOQFf+nniajIobATjNHaZJ76Xnc6rtoreK6+ZjO9wYF+797X/bhiV11Fpakvyrz6+t7bAd0PPQ2taTDg="
payload_bytes = bytes([0x34, 0x52, 0xe3, 0xd1, 0x1c, 0xdd, 0x70, 0xeb, 0x90, 0x32, 0x3f, 0x29, 0x1c, 0x06, 0xaf, 0xaf, 0xe1, 0x0e, 0x09, 0x8a])
key = RSA.importKey(base64.b64decode(mock_signing_key))
keylen = CustomRSA.byte_size(key.n)
padded = CustomRSA.pad_message(payload_bytes, keylen)
payload = CustomRSA.transform_bytes2int(padded)
encrypted = CustomRSA.normal_encrypt(key, payload)
block = CustomRSA.transform_int2bytes(encrypted, keylen)
block = CustomRSA.encrypt_for_adobe_signature(base64.b64decode(mock_signing_key), payload_bytes)
signature = base64.b64encode(block).decode()
expected_signature = "RO/JmWrustzT50GB9bSb4VdRZCP77y0ZuFFmn8gk/p0E6EbQnqP10QkB5HM8JSV9lKaKJuZpDBJ8cp+FxZmMSPe6odTUiL134Y6tXOCllBtKwVamQjsYbIFLv/HOX68rUadSHpr4QKMle2jeQinIT0viS5kwO7XofKHaSLM2XjE="
self.assertEqual(signature, expected_signature, "CustomRSA encryption is broken")
self.assertEqual(signature, expected_signature, "CustomRSA node hash signing method is broken")
def test_pkcs12_extract(self):
'''Check if oscrypto can extract pkcs12'''
from oscrypto import keys
from oscrypto.asymmetric import dump_certificate, dump_private_key
mock_p12_data = "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"
mock_p12_password = b"1234"
# I don't like having to import two external libraries just to extract pkcs12 data,
# but I didn't find a proper Python-only solution for this,
# and I didn't want to implement OpenSSL interfaces on my own.
privkey, cert, _ = keys.parse_pkcs12(base64.b64decode(mock_p12_data), mock_p12_password)
cert = dump_certificate(cert, encoding="der")
privkey = dump_private_key(privkey, None, "der")
# privkey now is in PKCS#8 format. That's a wrapper around the actual PKCS#1 key.
# See https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem for details.
# Throw away the PKCS#8 wrapper:
privkey = privkey[26:]
# Verify result:
dummy_cert_data = base64.b64decode("MIICLjCCAZegAwIBAgIUI48XmtNFINg1znympL1l1SmSYnMwDQYJKoZIhvcNAQELBQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEURlQUNTTSB1bml0IHRlc3RzMB4XDTIxMTIxOTE0MzUyMVoXDTIxMTIyOTE0MzUyMVowKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEURlQUNTTSB1bml0IHRlc3RzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCic4kj2kdZxLjW00l8to9hV4+gWAPJFhgvG2Io6pDR/rA1cPAR3Pu4Q4/cwab/gwmCXnnmeQwy3TyzmyCZj9tnBFeNfDsnq0TxoRoTdr0bWv0pGy1uBQ90jZVc85v2whmC9lSigueY4GlR5rOIlNsiuKBWBl/CN/6X3PaYkv04QIDAQABo1MwUTAdBgNVHQ4EFgQUiG9zxWck82kn+BoVp3So+p6tFc8wHwYDVR0jBBgwFoAUiG9zxWck82kn+BoVp3So+p6tFc8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQCpLSNaGDRNMGAzbTtUbyWzbvgT2+hcOaqr8+fyCWtUxZ1FiPIKZEcQRQOuqZZpWft5QJSYBZ2oc3O6NFU5VrBi5UI3rTqr2S97PiJCiR4Jt1vAcpXVy6qcEjTswdpipdsN4RSeiztYk4xoqYztPoiqKJgq9nOzuXFnDKrrrMo+5w==")
dummy_key_data = base64.b64decode("MIICXQIBAAKBgQDCic4kj2kdZxLjW00l8to9hV4+gWAPJFhgvG2Io6pDR/rA1cPAR3Pu4Q4/cwab/gwmCXnnmeQwy3TyzmyCZj9tnBFeNfDsnq0TxoRoTdr0bWv0pGy1uBQ90jZVc85v2whmC9lSigueY4GlR5rOIlNsiuKBWBl/CN/6X3PaYkv04QIDAQABAoGAI2CIGmHyDaTG7I2X9AS752AviVJhs586ay0ZBjYtKlsWoKa/GGJmFNTckHFMjGWgs/IZNyLnOnBlbhpX5UbO1cB7r9Vk1Q3fbIeQdBySyqOG9JfZxd0n4bBSHnopL0naGA0CpSv/tVaUr0BzvNYslw5rcwVinEbGVPP6JNNqbqECQQD1Igbf2qxMHcdEA81qMdExFGmlq61W7gpKtl7XwPhtHIiXyhQsT7M996ZF64EJVg/2/6gQneZ/awJ0Aw8xDkJdAkEAyymWou2v6wPtX+X0hnXnK6OfeEfZGnExE4LpEjNTiQriabvwmCQcgHFBLxKN+C4uVK5HBlHug3jtN0jozSjcVQJBAI+ynLkJJUuRgUhbukTwYyMURkI5+2kkLaBSfBKaKoc73M6uRVkcd4Rx8mS2g3QHoWA3yjvDdGVpQ4ziZjtpknkCQQC2FSsGEYM2Xgm0hlO24xrx+K7nTXWeBk7WzuB3SHsY+yFbZG7I3KySzW5/cuC8yx8JFD1hw7LCMHJitzy3C2UVAkAQGY8PQ9u40krQekUI+imFsPSPdMZqfMKJDLwrXx0mmElUYYZBGtY0q781UYP4GArtwyusDelk6BNjVRjiWUhg")
self.assertEqual(cert, dummy_cert_data, "p12 cert invalid after extraction")
self.assertEqual(privkey, dummy_key_data, "p12 key invalid after extraction")
def test_pkcs12_extract_with_cryptography(self):
'''Check if cryptography (unused) can extract pkcs12'''
# This uses cryptography.hazmat to extract the PKCS data. That's not what the plugin is using right now.
mock_p12_data = "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"
mock_p12_password = b"1234"
try:
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
except:
return self.skipTest("cryptography not installed")
privkey, cert, _ = pkcs12.load_key_and_certificates(base64.b64decode(mock_p12_data), mock_p12_password)
privkey = privkey.private_bytes(Encoding.DER, PrivateFormat.PKCS8, NoEncryption())
cert = cert.public_bytes(Encoding.DER)
# privkey now is in PKCS#8 format. That's a wrapper around the actual PKCS#1 key.
# See https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem for details.
# Throw away the PKCS#8 wrapper:
privkey = privkey[26:]
# Verify result:
dummy_cert_data = base64.b64decode("MIICLjCCAZegAwIBAgIUI48XmtNFINg1znympL1l1SmSYnMwDQYJKoZIhvcNAQELBQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEURlQUNTTSB1bml0IHRlc3RzMB4XDTIxMTIxOTE0MzUyMVoXDTIxMTIyOTE0MzUyMVowKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEURlQUNTTSB1bml0IHRlc3RzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCic4kj2kdZxLjW00l8to9hV4+gWAPJFhgvG2Io6pDR/rA1cPAR3Pu4Q4/cwab/gwmCXnnmeQwy3TyzmyCZj9tnBFeNfDsnq0TxoRoTdr0bWv0pGy1uBQ90jZVc85v2whmC9lSigueY4GlR5rOIlNsiuKBWBl/CN/6X3PaYkv04QIDAQABo1MwUTAdBgNVHQ4EFgQUiG9zxWck82kn+BoVp3So+p6tFc8wHwYDVR0jBBgwFoAUiG9zxWck82kn+BoVp3So+p6tFc8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQCpLSNaGDRNMGAzbTtUbyWzbvgT2+hcOaqr8+fyCWtUxZ1FiPIKZEcQRQOuqZZpWft5QJSYBZ2oc3O6NFU5VrBi5UI3rTqr2S97PiJCiR4Jt1vAcpXVy6qcEjTswdpipdsN4RSeiztYk4xoqYztPoiqKJgq9nOzuXFnDKrrrMo+5w==")
dummy_key_data = base64.b64decode("MIICXQIBAAKBgQDCic4kj2kdZxLjW00l8to9hV4+gWAPJFhgvG2Io6pDR/rA1cPAR3Pu4Q4/cwab/gwmCXnnmeQwy3TyzmyCZj9tnBFeNfDsnq0TxoRoTdr0bWv0pGy1uBQ90jZVc85v2whmC9lSigueY4GlR5rOIlNsiuKBWBl/CN/6X3PaYkv04QIDAQABAoGAI2CIGmHyDaTG7I2X9AS752AviVJhs586ay0ZBjYtKlsWoKa/GGJmFNTckHFMjGWgs/IZNyLnOnBlbhpX5UbO1cB7r9Vk1Q3fbIeQdBySyqOG9JfZxd0n4bBSHnopL0naGA0CpSv/tVaUr0BzvNYslw5rcwVinEbGVPP6JNNqbqECQQD1Igbf2qxMHcdEA81qMdExFGmlq61W7gpKtl7XwPhtHIiXyhQsT7M996ZF64EJVg/2/6gQneZ/awJ0Aw8xDkJdAkEAyymWou2v6wPtX+X0hnXnK6OfeEfZGnExE4LpEjNTiQriabvwmCQcgHFBLxKN+C4uVK5HBlHug3jtN0jozSjcVQJBAI+ynLkJJUuRgUhbukTwYyMURkI5+2kkLaBSfBKaKoc73M6uRVkcd4Rx8mS2g3QHoWA3yjvDdGVpQ4ziZjtpknkCQQC2FSsGEYM2Xgm0hlO24xrx+K7nTXWeBk7WzuB3SHsY+yFbZG7I3KySzW5/cuC8yx8JFD1hw7LCMHJitzy3C2UVAkAQGY8PQ9u40krQekUI+imFsPSPdMZqfMKJDLwrXx0mmElUYYZBGtY0q781UYP4GArtwyusDelk6BNjVRjiWUhg")
self.assertEqual(cert, dummy_cert_data, "p12 cert invalid after extraction")
self.assertEqual(privkey, dummy_key_data, "p12 key invalid after extraction")
def test_Account_loginCredentialEncryption(self):
'''Check if we can properly encrypt login credentials.'''
'''Check if we can properly encrypt login credentials'''
# This cert is not the actual Adobe auth cert.
# I added a fake one in here so we can also test if the encrypted data is valid
@ -238,14 +313,15 @@ class TestOther(unittest.TestCase):
def forcefail(self):
self.assertEqual(1, 2, "force fail")
@unittest.skipIf(os.name == "nt", "not testing wine on Windows")
def test_wineUsernameEscaping(self):
'''Wine uses a weird encoding for the username inside the registry. Make sure that the decoder is working correctly.'''
'''Check if Wine username decoder is working properly'''
self.assertEqual(fix_wine_username(r'"1234"'), b'1234', "Wine username mismatch")
self.assertEqual(fix_wine_username(r'"a\x00e931"'), b'a\xe931', "Wine username mismatch")
def test_pdf_trimEncrypt(self):
'''Makes sure the encryption string trimming code for PDFs is working fine.'''
'''Check if PDF encryption string trimming code is working properly'''
input = "<</Root 1 0 R/Info 1 0 R/Encrypt 1 0 R/ID[<1111><2222>]/Size 3>>AppendedData"
output = "<</Root 1 0 R/Info 1 0 R/Encrypt 1 0 R/ID[<1111><2222>]/Size 3>>"
@ -253,13 +329,13 @@ class TestOther(unittest.TestCase):
self.assertEqual(trim_encrypt_string(input), output, "PDF string trimming broken")
def test_pdf_cleanupEncrypt(self):
'''Makes sure the encryption string cleanup / spacing code for PDFs is working fine.'''
'''Check if PDF encryption string spacing code is working properly'''
self.assertEqual(cleanup_encrypt_element("ID[<1111><2222>]"), "ID[<1111> <2222>]")
self.assertEqual(cleanup_encrypt_element("ID[ <1111> <2222>] "), "ID[<1111> <2222>]")
self.assertEqual(cleanup_encrypt_element("ID[<1111><2222>]"), "ID[<1111> <2222>]", "angle bracket spacing invalid")
self.assertEqual(cleanup_encrypt_element("ID[ <1111> <2222>] "), "ID[<1111> <2222>]", "square bracket spacing invalid")
def test_pdf_deflateCompression(self):
'''Makes sure the deflate code for PDF rights.xml works properly.'''
'''Check if PDF rights.xml deflare code is working properly'''
self.assertEqual(deflate_and_base64_encode(b""), b"AwA=", "deflate code error in empty string")
self.assertEqual(deflate_and_base64_encode(b"Hello world"), b"80jNyclXKM8vykkBAA==", "deflate code error")
@ -267,9 +343,30 @@ class TestOther(unittest.TestCase):
# Patch to only display the docstring info, not the weird autogenerated name.
def monkeypatch_getDescription(self, test):
if test.shortDescription() is not None:
return test.shortDescription()
return str(test)
# Patch the error list at the end to include the autogenerated name (use original getDescription, not my override)
def monkeypatch_printErrorList(self, flavour, errors):
for test, err in errors:
self.stream.writeln(self.separator1)
self.stream.writeln("%s: %s" % (flavour,self.getFullDescription(test)))
self.stream.writeln(self.separator2)
self.stream.writeln("%s" % err)
self.stream.flush()
if __name__ == "__main__":
unittest.main(verbosity=1)
# Monkey patch the runner to get the output format I want:
unittest.runner.TextTestResult.getFullDescription = unittest.runner.TextTestResult.getDescription
unittest.runner.TextTestResult.getDescription = monkeypatch_getDescription
unittest.runner.TextTestResult.printErrorList = monkeypatch_printErrorList
# Run tests
unittest.main(verbosity=2)