source: trunk/src/allmydata/crypto/rsa.py

Last change on this file was 1a807a0, checked in by Jean-Paul Calderone <exarkun@…>, at 2023-01-12T21:32:32Z

mollify the type checker

  • Property mode set to 100644
File size: 7.0 KB
Line 
1"""
2Helper functions for cryptography-related operations inside Tahoe
3using RSA public-key encryption and decryption.
4
5In cases where these functions happen to use and return objects that
6are documented in the `cryptography` library, code outside this module
7should only use functions from allmydata.crypto.rsa and not rely on
8features of any objects that `cryptography` documents.
9
10That is, the public and private keys are opaque objects; DO NOT depend
11on any of their methods.
12"""
13
14from __future__ import annotations
15
16from typing_extensions import TypeAlias
17from typing import Callable
18
19from functools import partial
20
21from cryptography.exceptions import InvalidSignature
22from cryptography.hazmat.backends import default_backend
23from cryptography.hazmat.primitives import hashes
24from cryptography.hazmat.primitives.asymmetric import rsa, padding
25from cryptography.hazmat.primitives.serialization import load_der_private_key, load_der_public_key, \
26    Encoding, PrivateFormat, PublicFormat, NoEncryption
27
28from allmydata.crypto.error import BadSignature
29
30PublicKey: TypeAlias = rsa.RSAPublicKey
31PrivateKey: TypeAlias = rsa.RSAPrivateKey
32
33# This is the value that was used by `pycryptopp`, and we must continue to use it for
34# both backwards compatibility and interoperability.
35#
36# The docs for `cryptography` suggest to use the constant defined at
37# `cryptography.hazmat.primitives.asymmetric.padding.PSS.MAX_LENGTH`, but this causes old
38# signatures to fail to validate.
39RSA_PSS_SALT_LENGTH = 32
40
41RSA_PADDING = padding.PSS(
42    mgf=padding.MGF1(hashes.SHA256()),
43    salt_length=RSA_PSS_SALT_LENGTH,
44)
45
46
47
48def create_signing_keypair(key_size: int) -> tuple[PrivateKey, PublicKey]:
49    """
50    Create a new RSA signing (private) keypair from scratch. Can be used with
51    `sign_data` function.
52
53    :param key_size: length of key in bits
54
55    :returns: 2-tuple of (private_key, public_key)
56    """
57    priv_key = rsa.generate_private_key(
58        public_exponent=65537,
59        key_size=key_size,
60        backend=default_backend()
61    )
62    return priv_key, priv_key.public_key()
63
64
65def create_signing_keypair_from_string(private_key_der: bytes) -> tuple[PrivateKey, PublicKey]:
66    """
67    Create an RSA signing (private) key from previously serialized
68    private key bytes.
69
70    :param private_key_der: blob as returned from `der_string_from_signing_keypair`
71
72    :returns: 2-tuple of (private_key, public_key)
73    """
74    _load = partial(
75        load_der_private_key,
76        private_key_der,
77        password=None,
78        backend=default_backend(),
79    )
80
81    def load_with_validation() -> PrivateKey:
82        k = _load()
83        assert isinstance(k, PrivateKey)
84        return k
85
86    def load_without_validation() -> PrivateKey:
87        k = _load(unsafe_skip_rsa_key_validation=True)
88        assert isinstance(k, PrivateKey)
89        return k
90
91    # Load it once without the potentially expensive OpenSSL validation
92    # checks.  These have superlinear complexity.  We *will* run them just
93    # below - but first we'll apply our own constant-time checks.
94    load: Callable[[], PrivateKey] = load_without_validation
95    try:
96        unsafe_priv_key = load()
97    except TypeError:
98        # cryptography<39 does not support this parameter, so just load the
99        # key with validation...
100        unsafe_priv_key = load_with_validation()
101        # But avoid *reloading* it since that will run the expensive
102        # validation *again*.
103        load = lambda: unsafe_priv_key
104
105    if not isinstance(unsafe_priv_key, rsa.RSAPrivateKey):
106        raise ValueError(
107            "Private Key did not decode to an RSA key"
108        )
109    if unsafe_priv_key.key_size != 2048:
110        raise ValueError(
111            "Private Key must be 2048 bits"
112        )
113
114    # Now re-load it with OpenSSL's validation applied.
115    safe_priv_key = load()
116
117    return safe_priv_key, safe_priv_key.public_key()
118
119
120def der_string_from_signing_key(private_key: PrivateKey) -> bytes:
121    """
122    Serializes a given RSA private key to a DER string
123
124    :param private_key: a private key object as returned from
125        `create_signing_keypair` or `create_signing_keypair_from_string`
126
127    :returns: bytes representing `private_key`
128    """
129    _validate_private_key(private_key)
130    return private_key.private_bytes( # type: ignore[attr-defined]
131        encoding=Encoding.DER,
132        format=PrivateFormat.PKCS8,
133        encryption_algorithm=NoEncryption(),
134    )
135
136
137def der_string_from_verifying_key(public_key: PublicKey) -> bytes:
138    """
139    Serializes a given RSA public key to a DER string.
140
141    :param public_key: a public key object as returned from
142        `create_signing_keypair` or `create_signing_keypair_from_string`
143
144    :returns: bytes representing `public_key`
145    """
146    _validate_public_key(public_key)
147    return public_key.public_bytes(
148        encoding=Encoding.DER,
149        format=PublicFormat.SubjectPublicKeyInfo,
150    )
151
152
153def create_verifying_key_from_string(public_key_der: bytes) -> PublicKey:
154    """
155    Create an RSA verifying key from a previously serialized public key
156
157    :param bytes public_key_der: a blob as returned by `der_string_from_verifying_key`
158
159    :returns: a public key object suitable for use with other
160        functions in this module
161    """
162    pub_key = load_der_public_key(
163        public_key_der,
164        backend=default_backend(),
165    )
166    assert isinstance(pub_key, PublicKey)
167    return pub_key
168
169
170def sign_data(private_key: PrivateKey, data: bytes) -> bytes:
171    """
172    :param private_key: the private part of a keypair returned from
173        `create_signing_keypair_from_string` or `create_signing_keypair`
174
175    :param data: the bytes to sign
176
177    :returns: bytes which are a signature of the bytes given as `data`.
178    """
179    _validate_private_key(private_key)
180    return private_key.sign(
181        data,
182        RSA_PADDING,
183        hashes.SHA256(),
184    )
185
186def verify_signature(public_key: PublicKey, alleged_signature: bytes, data: bytes) -> None:
187    """
188    :param public_key: a verifying key, returned from `create_verifying_key_from_string` or `create_verifying_key_from_private_key`
189
190    :param bytes alleged_signature: the bytes of the alleged signature
191
192    :param bytes data: the data which was allegedly signed
193    """
194    _validate_public_key(public_key)
195    try:
196        public_key.verify(
197            alleged_signature,
198            data,
199            RSA_PADDING,
200            hashes.SHA256(),
201        )
202    except InvalidSignature:
203        raise BadSignature()
204
205
206def _validate_public_key(public_key: PublicKey) -> None:
207    """
208    Internal helper. Checks that `public_key` is a valid cryptography
209    object
210    """
211    if not isinstance(public_key, rsa.RSAPublicKey):
212        raise ValueError(
213            f"public_key must be an RSAPublicKey not {type(public_key)}"
214        )
215
216
217def _validate_private_key(private_key: PrivateKey) -> None:
218    """
219    Internal helper. Checks that `public_key` is a valid cryptography
220    object
221    """
222    if not isinstance(private_key, rsa.RSAPrivateKey):
223        raise ValueError(
224            f"private_key must be an RSAPrivateKey not {type(private_key)}"
225        )
Note: See TracBrowser for help on using the repository browser.