• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) 2008 Arnaud Ebalard <arno@natisbad.org>
5#   2015, 2016, 2017 Maxence Tury <maxence.tury@ssi.gouv.fr>
6
7"""
8PKCS #1 methods as defined in RFC 3447.
9
10We cannot rely solely on the cryptography library, because the openssl package
11used by the cryptography library may not implement the md5-sha1 hash, as with
12Ubuntu or OSX. This is why we reluctantly keep some legacy crypto here.
13"""
14
15from scapy.compat import bytes_encode, hex_bytes, bytes_hex
16
17from scapy.config import conf, crypto_validator
18from scapy.error import warning
19if conf.crypto_valid:
20    from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
21    from cryptography.hazmat.backends import default_backend
22    from cryptography.hazmat.primitives import hashes
23    from cryptography.hazmat.primitives.asymmetric import padding
24    from cryptography.hazmat.primitives.hashes import HashAlgorithm
25
26
27#####################################################################
28# Some helpers
29#####################################################################
30
31def pkcs_os2ip(s):
32    """
33    OS2IP conversion function from RFC 3447.
34
35    :param s: octet string to be converted
36    :return: n, the corresponding nonnegative integer
37    """
38    return int(bytes_hex(s), 16)
39
40
41def pkcs_i2osp(n, sLen):
42    """
43    I2OSP conversion function from RFC 3447.
44    The length parameter allows the function to perform the padding needed.
45    Note that the user is responsible for providing a sufficient xLen.
46
47    :param n: nonnegative integer to be converted
48    :param sLen: intended length of the resulting octet string
49    :return: corresponding octet string
50    """
51    # if n >= 256**sLen:
52    #    raise Exception("Integer too large for provided sLen %d" % sLen)
53    fmt = "%%0%dx" % (2 * sLen)
54    return hex_bytes(fmt % n)
55
56
57def pkcs_ilen(n):
58    """
59    This is a log base 256 which determines the minimum octet string
60    length for unequivocal representation of integer n by pkcs_i2osp.
61    """
62    i = 0
63    while n > 0:
64        n >>= 8
65        i += 1
66    return i
67
68
69@crypto_validator
70def _legacy_pkcs1_v1_5_encode_md5_sha1(M, emLen):
71    """
72    Legacy method for PKCS1 v1.5 encoding with MD5-SHA1 hash.
73    """
74    M = bytes_encode(M)
75    md5_hash = hashes.Hash(_get_hash("md5"), backend=default_backend())
76    md5_hash.update(M)
77    sha1_hash = hashes.Hash(_get_hash("sha1"), backend=default_backend())
78    sha1_hash.update(M)
79    H = md5_hash.finalize() + sha1_hash.finalize()
80    if emLen < 36 + 11:
81        warning("pkcs_emsa_pkcs1_v1_5_encode: "
82                "intended encoded message length too short")
83        return None
84    PS = b'\xff' * (emLen - 36 - 3)
85    return b'\x00' + b'\x01' + PS + b'\x00' + H
86
87
88#####################################################################
89# Hash and padding helpers
90#####################################################################
91
92_get_hash = None
93if conf.crypto_valid:
94
95    # first, we add the "md5-sha1" hash from openssl to python-cryptography
96    class MD5_SHA1(HashAlgorithm):
97        name = "md5-sha1"
98        digest_size = 36
99        block_size = 64
100
101    _hashes = {
102        "md5": hashes.MD5,
103        "sha1": hashes.SHA1,
104        "sha224": hashes.SHA224,
105        "sha256": hashes.SHA256,
106        "sha384": hashes.SHA384,
107        "sha512": hashes.SHA512,
108        "md5-sha1": MD5_SHA1
109    }
110
111    def _get_hash(hashStr):
112        try:
113            return _hashes[hashStr]()
114        except KeyError:
115            raise KeyError("Unknown hash function %s" % hashStr)
116
117    def _get_padding(padStr, mgf=padding.MGF1, h=hashes.SHA256, label=None):
118        if padStr == "pkcs":
119            return padding.PKCS1v15()
120        elif padStr == "pss":
121            # Can't find where this is written, but we have to use the digest
122            # size instead of the automatic padding.PSS.MAX_LENGTH.
123            return padding.PSS(mgf=mgf(h), salt_length=h.digest_size)
124        elif padStr == "oaep":
125            return padding.OAEP(mgf=mgf(h), algorithm=h, label=label)
126        else:
127            warning("Key.encrypt(): Unknown padding type (%s)", padStr)
128            return None
129
130
131#####################################################################
132# Asymmetric Cryptography wrappers
133#####################################################################
134
135# Make sure that default values are consistent across the whole TLS module,
136# lest they be explicitly set to None between cert.py and pkcs1.py.
137
138class _EncryptAndVerifyRSA(object):
139
140    @crypto_validator
141    def encrypt(self, m, t="pkcs", h="sha256", mgf=None, L=None):
142        mgf = mgf or padding.MGF1
143        h = _get_hash(h)
144        pad = _get_padding(t, mgf, h, L)
145        return self.pubkey.encrypt(m, pad)
146
147    @crypto_validator
148    def verify(self, M, S, t="pkcs", h="sha256", mgf=None, L=None):
149        M = bytes_encode(M)
150        mgf = mgf or padding.MGF1
151        h = _get_hash(h)
152        pad = _get_padding(t, mgf, h, L)
153        try:
154            try:
155                self.pubkey.verify(S, M, pad, h)
156            except UnsupportedAlgorithm:
157                if t != "pkcs" and h != "md5-sha1":
158                    raise UnsupportedAlgorithm("RSA verification with %s" % h)
159                self._legacy_verify_md5_sha1(M, S)
160            return True
161        except InvalidSignature:
162            return False
163
164    def _legacy_verify_md5_sha1(self, M, S):
165        k = self._modulusLen // 8
166        if len(S) != k:
167            warning("invalid signature (len(S) != k)")
168            return False
169        s = pkcs_os2ip(S)
170        n = self._modulus
171        if s > n - 1:
172            warning("Key._rsaep() expects a long between 0 and n-1")
173            return None
174        m = pow(s, self._pubExp, n)
175        EM = pkcs_i2osp(m, k)
176        EMPrime = _legacy_pkcs1_v1_5_encode_md5_sha1(M, k)
177        if EMPrime is None:
178            warning("Key._rsassa_pkcs1_v1_5_verify(): unable to encode.")
179            return False
180        return EM == EMPrime
181
182
183class _DecryptAndSignRSA(object):
184
185    @crypto_validator
186    def decrypt(self, C, t="pkcs", h="sha256", mgf=None, L=None):
187        mgf = mgf or padding.MGF1
188        h = _get_hash(h)
189        pad = _get_padding(t, mgf, h, L)
190        return self.key.decrypt(C, pad)
191
192    @crypto_validator
193    def sign(self, M, t="pkcs", h="sha256", mgf=None, L=None):
194        M = bytes_encode(M)
195        mgf = mgf or padding.MGF1
196        h = _get_hash(h)
197        pad = _get_padding(t, mgf, h, L)
198        try:
199            return self.key.sign(M, pad, h)
200        except UnsupportedAlgorithm:
201            if t != "pkcs" and h != "md5-sha1":
202                raise UnsupportedAlgorithm("RSA signature with %s" % h)
203            return self._legacy_sign_md5_sha1(M)
204
205    def _legacy_sign_md5_sha1(self, M):
206        M = bytes_encode(M)
207        k = self._modulusLen // 8
208        EM = _legacy_pkcs1_v1_5_encode_md5_sha1(M, k)
209        if EM is None:
210            warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode")
211            return None
212        m = pkcs_os2ip(EM)
213        n = self._modulus
214        if m > n - 1:
215            warning("Key._rsaep() expects a long between 0 and n-1")
216            return None
217        privExp = self.key.private_numbers().d
218        s = pow(m, privExp, n)
219        return pkcs_i2osp(s, k)
220