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