Codebase list pypsrp / master tests / test_encryption.py
master

Tree @master (Download .tar.gz)

test_encryption.py @masterraw · history · blame

import collections
import pytest

from pypsrp.encryption import WinRMEncryption
from pypsrp.exceptions import WinRMError


WrapIOVResult = collections.namedtuple('WrapIOVResult', ['buffers'])
WrapResult = collections.namedtuple('WrapResult', ['data'])


class MockAuthCREDSSP(object):

    def __init__(self):
        class TlsConnection(object):
            def get_cipher_name(self):
                return "ECDHE-RSA-AES256-GCM-SHA384"
        self.tls_connection = TlsConnection()

    def wrap(self, data):
        return data + b"-encrypted"

    def unwrap(self, data):
        return data[:len(data) - 10]


class MockAuthSPNEGO(object):

    def __init__(self, padding=False):
        self.padding = padding

    def wrap_winrm(self, data):
        return b"reallylongheader", data + b"-encrypted", 1 if self.padding else 0

    def unwrap_winrm(self, header, data):
        return data[:len(data) - 10]


class TestWinRMEncryption(object):

    def test_wrap_small_spnego(self):
        plaintext = b"plaintext"
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.SPNEGO)
        expected = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-SPNEGO-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"--Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00reallylongheaderplaintext-" \
                   b"encrypted--Encrypted Boundary--\r\n"
        actual_type, actual = encryption.wrap_message(plaintext)

        assert "multipart/encrypted" == actual_type
        assert expected == actual

    def test_wrap_spnego_padded(self):
        plaintext = b"plaintext"
        encryption = WinRMEncryption(MockAuthSPNEGO(padding=True), WinRMEncryption.SPNEGO)
        expected = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-SPNEGO-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=10\r\n" \
                   b"--Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00reallylongheaderplaintext-" \
                   b"encrypted--Encrypted Boundary--\r\n"
        actual_type, actual = encryption.wrap_message(plaintext)

        assert "multipart/encrypted" == actual_type
        assert expected == actual

    def test_wrap_small_kerberos(self):
        plaintext = b"plaintext"
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.KERBEROS)
        expected = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-Kerberos-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"--Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00reallylongheaderplaintext-" \
                   b"encrypted--Encrypted Boundary--\r\n"
        actual_type, actual = encryption.wrap_message(plaintext)

        assert "multipart/encrypted" == actual_type
        assert expected == actual

    def test_wrap_small_credsp(self):
        plaintext = b"plaintext"
        encryption = WinRMEncryption(MockAuthCREDSSP(), WinRMEncryption.CREDSSP)
        expected = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-CredSSP-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"--Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00plaintext-encrypted" \
                   b"--Encrypted Boundary--\r\n"
        actual_type, actual = encryption.wrap_message(plaintext)

        assert "multipart/encrypted" == actual_type
        assert expected == actual

    def test_wrap_large_spnego(self):
        plaintext = b"a" * 20000
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.SPNEGO)
        expected = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-SPNEGO-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=20000" \
                   b"\r\n--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/octet-stream\r\n\x10\x00\x00\x00reallylongheader" + plaintext + \
                   b"-encrypted--Encrypted Boundary--\r\n"
        actual_type, actual = encryption.wrap_message(plaintext)

        assert "multipart/encrypted" == actual_type
        assert expected == actual

    def test_wrap_large_kerberos(self):
        plaintext = b"a" * 20000
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.KERBEROS)
        expected = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-Kerberos-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=20000" \
                   b"\r\n--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/octet-stream\r\n\x10\x00\x00\x00reallylongheader" + plaintext + \
                   b"-encrypted--Encrypted Boundary--\r\n"
        actual_type, actual = encryption.wrap_message(plaintext)

        assert "multipart/encrypted" == actual_type
        assert expected == actual

    def test_wrap_large_credsp(self):
        plaintext = b"a" * 20000
        encryption = WinRMEncryption(MockAuthCREDSSP(), WinRMEncryption.CREDSSP)
        expected = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-CredSSP-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=16384" \
                   b"\r\n--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/octet-stream\r\n\x10\x00\x00\x00" + b"a" * 16384 + \
                   b"-encrypted--Encrypted Boundary\r\n\tContent-Type: " \
                   b"application/HTTP-CredSSP-session-encrypted\r\n" \
                   b"\tOriginalContent: type=application/soap+xml;" \
                   b"charset=UTF-8;Length=3616\r\n--Encrypted Boundary\r\n" \
                   b"\tContent-Type: application/octet-stream\r\n" \
                   b"\x10\x00\x00\x00" + b"a" * 3616 + \
                   b"-encrypted--Encrypted Boundary--\r\n"
        actual_type, actual = encryption.wrap_message(plaintext)

        assert "multipart/x-multi-encrypted" == actual_type
        assert expected == actual

    def test_unwrap_small_spnego(self):
        expected = b"plaintext"
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.SPNEGO)
        bwrapped = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-SPNEGO-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"--Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00reallylongheaderplaintext-" \
                   b"encrypted--Encrypted Boundary--\r\n"
        actual = encryption.unwrap_message(bwrapped, "Encrypted Boundary")
        assert expected == actual

    def test_unwrap_small_spnego_without_end_hyphens(self):
        expected = b"plaintext"
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.SPNEGO)
        bwrapped = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-SPNEGO-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"--Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00reallylongheaderplaintext-" \
                   b"encrypted--Encrypted Boundary\r\n"
        actual = encryption.unwrap_message(bwrapped, "Encrypted Boundary")
        assert expected == actual

    def test_unwrap_small_kerberos(self):
        expected = b"plaintext"
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.KERBEROS)

        # The spaces after -- on each boundary is on purpose, some MS implementations do this.
        bwrapped = b"-- Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-Kerberos-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"-- Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00reallylongheaderplaintext-" \
                   b"encrypted-- Encrypted Boundary--\r\n"
        actual = encryption.unwrap_message(bwrapped, "Encrypted Boundary")
        assert expected == actual

    def test_unwrap_small_credsp(self):
        expected = b"plaintext"
        encryption = WinRMEncryption(MockAuthCREDSSP(), WinRMEncryption.CREDSSP)
        bwrapped = b"--Encrypted Boundary2\r\n\tContent-Type: application" \
                   b"/HTTP-CredSSP-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"--Encrypted Boundary2\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00plaintext-encrypted" \
                   b"--Encrypted Boundary2--\r\n"
        actual = encryption.unwrap_message(bwrapped, "Encrypted Boundary2")

        assert expected == actual

    def test_unwrap_large_spnego(self):
        expected = b"a" * 20000
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.SPNEGO)
        bwrapped = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-SPNEGO-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=20000" \
                   b"\r\n--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/octet-stream\r\n\x10\x00\x00\x00reallylongheader" + expected + \
                   b"-encrypted--Encrypted Boundary--\r\n"
        actual = encryption.unwrap_message(bwrapped, "Encrypted Boundary")

        assert expected == actual

    def test_unwrap_large_kerberos(self):
        expected = b"a" * 20000
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.KERBEROS)
        bwrapped = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-Kerberos-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=20000" \
                   b"\r\n--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/octet-stream\r\n\x10\x00\x00\x00reallylongheader" + expected + \
                   b"-encrypted--Encrypted Boundary--\r\n"
        actual = encryption.unwrap_message(bwrapped, "Encrypted Boundary")

        assert expected == actual

    def test_unwrap_large_credsp(self):
        expected = b"a" * 20000
        encryption = WinRMEncryption(MockAuthCREDSSP(), WinRMEncryption.CREDSSP)
        bwrapped = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-CredSSP-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=16384" \
                   b"\r\n--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/octet-stream\r\n\x10\x00\x00\x00" + b"a" * 16384 + \
                   b"-encrypted--Encrypted Boundary\r\n\tContent-Type: " \
                   b"application/HTTP-CredSSP-session-encrypted\r\n" \
                   b"\tOriginalContent: type=application/soap+xml;" \
                   b"charset=UTF-8;Length=3616\r\n--Encrypted Boundary\r\n" \
                   b"\tContent-Type: application/octet-stream\r\n" \
                   b"\x10\x00\x00\x00" + b"a" * 3616 + \
                   b"-encrypted--Encrypted Boundary--\r\n"
        actual = encryption.unwrap_message(bwrapped, "Encrypted Boundary")

        assert expected == actual

    def test_unwrap_length_mismatch(self):
        encryption = WinRMEncryption(MockAuthSPNEGO(), WinRMEncryption.SPNEGO)
        bwrapped = b"--Encrypted Boundary\r\n\tContent-Type: application" \
                   b"/HTTP-SPNEGO-session-encrypted\r\n\tOriginalContent: " \
                   b"type=application/soap+xml;charset=UTF-8;Length=9\r\n" \
                   b"--Encrypted Boundary\r\n\tContent-Type: application/" \
                   b"octet-stream\r\n\x10\x00\x00\x00reallylongheaderplain-" \
                   b"encrypted--Encrypted Boundary--\r\n"

        with pytest.raises(WinRMError) as err:
            encryption.unwrap_message(bwrapped, "Encrypted Boundary")

        assert str(err.value) == \
            "The encrypted length from the server does not match the " \
            "expected length, decryption failed, actual: 5 != expected: 9"

    @pytest.mark.parametrize('cipher, expected', [
        ['ECDHE-RSA-AES128-GCM-SHA256', 16],
        ['RC4-MD5', 16],
        ['ECDH-ECDSA-3DES-SHA256', 34],
        ['ECDH-RSA-AES-SHA384', 50],
        ['ECDH-RSA-AES', 2],

    ])
    def test_get_credssp_trailer_length(self, cipher, expected):
        encryption = WinRMEncryption(None, WinRMEncryption.CREDSSP)
        actual = encryption._credssp_trailer(30, cipher)

        assert expected == actual