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)