diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml
index 75d4d4696a50..f577fbd73de3 100644
--- a/.github/actions/cache/action.yml
+++ b/.github/actions/cache/action.yml
@@ -15,7 +15,7 @@ runs:
       id: normalized-key
       run: echo "key=$(echo "${{ inputs.key }}" | tr -d ',')" >> $GITHUB_OUTPUT
       shell: bash
-    - uses: Swatinem/rust-cache@578b235f6e5f613f7727f1c17bd3305b4d4d4e1f  # v2.6.1
+    - uses: Swatinem/rust-cache@e207df5d269b42b69c8bc5101da26f7d31feddb4  # v2.6.2
       with:
         key: ${{ steps.normalized-key.outputs.key }}
         workspaces: "./src/rust/ -> target"
diff --git a/.github/actions/wycheproof/action.yml b/.github/actions/wycheproof/action.yml
index 0c0a9d329a06..7d2718871921 100644
--- a/.github/actions/wycheproof/action.yml
+++ b/.github/actions/wycheproof/action.yml
@@ -5,7 +5,7 @@ runs:
   using: "composite"
 
   steps:
-    - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+    - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
       with:
         repository: "google/wycheproof"
         path: "wycheproof"
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 45e0817cd3ce..8a3b8d517b14 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,28 +4,38 @@ updates:
     directory: "/"
     schedule:
       interval: "daily"
+      time: "06:00"
+      timezone: "America/New_York"
     open-pull-requests-limit: 1024
 
   - package-ecosystem: "github-actions"
     directory: "/.github/actions/cache/"
     schedule:
       interval: "daily"
+      time: "06:00"
+      timezone: "America/New_York"
     open-pull-requests-limit: 1024
   - package-ecosystem: "github-actions"
     directory: "/.github/actions/upload-coverage/"
     schedule:
       interval: "daily"
+      time: "06:00"
+      timezone: "America/New_York"
     open-pull-requests-limit: 1024
   - package-ecosystem: "github-actions"
     directory: "/.github/actions/wycheproof/"
     schedule:
       interval: "daily"
+      time: "06:00"
+      timezone: "America/New_York"
     open-pull-requests-limit: 1024
 
   - package-ecosystem: cargo
     directory: "/src/rust/"
     schedule:
       interval: daily
+      time: "06:00"
+      timezone: "America/New_York"
     allow:
       # Also update indirect dependencies
       - dependency-type: all
@@ -35,6 +45,8 @@ updates:
     directory: "/"
     schedule:
       interval: daily
+      time: "06:00"
+      timezone: "America/New_York"
     allow:
       # Also update indirect dependencies
       - dependency-type: all
@@ -44,6 +56,8 @@ updates:
     directory: "/.github/requirements/"
     schedule:
       interval: daily
+      time: "06:00"
+      timezone: "America/New_York"
     allow:
       # Also update indirect dependencies
       - dependency-type: all
diff --git a/.github/requirements/build-requirements.txt b/.github/requirements/build-requirements.txt
index 7bd5ab8d59fc..971a6f9807df 100644
--- a/.github/requirements/build-requirements.txt
+++ b/.github/requirements/build-requirements.txt
@@ -78,23 +78,27 @@ semantic-version==2.10.0 \
     --hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \
     --hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177
     # via setuptools-rust
-setuptools-rust==1.6.0 \
-    --hash=sha256:c86e734deac330597998bfbc08da45187e6b27837e23bd91eadb320732392262 \
-    --hash=sha256:e28ae09fb7167c44ab34434eb49279307d611547cb56cb9789955cdb54a1aed9
-    # via -r build-requirements.in
+setuptools-rust==1.7.0 \
+    --hash=sha256:071099885949132a2180d16abf907b60837e74b4085047ba7e9c0f5b365310c1 \
+    --hash=sha256:c7100999948235a38ae7e555fe199aa66c253dc384b125f5d85473bf81eae3a3
+     # via -r build-requirements.in
+tomli==2.0.1 \
+    --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+    --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+    # via setuptools-rust
 typing-extensions==4.7.1 \
     --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \
     --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2
     # via setuptools-rust
-wheel==0.41.1 \
-    --hash=sha256:12b911f083e876e10c595779709f8a88a59f45aacc646492a67fe9ef796c1b47 \
-    --hash=sha256:473219bd4cbedc62cea0cb309089b593e47c15c4a2531015f94e4e3b9a0f6981
+wheel==0.41.2 \
+    --hash=sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985 \
+    --hash=sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8
     # via -r build-requirements.in
 
 # The following packages are considered to be unsafe in a requirements file:
-setuptools==68.1.0 \
-    --hash=sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91 \
-    --hash=sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715
+setuptools==68.1.2 \
+    --hash=sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d \
+    --hash=sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b
     # via
     #   -r build-requirements.in
     #   setuptools-rust
diff --git a/.github/requirements/publish-requirements.txt b/.github/requirements/publish-requirements.txt
index 7039d9fbd353..7a75e689ced9 100644
--- a/.github/requirements/publish-requirements.txt
+++ b/.github/requirements/publish-requirements.txt
@@ -210,9 +210,9 @@ hyperframe==6.0.1 \
     --hash=sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15 \
     --hash=sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914
     # via h2
-id==1.0.0 \
-    --hash=sha256:8822ba0454bb8660c4fff439eadbf06236cc354dcabd7ae00d907143d92215f5 \
-    --hash=sha256:d4b3e75ce0d5f38c9e467826436babe8b9bc5f78e22bae716a22a6a0add570ea
+id==1.1.0 \
+    --hash=sha256:726b995ffea6954ecbe3f2bb9e9d52b8502b2683b8470b13c58a429cd8e701e8 \
+    --hash=sha256:a15f919fa1e847f57572748d37cf40192913a861a2669059b4cb5079bbbbbdbd
     # via sigstore
 idna==3.4 \
     --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
@@ -392,9 +392,9 @@ python-dateutil==2.8.2 \
     --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
     --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
     # via betterproto
-readme-renderer==40.0 \
-    --hash=sha256:9f77b519d96d03d7d7dce44977ba543090a14397c4f60de5b6eb5b8048110aa4 \
-    --hash=sha256:e18feb2a1e7706f2865b81ebb460056d93fb29d69daa10b223c00faa7bd9a00a
+readme-renderer==41.0 \
+    --hash=sha256:4f4b11e5893f5a5d725f592c5a343e0dc74f5f273cb3dcf8c42d9703a27073f7 \
+    --hash=sha256:a38243d5b6741b700a850026e62da4bd739edc7422071e95fd5c4bb60171df86
     # via twine
 requests==2.31.0 \
     --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index e943b6b00cb8..5eb8a12b7beb 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -21,12 +21,12 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 15
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           persist-credentials: false
           path: "cryptography-pr"
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           repository: "pyca/cryptography"
diff --git a/.github/workflows/boring-open-version-bump.yml b/.github/workflows/boring-open-version-bump.yml
index fccc8a150753..3765894b7182 100644
--- a/.github/workflows/boring-open-version-bump.yml
+++ b/.github/workflows/boring-open-version-bump.yml
@@ -13,7 +13,7 @@ jobs:
     if: github.repository_owner == 'pyca'
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
       - id: check-sha-boring
         run: |
           SHA=$(git ls-remote https://boringssl.googlesource.com/boringssl refs/heads/master | cut -f1)
@@ -51,7 +51,7 @@ jobs:
           sed -E -i "s/TYPE: \"openssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"openssl\", VERSION: \"${{ steps.check-sha-openssl.outputs.COMMIT_SHA }}\"/" .github/workflows/ci.yml
           git status
         if: steps.check-sha-openssl.outputs.COMMIT_SHA
-      - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 # v1.8.0
+      - uses: tibdex/github-app-token@0d49dd721133f900ebd5e0dff2810704e8defbc6 # v1.8.2
         id: generate-token
         with:
           app_id: ${{ secrets.BORINGBOT_APP_ID }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4f8e38cafa75..2510715bcff2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,13 +40,13 @@ jobs:
           - {VERSION: "3.11", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.1.2"}}
           - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.6.3"}}
           - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.7.3"}}
-          - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.8.0"}}
+          - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.8.1"}}
           - {VERSION: "3.11", NOXSESSION: "tests-randomorder"}
           - {VERSION: "3.12-dev", NOXSESSION: "tests"}
-          # Latest commit on the BoringSSL master branch, as of Aug 17, 2023.
-          - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "9f4cad2208b703350fe11d9469125dad55c34d30"}}
-          # Latest commit on the OpenSSL master branch, as of Aug 17, 2023.
-          - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "39ed7636e0d8a90512e7ccb811cd0bfcb7a79650"}}
+          # Latest commit on the BoringSSL master branch, as of Sep 05, 2023.
+          - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "fa343af32b77f5f005a651656732ae3f0b526774"}}
+          # Latest commit on the OpenSSL master branch, as of Sep 06, 2023.
+          - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "c1673a60e40f6dcd110d1a4ff3e11a3297ada2da"}}
           # Builds with various Rust versions. Includes MSRV and next
           # potential future MSRV:
           # 1.64 - maturin
@@ -57,7 +57,7 @@ jobs:
           - {VERSION: "3.11", NOXSESSION: "rust,tests", RUST: "nightly"}
     timeout-minutes: 15
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           persist-credentials: false
@@ -178,7 +178,7 @@ jobs:
           sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release
         if: matrix.IMAGE.IMAGE == 'alpine:aarch64'
 
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           persist-credentials: false
@@ -229,7 +229,7 @@ jobs:
             RUNNER: {OS: [self-hosted, macos, ARM64, tart], ARCH: 'arm64'}
     timeout-minutes: 15
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           persist-credentials: false
@@ -293,7 +293,7 @@ jobs:
           - {VERSION: "3.11", NOXSESSION: "tests"}
     timeout-minutes: 15
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           persist-credentials: false
@@ -366,7 +366,7 @@ jobs:
     name: "Downstream tests for ${{ matrix.DOWNSTREAM }}"
     timeout-minutes: 15
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           persist-credentials: false
@@ -409,7 +409,7 @@ jobs:
     if: ${{ always() }}
     timeout-minutes: 3
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         timeout-minutes: 3
         with:
           persist-credentials: false
diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml
index 2d959ccd9e87..13f89bbc1f9b 100644
--- a/.github/workflows/linkcheck.yml
+++ b/.github/workflows/linkcheck.yml
@@ -20,7 +20,7 @@ jobs:
     name: "linkcheck"
     timeout-minutes: 10
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           persist-credentials: false
       - name: Setup python
diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml
index 6ae41538c2dd..af2578af6ce4 100644
--- a/.github/workflows/pypi-publish.yml
+++ b/.github/workflows/pypi-publish.yml
@@ -32,7 +32,7 @@ jobs:
         with:
           python-version: "3.11"
       - name: Get publish-requirements.txt from repository
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           sparse-checkout: |
             ${{ env.PUBLISH_REQUIREMENTS_PATH }}
diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml
index 989f428adfcb..439b80f461e9 100644
--- a/.github/workflows/wheel-builder.yml
+++ b/.github/workflows/wheel-builder.yml
@@ -16,7 +16,6 @@ on:
     paths:
       - .github/workflows/wheel-builder.yml
       - .github/requirements/**
-      - setup.py
       - pyproject.toml
       - vectors/pyproject.toml
 
@@ -28,7 +27,7 @@ jobs:
     runs-on: ubuntu-latest
     name: sdists
     steps:
-      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           # The tag to build or the tag received by the tag event
           ref: ${{ github.event.inputs.version || github.ref }}
@@ -112,7 +111,7 @@ jobs:
         if: startsWith(matrix.MANYLINUX.NAME, 'musllinux') && endsWith(matrix.MANYLINUX.NAME, 'aarch64')
 
       - name: Get build-requirements.txt from repository
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           # The tag to build or the tag received by the tag event
           ref: ${{ github.event.inputs.version || github.ref }}
@@ -200,7 +199,7 @@ jobs:
     name: "${{ matrix.PYTHON.VERSION }} ABI ${{ matrix.PYTHON.ABI_VERSION }} macOS ${{ matrix.PYTHON.ARCHFLAGS }}"
     steps:
       - name: Get build-requirements.txt from repository
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           # The tag to build or the tag received by the tag event
           ref: ${{ github.event.inputs.version || github.ref }}
@@ -293,7 +292,7 @@ jobs:
     name: "${{ matrix.PYTHON.VERSION }} ${{ matrix.WINDOWS.WINDOWS }} ${{ matrix.PYTHON.ABI_VERSION }}"
     steps:
       - name: Get build-requirements.txt from repository
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           # The tag to build or the tag received by the tag event
           ref: ${{ github.event.inputs.version || github.ref }}
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 95b3c4f46e7c..40d9cc7ae84f 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -7,6 +7,9 @@ sphinx:
   # https://github.com/pyca/cryptography/issues/5863#issuecomment-817828152
   builder: dirhtml
 
+formats:
+  - pdf
+
 build:
   # readdocs master now includes a rust toolchain
   os: "ubuntu-22.04"
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index f602278eca00..8a39465f2fee 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,13 @@ Changelog
 * Parsing SSH certificates no longer permits malformed critical options with
   values, as documented in the 41.0.2 release notes.
 * Updated the minimum supported Rust version (MSRV) to 1.63.0, from 1.56.0.
+* Support :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` for
+  X.509 certificate signing requests with the keyword-only argument
+  ``rsa_padding`` on
+  :meth:`~cryptography.x509.CertificateSigningRequestBuilder.sign`.
+* Added support for obtaining X.509 certificate signing request signature
+  algorithm parameters (including PSS) via
+  :meth:`~cryptography.x509.CertificateSigningRequest.signature_algorithm_parameters`.
 
 .. _v41-0-3:
 
diff --git a/ci-constraints-requirements.txt b/ci-constraints-requirements.txt
index bc34432e60d7..e47092a758f5 100644
--- a/ci-constraints-requirements.txt
+++ b/ci-constraints-requirements.txt
@@ -15,7 +15,7 @@ black==23.7.0
     # via cryptography (pyproject.toml)
 bleach==6.0.0
     # via readme-renderer
-build==0.10.0
+build==1.0.0
     # via
     #   check-sdist
     #   cryptography (pyproject.toml)
@@ -42,7 +42,7 @@ exceptiongroup==1.1.3
     # via pytest
 execnet==2.0.2
     # via pytest-xdist
-filelock==3.12.2
+filelock==3.12.3; python_version >= "3.8"
     # via virtualenv
 idna==3.4
     # via requests
@@ -93,7 +93,7 @@ platformdirs==3.10.0
     # via
     #   black
     #   virtualenv
-pluggy==1.2.0
+pluggy==1.3.0; python_version >= "3.8"
     # via pytest
 pretend==1.0.9
     # via cryptography (pyproject.toml)
@@ -110,7 +110,7 @@ pygments==2.16.1
     #   sphinx
 pyproject-hooks==1.0.0
     # via build
-pytest==7.4.0
+pytest==7.4.1
     # via
     #   cryptography (pyproject.toml)
     #   pytest-benchmark
@@ -125,7 +125,7 @@ pytest-randomly==3.15.0
     # via cryptography (pyproject.toml)
 pytest-xdist==3.3.1
     # via cryptography (pyproject.toml)
-readme-renderer==40.0
+readme-renderer==41.0
     # via twine
 requests==2.31.0
     # via
@@ -138,19 +138,19 @@ rfc3986==2.0.0
     # via twine
 rich==13.5.2
     # via twine
-ruff==0.0.284
+ruff==0.0.287
     # via cryptography (pyproject.toml)
 six==1.16.0
     # via bleach
 snowballstemmer==2.2.0
     # via sphinx
-sphinx==6.2.1
+sphinx==7.2.5
     # via
     #   cryptography (pyproject.toml)
     #   sphinx-rtd-theme
     #   sphinxcontrib-jquery
     #   sphinxcontrib-spelling
-sphinx-rtd-theme==1.2.2
+sphinx-rtd-theme==1.3.0
     # via cryptography (pyproject.toml)
 sphinxcontrib-applehelp==1.0.7
     # via sphinx
@@ -164,7 +164,7 @@ sphinxcontrib-jsmath==1.0.1
     # via sphinx
 sphinxcontrib-qthelp==1.0.6
     # via sphinx
-sphinxcontrib-serializinghtml==1.1.8
+sphinxcontrib-serializinghtml==1.1.9
     # via sphinx
 sphinxcontrib-spelling==8.0.0
     # via cryptography (pyproject.toml)
@@ -185,7 +185,7 @@ urllib3==2.0.4
     # via
     #   requests
     #   twine
-virtualenv==20.24.3
+virtualenv==20.24.4
     # via nox
 webencodings==0.5.1
     # via bleach
diff --git a/docs/faq.rst b/docs/faq.rst
index ac7f4152c731..f66cfba867d0 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -97,17 +97,6 @@ as secure as possible while retaining the advantages of OpenSSL, so we've
 chosen to rewrite non-cryptographic operations (such as ASN.1 parsing) in a
 high performance memory safe language: Rust.
 
-Installing ``cryptography`` produces a ``fatal error: 'openssl/opensslv.h' file not found`` error
--------------------------------------------------------------------------------------------------
-
-``cryptography`` provides wheels which include a statically linked copy of
-OpenSSL. If you see this error it is likely because your copy of ``pip`` is too
-old to find our wheel files. Upgrade your ``pip`` with ``pip install -U pip``
-and then try to install ``cryptography`` again.
-
-Users on unusual CPU architectures will need to compile ``cryptography``
-themselves. Please view our :doc:`/installation` documentation.
-
 ``cryptography`` raised an ``InternalError`` and I'm not sure what to do?
 -------------------------------------------------------------------------
 
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 87ebe62f2669..3b014def579a 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -970,6 +970,27 @@ X.509 CSR (Certificate Signing Request) Object
             >>> csr.signature_algorithm_oid
             <ObjectIdentifier(oid=1.2.840.113549.1.1.11, name=sha256WithRSAEncryption)>
 
+    .. attribute:: signature_algorithm_parameters
+
+        .. versionadded:: 42.0.0
+
+        Returns the parameters of the signature algorithm used to sign the
+        certificate signing request. For RSA signatures it will return either a
+        :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or
+        :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` object.
+
+        For ECDSA signatures it will
+        return an :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA`.
+
+        For EdDSA and DSA signatures it will return ``None``.
+
+        These objects can be used to verify signatures on the signing request.
+
+        :returns: None,
+            :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`,
+            :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`, or
+            :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA`
+
     .. attribute:: extensions
 
         :type: :class:`Extensions`
@@ -1288,7 +1309,7 @@ X.509 CSR (Certificate Signing Request) Builder Object
         :returns: A new
             :class:`~cryptography.x509.CertificateSigningRequestBuilder`.
 
-    .. method:: sign(private_key, algorithm)
+    .. method:: sign(private_key, algorithm, *, rsa_padding=None)
 
         :param private_key: The private key
             that will be used to sign the request.  When the request is
@@ -1307,6 +1328,22 @@ X.509 CSR (Certificate Signing Request) Builder Object
             :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
             otherwise.
 
+        :param rsa_padding:
+
+            .. versionadded:: 42.0.0
+
+            This is a keyword-only argument. If ``private_key`` is an
+            ``RSAPrivateKey`` then this can be set to either
+            :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or
+            :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign
+            with those respective paddings. If this is ``None`` then RSA
+            keys will default to ``PKCS1v15`` padding. All other key types **must**
+            not pass a value other than ``None``.
+
+        :type rsa_padding: ``None``,
+            :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`,
+            or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`
+
         :returns: A new
             :class:`~cryptography.x509.CertificateSigningRequest`.
 
diff --git a/pyproject.toml b/pyproject.toml
index 21d17b508557..b8ba5f5e7d0d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ requires = [
     "wheel",
     # Must be kept in sync with `project.dependencies`
     "cffi>=1.12; platform_python_implementation != 'PyPy'",
-    "setuptools-rust>=0.11.4",
+    "setuptools-rust>=1.7.0",
 ]
 build-backend = "setuptools.build_meta"
 
@@ -38,6 +38,7 @@ classifiers = [
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: Implementation :: CPython",
     "Programming Language :: Python :: Implementation :: PyPy",
     "Topic :: Security :: Cryptography",
@@ -81,6 +82,13 @@ docstest =  ["pyenchant >=1.6.11", "twine >=1.12.0", "sphinxcontrib-spelling >=4
 sdist = ["build"]
 pep8test = ["black", "ruff", "mypy", "check-sdist"]
 
+[[tool.setuptools-rust.ext-modules]]
+target = "cryptography.hazmat.bindings._rust"
+path = "src/rust/Cargo.toml"
+py-limited-api = true
+rust-version = ">=1.63.0"
+
+
 [tool.black]
 line-length = 79
 target-version = ["py37"]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 60b7b713ba7b..000000000000
--- a/setup.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/env python
-
-# This file is dual licensed under the terms of the Apache License, Version
-# 2.0, and the BSD License. See the LICENSE file in the root of this repository
-# for complete details.
-
-import os
-import platform
-import re
-import shutil
-import subprocess
-import sys
-import warnings
-
-from setuptools import setup
-
-try:
-    from setuptools_rust import RustExtension
-except ImportError:
-    print(
-        """
-        =============================DEBUG ASSISTANCE==========================
-        If you are seeing an error here please try the following to
-        successfully install cryptography:
-
-        Upgrade to the latest pip and try again. This will fix errors for most
-        users. See: https://pip.pypa.io/en/stable/installing/#upgrading-pip
-        =============================DEBUG ASSISTANCE==========================
-        """
-    )
-    raise
-
-
-# distutils emits this warning if you pass `setup()` an unknown option. This
-# is what happens if you somehow run this file without `cffi` installed:
-# `cffi_modules` is an unknown option.
-warnings.filterwarnings("error", message="Unknown distribution option")
-
-base_dir = os.path.dirname(__file__)
-src_dir = os.path.join(base_dir, "src")
-
-# When executing the setup.py, we need to be able to import ourselves, this
-# means that we need to add the src/ directory to the sys.path.
-sys.path.insert(0, src_dir)
-
-if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (7, 3, 10):
-    raise RuntimeError("cryptography is not compatible with PyPy3 < 7.3.10")
-
-try:
-    # See pyproject.toml for most of the config metadata.
-    setup(
-        rust_extensions=[
-            RustExtension(
-                "cryptography.hazmat.bindings._rust",
-                "src/rust/Cargo.toml",
-                py_limited_api=True,
-                rust_version=">=1.63.0",
-            )
-        ],
-    )
-except:
-    # Note: This is a bare exception that re-raises so that we don't interfere
-    # with anything the installation machinery might want to do. Because we
-    # print this for any exception this msg can appear (e.g. in verbose logs)
-    # even if there's no failure. For example, SetupRequirementsError is raised
-    # during PEP517 building and prints this text. setuptools raises SystemExit
-    # when compilation fails right now, but it's possible this isn't stable
-    # or a public API commitment so we'll remain ultra conservative.
-
-    import pkg_resources
-
-    print(
-        """
-    =============================DEBUG ASSISTANCE=============================
-    If you are seeing a compilation error please try the following steps to
-    successfully install cryptography:
-    1) Upgrade to the latest pip and try again. This will fix errors for most
-       users. See: https://pip.pypa.io/en/stable/installing/#upgrading-pip
-    2) Read https://cryptography.io/en/latest/installation/ for specific
-       instructions for your platform.
-    3) Check our frequently asked questions for more information:
-       https://cryptography.io/en/latest/faq/
-    4) Ensure you have a recent Rust toolchain installed:
-       https://cryptography.io/en/latest/installation/#rust
-    """
-    )
-    print(f"    Python: {'.'.join(str(v) for v in sys.version_info[:3])}")
-    print(f"    platform: {platform.platform()}")
-    for dist in ["pip", "setuptools", "setuptools_rust"]:
-        try:
-            version = pkg_resources.get_distribution(dist).version
-        except pkg_resources.DistributionNotFound:
-            version = "n/a"
-        print(f"    {dist}: {version}")
-    version = "n/a"
-    if shutil.which("rustc") is not None:
-        try:
-            # If for any reason `rustc --version` fails, silently ignore it
-            rustc_output = subprocess.run(
-                ["rustc", "--version"],
-                capture_output=True,
-                timeout=0.5,
-                encoding="utf8",
-                check=True,
-            ).stdout
-            version = re.sub("^rustc ", "", rustc_output.strip())
-        except subprocess.SubprocessError:
-            pass
-    print(f"    rustc: {version}")
-
-    print(
-        """\
-    =============================DEBUG ASSISTANCE=============================
-    """
-    )
-    raise
diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py
index 609313ec57ae..9629a2c8f929 100644
--- a/src/_cffi_src/openssl/engine.py
+++ b/src/_cffi_src/openssl/engine.py
@@ -42,18 +42,20 @@
 typedef void UI_METHOD;
 #endif
 
-/* Despite being OPENSSL_NO_ENGINE, BoringSSL defines these symbols. */
-#if !CRYPTOGRAPHY_IS_BORINGSSL
+/* Despite being OPENSSL_NO_ENGINE, BoringSSL/LibreSSL define these symbols. */
+#if !CRYPTOGRAPHY_IS_BORINGSSL && !CRYPTOGRAPHY_IS_LIBRESSL
 int (*ENGINE_free)(ENGINE *) = NULL;
 void (*ENGINE_load_builtin_engines)(void) = NULL;
 #endif
 
-ENGINE *(*ENGINE_by_id)(const char *) = NULL;
-int (*ENGINE_init)(ENGINE *) = NULL;
-int (*ENGINE_finish)(ENGINE *) = NULL;
 ENGINE *(*ENGINE_get_default_RAND)(void) = NULL;
 int (*ENGINE_set_default_RAND)(ENGINE *) = NULL;
 void (*ENGINE_unregister_RAND)(ENGINE *) = NULL;
+
+#if !CRYPTOGRAPHY_IS_LIBRESSL
+ENGINE *(*ENGINE_by_id)(const char *) = NULL;
+int (*ENGINE_init)(ENGINE *) = NULL;
+int (*ENGINE_finish)(ENGINE *) = NULL;
 int (*ENGINE_ctrl_cmd)(ENGINE *, const char *, long, void *,
                        void (*)(void), int) = NULL;
 
@@ -66,6 +68,7 @@
                                      void *) = NULL;
 EVP_PKEY *(*ENGINE_load_public_key)(ENGINE *, const char *,
                                     UI_METHOD *, void *) = NULL;
+#endif
 
 #else
 static const long Cryptography_HAS_ENGINE = 1;
diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py
index 73221219b83e..7e7b2b8bd91b 100644
--- a/src/_cffi_src/openssl/ssl.py
+++ b/src/_cffi_src/openssl/ssl.py
@@ -108,6 +108,7 @@
 static const long SSL_CB_HANDSHAKE_DONE;
 static const long SSL_MODE_RELEASE_BUFFERS;
 static const long SSL_MODE_ENABLE_PARTIAL_WRITE;
+static const long SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER;
 static const long SSL_MODE_AUTO_RETRY;
 static const long TLS_ST_BEFORE;
 static const long TLS_ST_OK;
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 900481e4c07c..3797d1df83e3 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -14,10 +14,6 @@
 from cryptography.hazmat.backends.openssl import aead
 from cryptography.hazmat.backends.openssl.ciphers import _CipherContext
 from cryptography.hazmat.backends.openssl.cmac import _CMACContext
-from cryptography.hazmat.backends.openssl.rsa import (
-    _RSAPrivateKey,
-    _RSAPublicKey,
-)
 from cryptography.hazmat.bindings._rust import openssl as rust_openssl
 from cryptography.hazmat.bindings.openssl import binding
 from cryptography.hazmat.primitives import hashes, serialization
@@ -63,7 +59,6 @@
     XTS,
     Mode,
 )
-from cryptography.hazmat.primitives.serialization import ssh
 from cryptography.hazmat.primitives.serialization.pkcs12 import (
     PBES,
     PKCS12Certificate,
@@ -358,24 +353,7 @@ def generate_rsa_private_key(
         self, public_exponent: int, key_size: int
     ) -> rsa.RSAPrivateKey:
         rsa._verify_rsa_parameters(public_exponent, key_size)
-
-        rsa_cdata = self._lib.RSA_new()
-        self.openssl_assert(rsa_cdata != self._ffi.NULL)
-        rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
-
-        bn = self._int_to_bn(public_exponent)
-        bn = self._ffi.gc(bn, self._lib.BN_free)
-
-        res = self._lib.RSA_generate_key_ex(
-            rsa_cdata, key_size, bn, self._ffi.NULL
-        )
-        self.openssl_assert(res == 1)
-        evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata)
-
-        # We can skip RSA key validation here since we just generated the key
-        return _RSAPrivateKey(
-            self, rsa_cdata, evp_pkey, unsafe_skip_rsa_key_validation=True
-        )
+        return rust_openssl.rsa.generate_private_key(public_exponent, key_size)
 
     def generate_rsa_parameters_supported(
         self, public_exponent: int, key_size: int
@@ -401,46 +379,15 @@ def load_rsa_private_numbers(
             numbers.public_numbers.e,
             numbers.public_numbers.n,
         )
-        rsa_cdata = self._lib.RSA_new()
-        self.openssl_assert(rsa_cdata != self._ffi.NULL)
-        rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
-        p = self._int_to_bn(numbers.p)
-        q = self._int_to_bn(numbers.q)
-        d = self._int_to_bn(numbers.d)
-        dmp1 = self._int_to_bn(numbers.dmp1)
-        dmq1 = self._int_to_bn(numbers.dmq1)
-        iqmp = self._int_to_bn(numbers.iqmp)
-        e = self._int_to_bn(numbers.public_numbers.e)
-        n = self._int_to_bn(numbers.public_numbers.n)
-        res = self._lib.RSA_set0_factors(rsa_cdata, p, q)
-        self.openssl_assert(res == 1)
-        res = self._lib.RSA_set0_key(rsa_cdata, n, e, d)
-        self.openssl_assert(res == 1)
-        res = self._lib.RSA_set0_crt_params(rsa_cdata, dmp1, dmq1, iqmp)
-        self.openssl_assert(res == 1)
-        evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata)
-
-        return _RSAPrivateKey(
-            self,
-            rsa_cdata,
-            evp_pkey,
-            unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation,
+        return rust_openssl.rsa.from_private_numbers(
+            numbers, unsafe_skip_rsa_key_validation
         )
 
     def load_rsa_public_numbers(
         self, numbers: rsa.RSAPublicNumbers
     ) -> rsa.RSAPublicKey:
         rsa._check_public_key_components(numbers.e, numbers.n)
-        rsa_cdata = self._lib.RSA_new()
-        self.openssl_assert(rsa_cdata != self._ffi.NULL)
-        rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
-        e = self._int_to_bn(numbers.e)
-        n = self._int_to_bn(numbers.n)
-        res = self._lib.RSA_set0_key(rsa_cdata, n, e, self._ffi.NULL)
-        self.openssl_assert(res == 1)
-        evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata)
-
-        return _RSAPublicKey(self, rsa_cdata, evp_pkey)
+        return rust_openssl.rsa.from_public_numbers(numbers)
 
     def _create_evp_pkey_gc(self):
         evp_pkey = self._lib.EVP_PKEY_new()
@@ -500,13 +447,8 @@ def _evp_pkey_to_private_key(
         key_type = self._lib.EVP_PKEY_id(evp_pkey)
 
         if key_type == self._lib.EVP_PKEY_RSA:
-            rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey)
-            self.openssl_assert(rsa_cdata != self._ffi.NULL)
-            rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
-            return _RSAPrivateKey(
-                self,
-                rsa_cdata,
-                evp_pkey,
+            return rust_openssl.rsa.private_key_from_ptr(
+                int(self._ffi.cast("uintptr_t", evp_pkey)),
                 unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation,
             )
         elif (
@@ -573,10 +515,9 @@ def _evp_pkey_to_public_key(self, evp_pkey) -> PublicKeyTypes:
         key_type = self._lib.EVP_PKEY_id(evp_pkey)
 
         if key_type == self._lib.EVP_PKEY_RSA:
-            rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey)
-            self.openssl_assert(rsa_cdata != self._ffi.NULL)
-            rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
-            return _RSAPublicKey(self, rsa_cdata, evp_pkey)
+            return rust_openssl.rsa.public_key_from_ptr(
+                int(self._ffi.cast("uintptr_t", evp_pkey))
+            )
         elif (
             key_type == self._lib.EVP_PKEY_RSA_PSS
             and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL
@@ -733,7 +674,9 @@ def load_pem_public_key(self, data: bytes) -> PublicKeyTypes:
             if rsa_cdata != self._ffi.NULL:
                 rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
                 evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata)
-                return _RSAPublicKey(self, rsa_cdata, evp_pkey)
+                return rust_openssl.rsa.public_key_from_ptr(
+                    int(self._ffi.cast("uintptr_t", evp_pkey))
+                )
             else:
                 self._handle_key_loading_error()
 
@@ -796,7 +739,9 @@ def load_der_public_key(self, data: bytes) -> PublicKeyTypes:
             if rsa_cdata != self._ffi.NULL:
                 rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
                 evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata)
-                return _RSAPublicKey(self, rsa_cdata, evp_pkey)
+                return rust_openssl.rsa.public_key_from_ptr(
+                    int(self._ffi.cast("uintptr_t", evp_pkey))
+                )
             else:
                 self._handle_key_loading_error()
 
@@ -984,191 +929,6 @@ def elliptic_curve_exchange_algorithm_supported(
             algorithm, ec.ECDH
         )
 
-    def _private_key_bytes(
-        self,
-        encoding: serialization.Encoding,
-        format: serialization.PrivateFormat,
-        encryption_algorithm: serialization.KeySerializationEncryption,
-        key,
-        evp_pkey,
-        cdata,
-    ) -> bytes:
-        # validate argument types
-        if not isinstance(encoding, serialization.Encoding):
-            raise TypeError("encoding must be an item from the Encoding enum")
-        if not isinstance(format, serialization.PrivateFormat):
-            raise TypeError(
-                "format must be an item from the PrivateFormat enum"
-            )
-        if not isinstance(
-            encryption_algorithm, serialization.KeySerializationEncryption
-        ):
-            raise TypeError(
-                "Encryption algorithm must be a KeySerializationEncryption "
-                "instance"
-            )
-
-        # validate password
-        if isinstance(encryption_algorithm, serialization.NoEncryption):
-            password = b""
-        elif isinstance(
-            encryption_algorithm, serialization.BestAvailableEncryption
-        ):
-            password = encryption_algorithm.password
-            if len(password) > 1023:
-                raise ValueError(
-                    "Passwords longer than 1023 bytes are not supported by "
-                    "this backend"
-                )
-        elif (
-            isinstance(
-                encryption_algorithm, serialization._KeySerializationEncryption
-            )
-            and encryption_algorithm._format
-            is format
-            is serialization.PrivateFormat.OpenSSH
-        ):
-            password = encryption_algorithm.password
-        else:
-            raise ValueError("Unsupported encryption type")
-
-        # PKCS8 + PEM/DER
-        if format is serialization.PrivateFormat.PKCS8:
-            if encoding is serialization.Encoding.PEM:
-                write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey
-            elif encoding is serialization.Encoding.DER:
-                write_bio = self._lib.i2d_PKCS8PrivateKey_bio
-            else:
-                raise ValueError("Unsupported encoding for PKCS8")
-            return self._private_key_bytes_via_bio(
-                write_bio, evp_pkey, password
-            )
-
-        # TraditionalOpenSSL + PEM/DER
-        if format is serialization.PrivateFormat.TraditionalOpenSSL:
-            if self._fips_enabled and not isinstance(
-                encryption_algorithm, serialization.NoEncryption
-            ):
-                raise ValueError(
-                    "Encrypted traditional OpenSSL format is not "
-                    "supported in FIPS mode."
-                )
-            key_type = self._lib.EVP_PKEY_id(evp_pkey)
-
-            if encoding is serialization.Encoding.PEM:
-                assert key_type == self._lib.EVP_PKEY_RSA
-                write_bio = self._lib.PEM_write_bio_RSAPrivateKey
-                return self._private_key_bytes_via_bio(
-                    write_bio, cdata, password
-                )
-
-            if encoding is serialization.Encoding.DER:
-                if password:
-                    raise ValueError(
-                        "Encryption is not supported for DER encoded "
-                        "traditional OpenSSL keys"
-                    )
-                assert key_type == self._lib.EVP_PKEY_RSA
-                write_bio = self._lib.i2d_RSAPrivateKey_bio
-                return self._bio_func_output(write_bio, cdata)
-
-            raise ValueError("Unsupported encoding for TraditionalOpenSSL")
-
-        # OpenSSH + PEM
-        if format is serialization.PrivateFormat.OpenSSH:
-            if encoding is serialization.Encoding.PEM:
-                return ssh._serialize_ssh_private_key(
-                    key, password, encryption_algorithm
-                )
-
-            raise ValueError(
-                "OpenSSH private key format can only be used"
-                " with PEM encoding"
-            )
-
-        # Anything that key-specific code was supposed to handle earlier,
-        # like Raw.
-        raise ValueError("format is invalid with this key")
-
-    def _private_key_bytes_via_bio(
-        self, write_bio, evp_pkey, password
-    ) -> bytes:
-        if not password:
-            evp_cipher = self._ffi.NULL
-        else:
-            # This is a curated value that we will update over time.
-            evp_cipher = self._lib.EVP_get_cipherbyname(b"aes-256-cbc")
-
-        return self._bio_func_output(
-            write_bio,
-            evp_pkey,
-            evp_cipher,
-            password,
-            len(password),
-            self._ffi.NULL,
-            self._ffi.NULL,
-        )
-
-    def _bio_func_output(self, write_bio, *args) -> bytes:
-        bio = self._create_mem_bio_gc()
-        res = write_bio(bio, *args)
-        self.openssl_assert(res == 1)
-        return self._read_mem_bio(bio)
-
-    def _public_key_bytes(
-        self,
-        encoding: serialization.Encoding,
-        format: serialization.PublicFormat,
-        key,
-        evp_pkey,
-        cdata,
-    ) -> bytes:
-        if not isinstance(encoding, serialization.Encoding):
-            raise TypeError("encoding must be an item from the Encoding enum")
-        if not isinstance(format, serialization.PublicFormat):
-            raise TypeError(
-                "format must be an item from the PublicFormat enum"
-            )
-
-        # SubjectPublicKeyInfo + PEM/DER
-        if format is serialization.PublicFormat.SubjectPublicKeyInfo:
-            if encoding is serialization.Encoding.PEM:
-                write_bio = self._lib.PEM_write_bio_PUBKEY
-            elif encoding is serialization.Encoding.DER:
-                write_bio = self._lib.i2d_PUBKEY_bio
-            else:
-                raise ValueError(
-                    "SubjectPublicKeyInfo works only with PEM or DER encoding"
-                )
-            return self._bio_func_output(write_bio, evp_pkey)
-
-        # PKCS1 + PEM/DER
-        if format is serialization.PublicFormat.PKCS1:
-            # Only RSA is supported here.
-            key_type = self._lib.EVP_PKEY_id(evp_pkey)
-            self.openssl_assert(key_type == self._lib.EVP_PKEY_RSA)
-
-            if encoding is serialization.Encoding.PEM:
-                write_bio = self._lib.PEM_write_bio_RSAPublicKey
-            elif encoding is serialization.Encoding.DER:
-                write_bio = self._lib.i2d_RSAPublicKey_bio
-            else:
-                raise ValueError("PKCS1 works only with PEM or DER encoding")
-            return self._bio_func_output(write_bio, cdata)
-
-        # OpenSSH + OpenSSH
-        if format is serialization.PublicFormat.OpenSSH:
-            if encoding is serialization.Encoding.OpenSSH:
-                return ssh.serialize_ssh_public_key(key)
-
-            raise ValueError(
-                "OpenSSH format must be used with OpenSSH encoding"
-            )
-
-        # Anything that key-specific code was supposed to handle earlier,
-        # like Raw, CompressedPoint, UncompressedPoint
-        raise ValueError("format is invalid with this key")
-
     def dh_supported(self) -> bool:
         return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL
 
@@ -1496,7 +1256,13 @@ def serialize_key_and_certificates_to_pkcs12(
     def poly1305_supported(self) -> bool:
         if self._fips_enabled:
             return False
-        return self._lib.Cryptography_HAS_POLY1305 == 1
+        elif (
+            self._lib.CRYPTOGRAPHY_IS_BORINGSSL
+            or self._lib.CRYPTOGRAPHY_IS_LIBRESSL
+        ):
+            return True
+        else:
+            return self._lib.Cryptography_HAS_POLY1305 == 1
 
     def pkcs7_supported(self) -> bool:
         return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL
diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py
deleted file mode 100644
index b386581ffe69..000000000000
--- a/src/cryptography/hazmat/backends/openssl/rsa.py
+++ /dev/null
@@ -1,572 +0,0 @@
-# This file is dual licensed under the terms of the Apache License, Version
-# 2.0, and the BSD License. See the LICENSE file in the root of this repository
-# for complete details.
-
-from __future__ import annotations
-
-import typing
-
-from cryptography.exceptions import (
-    InvalidSignature,
-    UnsupportedAlgorithm,
-    _Reasons,
-)
-from cryptography.hazmat.backends.openssl.utils import (
-    _calculate_digest_and_algorithm,
-)
-from cryptography.hazmat.primitives import hashes, serialization
-from cryptography.hazmat.primitives.asymmetric import utils as asym_utils
-from cryptography.hazmat.primitives.asymmetric.padding import (
-    MGF1,
-    OAEP,
-    PSS,
-    AsymmetricPadding,
-    PKCS1v15,
-    _Auto,
-    _DigestLength,
-    _MaxLength,
-    calculate_max_pss_salt_length,
-)
-from cryptography.hazmat.primitives.asymmetric.rsa import (
-    RSAPrivateKey,
-    RSAPrivateNumbers,
-    RSAPublicKey,
-    RSAPublicNumbers,
-)
-
-if typing.TYPE_CHECKING:
-    from cryptography.hazmat.backends.openssl.backend import Backend
-
-
-def _get_rsa_pss_salt_length(
-    backend: Backend,
-    pss: PSS,
-    key: RSAPrivateKey | RSAPublicKey,
-    hash_algorithm: hashes.HashAlgorithm,
-) -> int:
-    salt = pss._salt_length
-
-    if isinstance(salt, _MaxLength):
-        return calculate_max_pss_salt_length(key, hash_algorithm)
-    elif isinstance(salt, _DigestLength):
-        return hash_algorithm.digest_size
-    elif isinstance(salt, _Auto):
-        if isinstance(key, RSAPrivateKey):
-            raise ValueError(
-                "PSS salt length can only be set to AUTO when verifying"
-            )
-        return backend._lib.RSA_PSS_SALTLEN_AUTO
-    else:
-        return salt
-
-
-def _enc_dec_rsa(
-    backend: Backend,
-    key: _RSAPrivateKey | _RSAPublicKey,
-    data: bytes,
-    padding: AsymmetricPadding,
-) -> bytes:
-    if not isinstance(padding, AsymmetricPadding):
-        raise TypeError("Padding must be an instance of AsymmetricPadding.")
-
-    if isinstance(padding, PKCS1v15):
-        padding_enum = backend._lib.RSA_PKCS1_PADDING
-    elif isinstance(padding, OAEP):
-        padding_enum = backend._lib.RSA_PKCS1_OAEP_PADDING
-
-        if not isinstance(padding._mgf, MGF1):
-            raise UnsupportedAlgorithm(
-                "Only MGF1 is supported by this backend.",
-                _Reasons.UNSUPPORTED_MGF,
-            )
-
-        if not backend.rsa_padding_supported(padding):
-            raise UnsupportedAlgorithm(
-                "This combination of padding and hash algorithm is not "
-                "supported by this backend.",
-                _Reasons.UNSUPPORTED_PADDING,
-            )
-
-    else:
-        raise UnsupportedAlgorithm(
-            f"{padding.name} is not supported by this backend.",
-            _Reasons.UNSUPPORTED_PADDING,
-        )
-
-    return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding)
-
-
-def _enc_dec_rsa_pkey_ctx(
-    backend: Backend,
-    key: _RSAPrivateKey | _RSAPublicKey,
-    data: bytes,
-    padding_enum: int,
-    padding: AsymmetricPadding,
-) -> bytes:
-    init: typing.Callable[[typing.Any], int]
-    crypt: typing.Callable[[typing.Any, typing.Any, int, bytes, int], int]
-    if isinstance(key, _RSAPublicKey):
-        init = backend._lib.EVP_PKEY_encrypt_init
-        crypt = backend._lib.EVP_PKEY_encrypt
-    else:
-        init = backend._lib.EVP_PKEY_decrypt_init
-        crypt = backend._lib.EVP_PKEY_decrypt
-
-    pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL)
-    backend.openssl_assert(pkey_ctx != backend._ffi.NULL)
-    pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free)
-    res = init(pkey_ctx)
-    backend.openssl_assert(res == 1)
-    res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum)
-    backend.openssl_assert(res > 0)
-    buf_size = backend._lib.EVP_PKEY_size(key._evp_pkey)
-    backend.openssl_assert(buf_size > 0)
-    if isinstance(padding, OAEP):
-        mgf1_md = backend._evp_md_non_null_from_algorithm(
-            padding._mgf._algorithm
-        )
-        res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md)
-        backend.openssl_assert(res > 0)
-        oaep_md = backend._evp_md_non_null_from_algorithm(padding._algorithm)
-        res = backend._lib.EVP_PKEY_CTX_set_rsa_oaep_md(pkey_ctx, oaep_md)
-        backend.openssl_assert(res > 0)
-
-    if (
-        isinstance(padding, OAEP)
-        and padding._label is not None
-        and len(padding._label) > 0
-    ):
-        # set0_rsa_oaep_label takes ownership of the char * so we need to
-        # copy it into some new memory
-        labelptr = backend._lib.OPENSSL_malloc(len(padding._label))
-        backend.openssl_assert(labelptr != backend._ffi.NULL)
-        backend._ffi.memmove(labelptr, padding._label, len(padding._label))
-        res = backend._lib.EVP_PKEY_CTX_set0_rsa_oaep_label(
-            pkey_ctx, labelptr, len(padding._label)
-        )
-        backend.openssl_assert(res == 1)
-
-    outlen = backend._ffi.new("size_t *", buf_size)
-    buf = backend._ffi.new("unsigned char[]", buf_size)
-    # Everything from this line onwards is written with the goal of being as
-    # constant-time as is practical given the constraints of Python and our
-    # API. See Bleichenbacher's '98 attack on RSA, and its many many variants.
-    # As such, you should not attempt to change this (particularly to "clean it
-    # up") without understanding why it was written this way (see
-    # Chesterton's Fence), and without measuring to verify you have not
-    # introduced observable time differences.
-    res = crypt(pkey_ctx, buf, outlen, data, len(data))
-    resbuf = backend._ffi.buffer(buf)[: outlen[0]]
-    backend._lib.ERR_clear_error()
-    if res <= 0:
-        raise ValueError("Encryption/decryption failed.")
-    return resbuf
-
-
-def _rsa_sig_determine_padding(
-    backend: Backend,
-    key: _RSAPrivateKey | _RSAPublicKey,
-    padding: AsymmetricPadding,
-    algorithm: hashes.HashAlgorithm | None,
-) -> int:
-    if not isinstance(padding, AsymmetricPadding):
-        raise TypeError("Expected provider of AsymmetricPadding.")
-
-    pkey_size = backend._lib.EVP_PKEY_size(key._evp_pkey)
-    backend.openssl_assert(pkey_size > 0)
-
-    if isinstance(padding, PKCS1v15):
-        # Hash algorithm is ignored for PKCS1v15-padding, may be None.
-        padding_enum = backend._lib.RSA_PKCS1_PADDING
-    elif isinstance(padding, PSS):
-        if not isinstance(padding._mgf, MGF1):
-            raise UnsupportedAlgorithm(
-                "Only MGF1 is supported by this backend.",
-                _Reasons.UNSUPPORTED_MGF,
-            )
-
-        # PSS padding requires a hash algorithm
-        if not isinstance(algorithm, hashes.HashAlgorithm):
-            raise TypeError("Expected instance of hashes.HashAlgorithm.")
-
-        # Size of key in bytes - 2 is the maximum
-        # PSS signature length (salt length is checked later)
-        if pkey_size - algorithm.digest_size - 2 < 0:
-            raise ValueError(
-                "Digest too large for key size. Use a larger "
-                "key or different digest."
-            )
-
-        padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING
-    else:
-        raise UnsupportedAlgorithm(
-            f"{padding.name} is not supported by this backend.",
-            _Reasons.UNSUPPORTED_PADDING,
-        )
-
-    return padding_enum
-
-
-# Hash algorithm can be absent (None) to initialize the context without setting
-# any message digest algorithm. This is currently only valid for the PKCS1v15
-# padding type, where it means that the signature data is encoded/decoded
-# as provided, without being wrapped in a DigestInfo structure.
-def _rsa_sig_setup(
-    backend: Backend,
-    padding: AsymmetricPadding,
-    algorithm: hashes.HashAlgorithm | None,
-    key: _RSAPublicKey | _RSAPrivateKey,
-    init_func: typing.Callable[[typing.Any], int],
-):
-    padding_enum = _rsa_sig_determine_padding(backend, key, padding, algorithm)
-    pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL)
-    backend.openssl_assert(pkey_ctx != backend._ffi.NULL)
-    pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free)
-    res = init_func(pkey_ctx)
-    if res != 1:
-        errors = backend._consume_errors()
-        raise ValueError("Unable to sign/verify with this key", errors)
-
-    if algorithm is not None:
-        evp_md = backend._evp_md_non_null_from_algorithm(algorithm)
-        res = backend._lib.EVP_PKEY_CTX_set_signature_md(pkey_ctx, evp_md)
-        if res <= 0:
-            backend._consume_errors()
-            raise UnsupportedAlgorithm(
-                "{} is not supported by this backend for RSA signing.".format(
-                    algorithm.name
-                ),
-                _Reasons.UNSUPPORTED_HASH,
-            )
-    res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum)
-    if res <= 0:
-        backend._consume_errors()
-        raise UnsupportedAlgorithm(
-            f"{padding.name} is not supported for the RSA signature operation",
-            _Reasons.UNSUPPORTED_PADDING,
-        )
-    if isinstance(padding, PSS):
-        assert isinstance(algorithm, hashes.HashAlgorithm)
-        res = backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen(
-            pkey_ctx,
-            _get_rsa_pss_salt_length(backend, padding, key, algorithm),
-        )
-        backend.openssl_assert(res > 0)
-
-        mgf1_md = backend._evp_md_non_null_from_algorithm(
-            padding._mgf._algorithm
-        )
-        res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md)
-        backend.openssl_assert(res > 0)
-
-    return pkey_ctx
-
-
-def _rsa_sig_sign(
-    backend: Backend,
-    padding: AsymmetricPadding,
-    algorithm: hashes.HashAlgorithm,
-    private_key: _RSAPrivateKey,
-    data: bytes,
-) -> bytes:
-    pkey_ctx = _rsa_sig_setup(
-        backend,
-        padding,
-        algorithm,
-        private_key,
-        backend._lib.EVP_PKEY_sign_init,
-    )
-    buflen = backend._ffi.new("size_t *")
-    res = backend._lib.EVP_PKEY_sign(
-        pkey_ctx, backend._ffi.NULL, buflen, data, len(data)
-    )
-    backend.openssl_assert(res == 1)
-    buf = backend._ffi.new("unsigned char[]", buflen[0])
-    res = backend._lib.EVP_PKEY_sign(pkey_ctx, buf, buflen, data, len(data))
-    if res != 1:
-        errors = backend._consume_errors()
-        raise ValueError(
-            "Digest or salt length too long for key size. Use a larger key "
-            "or shorter salt length if you are specifying a PSS salt",
-            errors,
-        )
-
-    return backend._ffi.buffer(buf)[:]
-
-
-def _rsa_sig_verify(
-    backend: Backend,
-    padding: AsymmetricPadding,
-    algorithm: hashes.HashAlgorithm,
-    public_key: _RSAPublicKey,
-    signature: bytes,
-    data: bytes,
-) -> None:
-    pkey_ctx = _rsa_sig_setup(
-        backend,
-        padding,
-        algorithm,
-        public_key,
-        backend._lib.EVP_PKEY_verify_init,
-    )
-    res = backend._lib.EVP_PKEY_verify(
-        pkey_ctx, signature, len(signature), data, len(data)
-    )
-    # The previous call can return negative numbers in the event of an
-    # error. This is not a signature failure but we need to fail if it
-    # occurs.
-    backend.openssl_assert(res >= 0)
-    if res == 0:
-        backend._consume_errors()
-        raise InvalidSignature
-
-
-def _rsa_sig_recover(
-    backend: Backend,
-    padding: AsymmetricPadding,
-    algorithm: hashes.HashAlgorithm | None,
-    public_key: _RSAPublicKey,
-    signature: bytes,
-) -> bytes:
-    pkey_ctx = _rsa_sig_setup(
-        backend,
-        padding,
-        algorithm,
-        public_key,
-        backend._lib.EVP_PKEY_verify_recover_init,
-    )
-
-    # Attempt to keep the rest of the code in this function as constant/time
-    # as possible. See the comment in _enc_dec_rsa_pkey_ctx. Note that the
-    # buflen parameter is used even though its value may be undefined in the
-    # error case. Due to the tolerant nature of Python slicing this does not
-    # trigger any exceptions.
-    maxlen = backend._lib.EVP_PKEY_size(public_key._evp_pkey)
-    backend.openssl_assert(maxlen > 0)
-    buf = backend._ffi.new("unsigned char[]", maxlen)
-    buflen = backend._ffi.new("size_t *", maxlen)
-    res = backend._lib.EVP_PKEY_verify_recover(
-        pkey_ctx, buf, buflen, signature, len(signature)
-    )
-    resbuf = backend._ffi.buffer(buf)[: buflen[0]]
-    backend._lib.ERR_clear_error()
-    # Assume that all parameter errors are handled during the setup phase and
-    # any error here is due to invalid signature.
-    if res != 1:
-        raise InvalidSignature
-    return resbuf
-
-
-class _RSAPrivateKey(RSAPrivateKey):
-    _evp_pkey: object
-    _rsa_cdata: object
-    _key_size: int
-
-    def __init__(
-        self,
-        backend: Backend,
-        rsa_cdata,
-        evp_pkey,
-        *,
-        unsafe_skip_rsa_key_validation: bool,
-    ):
-        res: int
-        # RSA_check_key is slower in OpenSSL 3.0.0 due to improved
-        # primality checking. In normal use this is unlikely to be a problem
-        # since users don't load new keys constantly, but for TESTING we've
-        # added an init arg that allows skipping the checks. You should not
-        # use this in production code unless you understand the consequences.
-        if not unsafe_skip_rsa_key_validation:
-            res = backend._lib.RSA_check_key(rsa_cdata)
-            if res != 1:
-                errors = backend._consume_errors()
-                raise ValueError("Invalid private key", errors)
-            # 2 is prime and passes an RSA key check, so we also check
-            # if p and q are odd just to be safe.
-            p = backend._ffi.new("BIGNUM **")
-            q = backend._ffi.new("BIGNUM **")
-            backend._lib.RSA_get0_factors(rsa_cdata, p, q)
-            backend.openssl_assert(p[0] != backend._ffi.NULL)
-            backend.openssl_assert(q[0] != backend._ffi.NULL)
-            p_odd = backend._lib.BN_is_odd(p[0])
-            q_odd = backend._lib.BN_is_odd(q[0])
-            if p_odd != 1 or q_odd != 1:
-                errors = backend._consume_errors()
-                raise ValueError("Invalid private key", errors)
-
-        self._backend = backend
-        self._rsa_cdata = rsa_cdata
-        self._evp_pkey = evp_pkey
-
-        n = self._backend._ffi.new("BIGNUM **")
-        self._backend._lib.RSA_get0_key(
-            self._rsa_cdata,
-            n,
-            self._backend._ffi.NULL,
-            self._backend._ffi.NULL,
-        )
-        self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
-        self._key_size = self._backend._lib.BN_num_bits(n[0])
-
-    @property
-    def key_size(self) -> int:
-        return self._key_size
-
-    def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes:
-        key_size_bytes = (self.key_size + 7) // 8
-        if key_size_bytes != len(ciphertext):
-            raise ValueError("Ciphertext length must be equal to key size.")
-
-        return _enc_dec_rsa(self._backend, self, ciphertext, padding)
-
-    def public_key(self) -> RSAPublicKey:
-        ctx = self._backend._lib.RSAPublicKey_dup(self._rsa_cdata)
-        self._backend.openssl_assert(ctx != self._backend._ffi.NULL)
-        ctx = self._backend._ffi.gc(ctx, self._backend._lib.RSA_free)
-        evp_pkey = self._backend._rsa_cdata_to_evp_pkey(ctx)
-        return _RSAPublicKey(self._backend, ctx, evp_pkey)
-
-    def private_numbers(self) -> RSAPrivateNumbers:
-        n = self._backend._ffi.new("BIGNUM **")
-        e = self._backend._ffi.new("BIGNUM **")
-        d = self._backend._ffi.new("BIGNUM **")
-        p = self._backend._ffi.new("BIGNUM **")
-        q = self._backend._ffi.new("BIGNUM **")
-        dmp1 = self._backend._ffi.new("BIGNUM **")
-        dmq1 = self._backend._ffi.new("BIGNUM **")
-        iqmp = self._backend._ffi.new("BIGNUM **")
-        self._backend._lib.RSA_get0_key(self._rsa_cdata, n, e, d)
-        self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
-        self._backend.openssl_assert(e[0] != self._backend._ffi.NULL)
-        self._backend.openssl_assert(d[0] != self._backend._ffi.NULL)
-        self._backend._lib.RSA_get0_factors(self._rsa_cdata, p, q)
-        self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
-        self._backend.openssl_assert(q[0] != self._backend._ffi.NULL)
-        self._backend._lib.RSA_get0_crt_params(
-            self._rsa_cdata, dmp1, dmq1, iqmp
-        )
-        self._backend.openssl_assert(dmp1[0] != self._backend._ffi.NULL)
-        self._backend.openssl_assert(dmq1[0] != self._backend._ffi.NULL)
-        self._backend.openssl_assert(iqmp[0] != self._backend._ffi.NULL)
-        return RSAPrivateNumbers(
-            p=self._backend._bn_to_int(p[0]),
-            q=self._backend._bn_to_int(q[0]),
-            d=self._backend._bn_to_int(d[0]),
-            dmp1=self._backend._bn_to_int(dmp1[0]),
-            dmq1=self._backend._bn_to_int(dmq1[0]),
-            iqmp=self._backend._bn_to_int(iqmp[0]),
-            public_numbers=RSAPublicNumbers(
-                e=self._backend._bn_to_int(e[0]),
-                n=self._backend._bn_to_int(n[0]),
-            ),
-        )
-
-    def private_bytes(
-        self,
-        encoding: serialization.Encoding,
-        format: serialization.PrivateFormat,
-        encryption_algorithm: serialization.KeySerializationEncryption,
-    ) -> bytes:
-        return self._backend._private_key_bytes(
-            encoding,
-            format,
-            encryption_algorithm,
-            self,
-            self._evp_pkey,
-            self._rsa_cdata,
-        )
-
-    def sign(
-        self,
-        data: bytes,
-        padding: AsymmetricPadding,
-        algorithm: asym_utils.Prehashed | hashes.HashAlgorithm,
-    ) -> bytes:
-        data, algorithm = _calculate_digest_and_algorithm(data, algorithm)
-        return _rsa_sig_sign(self._backend, padding, algorithm, self, data)
-
-
-class _RSAPublicKey(RSAPublicKey):
-    _evp_pkey: object
-    _rsa_cdata: object
-    _key_size: int
-
-    def __init__(self, backend: Backend, rsa_cdata, evp_pkey):
-        self._backend = backend
-        self._rsa_cdata = rsa_cdata
-        self._evp_pkey = evp_pkey
-
-        n = self._backend._ffi.new("BIGNUM **")
-        self._backend._lib.RSA_get0_key(
-            self._rsa_cdata,
-            n,
-            self._backend._ffi.NULL,
-            self._backend._ffi.NULL,
-        )
-        self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
-        self._key_size = self._backend._lib.BN_num_bits(n[0])
-
-    @property
-    def key_size(self) -> int:
-        return self._key_size
-
-    def __eq__(self, other: object) -> bool:
-        if not isinstance(other, _RSAPublicKey):
-            return NotImplemented
-
-        return (
-            self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey)
-            == 1
-        )
-
-    def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes:
-        return _enc_dec_rsa(self._backend, self, plaintext, padding)
-
-    def public_numbers(self) -> RSAPublicNumbers:
-        n = self._backend._ffi.new("BIGNUM **")
-        e = self._backend._ffi.new("BIGNUM **")
-        self._backend._lib.RSA_get0_key(
-            self._rsa_cdata, n, e, self._backend._ffi.NULL
-        )
-        self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
-        self._backend.openssl_assert(e[0] != self._backend._ffi.NULL)
-        return RSAPublicNumbers(
-            e=self._backend._bn_to_int(e[0]),
-            n=self._backend._bn_to_int(n[0]),
-        )
-
-    def public_bytes(
-        self,
-        encoding: serialization.Encoding,
-        format: serialization.PublicFormat,
-    ) -> bytes:
-        return self._backend._public_key_bytes(
-            encoding, format, self, self._evp_pkey, self._rsa_cdata
-        )
-
-    def verify(
-        self,
-        signature: bytes,
-        data: bytes,
-        padding: AsymmetricPadding,
-        algorithm: asym_utils.Prehashed | hashes.HashAlgorithm,
-    ) -> None:
-        data, algorithm = _calculate_digest_and_algorithm(data, algorithm)
-        _rsa_sig_verify(
-            self._backend, padding, algorithm, self, signature, data
-        )
-
-    def recover_data_from_signature(
-        self,
-        signature: bytes,
-        padding: AsymmetricPadding,
-        algorithm: hashes.HashAlgorithm | None,
-    ) -> bytes:
-        if isinstance(algorithm, asym_utils.Prehashed):
-            raise TypeError(
-                "Prehashed is only supported in the sign and verify methods. "
-                "It cannot be used with recover_data_from_signature."
-            )
-        return _rsa_sig_recover(
-            self._backend, padding, algorithm, self, signature
-        )
diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi
index e8b565443bfc..21c860265867 100644
--- a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi
+++ b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi
@@ -15,6 +15,7 @@ from cryptography.hazmat.bindings._rust.openssl import (
     hmac,
     kdf,
     poly1305,
+    rsa,
     x448,
     x25519,
 )
@@ -31,6 +32,7 @@ __all__ = [
     "kdf",
     "ed448",
     "ed25519",
+    "rsa",
     "poly1305",
     "x448",
     "x25519",
diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi
new file mode 100644
index 000000000000..d42134f72c74
--- /dev/null
+++ b/src/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi
@@ -0,0 +1,23 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from cryptography.hazmat.primitives.asymmetric import rsa
+
+class RSAPrivateKey: ...
+class RSAPublicKey: ...
+
+def generate_private_key(
+    public_exponent: int,
+    key_size: int,
+) -> rsa.RSAPrivateKey: ...
+def private_key_from_ptr(
+    ptr: int,
+    unsafe_skip_rsa_key_validation: bool,
+) -> rsa.RSAPrivateKey: ...
+def public_key_from_ptr(ptr: int) -> rsa.RSAPublicKey: ...
+def from_private_numbers(
+    numbers: rsa.RSAPrivateNumbers,
+    unsafe_skip_rsa_key_validation: bool,
+) -> rsa.RSAPrivateKey: ...
+def from_public_numbers(numbers: rsa.RSAPublicNumbers) -> rsa.RSAPublicKey: ...
diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi
index 9be3dabe6703..4ad055f1fc7a 100644
--- a/src/cryptography/hazmat/bindings/_rust/x509.pyi
+++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi
@@ -28,6 +28,7 @@ def create_x509_csr(
     builder: x509.CertificateSigningRequestBuilder,
     private_key: PrivateKeyTypes,
     hash_algorithm: hashes.HashAlgorithm | None,
+    padding: PKCS1v15 | PSS | None,
 ) -> x509.CertificateSigningRequest: ...
 def create_x509_crl(
     builder: x509.CertificateRevocationListBuilder,
diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
index 140b18a7f7b3..64b9d712258b 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -8,6 +8,7 @@
 import typing
 from math import gcd
 
+from cryptography.hazmat.bindings._rust import openssl as rust_openssl
 from cryptography.hazmat.primitives import _serialization, hashes
 from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding
 from cryptography.hazmat.primitives.asymmetric import utils as asym_utils
@@ -63,6 +64,7 @@ def private_bytes(
 
 
 RSAPrivateKeyWithSerialization = RSAPrivateKey
+RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey)
 
 
 class RSAPublicKey(metaclass=abc.ABCMeta):
@@ -126,6 +128,7 @@ def __eq__(self, other: object) -> bool:
 
 
 RSAPublicKeyWithSerialization = RSAPublicKey
+RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey)
 
 
 def generate_private_key(
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
index 051f7c350a04..9288ddc031f8 100644
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -521,6 +521,15 @@ def signature_algorithm_oid(self) -> ObjectIdentifier:
         Returns the ObjectIdentifier of the signature algorithm.
         """
 
+    @property
+    @abc.abstractmethod
+    def signature_algorithm_parameters(
+        self,
+    ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA:
+        """
+        Returns the signature algorithm parameters.
+        """
+
     @property
     @abc.abstractmethod
     def extensions(self) -> Extensions:
@@ -701,13 +710,24 @@ def sign(
         private_key: CertificateIssuerPrivateKeyTypes,
         algorithm: _AllowedHashTypes | None,
         backend: typing.Any = None,
+        *,
+        rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
     ) -> CertificateSigningRequest:
         """
         Signs the request using the requestor's private key.
         """
         if self._subject_name is None:
             raise ValueError("A CertificateSigningRequest must have a subject")
-        return rust_x509.create_x509_csr(self, private_key, algorithm)
+
+        if rsa_padding is not None:
+            if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
+                raise TypeError("Padding must be PSS or PKCS1v15")
+            if not isinstance(private_key, rsa.RSAPrivateKey):
+                raise TypeError("Padding is only supported for RSA keys")
+
+        return rust_x509.create_x509_csr(
+            self, private_key, algorithm, rsa_padding
+        )
 
 
 class CertificateBuilder:
diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py
index 824a13315f99..c237f8647cb7 100644
--- a/src/cryptography/x509/name.py
+++ b/src/cryptography/x509/name.py
@@ -346,8 +346,7 @@ def __hash__(self) -> int:
 
     def __iter__(self) -> typing.Iterator[NameAttribute]:
         for rdn in self._attributes:
-            for ava in rdn:
-                yield ava
+            yield from rdn
 
     def __len__(self) -> int:
         return sum(len(rdn) for rdn in self._attributes)
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 500923071f66..b9e65bb89fbc 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "861af988fac460ac69a09f41e6217a8fb9178797b76fcc9478444be6a59be19c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
@@ -30,9 +30,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "base64"
-version = "0.21.2"
+version = "0.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
 
 [[package]]
 name = "bitflags"
@@ -40,11 +40,17 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "bitflags"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+
 [[package]]
 name = "cc"
-version = "1.0.82"
+version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
 dependencies = [
  "libc",
 ]
@@ -163,11 +169,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
 name = "openssl"
-version = "0.10.56"
+version = "0.10.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e"
+checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
 dependencies = [
- "bitflags",
+ "bitflags 2.4.0",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -184,14 +190,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.91"
+version = "0.9.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
+checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
 dependencies = [
  "cc",
  "libc",
@@ -224,9 +230,9 @@ dependencies = [
 
 [[package]]
 name = "pem"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed3127afbfc30b4cad60c34aeb741fb562a808642b81142bcf4afb73142da960"
+checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923"
 dependencies = [
  "base64",
 ]
@@ -321,7 +327,7 @@ version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
 dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
 ]
 
 [[package]]
@@ -355,9 +361,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.18"
+version = "2.0.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -390,9 +396,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
 [[package]]
 name = "windows-targets"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27f51fb4c64f8b770a823c043c7fad036323e1c48f55287b7bbb7987b2fcdf3b"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
  "windows_aarch64_gnullvm",
  "windows_aarch64_msvc",
@@ -405,42 +411,42 @@ dependencies = [
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fde1bb55ae4ce76a597a8566d82c57432bc69c039449d61572a7a353da28f68c"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1513e8d48365a78adad7322fd6b5e4c4e99d92a69db8df2d435b25b1f1f286d4"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60587c0265d2b842298f5858e1a5d79d146f9ee0c37be5782e92a6eb5e1d7a83"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224fe0e0ffff5d2ea6a29f82026c8f43870038a0ffc247aa95a52b47df381ac4"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62fc52a0f50a088de499712cbc012df7ebd94e2d6eb948435449d76a6287e7ad"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2093925509d91ea3d69bcd20238f4c2ecdb1a29d3c281d026a09705d0dd35f3d"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.48.3"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6ade45bc8bf02ae2aa34a9d54ba660a1a58204da34ba793c00d83ca3730b5f1"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index a883b01f82d3..3090b332c739 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -16,13 +16,13 @@ cryptography-x509 = { path = "cryptography-x509" }
 cryptography-openssl = { path = "cryptography-openssl" }
 cryptography-x509-validation = { path = "cryptography-x509-validation" }
 pem = { version = "3", default-features = false }
-openssl = "0.10.56"
-openssl-sys = "0.9.91"
+openssl = "0.10.57"
+openssl-sys = "0.9.93"
 foreign-types-shared = "0.1"
 self_cell = "1"
 
 [build-dependencies]
-cc = "1.0.82"
+cc = "1.0.83"
 
 [features]
 extension-module = ["pyo3/extension-module"]
diff --git a/src/rust/cryptography-cffi/Cargo.toml b/src/rust/cryptography-cffi/Cargo.toml
index 46da116c5d97..9c3f2eb86e74 100644
--- a/src/rust/cryptography-cffi/Cargo.toml
+++ b/src/rust/cryptography-cffi/Cargo.toml
@@ -9,7 +9,7 @@ rust-version = "1.63.0"
 
 [dependencies]
 pyo3 = { version = "0.19", features = ["abi3-py37"] }
-openssl-sys = "0.9.91"
+openssl-sys = "0.9.93"
 
 [build-dependencies]
-cc = "1.0.82"
+cc = "1.0.83"
diff --git a/src/rust/cryptography-openssl/Cargo.toml b/src/rust/cryptography-openssl/Cargo.toml
index 75588a2953a2..e629b3717236 100644
--- a/src/rust/cryptography-openssl/Cargo.toml
+++ b/src/rust/cryptography-openssl/Cargo.toml
@@ -8,7 +8,7 @@ publish = false
 rust-version = "1.63.0"
 
 [dependencies]
-openssl = "0.10.56"
-ffi = { package = "openssl-sys", version = "0.9.85" }
+openssl = "0.10.57"
+ffi = { package = "openssl-sys", version = "0.9.91" }
 foreign-types = "0.3"
 foreign-types-shared = "0.1"
diff --git a/src/rust/cryptography-openssl/src/lib.rs b/src/rust/cryptography-openssl/src/lib.rs
index 0a2b48149e0f..3ddf4adbd7f6 100644
--- a/src/rust/cryptography-openssl/src/lib.rs
+++ b/src/rust/cryptography-openssl/src/lib.rs
@@ -4,6 +4,8 @@
 
 pub mod fips;
 pub mod hmac;
+#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))]
+pub mod poly1305;
 
 pub type OpenSSLResult<T> = Result<T, openssl::error::ErrorStack>;
 
diff --git a/src/rust/cryptography-openssl/src/poly1305.rs b/src/rust/cryptography-openssl/src/poly1305.rs
new file mode 100644
index 000000000000..262062eedd3f
--- /dev/null
+++ b/src/rust/cryptography-openssl/src/poly1305.rs
@@ -0,0 +1,45 @@
+// This file is dual licensed under the terms of the Apache License, Version
+// 2.0, and the BSD License. See the LICENSE file in the root of this repository
+// for complete details.
+
+use std::mem::MaybeUninit;
+
+pub struct Poly1305State {
+    // The state data must be allocated in the heap so that its address does not change. This is
+    // because BoringSSL APIs that take a `poly1305_state*` ignore all the data before an aligned
+    // address. Since a stack-allocated struct would change address on every copy, BoringSSL would
+    // interpret each copy differently, causing unexpected behavior.
+    context: Box<ffi::poly1305_state>,
+}
+
+impl Poly1305State {
+    pub fn new(key: &[u8]) -> Poly1305State {
+        assert_eq!(key.len(), 32);
+        let mut ctx: Box<MaybeUninit<ffi::poly1305_state>> =
+            Box::new(MaybeUninit::<ffi::poly1305_state>::uninit());
+
+        // After initializing the context, unwrap the Box<MaybeUninit<poly1305_state>> into
+        // a Box<poly1305_state> while keeping the same memory address. See the docstring of the
+        // Poly1305State struct above for the rationale.
+        let initialized_ctx: Box<ffi::poly1305_state> = unsafe {
+            ffi::CRYPTO_poly1305_init(ctx.as_mut().as_mut_ptr(), key.as_ptr());
+            let raw_ctx_ptr = (*Box::into_raw(ctx)).as_mut_ptr();
+            Box::from_raw(raw_ctx_ptr)
+        };
+
+        Poly1305State {
+            context: initialized_ctx,
+        }
+    }
+
+    pub fn update(&mut self, data: &[u8]) -> () {
+        unsafe {
+            ffi::CRYPTO_poly1305_update(self.context.as_mut(), data.as_ptr(), data.len());
+        };
+    }
+
+    pub fn finalize(&mut self, output: &mut [u8]) -> () {
+        assert_eq!(output.len(), 16);
+        unsafe { ffi::CRYPTO_poly1305_finish(self.context.as_mut(), output.as_mut_ptr()) };
+    }
+}
diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs
index 93e98f091f69..5d8f2e1a95f2 100644
--- a/src/rust/src/asn1.rs
+++ b/src/rust/src/asn1.rs
@@ -3,6 +3,7 @@
 // for complete details.
 
 use crate::error::{CryptographyError, CryptographyResult};
+use crate::types;
 use asn1::SimpleAsn1Readable;
 use cryptography_x509::certificate::Certificate;
 use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo, Time};
@@ -91,16 +92,9 @@ pub(crate) fn encode_der_data<'p>(
     data: Vec<u8>,
     encoding: &'p pyo3::PyAny,
 ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-    let encoding_class = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.serialization"
-        ))?
-        .getattr(pyo3::intern!(py, "Encoding"))?;
-
-    if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) {
+    if encoding.is(types::ENCODING_DER.get(py)?) {
         Ok(pyo3::types::PyBytes::new(py, &data))
-    } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) {
+    } else if encoding.is(types::ENCODING_PEM.get(py)?) {
         Ok(pyo3::types::PyBytes::new(
             py,
             &pem::encode_config(
diff --git a/src/rust/src/backend/aead.rs b/src/rust/src/backend/aead.rs
index 2a6641afa371..9dc3395e7140 100644
--- a/src/rust/src/backend/aead.rs
+++ b/src/rust/src/backend/aead.rs
@@ -4,7 +4,7 @@
 
 use crate::buf::CffiBuf;
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::exceptions;
+use crate::{exceptions, types};
 
 fn check_length(data: &[u8]) -> CryptographyResult<()> {
     if data.len() > (i32::MAX as usize) {
@@ -19,61 +19,126 @@ fn check_length(data: &[u8]) -> CryptographyResult<()> {
     Ok(())
 }
 
-fn encrypt_value<'p>(
-    py: pyo3::Python<'p>,
-    mut ctx: openssl::cipher_ctx::CipherCtx,
-    plaintext: &[u8],
+enum Aad<'a> {
+    List(&'a pyo3::types::PyList),
+}
+
+struct EvpCipherAead {
+    base_ctx: openssl::cipher_ctx::CipherCtx,
     tag_len: usize,
     tag_first: bool,
-) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-    Ok(pyo3::types::PyBytes::new_with(
-        py,
-        plaintext.len() + tag_len,
-        |b| {
-            let ciphertext;
-            let tag;
-            // TODO: remove once we have a second AEAD implemented here.
-            assert!(tag_first);
-            (tag, ciphertext) = b.split_at_mut(tag_len);
+}
+
+impl EvpCipherAead {
+    fn new(
+        base_ctx: openssl::cipher_ctx::CipherCtx,
+        tag_len: usize,
+        tag_first: bool,
+    ) -> EvpCipherAead {
+        EvpCipherAead {
+            base_ctx,
+            tag_len,
+            tag_first,
+        }
+    }
 
+    fn process_aad(
+        &self,
+        ctx: &mut openssl::cipher_ctx::CipherCtx,
+        aad: Option<Aad<'_>>,
+    ) -> CryptographyResult<()> {
+        if let Some(Aad::List(ads)) = aad {
+            for ad in ads.iter() {
+                let ad = ad.extract::<CffiBuf<'_>>()?;
+                check_length(ad.as_bytes())?;
+                ctx.cipher_update(ad.as_bytes(), None)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    fn encrypt<'p>(
+        &self,
+        py: pyo3::Python<'p>,
+        plaintext: &[u8],
+        aad: Option<Aad<'_>>,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        check_length(plaintext)?;
+
+        let mut ctx = openssl::cipher_ctx::CipherCtx::new()?;
+        ctx.copy(&self.base_ctx)?;
+        ctx.encrypt_init(None, None, None)?;
+
+        self.process_aad(&mut ctx, aad)?;
+
+        Ok(pyo3::types::PyBytes::new_with(
+            py,
+            plaintext.len() + self.tag_len,
+            |b| {
+                let ciphertext;
+                let tag;
+                // TODO: remove once we have a second AEAD implemented here.
+                assert!(self.tag_first);
+                (tag, ciphertext) = b.split_at_mut(self.tag_len);
+
+                let n = ctx
+                    .cipher_update(plaintext, Some(ciphertext))
+                    .map_err(CryptographyError::from)?;
+                assert_eq!(n, ciphertext.len());
+
+                let mut final_block = [0];
+                let n = ctx
+                    .cipher_final(&mut final_block)
+                    .map_err(CryptographyError::from)?;
+                assert_eq!(n, 0);
+
+                ctx.tag(tag).map_err(CryptographyError::from)?;
+
+                Ok(())
+            },
+        )?)
+    }
+
+    fn decrypt<'p>(
+        &self,
+        py: pyo3::Python<'p>,
+        ciphertext: &[u8],
+        aad: Option<Aad<'_>>,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        if ciphertext.len() < self.tag_len {
+            return Err(CryptographyError::from(exceptions::InvalidTag::new_err(())));
+        }
+
+        let mut ctx = openssl::cipher_ctx::CipherCtx::new()?;
+        ctx.copy(&self.base_ctx)?;
+        ctx.decrypt_init(None, None, None)?;
+
+        assert!(self.tag_first);
+        // RFC 5297 defines the output as IV || C, where the tag we generate
+        // is the "IV" and C is the ciphertext. This is the opposite of our
+        // other AEADs, which are Ciphertext || Tag.
+        let (tag, ciphertext) = ciphertext.split_at(self.tag_len);
+        ctx.set_tag(tag)?;
+
+        self.process_aad(&mut ctx, aad)?;
+
+        Ok(pyo3::types::PyBytes::new_with(py, ciphertext.len(), |b| {
+            // AES SIV can error here if the data is invalid on decrypt
             let n = ctx
-                .cipher_update(plaintext, Some(ciphertext))
-                .map_err(CryptographyError::from)?;
-            assert_eq!(n, ciphertext.len());
+                .cipher_update(ciphertext, Some(b))
+                .map_err(|_| exceptions::InvalidTag::new_err(()))?;
+            assert_eq!(n, b.len());
 
             let mut final_block = [0];
             let n = ctx
                 .cipher_final(&mut final_block)
-                .map_err(CryptographyError::from)?;
+                .map_err(|_| exceptions::InvalidTag::new_err(()))?;
             assert_eq!(n, 0);
 
-            ctx.tag(tag).map_err(CryptographyError::from)?;
-
             Ok(())
-        },
-    )?)
-}
-
-fn decrypt_value<'p>(
-    py: pyo3::Python<'p>,
-    mut ctx: openssl::cipher_ctx::CipherCtx,
-    ciphertext: &[u8],
-) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-    Ok(pyo3::types::PyBytes::new_with(py, ciphertext.len(), |b| {
-        // AES SIV can error here if the data is invalid on decrypt
-        let n = ctx
-            .cipher_update(ciphertext, Some(b))
-            .map_err(|_| exceptions::InvalidTag::new_err(()))?;
-        assert_eq!(n, b.len());
-
-        let mut final_block = [0];
-        let n = ctx
-            .cipher_final(&mut final_block)
-            .map_err(|_| exceptions::InvalidTag::new_err(()))?;
-        assert_eq!(n, 0);
-
-        Ok(())
-    })?)
+        })?)
+    }
 }
 
 #[pyo3::prelude::pyclass(
@@ -82,8 +147,7 @@ fn decrypt_value<'p>(
     name = "AESSIV"
 )]
 struct AesSiv {
-    key: pyo3::Py<pyo3::PyAny>,
-    cipher: openssl::cipher::Cipher,
+    ctx: EvpCipherAead,
 }
 
 #[pyo3::prelude::pymethods]
@@ -125,7 +189,11 @@ impl AesSiv {
             }
 
             let cipher = openssl::cipher::Cipher::fetch(None, cipher_name, None)?;
-            Ok(AesSiv { key, cipher })
+            let mut ctx = openssl::cipher_ctx::CipherCtx::new()?;
+            ctx.encrypt_init(Some(&cipher), Some(key_buf.as_bytes()), None)?;
+            Ok(AesSiv {
+                ctx: EvpCipherAead::new(ctx, 16, true),
+            })
         }
     }
 
@@ -137,9 +205,7 @@ impl AesSiv {
             ));
         }
 
-        Ok(py
-            .import(pyo3::intern!(py, "os"))?
-            .call_method1(pyo3::intern!(py, "urandom"), (bit_length / 8,))?)
+        Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?)
     }
 
     fn encrypt<'p>(
@@ -148,28 +214,15 @@ impl AesSiv {
         data: CffiBuf<'_>,
         associated_data: Option<&pyo3::types::PyList>,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let key_buf = self.key.extract::<CffiBuf<'_>>(py)?;
         let data_bytes = data.as_bytes();
+        let aad = associated_data.map(Aad::List);
 
         if data_bytes.is_empty() {
             return Err(CryptographyError::from(
                 pyo3::exceptions::PyValueError::new_err("data must not be zero length"),
             ));
         };
-        check_length(data_bytes)?;
-
-        let mut ctx = openssl::cipher_ctx::CipherCtx::new()?;
-        ctx.encrypt_init(Some(&self.cipher), Some(key_buf.as_bytes()), None)?;
-
-        if let Some(ads) = associated_data {
-            for ad in ads.iter() {
-                let ad = ad.extract::<CffiBuf<'_>>()?;
-                check_length(ad.as_bytes())?;
-                ctx.cipher_update(ad.as_bytes(), None)?;
-            }
-        }
-
-        encrypt_value(py, ctx, data_bytes, 16, true)
+        self.ctx.encrypt(py, data_bytes, aad)
     }
 
     fn decrypt<'p>(
@@ -178,37 +231,8 @@ impl AesSiv {
         data: CffiBuf<'_>,
         associated_data: Option<&pyo3::types::PyList>,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let key_buf = self.key.extract::<CffiBuf<'_>>(py)?;
-        let data_bytes = data.as_bytes();
-
-        if data_bytes.is_empty() {
-            return Err(CryptographyError::from(
-                pyo3::exceptions::PyValueError::new_err("data must not be zero length"),
-            ));
-        }
-
-        let mut ctx = openssl::cipher_ctx::CipherCtx::new()?;
-        ctx.decrypt_init(Some(&self.cipher), Some(key_buf.as_bytes()), None)?;
-
-        if data_bytes.len() < 16 {
-            return Err(CryptographyError::from(exceptions::InvalidTag::new_err(())));
-        }
-        // RFC 5297 defines the output as IV || C, where the tag we generate
-        // is the "IV" and C is the ciphertext. This is the opposite of our
-        // other AEADs, which are Ciphertext || Tag.
-        let (tag, ciphertext) = data_bytes.split_at(16);
-        ctx.set_tag(tag)?;
-
-        if let Some(ads) = associated_data {
-            for ad in ads.iter() {
-                let ad = ad.extract::<CffiBuf<'_>>()?;
-                check_length(ad.as_bytes())?;
-
-                ctx.cipher_update(ad.as_bytes(), None)?;
-            }
-        }
-
-        decrypt_value(py, ctx, ciphertext)
+        let aad = associated_data.map(Aad::List);
+        self.ctx.decrypt(py, data.as_bytes(), aad)
     }
 }
 
diff --git a/src/rust/src/backend/dh.rs b/src/rust/src/backend/dh.rs
index cbfd0d374009..12629ecabbd0 100644
--- a/src/rust/src/backend/dh.rs
+++ b/src/rust/src/backend/dh.rs
@@ -5,7 +5,7 @@
 use crate::asn1::encode_der_data;
 use crate::backend::utils;
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::x509;
+use crate::{types, x509};
 use cryptography_x509::common;
 use foreign_types_shared::ForeignTypeRef;
 
@@ -210,22 +210,16 @@ impl DHPrivateKey {
         let py_pub_key = utils::bn_to_py_int(py, dh.public_key())?;
         let py_private_key = utils::bn_to_py_int(py, dh.private_key())?;
 
-        let dh_mod = py.import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.dh"
-        ))?;
-
-        let parameter_numbers =
-            dh_mod.call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?;
-        let public_numbers = dh_mod.call_method1(
-            pyo3::intern!(py, "DHPublicNumbers"),
-            (py_pub_key, parameter_numbers),
-        )?;
-
-        Ok(dh_mod.call_method1(
-            pyo3::intern!(py, "DHPrivateNumbers"),
-            (py_private_key, public_numbers),
-        )?)
+        let parameter_numbers = types::DH_PARAMETER_NUMBERS
+            .get(py)?
+            .call1((py_p, py_g, py_q))?;
+        let public_numbers = types::DH_PUBLIC_NUMBERS
+            .get(py)?
+            .call1((py_pub_key, parameter_numbers))?;
+
+        Ok(types::DH_PRIVATE_NUMBERS
+            .get(py)?
+            .call1((py_private_key, public_numbers))?)
     }
 
     #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))]
@@ -252,13 +246,7 @@ impl DHPrivateKey {
         format: &pyo3::PyAny,
         encryption_algorithm: &pyo3::PyAny,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let private_format_class = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization"
-            ))?
-            .getattr(pyo3::intern!(py, "PrivateFormat"))?;
-        if !format.is(private_format_class.getattr(pyo3::intern!(py, "PKCS8"))?) {
+        if !format.is(types::PRIVATE_FORMAT_PKCS8.get(py)?) {
             return Err(CryptographyError::from(
                 pyo3::exceptions::PyValueError::new_err(
                     "DH private keys support only PKCS8 serialization",
@@ -292,13 +280,7 @@ impl DHPublicKey {
         encoding: &pyo3::PyAny,
         format: &pyo3::PyAny,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let public_format_class = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization"
-            ))?
-            .getattr(pyo3::intern!(py, "PublicFormat"))?;
-        if !format.is(public_format_class.getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?) {
+        if !format.is(types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) {
             return Err(CryptographyError::from(
                 pyo3::exceptions::PyValueError::new_err(
                     "DH public keys support only SubjectPublicKeyInfo serialization",
@@ -327,18 +309,13 @@ impl DHPublicKey {
 
         let py_pub_key = utils::bn_to_py_int(py, dh.public_key())?;
 
-        let dh_mod = py.import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.dh"
-        ))?;
-
-        let parameter_numbers =
-            dh_mod.call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?;
+        let parameter_numbers = types::DH_PARAMETER_NUMBERS
+            .get(py)?
+            .call1((py_p, py_g, py_q))?;
 
-        Ok(dh_mod.call_method1(
-            pyo3::intern!(py, "DHPublicNumbers"),
-            (py_pub_key, parameter_numbers),
-        )?)
+        Ok(types::DH_PUBLIC_NUMBERS
+            .get(py)?
+            .call1((py_pub_key, parameter_numbers))?)
     }
 
     fn __richcmp__(
@@ -377,12 +354,9 @@ impl DHParameters {
             .transpose()?;
         let py_g = utils::bn_to_py_int(py, self.dh.generator())?;
 
-        Ok(py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.asymmetric.dh"
-            ))?
-            .call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?)
+        Ok(types::DH_PARAMETER_NUMBERS
+            .get(py)?
+            .call1((py_p, py_g, py_q))?)
     }
 
     fn parameter_bytes<'p>(
@@ -391,13 +365,7 @@ impl DHParameters {
         encoding: &'p pyo3::PyAny,
         format: &pyo3::PyAny,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let parameter_format_class = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization"
-            ))?
-            .getattr(pyo3::intern!(py, "ParameterFormat"))?;
-        if !format.is(parameter_format_class.getattr(pyo3::intern!(py, "PKCS3"))?) {
+        if !format.is(types::PARAMETER_FORMAT_PKCS3.get(py)?) {
             return Err(CryptographyError::from(
                 pyo3::exceptions::PyValueError::new_err("Only PKCS3 serialization is supported"),
             ));
diff --git a/src/rust/src/backend/dsa.rs b/src/rust/src/backend/dsa.rs
index 7d740d281d72..aaa90f9ddcf6 100644
--- a/src/rust/src/backend/dsa.rs
+++ b/src/rust/src/backend/dsa.rs
@@ -4,7 +4,7 @@
 
 use crate::backend::utils;
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::exceptions;
+use crate::{exceptions, types};
 use foreign_types_shared::ForeignTypeRef;
 
 #[pyo3::prelude::pyclass(
@@ -122,15 +122,9 @@ impl DsaPrivateKey {
         data: &pyo3::types::PyBytes,
         algorithm: &pyo3::PyAny,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let (data, _): (&[u8], &pyo3::PyAny) = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.backends.openssl.utils"
-            ))?
-            .call_method1(
-                pyo3::intern!(py, "_calculate_digest_and_algorithm"),
-                (data, algorithm),
-            )?
+        let (data, _): (&[u8], &pyo3::PyAny) = types::CALCULATE_DIGEST_AND_ALGORITHM
+            .get(py)?
+            .call1((data, algorithm))?
             .extract()?;
 
         let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
@@ -173,22 +167,16 @@ impl DsaPrivateKey {
         let py_pub_key = utils::bn_to_py_int(py, dsa.pub_key())?;
         let py_private_key = utils::bn_to_py_int(py, dsa.priv_key())?;
 
-        let dsa_mod = py.import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.dsa"
-        ))?;
-
-        let parameter_numbers =
-            dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?;
-        let public_numbers = dsa_mod.call_method1(
-            pyo3::intern!(py, "DSAPublicNumbers"),
-            (py_pub_key, parameter_numbers),
-        )?;
-
-        Ok(dsa_mod.call_method1(
-            pyo3::intern!(py, "DSAPrivateNumbers"),
-            (py_private_key, public_numbers),
-        )?)
+        let parameter_numbers = types::DSA_PARAMETER_NUMBERS
+            .get(py)?
+            .call1((py_p, py_q, py_g))?;
+        let public_numbers = types::DSA_PUBLIC_NUMBERS
+            .get(py)?
+            .call1((py_pub_key, parameter_numbers))?;
+
+        Ok(types::DSA_PRIVATE_NUMBERS
+            .get(py)?
+            .call1((py_private_key, public_numbers))?)
     }
 
     fn private_bytes<'p>(
@@ -220,15 +208,9 @@ impl DsaPublicKey {
         data: &pyo3::types::PyBytes,
         algorithm: &pyo3::PyAny,
     ) -> CryptographyResult<()> {
-        let (data, _): (&[u8], &pyo3::PyAny) = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.backends.openssl.utils"
-            ))?
-            .call_method1(
-                pyo3::intern!(py, "_calculate_digest_and_algorithm"),
-                (data, algorithm),
-            )?
+        let (data, _): (&[u8], &pyo3::PyAny) = types::CALCULATE_DIGEST_AND_ALGORITHM
+            .get(py)?
+            .call1((data, algorithm))?
             .extract()?;
 
         let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
@@ -262,17 +244,12 @@ impl DsaPublicKey {
 
         let py_pub_key = utils::bn_to_py_int(py, dsa.pub_key())?;
 
-        let dsa_mod = py.import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.dsa"
-        ))?;
-
-        let parameter_numbers =
-            dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?;
-        Ok(dsa_mod.call_method1(
-            pyo3::intern!(py, "DSAPublicNumbers"),
-            (py_pub_key, parameter_numbers),
-        )?)
+        let parameter_numbers = types::DSA_PARAMETER_NUMBERS
+            .get(py)?
+            .call1((py_p, py_q, py_g))?;
+        Ok(types::DSA_PUBLIC_NUMBERS
+            .get(py)?
+            .call1((py_pub_key, parameter_numbers))?)
     }
 
     fn public_bytes<'p>(
@@ -314,12 +291,9 @@ impl DsaParameters {
         let py_q = utils::bn_to_py_int(py, self.dsa.q())?;
         let py_g = utils::bn_to_py_int(py, self.dsa.g())?;
 
-        let dsa_mod = py.import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.dsa"
-        ))?;
-
-        Ok(dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?)
+        Ok(types::DSA_PARAMETER_NUMBERS
+            .get(py)?
+            .call1((py_p, py_q, py_g))?)
     }
 }
 
diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs
index a4c4afc9d231..f0f4e5c735be 100644
--- a/src/rust/src/backend/ec.rs
+++ b/src/rust/src/backend/ec.rs
@@ -4,7 +4,7 @@
 
 use crate::backend::utils;
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::exceptions;
+use crate::{exceptions, types};
 use foreign_types_shared::ForeignTypeRef;
 use pyo3::basic::CompareOp;
 use pyo3::ToPyObject;
@@ -91,12 +91,8 @@ fn py_curve_from_curve<'p>(
         ));
     }
 
-    Ok(py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ec"
-        ))?
-        .getattr(pyo3::intern!(py, "_CURVE_TYPES"))?
+    Ok(types::CURVE_TYPES
+        .get(py)?
         .extract::<&pyo3::types::PyDict>()?
         .get_item(name)
         .ok_or_else(|| {
@@ -310,15 +306,7 @@ impl ECPrivateKey {
         algorithm: &pyo3::PyAny,
         public_key: &ECPublicKey,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let ecdh_class: &pyo3::types::PyType = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.asymmetric.ec"
-            ))?
-            .getattr(pyo3::intern!(py, "ECDH"))?
-            .extract()?;
-
-        if !algorithm.is_instance(ecdh_class)? {
+        if !algorithm.is_instance(types::ECDH.get(py)?)? {
             return Err(CryptographyError::from(
                 exceptions::UnsupportedAlgorithm::new_err((
                     "Unsupported EC exchange algorithm",
@@ -356,15 +344,7 @@ impl ECPrivateKey {
         data: &pyo3::types::PyBytes,
         algorithm: &pyo3::PyAny,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let ecdsa_class: &pyo3::types::PyType = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.asymmetric.ec"
-            ))?
-            .getattr(pyo3::intern!(py, "ECDSA"))?
-            .extract()?;
-
-        if !algorithm.is_instance(ecdsa_class)? {
+        if !algorithm.is_instance(types::ECDSA.get(py)?)? {
             return Err(CryptographyError::from(
                 exceptions::UnsupportedAlgorithm::new_err((
                     "Unsupported elliptic curve signature algorithm",
@@ -373,15 +353,9 @@ impl ECPrivateKey {
             ));
         }
 
-        let (data, _): (&[u8], &pyo3::PyAny) = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.backends.openssl.utils"
-            ))?
-            .call_method1(
-                pyo3::intern!(py, "_calculate_digest_and_algorithm"),
-                (data, algorithm.getattr(pyo3::intern!(py, "algorithm"))?),
-            )?
+        let (data, _): (&[u8], &pyo3::PyAny) = types::CALCULATE_DIGEST_AND_ALGORITHM
+            .get(py)?
+            .call1((data, algorithm.getattr(pyo3::intern!(py, "algorithm"))?))?
             .extract()?;
 
         let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
@@ -419,20 +393,15 @@ impl ECPrivateKey {
 
         let py_private_key = utils::bn_to_py_int(py, ec.private_key())?;
 
-        let ec_mod = py.import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ec"
+        let public_numbers = types::ELLIPTIC_CURVE_PUBLIC_NUMBERS.get(py)?.call1((
+            py_x,
+            py_y,
+            self.curve.clone_ref(py),
         ))?;
 
-        let public_numbers = ec_mod.call_method1(
-            pyo3::intern!(py, "EllipticCurvePublicNumbers"),
-            (py_x, py_y, self.curve.clone_ref(py)),
-        )?;
-
-        Ok(ec_mod.call_method1(
-            pyo3::intern!(py, "EllipticCurvePrivateNumbers"),
-            (py_private_key, public_numbers),
-        )?)
+        Ok(types::ELLIPTIC_CURVE_PRIVATE_NUMBERS
+            .get(py)?
+            .call1((py_private_key, public_numbers))?)
     }
 
     fn private_bytes<'p>(
@@ -469,15 +438,7 @@ impl ECPublicKey {
         data: &pyo3::types::PyBytes,
         signature_algorithm: &pyo3::PyAny,
     ) -> CryptographyResult<()> {
-        let ecdsa_class: &pyo3::types::PyType = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.asymmetric.ec"
-            ))?
-            .getattr(pyo3::intern!(py, "ECDSA"))?
-            .extract()?;
-
-        if !signature_algorithm.is_instance(ecdsa_class)? {
+        if !signature_algorithm.is_instance(types::ECDSA.get(py)?)? {
             return Err(CryptographyError::from(
                 exceptions::UnsupportedAlgorithm::new_err((
                     "Unsupported elliptic curve signature algorithm",
@@ -486,27 +447,17 @@ impl ECPublicKey {
             ));
         }
 
-        let (data, _): (&[u8], &pyo3::PyAny) = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.backends.openssl.utils"
+        let (data, _): (&[u8], &pyo3::PyAny) = types::CALCULATE_DIGEST_AND_ALGORITHM
+            .get(py)?
+            .call1((
+                data,
+                signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?,
             ))?
-            .call_method1(
-                pyo3::intern!(py, "_calculate_digest_and_algorithm"),
-                (
-                    data,
-                    signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?,
-                ),
-            )?
             .extract()?;
 
         let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
         verifier.verify_init()?;
         let valid = verifier.verify(data, signature).unwrap_or(false);
-        // TODO: Empty the error stack. BoringSSL leaves one in the event of
-        // signature validation failure. Upstream to rust-openssl?
-        #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)]
-        openssl::error::ErrorStack::get();
         if !valid {
             return Err(CryptographyError::from(
                 exceptions::InvalidSignature::new_err(()),
@@ -527,15 +478,11 @@ impl ECPublicKey {
         let py_x = utils::bn_to_py_int(py, &x)?;
         let py_y = utils::bn_to_py_int(py, &y)?;
 
-        let ec_mod = py.import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ec"
-        ))?;
-
-        Ok(ec_mod.call_method1(
-            pyo3::intern!(py, "EllipticCurvePublicNumbers"),
-            (py_x, py_y, self.curve.clone_ref(py)),
-        )?)
+        Ok(types::ELLIPTIC_CURVE_PUBLIC_NUMBERS.get(py)?.call1((
+            py_x,
+            py_y,
+            self.curve.clone_ref(py),
+        ))?)
     }
 
     fn public_bytes<'p>(
diff --git a/src/rust/src/backend/ed25519.rs b/src/rust/src/backend/ed25519.rs
index 5a51cd7d8405..4c372a938e3b 100644
--- a/src/rust/src/backend/ed25519.rs
+++ b/src/rust/src/backend/ed25519.rs
@@ -121,7 +121,8 @@ impl Ed25519PrivateKey {
 impl Ed25519PublicKey {
     fn verify(&self, signature: &[u8], data: &[u8]) -> CryptographyResult<()> {
         let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)?
-            .verify_oneshot(signature, data)?;
+            .verify_oneshot(signature, data)
+            .unwrap_or(false);
 
         if !valid {
             return Err(CryptographyError::from(
diff --git a/src/rust/src/backend/hashes.rs b/src/rust/src/backend/hashes.rs
index 8da7fa53a365..f315761f26dd 100644
--- a/src/rust/src/backend/hashes.rs
+++ b/src/rust/src/backend/hashes.rs
@@ -4,7 +4,7 @@
 
 use crate::buf::CffiBuf;
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::exceptions;
+use crate::{exceptions, types};
 use std::borrow::Cow;
 
 #[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")]
@@ -40,10 +40,7 @@ pub(crate) fn message_digest_from_algorithm(
     py: pyo3::Python<'_>,
     algorithm: &pyo3::PyAny,
 ) -> CryptographyResult<openssl::hash::MessageDigest> {
-    let hash_algorithm_class = py
-        .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?
-        .getattr(pyo3::intern!(py, "HashAlgorithm"))?;
-    if !algorithm.is_instance(hash_algorithm_class)? {
+    if !algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? {
         return Err(CryptographyError::from(
             pyo3::exceptions::PyTypeError::new_err("Expected instance of hashes.HashAlgorithm."),
         ));
@@ -109,12 +106,9 @@ impl Hash {
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
         #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))]
         {
-            let xof_class = py
-                .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?
-                .getattr(pyo3::intern!(py, "ExtendableOutputFunction"))?;
             let algorithm = self.algorithm.clone_ref(py);
             let algorithm = algorithm.as_ref(py);
-            if algorithm.is_instance(xof_class)? {
+            if algorithm.is_instance(types::EXTENDABLE_OUTPUT_FUNCTION.get(py)?)? {
                 let ctx = self.get_mut_ctx()?;
                 let digest_size = algorithm
                     .getattr(pyo3::intern!(py, "digest_size"))?
diff --git a/src/rust/src/backend/mod.rs b/src/rust/src/backend/mod.rs
index 717a09af8ad4..eb5ef8144146 100644
--- a/src/rust/src/backend/mod.rs
+++ b/src/rust/src/backend/mod.rs
@@ -14,6 +14,7 @@ pub(crate) mod hashes;
 pub(crate) mod hmac;
 pub(crate) mod kdf;
 pub(crate) mod poly1305;
+pub(crate) mod rsa;
 pub(crate) mod utils;
 #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))]
 pub(crate) mod x25519;
@@ -41,6 +42,7 @@ pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<
     module.add_submodule(hashes::create_module(module.py())?)?;
     module.add_submodule(hmac::create_module(module.py())?)?;
     module.add_submodule(kdf::create_module(module.py())?)?;
+    module.add_submodule(rsa::create_module(module.py())?)?;
 
     Ok(())
 }
diff --git a/src/rust/src/backend/poly1305.rs b/src/rust/src/backend/poly1305.rs
index 17d279a4023f..66fc6239fa02 100644
--- a/src/rust/src/backend/poly1305.rs
+++ b/src/rust/src/backend/poly1305.rs
@@ -7,26 +7,48 @@ use crate::buf::CffiBuf;
 use crate::error::{CryptographyError, CryptographyResult};
 use crate::exceptions;
 
-#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")]
-struct Poly1305 {
-    signer: Option<openssl::sign::Signer<'static>>,
+#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))]
+struct Poly1305Boring {
+    context: cryptography_openssl::poly1305::Poly1305State,
 }
 
-impl Poly1305 {
-    fn get_mut_signer(&mut self) -> CryptographyResult<&mut openssl::sign::Signer<'static>> {
-        if let Some(signer) = self.signer.as_mut() {
-            return Ok(signer);
-        };
-        Err(already_finalized_error())
+#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))]
+impl Poly1305Boring {
+    fn new(key: CffiBuf<'_>) -> CryptographyResult<Poly1305Boring> {
+        if key.as_bytes().len() != 32 {
+            return Err(CryptographyError::from(
+                pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long"),
+            ));
+        }
+        let ctx = cryptography_openssl::poly1305::Poly1305State::new(key.as_bytes());
+        Ok(Poly1305Boring { context: ctx })
+    }
+
+    fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> {
+        self.context.update(data.as_bytes());
+        Ok(())
+    }
+    fn finalize<'p>(
+        &mut self,
+        py: pyo3::Python<'p>,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        let result = pyo3::types::PyBytes::new_with(py, 16usize, |b| {
+            self.context.finalize(b.as_mut());
+            Ok(())
+        })?;
+        Ok(result)
     }
 }
 
-#[pyo3::pymethods]
-impl Poly1305 {
-    #[new]
-    fn new(key: CffiBuf<'_>) -> CryptographyResult<Poly1305> {
-        #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))]
-        {
+#[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))]
+struct Poly1305Open {
+    signer: openssl::sign::Signer<'static>,
+}
+
+#[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))]
+impl Poly1305Open {
+    fn new(key: CffiBuf<'_>) -> CryptographyResult<Poly1305Open> {
+        if cryptography_openssl::fips::is_enabled() {
             return Err(CryptographyError::from(
                 exceptions::UnsupportedAlgorithm::new_err((
                     "poly1305 is not supported by this version of OpenSSL.",
@@ -35,33 +57,56 @@ impl Poly1305 {
             ));
         }
 
-        #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))]
-        {
-            if cryptography_openssl::fips::is_enabled() {
-                return Err(CryptographyError::from(
-                    exceptions::UnsupportedAlgorithm::new_err((
-                        "poly1305 is not supported by this version of OpenSSL.",
-                        exceptions::Reasons::UNSUPPORTED_MAC,
-                    )),
-                ));
-            }
-
-            let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(
-                key.as_bytes(),
-                openssl::pkey::Id::POLY1305,
-            )
-            .map_err(|_| {
+        let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(
+            key.as_bytes(),
+            openssl::pkey::Id::POLY1305,
+        )
+        .map_err(|_| pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long"))?;
+
+        Ok(Poly1305Open {
+            signer: openssl::sign::Signer::new_without_digest(&pkey).map_err(|_| {
                 pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long")
-            })?;
-
-            Ok(Poly1305 {
-                signer: Some(
-                    openssl::sign::Signer::new_without_digest(&pkey).map_err(|_| {
-                        pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long")
-                    })?,
-                ),
-            })
-        }
+            })?,
+        })
+    }
+    fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> {
+        let buf = data.as_bytes();
+        self.signer.update(buf)?;
+        Ok(())
+    }
+    fn finalize<'p>(
+        &mut self,
+        py: pyo3::Python<'p>,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        let result = pyo3::types::PyBytes::new_with(py, self.signer.len()?, |b| {
+            let n = self.signer.sign(b).unwrap();
+            assert_eq!(n, b.len());
+            Ok(())
+        })?;
+        Ok(result)
+    }
+}
+
+#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")]
+struct Poly1305 {
+    #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))]
+    inner: Option<Poly1305Boring>,
+    #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))]
+    inner: Option<Poly1305Open>,
+}
+
+#[pyo3::pymethods]
+impl Poly1305 {
+    #[new]
+    fn new(key: CffiBuf<'_>) -> CryptographyResult<Poly1305> {
+        #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))]
+        return Ok(Poly1305 {
+            inner: Some(Poly1305Boring::new(key)?),
+        });
+        #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))]
+        return Ok(Poly1305 {
+            inner: Some(Poly1305Open::new(key)?),
+        });
     }
 
     #[staticmethod]
@@ -88,22 +133,22 @@ impl Poly1305 {
     }
 
     fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> {
-        self.get_mut_signer()?.update(data.as_bytes())?;
-        Ok(())
+        self.inner
+            .as_mut()
+            .map_or(Err(already_finalized_error()), |b| b.update(data))
     }
 
     fn finalize<'p>(
         &mut self,
         py: pyo3::Python<'p>,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let signer = self.get_mut_signer()?;
-        let result = pyo3::types::PyBytes::new_with(py, signer.len()?, |b| {
-            let n = signer.sign(b).unwrap();
-            assert_eq!(n, b.len());
-            Ok(())
-        })?;
-        self.signer = None;
-        Ok(result)
+        let res = self
+            .inner
+            .as_mut()
+            .map_or(Err(already_finalized_error()), |b| b.finalize(py));
+        self.inner = None;
+
+        res
     }
 
     fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> {
diff --git a/src/rust/src/backend/rsa.rs b/src/rust/src/backend/rsa.rs
new file mode 100644
index 000000000000..7e068be1a552
--- /dev/null
+++ b/src/rust/src/backend/rsa.rs
@@ -0,0 +1,587 @@
+// This file is dual licensed under the terms of the Apache License, Version
+// 2.0, and the BSD License. See the LICENSE file in the root of this repository
+// for complete details.
+
+use crate::backend::{hashes, utils};
+use crate::error::{CryptographyError, CryptographyResult};
+use crate::{exceptions, types};
+use foreign_types_shared::ForeignTypeRef;
+
+#[pyo3::prelude::pyclass(
+    frozen,
+    module = "cryptography.hazmat.bindings._rust.openssl.rsa",
+    name = "RSAPrivateKey"
+)]
+struct RsaPrivateKey {
+    pkey: openssl::pkey::PKey<openssl::pkey::Private>,
+}
+
+#[pyo3::prelude::pyclass(
+    frozen,
+    module = "cryptography.hazmat.bindings._rust.openssl.rsa",
+    name = "RSAPublicKey"
+)]
+struct RsaPublicKey {
+    pkey: openssl::pkey::PKey<openssl::pkey::Public>,
+}
+
+fn check_rsa_private_key(
+    rsa: &openssl::rsa::Rsa<openssl::pkey::Private>,
+) -> CryptographyResult<()> {
+    if !rsa.check_key().unwrap_or(false) || rsa.p().unwrap().is_even() || rsa.q().unwrap().is_even()
+    {
+        return Err(CryptographyError::from(
+            pyo3::exceptions::PyValueError::new_err("Invalid private key"),
+        ));
+    }
+    Ok(())
+}
+
+#[pyo3::prelude::pyfunction]
+fn private_key_from_ptr(
+    ptr: usize,
+    unsafe_skip_rsa_key_validation: bool,
+) -> CryptographyResult<RsaPrivateKey> {
+    let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) };
+    if !unsafe_skip_rsa_key_validation {
+        check_rsa_private_key(&pkey.rsa().unwrap())?;
+    }
+    Ok(RsaPrivateKey {
+        pkey: pkey.to_owned(),
+    })
+}
+
+#[pyo3::prelude::pyfunction]
+fn public_key_from_ptr(ptr: usize) -> RsaPublicKey {
+    let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) };
+    RsaPublicKey {
+        pkey: pkey.to_owned(),
+    }
+}
+
+#[pyo3::prelude::pyfunction]
+fn generate_private_key(public_exponent: u32, key_size: u32) -> CryptographyResult<RsaPrivateKey> {
+    let e = openssl::bn::BigNum::from_u32(public_exponent)?;
+    let rsa = openssl::rsa::Rsa::generate_with_e(key_size, &e)?;
+    let pkey = openssl::pkey::PKey::from_rsa(rsa)?;
+    Ok(RsaPrivateKey { pkey })
+}
+
+#[pyo3::prelude::pyfunction]
+fn from_private_numbers(
+    py: pyo3::Python<'_>,
+    numbers: &pyo3::PyAny,
+    unsafe_skip_rsa_key_validation: bool,
+) -> CryptographyResult<RsaPrivateKey> {
+    let public_numbers = numbers.getattr(pyo3::intern!(py, "public_numbers"))?;
+
+    let rsa = openssl::rsa::Rsa::from_private_components(
+        utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "n"))?)?,
+        utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "e"))?)?,
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "d"))?)?,
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "p"))?)?,
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "q"))?)?,
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "dmp1"))?)?,
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "dmq1"))?)?,
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "iqmp"))?)?,
+    )
+    .unwrap();
+    if !unsafe_skip_rsa_key_validation {
+        check_rsa_private_key(&rsa)?;
+    }
+    let pkey = openssl::pkey::PKey::from_rsa(rsa)?;
+    Ok(RsaPrivateKey { pkey })
+}
+
+#[pyo3::prelude::pyfunction]
+fn from_public_numbers(
+    py: pyo3::Python<'_>,
+    numbers: &pyo3::PyAny,
+) -> CryptographyResult<RsaPublicKey> {
+    let rsa = openssl::rsa::Rsa::from_public_components(
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "n"))?)?,
+        utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "e"))?)?,
+    )
+    .unwrap();
+    let pkey = openssl::pkey::PKey::from_rsa(rsa)?;
+    Ok(RsaPublicKey { pkey })
+}
+
+fn oaep_hash_supported(md: &openssl::hash::MessageDigest) -> bool {
+    (!cryptography_openssl::fips::is_enabled() && md == &openssl::hash::MessageDigest::sha1())
+        || md == &openssl::hash::MessageDigest::sha224()
+        || md == &openssl::hash::MessageDigest::sha256()
+        || md == &openssl::hash::MessageDigest::sha384()
+        || md == &openssl::hash::MessageDigest::sha512()
+}
+
+fn setup_encryption_ctx(
+    py: pyo3::Python<'_>,
+    ctx: &mut openssl::pkey_ctx::PkeyCtx<impl openssl::pkey::HasPublic>,
+    padding: &pyo3::PyAny,
+) -> CryptographyResult<()> {
+    if !padding.is_instance(types::ASYMMETRIC_PADDING.get(py)?)? {
+        return Err(CryptographyError::from(
+            pyo3::exceptions::PyTypeError::new_err(
+                "Padding must be an instance of AsymmetricPadding.",
+            ),
+        ));
+    }
+
+    let padding_enum = if padding.is_instance(types::PKCS1V15.get(py)?)? {
+        openssl::rsa::Padding::PKCS1
+    } else if padding.is_instance(types::OAEP.get(py)?)? {
+        if !padding
+            .getattr(pyo3::intern!(py, "_mgf"))?
+            .is_instance(types::MGF1.get(py)?)?
+        {
+            return Err(CryptographyError::from(
+                exceptions::UnsupportedAlgorithm::new_err((
+                    "Only MGF1 is supported.",
+                    exceptions::Reasons::UNSUPPORTED_MGF,
+                )),
+            ));
+        }
+
+        openssl::rsa::Padding::PKCS1_OAEP
+    } else {
+        return Err(CryptographyError::from(
+            exceptions::UnsupportedAlgorithm::new_err((
+                format!(
+                    "{} is not supported by this backend.",
+                    padding.getattr(pyo3::intern!(py, "name"))?
+                ),
+                exceptions::Reasons::UNSUPPORTED_PADDING,
+            )),
+        ));
+    };
+
+    ctx.set_rsa_padding(padding_enum)?;
+
+    if padding_enum == openssl::rsa::Padding::PKCS1_OAEP {
+        let mgf1_md = hashes::message_digest_from_algorithm(
+            py,
+            padding
+                .getattr(pyo3::intern!(py, "_mgf"))?
+                .getattr(pyo3::intern!(py, "_algorithm"))?,
+        )?;
+        let oaep_md = hashes::message_digest_from_algorithm(
+            py,
+            padding.getattr(pyo3::intern!(py, "_algorithm"))?,
+        )?;
+
+        if !oaep_hash_supported(&mgf1_md) || !oaep_hash_supported(&oaep_md) {
+            return Err(CryptographyError::from(
+                exceptions::UnsupportedAlgorithm::new_err((
+                    "This combination of padding and hash algorithm is not supported",
+                    exceptions::Reasons::UNSUPPORTED_PADDING,
+                )),
+            ));
+        }
+
+        ctx.set_rsa_mgf1_md(openssl::md::Md::from_nid(mgf1_md.type_()).unwrap())?;
+        ctx.set_rsa_oaep_md(openssl::md::Md::from_nid(oaep_md.type_()).unwrap())?;
+
+        if let Some(label) = padding
+            .getattr(pyo3::intern!(py, "_label"))?
+            .extract::<Option<&[u8]>>()?
+        {
+            if !label.is_empty() {
+                ctx.set_rsa_oaep_label(label)?;
+            }
+        }
+    }
+
+    Ok(())
+}
+
+fn setup_signature_ctx(
+    py: pyo3::Python<'_>,
+    ctx: &mut openssl::pkey_ctx::PkeyCtx<impl openssl::pkey::HasPublic>,
+    padding: &pyo3::PyAny,
+    algorithm: &pyo3::PyAny,
+    key_size: usize,
+    is_signing: bool,
+) -> CryptographyResult<()> {
+    if !padding.is_instance(types::ASYMMETRIC_PADDING.get(py)?)? {
+        return Err(CryptographyError::from(
+            pyo3::exceptions::PyTypeError::new_err(
+                "Padding must be an instance of AsymmetricPadding.",
+            ),
+        ));
+    }
+
+    let padding_enum = if padding.is_instance(types::PKCS1V15.get(py)?)? {
+        openssl::rsa::Padding::PKCS1
+    } else if padding.is_instance(types::PSS.get(py)?)? {
+        if !padding
+            .getattr(pyo3::intern!(py, "_mgf"))?
+            .is_instance(types::MGF1.get(py)?)?
+        {
+            return Err(CryptographyError::from(
+                exceptions::UnsupportedAlgorithm::new_err((
+                    "Only MGF1 is supported.",
+                    exceptions::Reasons::UNSUPPORTED_MGF,
+                )),
+            ));
+        }
+
+        // PSS padding requires a hash algorithm
+        if !algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? {
+            return Err(CryptographyError::from(
+                pyo3::exceptions::PyTypeError::new_err(
+                    "Expected instance of hashes.HashAlgorithm.",
+                ),
+            ));
+        }
+
+        if algorithm
+            .getattr(pyo3::intern!(py, "digest_size"))?
+            .extract::<usize>()?
+            + 2
+            > key_size
+        {
+            return Err(CryptographyError::from(
+                pyo3::exceptions::PyValueError::new_err(
+                    "Digest too large for key size. Use a larger key or different digest.",
+                ),
+            ));
+        }
+
+        openssl::rsa::Padding::PKCS1_PSS
+    } else {
+        return Err(CryptographyError::from(
+            exceptions::UnsupportedAlgorithm::new_err((
+                format!(
+                    "{} is not supported by this backend.",
+                    padding.getattr(pyo3::intern!(py, "name"))?
+                ),
+                exceptions::Reasons::UNSUPPORTED_PADDING,
+            )),
+        ));
+    };
+
+    if !algorithm.is_none() {
+        let md = hashes::message_digest_from_algorithm(py, algorithm)?;
+        ctx.set_signature_md(openssl::md::Md::from_nid(md.type_()).unwrap())
+            .or_else(|_| {
+                Err(CryptographyError::from(
+                    exceptions::UnsupportedAlgorithm::new_err((
+                        format!(
+                            "{} is not supported by this backend for RSA signing.",
+                            algorithm.getattr(pyo3::intern!(py, "name"))?
+                        ),
+                        exceptions::Reasons::UNSUPPORTED_HASH,
+                    )),
+                ))
+            })?;
+    }
+    ctx.set_rsa_padding(padding_enum).or_else(|_| {
+        Err(exceptions::UnsupportedAlgorithm::new_err((
+            format!(
+                "{} is not supported for the RSA signature operation",
+                padding.getattr(pyo3::intern!(py, "name"))?
+            ),
+            exceptions::Reasons::UNSUPPORTED_PADDING,
+        )))
+    })?;
+
+    if padding_enum == openssl::rsa::Padding::PKCS1_PSS {
+        let salt = padding.getattr(pyo3::intern!(py, "_salt_length"))?;
+        if salt.is_instance(types::PADDING_MAX_LENGTH.get(py)?)? {
+            ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::MAXIMUM_LENGTH)?;
+        } else if salt.is_instance(types::PADDING_DIGEST_LENGTH.get(py)?)? {
+            ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?;
+        } else if salt.is_instance(types::PADDING_AUTO.get(py)?)? {
+            if is_signing {
+                return Err(CryptographyError::from(
+                    pyo3::exceptions::PyValueError::new_err(
+                        "PSS salt length can only be set to Auto when verifying",
+                    ),
+                ));
+            }
+        } else {
+            ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::custom(salt.extract::<i32>()?))?;
+        };
+
+        let mgf1_md = hashes::message_digest_from_algorithm(
+            py,
+            padding
+                .getattr(pyo3::intern!(py, "_mgf"))?
+                .getattr(pyo3::intern!(py, "_algorithm"))?,
+        )?;
+        ctx.set_rsa_mgf1_md(openssl::md::Md::from_nid(mgf1_md.type_()).unwrap())?;
+    }
+
+    Ok(())
+}
+
+#[pyo3::prelude::pymethods]
+impl RsaPrivateKey {
+    fn sign<'p>(
+        &self,
+        py: pyo3::Python<'p>,
+        data: &[u8],
+        padding: &pyo3::PyAny,
+        algorithm: &pyo3::PyAny,
+    ) -> CryptographyResult<&'p pyo3::PyAny> {
+        let (data, algorithm): (&[u8], &pyo3::PyAny) = types::CALCULATE_DIGEST_AND_ALGORITHM
+            .get(py)?
+            .call1((data, algorithm))?
+            .extract()?;
+
+        let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
+        ctx.sign_init().map_err(|_| {
+            pyo3::exceptions::PyValueError::new_err("Unable to sign/verify with this key")
+        })?;
+        setup_signature_ctx(py, &mut ctx, padding, algorithm, self.pkey.size(), true)?;
+
+        let length = ctx.sign(data, None)?;
+        Ok(pyo3::types::PyBytes::new_with(py, length, |b| {
+            let length = ctx.sign(data, Some(b)).map_err(|_| {
+                pyo3::exceptions::PyValueError::new_err(
+                    "Digest or salt length too long for key size. Use a larger key or shorter salt length if you are specifying a PSS salt",
+                )
+            })?;
+            assert_eq!(length, b.len());
+            Ok(())
+        })?)
+    }
+
+    fn decrypt<'p>(
+        &self,
+        py: pyo3::Python<'p>,
+        ciphertext: &[u8],
+        padding: &pyo3::PyAny,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        let key_size_bytes =
+            usize::try_from((self.pkey.rsa().unwrap().n().num_bits() + 7) / 8).unwrap();
+        if key_size_bytes != ciphertext.len() {
+            return Err(CryptographyError::from(
+                pyo3::exceptions::PyValueError::new_err(
+                    "Ciphertext length must be equal to key size.",
+                ),
+            ));
+        }
+
+        let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
+        ctx.decrypt_init()?;
+
+        setup_encryption_ctx(py, &mut ctx, padding)?;
+
+        // Everything from this line onwards is written with the goal of being
+        // as constant-time as is practical given the constraints of
+        // rust-openssl and our API. See Bleichenbacher's '98 attack on RSA,
+        // and its many many variants. As such, you should not attempt to
+        // change this (particularly to "clean it up") without understanding
+        // why it was written this way (see Chesterton's Fence), and without
+        // measuring to verify you have not introduced observable time
+        // differences.
+        //
+        // Once OpenSSL 3.2.0 is out, this can be simplified, as OpenSSL will
+        // have its own mitigations for Bleichenbacher's attack.
+        let length = ctx.decrypt(ciphertext, None).unwrap();
+        let mut plaintext = vec![0; length];
+        let result = ctx.decrypt(ciphertext, Some(&mut plaintext));
+
+        let py_result =
+            pyo3::types::PyBytes::new(py, &plaintext[..*result.as_ref().unwrap_or(&length)]);
+        if result.is_err() {
+            return Err(CryptographyError::from(
+                pyo3::exceptions::PyValueError::new_err("Decryption failed"),
+            ));
+        }
+        Ok(py_result)
+    }
+
+    #[getter]
+    fn key_size(&self) -> i32 {
+        self.pkey.rsa().unwrap().n().num_bits()
+    }
+
+    fn public_key(&self) -> CryptographyResult<RsaPublicKey> {
+        let priv_rsa = self.pkey.rsa().unwrap();
+        let rsa = openssl::rsa::Rsa::from_public_components(
+            priv_rsa.n().to_owned()?,
+            priv_rsa.e().to_owned()?,
+        )
+        .unwrap();
+        let pkey = openssl::pkey::PKey::from_rsa(rsa)?;
+        Ok(RsaPublicKey { pkey })
+    }
+
+    fn private_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> {
+        let rsa = self.pkey.rsa().unwrap();
+
+        let py_p = utils::bn_to_py_int(py, rsa.p().unwrap())?;
+        let py_q = utils::bn_to_py_int(py, rsa.q().unwrap())?;
+        let py_d = utils::bn_to_py_int(py, rsa.d())?;
+        let py_dmp1 = utils::bn_to_py_int(py, rsa.dmp1().unwrap())?;
+        let py_dmq1 = utils::bn_to_py_int(py, rsa.dmq1().unwrap())?;
+        let py_iqmp = utils::bn_to_py_int(py, rsa.iqmp().unwrap())?;
+        let py_e = utils::bn_to_py_int(py, rsa.e())?;
+        let py_n = utils::bn_to_py_int(py, rsa.n())?;
+
+        let public_numbers = types::RSA_PUBLIC_NUMBERS.get(py)?.call1((py_e, py_n))?;
+        Ok(types::RSA_PRIVATE_NUMBERS.get(py)?.call1((
+            py_p,
+            py_q,
+            py_d,
+            py_dmp1,
+            py_dmq1,
+            py_iqmp,
+            public_numbers,
+        ))?)
+    }
+
+    fn private_bytes<'p>(
+        slf: &pyo3::PyCell<Self>,
+        py: pyo3::Python<'p>,
+        encoding: &pyo3::PyAny,
+        format: &pyo3::PyAny,
+        encryption_algorithm: &pyo3::PyAny,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        utils::pkey_private_bytes(
+            py,
+            slf,
+            &slf.borrow().pkey,
+            encoding,
+            format,
+            encryption_algorithm,
+            true,
+            false,
+        )
+    }
+}
+
+#[pyo3::prelude::pymethods]
+impl RsaPublicKey {
+    fn verify(
+        &self,
+        py: pyo3::Python<'_>,
+        signature: &[u8],
+        data: &[u8],
+        padding: &pyo3::PyAny,
+        algorithm: &pyo3::PyAny,
+    ) -> CryptographyResult<()> {
+        let (data, algorithm): (&[u8], &pyo3::PyAny) = types::CALCULATE_DIGEST_AND_ALGORITHM
+            .get(py)?
+            .call1((data, algorithm))?
+            .extract()?;
+
+        let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
+        ctx.verify_init()?;
+        setup_signature_ctx(py, &mut ctx, padding, algorithm, self.pkey.size(), false)?;
+
+        let valid = ctx.verify(data, signature).unwrap_or(false);
+        if !valid {
+            return Err(CryptographyError::from(
+                exceptions::InvalidSignature::new_err(()),
+            ));
+        }
+
+        Ok(())
+    }
+
+    fn encrypt<'p>(
+        &self,
+        py: pyo3::Python<'p>,
+        plaintext: &[u8],
+        padding: &pyo3::PyAny,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
+        ctx.encrypt_init()?;
+
+        setup_encryption_ctx(py, &mut ctx, padding)?;
+
+        let length = ctx.encrypt(plaintext, None)?;
+        Ok(pyo3::types::PyBytes::new_with(py, length, |b| {
+            let length = ctx
+                .encrypt(plaintext, Some(b))
+                .map_err(|_| pyo3::exceptions::PyValueError::new_err("Encryption failed"))?;
+            assert_eq!(length, b.len());
+            Ok(())
+        })?)
+    }
+
+    fn recover_data_from_signature<'p>(
+        &self,
+        py: pyo3::Python<'p>,
+        signature: &[u8],
+        padding: &pyo3::PyAny,
+        algorithm: &pyo3::PyAny,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        if algorithm.is_instance(types::PREHASHED.get(py)?)? {
+            return Err(CryptographyError::from(
+                pyo3::exceptions::PyTypeError::new_err(
+                    "Prehashed is only supported in the sign and verify methods. It cannot be used with recover_data_from_signature.",
+                ),
+            ));
+        }
+
+        let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
+        ctx.verify_recover_init()?;
+        setup_signature_ctx(py, &mut ctx, padding, algorithm, self.pkey.size(), false)?;
+
+        let length = ctx.verify_recover(signature, None)?;
+        let mut buf = vec![0u8; length];
+        let length = ctx
+            .verify_recover(signature, Some(&mut buf))
+            .map_err(|_| exceptions::InvalidSignature::new_err(()))?;
+
+        Ok(pyo3::types::PyBytes::new(py, &buf[..length]))
+    }
+
+    #[getter]
+    fn key_size(&self) -> i32 {
+        self.pkey.rsa().unwrap().n().num_bits()
+    }
+
+    fn public_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> {
+        let rsa = self.pkey.rsa().unwrap();
+
+        let py_e = utils::bn_to_py_int(py, rsa.e())?;
+        let py_n = utils::bn_to_py_int(py, rsa.n())?;
+
+        Ok(types::RSA_PUBLIC_NUMBERS.get(py)?.call1((py_e, py_n))?)
+    }
+
+    fn public_bytes<'p>(
+        slf: &pyo3::PyCell<Self>,
+        py: pyo3::Python<'p>,
+        encoding: &pyo3::PyAny,
+        format: &pyo3::PyAny,
+    ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
+        utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false)
+    }
+
+    fn __richcmp__(
+        &self,
+        other: pyo3::PyRef<'_, RsaPublicKey>,
+        op: pyo3::basic::CompareOp,
+    ) -> pyo3::PyResult<bool> {
+        match op {
+            pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)),
+            pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)),
+            _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")),
+        }
+    }
+
+    fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> {
+        slf
+    }
+}
+
+pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> {
+    let m = pyo3::prelude::PyModule::new(py, "rsa")?;
+    m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?;
+    m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?;
+    m.add_function(pyo3::wrap_pyfunction!(generate_private_key, m)?)?;
+    m.add_function(pyo3::wrap_pyfunction!(from_private_numbers, m)?)?;
+    m.add_function(pyo3::wrap_pyfunction!(from_public_numbers, m)?)?;
+
+    m.add_class::<RsaPrivateKey>()?;
+    m.add_class::<RsaPublicKey>()?;
+
+    Ok(m)
+}
diff --git a/src/rust/src/backend/utils.rs b/src/rust/src/backend/utils.rs
index 086f88ab9360..6c387cbbb1f6 100644
--- a/src/rust/src/backend/utils.rs
+++ b/src/rust/src/backend/utils.rs
@@ -3,6 +3,7 @@
 // for complete details.
 
 use crate::error::{CryptographyError, CryptographyResult};
+use crate::types;
 
 pub(crate) fn py_int_to_bn(
     py: pyo3::Python<'_>,
@@ -48,44 +49,21 @@ pub(crate) fn pkey_private_bytes<'p>(
     openssh_allowed: bool,
     raw_allowed: bool,
 ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-    let serialization_mod = py.import(pyo3::intern!(
-        py,
-        "cryptography.hazmat.primitives.serialization"
-    ))?;
-    let encoding_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "Encoding"))?
-        .extract()?;
-    let private_format_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "PrivateFormat"))?
-        .extract()?;
-    let key_serialization_encryption_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "KeySerializationEncryption"))?
-        .extract()?;
-    let no_encryption_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "NoEncryption"))?
-        .extract()?;
-    let best_available_encryption_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "BestAvailableEncryption"))?
-        .extract()?;
-    let encryption_builder_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "_KeySerializationEncryption"))?
-        .extract()?;
-
-    if !encoding.is_instance(encoding_class)? {
+    if !encoding.is_instance(types::ENCODING.get(py)?)? {
         return Err(CryptographyError::from(
             pyo3::exceptions::PyTypeError::new_err(
                 "encoding must be an item from the Encoding enum",
             ),
         ));
     }
-    if !format.is_instance(private_format_class)? {
+    if !format.is_instance(types::PRIVATE_FORMAT.get(py)?)? {
         return Err(CryptographyError::from(
             pyo3::exceptions::PyTypeError::new_err(
                 "format must be an item from the PrivateFormat enum",
             ),
         ));
     }
-    if !encryption_algorithm.is_instance(key_serialization_encryption_class)? {
+    if !encryption_algorithm.is_instance(types::KEY_SERIALIZATION_ENCRYPTION.get(py)?)? {
         return Err(CryptographyError::from(
             pyo3::exceptions::PyTypeError::new_err(
                 "Encryption algorithm must be a KeySerializationEncryption instance",
@@ -95,12 +73,12 @@ pub(crate) fn pkey_private_bytes<'p>(
 
     #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))]
     if raw_allowed
-        && (encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?)
-            || format.is(private_format_class.getattr(pyo3::intern!(py, "Raw"))?))
+        && (encoding.is(types::ENCODING_RAW.get(py)?)
+            || format.is(types::PRIVATE_FORMAT_RAW.get(py)?))
     {
-        if !encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?)
-            || !format.is(private_format_class.getattr(pyo3::intern!(py, "Raw"))?)
-            || !encryption_algorithm.is_instance(no_encryption_class)?
+        if !encoding.is(types::ENCODING_RAW.get(py)?)
+            || !format.is(types::PRIVATE_FORMAT_RAW.get(py)?)
+            || !encryption_algorithm.is_instance(types::NO_ENCRYPTION.get(py)?)?
         {
             return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err(
                     "When using Raw both encoding and format must be Raw and encryption_algorithm must be NoEncryption()"
@@ -110,10 +88,10 @@ pub(crate) fn pkey_private_bytes<'p>(
         return Ok(pyo3::types::PyBytes::new(py, &raw_bytes));
     }
 
-    let password = if encryption_algorithm.is_instance(no_encryption_class)? {
+    let password = if encryption_algorithm.is_instance(types::NO_ENCRYPTION.get(py)?)? {
         b""
-    } else if encryption_algorithm.is_instance(best_available_encryption_class)?
-        || (encryption_algorithm.is_instance(encryption_builder_class)?
+    } else if encryption_algorithm.is_instance(types::BEST_AVAILABLE_ENCRYPTION.get(py)?)?
+        || (encryption_algorithm.is_instance(types::ENCRYPTION_BUILDER.get(py)?)?
             && encryption_algorithm
                 .getattr(pyo3::intern!(py, "_format"))?
                 .is(format))
@@ -135,8 +113,8 @@ pub(crate) fn pkey_private_bytes<'p>(
         ));
     }
 
-    if format.is(private_format_class.getattr(pyo3::intern!(py, "PKCS8"))?) {
-        if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) {
+    if format.is(types::PRIVATE_FORMAT_PKCS8.get(py)?) {
+        if encoding.is(types::ENCODING_PEM.get(py)?) {
             let pem_bytes = if password.is_empty() {
                 pkey.private_key_to_pem_pkcs8()?
             } else {
@@ -146,7 +124,7 @@ pub(crate) fn pkey_private_bytes<'p>(
                 )?
             };
             return Ok(pyo3::types::PyBytes::new(py, &pem_bytes));
-        } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) {
+        } else if encoding.is(types::ENCODING_DER.get(py)?) {
             let der_bytes = if password.is_empty() {
                 pkey.private_key_to_pkcs8()?
             } else {
@@ -162,9 +140,32 @@ pub(crate) fn pkey_private_bytes<'p>(
         ));
     }
 
-    if format.is(private_format_class.getattr(pyo3::intern!(py, "TraditionalOpenSSL"))?) {
-        if let Ok(dsa) = pkey.dsa() {
-            if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) {
+    if format.is(types::PRIVATE_FORMAT_TRADITIONAL_OPENSSL.get(py)?) {
+        if let Ok(rsa) = pkey.rsa() {
+            if encoding.is(types::ENCODING_PEM.get(py)?) {
+                let pem_bytes = if password.is_empty() {
+                    rsa.private_key_to_pem()?
+                } else {
+                    rsa.private_key_to_pem_passphrase(
+                        openssl::symm::Cipher::aes_256_cbc(),
+                        password,
+                    )?
+                };
+                return Ok(pyo3::types::PyBytes::new(py, &pem_bytes));
+            } else if encoding.is(types::ENCODING_DER.get(py)?) {
+                if !password.is_empty() {
+                    return Err(CryptographyError::from(
+                        pyo3::exceptions::PyValueError::new_err(
+                            "Encryption is not supported for DER encoded traditional OpenSSL keys",
+                        ),
+                    ));
+                }
+
+                let der_bytes = rsa.private_key_to_der()?;
+                return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
+            }
+        } else if let Ok(dsa) = pkey.dsa() {
+            if encoding.is(types::ENCODING_PEM.get(py)?) {
                 let pem_bytes = if password.is_empty() {
                     dsa.private_key_to_pem()?
                 } else {
@@ -174,7 +175,7 @@ pub(crate) fn pkey_private_bytes<'p>(
                     )?
                 };
                 return Ok(pyo3::types::PyBytes::new(py, &pem_bytes));
-            } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) {
+            } else if encoding.is(types::ENCODING_DER.get(py)?) {
                 if !password.is_empty() {
                     return Err(CryptographyError::from(
                         pyo3::exceptions::PyValueError::new_err(
@@ -187,7 +188,7 @@ pub(crate) fn pkey_private_bytes<'p>(
                 return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
             }
         } else if let Ok(ec) = pkey.ec_key() {
-            if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) {
+            if encoding.is(types::ENCODING_PEM.get(py)?) {
                 let pem_bytes = if password.is_empty() {
                     ec.private_key_to_pem()?
                 } else {
@@ -197,7 +198,7 @@ pub(crate) fn pkey_private_bytes<'p>(
                     )?
                 };
                 return Ok(pyo3::types::PyBytes::new(py, &pem_bytes));
-            } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) {
+            } else if encoding.is(types::ENCODING_DER.get(py)?) {
                 if !password.is_empty() {
                     return Err(CryptographyError::from(
                         pyo3::exceptions::PyValueError::new_err(
@@ -213,17 +214,11 @@ pub(crate) fn pkey_private_bytes<'p>(
     }
 
     // OpenSSH + PEM
-    if openssh_allowed && format.is(private_format_class.getattr(pyo3::intern!(py, "OpenSSH"))?) {
-        if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) {
-            return Ok(py
-                .import(pyo3::intern!(
-                    py,
-                    "cryptography.hazmat.primitives.serialization.ssh"
-                ))?
-                .call_method1(
-                    pyo3::intern!(py, "_serialize_ssh_private_key"),
-                    (key_obj, password, encryption_algorithm),
-                )?
+    if openssh_allowed && format.is(types::PRIVATE_FORMAT_OPENSSH.get(py)?) {
+        if encoding.is(types::ENCODING_PEM.get(py)?) {
+            return Ok(types::SERIALIZE_SSH_PRIVATE_KEY
+                .get(py)?
+                .call1((key_obj, password, encryption_algorithm))?
                 .extract()?);
         }
 
@@ -248,25 +243,14 @@ pub(crate) fn pkey_public_bytes<'p>(
     openssh_allowed: bool,
     raw_allowed: bool,
 ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-    let serialization_mod = py.import(pyo3::intern!(
-        py,
-        "cryptography.hazmat.primitives.serialization"
-    ))?;
-    let encoding_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "Encoding"))?
-        .extract()?;
-    let public_format_class: &pyo3::types::PyType = serialization_mod
-        .getattr(pyo3::intern!(py, "PublicFormat"))?
-        .extract()?;
-
-    if !encoding.is_instance(encoding_class)? {
+    if !encoding.is_instance(types::ENCODING.get(py)?)? {
         return Err(CryptographyError::from(
             pyo3::exceptions::PyTypeError::new_err(
                 "encoding must be an item from the Encoding enum",
             ),
         ));
     }
-    if !format.is_instance(public_format_class)? {
+    if !format.is_instance(types::PUBLIC_FORMAT.get(py)?)? {
         return Err(CryptographyError::from(
             pyo3::exceptions::PyTypeError::new_err(
                 "format must be an item from the PublicFormat enum",
@@ -276,11 +260,11 @@ pub(crate) fn pkey_public_bytes<'p>(
 
     #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))]
     if raw_allowed
-        && (encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?)
-            || format.is(public_format_class.getattr(pyo3::intern!(py, "Raw"))?))
+        && (encoding.is(types::ENCODING_RAW.get(py)?)
+            || format.is(types::PUBLIC_FORMAT_RAW.get(py)?))
     {
-        if !encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?)
-            || !format.is(public_format_class.getattr(pyo3::intern!(py, "Raw"))?)
+        if !encoding.is(types::ENCODING_RAW.get(py)?)
+            || !format.is(types::PUBLIC_FORMAT_RAW.get(py)?)
         {
             return Err(CryptographyError::from(
                 pyo3::exceptions::PyValueError::new_err(
@@ -293,11 +277,11 @@ pub(crate) fn pkey_public_bytes<'p>(
     }
 
     // SubjectPublicKeyInfo + PEM/DER
-    if format.is(public_format_class.getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?) {
-        if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) {
+    if format.is(types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) {
+        if encoding.is(types::ENCODING_PEM.get(py)?) {
             let pem_bytes = pkey.public_key_to_pem()?;
             return Ok(pyo3::types::PyBytes::new(py, &pem_bytes));
-        } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) {
+        } else if encoding.is(types::ENCODING_DER.get(py)?) {
             let der_bytes = pkey.public_key_to_der()?;
             return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
         }
@@ -309,13 +293,10 @@ pub(crate) fn pkey_public_bytes<'p>(
     }
 
     if let Ok(ec) = pkey.ec_key() {
-        if encoding.is(encoding_class.getattr(pyo3::intern!(py, "X962"))?) {
-            let point_form = if format
-                .is(public_format_class.getattr(pyo3::intern!(py, "UncompressedPoint"))?)
-            {
+        if encoding.is(types::ENCODING_X962.get(py)?) {
+            let point_form = if format.is(types::PUBLIC_FORMAT_UNCOMPRESSED_POINT.get(py)?) {
                 openssl::ec::PointConversionForm::UNCOMPRESSED
-            } else if format.is(public_format_class.getattr(pyo3::intern!(py, "CompressedPoint"))?)
-            {
+            } else if format.is(types::PUBLIC_FORMAT_COMPRESSED_POINT.get(py)?) {
                 openssl::ec::PointConversionForm::COMPRESSED
             } else {
                 return Err(CryptographyError::from(
@@ -332,15 +313,29 @@ pub(crate) fn pkey_public_bytes<'p>(
         }
     }
 
+    if let Ok(rsa) = pkey.rsa() {
+        if format.is(types::PUBLIC_FORMAT_PKCS1.get(py)?) {
+            if encoding.is(types::ENCODING_PEM.get(py)?) {
+                let pem_bytes = rsa.public_key_to_pem_pkcs1()?;
+                return Ok(pyo3::types::PyBytes::new(py, &pem_bytes));
+            } else if encoding.is(types::ENCODING_DER.get(py)?) {
+                let der_bytes = rsa.public_key_to_der_pkcs1()?;
+                return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
+            }
+            return Err(CryptographyError::from(
+                pyo3::exceptions::PyValueError::new_err(
+                    "PKCS1 works only with PEM or DER encoding",
+                ),
+            ));
+        }
+    }
+
     // OpenSSH + OpenSSH
-    if openssh_allowed && format.is(public_format_class.getattr(pyo3::intern!(py, "OpenSSH"))?) {
-        if encoding.is(encoding_class.getattr(pyo3::intern!(py, "OpenSSH"))?) {
-            return Ok(py
-                .import(pyo3::intern!(
-                    py,
-                    "cryptography.hazmat.primitives.serialization.ssh"
-                ))?
-                .call_method1(pyo3::intern!(py, "serialize_ssh_public_key"), (key_obj,))?
+    if openssh_allowed && format.is(types::PUBLIC_FORMAT_OPENSSH.get(py)?) {
+        if encoding.is(types::ENCODING_OPENSSH.get(py)?) {
+            return Ok(types::SERIALIZE_SSH_PUBLIC_KEY
+                .get(py)?
+                .call1((key_obj,))?
                 .extract()?);
         }
 
diff --git a/src/rust/src/buf.rs b/src/rust/src/buf.rs
index b7afcf047da4..0a39a80f4341 100644
--- a/src/rust/src/buf.rs
+++ b/src/rust/src/buf.rs
@@ -2,6 +2,7 @@
 // 2.0, and the BSD License. See the LICENSE file in the root of this repository
 // for complete details.
 
+use crate::types;
 use std::{ptr, slice};
 
 pub(crate) struct CffiBuf<'p> {
@@ -20,9 +21,9 @@ impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> {
     fn extract(pyobj: &'a pyo3::PyAny) -> pyo3::PyResult<Self> {
         let py = pyobj.py();
 
-        let (bufobj, ptrval): (&pyo3::PyAny, usize) = py
-            .import(pyo3::intern!(py, "cryptography.utils"))?
-            .call_method1(pyo3::intern!(py, "_extract_buffer_length"), (pyobj,))?
+        let (bufobj, ptrval): (&pyo3::PyAny, usize) = types::EXTRACT_BUFFER_LENGTH
+            .get(py)?
+            .call1((pyobj,))?
             .extract()?;
 
         let len = bufobj.len()?;
diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs
index 6699520cb397..fff5cf756937 100644
--- a/src/rust/src/error.rs
+++ b/src/rust/src/error.rs
@@ -2,7 +2,7 @@
 // 2.0, and the BSD License. See the LICENSE file in the root of this repository
 // for complete details.
 
-use crate::{exceptions, OpenSSLError};
+use crate::exceptions;
 use pyo3::ToPyObject;
 
 pub enum CryptographyError {
@@ -107,6 +107,57 @@ impl CryptographyError {
 // https://github.com/pyca/cryptography/pull/6173
 pub(crate) type CryptographyResult<T = pyo3::PyObject> = Result<T, CryptographyError>;
 
+#[pyo3::prelude::pyfunction]
+pub(crate) fn raise_openssl_error() -> crate::error::CryptographyResult<()> {
+    Err(openssl::error::ErrorStack::get().into())
+}
+
+#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl")]
+pub(crate) struct OpenSSLError {
+    e: openssl::error::Error,
+}
+
+#[pyo3::pymethods]
+impl OpenSSLError {
+    #[getter]
+    fn lib(&self) -> i32 {
+        self.e.library_code()
+    }
+
+    #[getter]
+    fn reason(&self) -> i32 {
+        self.e.reason_code()
+    }
+
+    #[getter]
+    fn reason_text(&self) -> &[u8] {
+        self.e.reason().unwrap_or("").as_bytes()
+    }
+
+    fn _lib_reason_match(&self, lib: i32, reason: i32) -> bool {
+        self.e.library_code() == lib && self.e.reason_code() == reason
+    }
+
+    fn __repr__(&self) -> pyo3::PyResult<String> {
+        Ok(format!(
+            "<OpenSSLError(code={}, lib={}, reason={}, reason_text={})>",
+            self.e.code(),
+            self.e.library_code(),
+            self.e.reason_code(),
+            self.e.reason().unwrap_or("")
+        ))
+    }
+}
+
+#[pyo3::prelude::pyfunction]
+pub(crate) fn capture_error_stack(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyList> {
+    let errs = pyo3::types::PyList::empty(py);
+    for e in openssl::error::ErrorStack::get().errors() {
+        errs.append(pyo3::PyCell::new(py, OpenSSLError { e: e.clone() })?)?;
+    }
+    Ok(errs)
+}
+
 #[cfg(test)]
 mod tests {
     use super::CryptographyError;
diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs
index 2da39a5523b9..af85f373c578 100644
--- a/src/rust/src/lib.rs
+++ b/src/rust/src/lib.rs
@@ -10,127 +10,17 @@ mod buf;
 mod error;
 mod exceptions;
 pub(crate) mod oid;
+mod padding;
 mod pkcs7;
 mod pool;
+pub(crate) mod types;
 mod x509;
 
-/// Returns the value of the input with the most-significant-bit copied to all
-/// of the bits.
-fn duplicate_msb_to_all(a: u8) -> u8 {
-    0u8.wrapping_sub(a >> 7)
-}
-
-/// This returns 0xFF if a < b else 0x00, but does so in a constant time
-/// fashion.
-fn constant_time_lt(a: u8, b: u8) -> u8 {
-    // Derived from:
-    // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1i/include/internal/constant_time.h#L120
-    duplicate_msb_to_all(a ^ ((a ^ b) | (a.wrapping_sub(b) ^ b)))
-}
-
-#[pyo3::prelude::pyfunction]
-fn check_pkcs7_padding(data: &[u8]) -> bool {
-    let mut mismatch = 0;
-    let pad_size = *data.last().unwrap();
-    let len: u8 = data.len().try_into().expect("data too long");
-    for (i, b) in (0..len).zip(data.iter().rev()) {
-        let mask = constant_time_lt(i, pad_size);
-        mismatch |= mask & (pad_size ^ b);
-    }
-
-    // Check to make sure the pad_size was within the valid range.
-    mismatch |= !constant_time_lt(0, pad_size);
-    mismatch |= constant_time_lt(len, pad_size);
-
-    // Make sure any bits set are copied to the lowest bit
-    mismatch |= mismatch >> 4;
-    mismatch |= mismatch >> 2;
-    mismatch |= mismatch >> 1;
-
-    // Now check the low bit to see if it's set
-    (mismatch & 1) == 0
-}
-
-#[pyo3::prelude::pyfunction]
-fn check_ansix923_padding(data: &[u8]) -> bool {
-    let mut mismatch = 0;
-    let pad_size = *data.last().unwrap();
-    let len: u8 = data.len().try_into().expect("data too long");
-    // Skip the first one with the pad size
-    for (i, b) in (1..len).zip(data[..data.len() - 1].iter().rev()) {
-        let mask = constant_time_lt(i, pad_size);
-        mismatch |= mask & b;
-    }
-
-    // Check to make sure the pad_size was within the valid range.
-    mismatch |= !constant_time_lt(0, pad_size);
-    mismatch |= constant_time_lt(len, pad_size);
-
-    // Make sure any bits set are copied to the lowest bit
-    mismatch |= mismatch >> 4;
-    mismatch |= mismatch >> 2;
-    mismatch |= mismatch >> 1;
-
-    // Now check the low bit to see if it's set
-    (mismatch & 1) == 0
-}
-
 #[pyo3::prelude::pyfunction]
 fn openssl_version() -> i64 {
     openssl::version::number()
 }
 
-#[pyo3::prelude::pyfunction]
-fn raise_openssl_error() -> crate::error::CryptographyResult<()> {
-    Err(openssl::error::ErrorStack::get().into())
-}
-
-#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl")]
-struct OpenSSLError {
-    e: openssl::error::Error,
-}
-
-#[pyo3::pymethods]
-impl OpenSSLError {
-    #[getter]
-    fn lib(&self) -> i32 {
-        self.e.library_code()
-    }
-
-    #[getter]
-    fn reason(&self) -> i32 {
-        self.e.reason_code()
-    }
-
-    #[getter]
-    fn reason_text(&self) -> &[u8] {
-        self.e.reason().unwrap_or("").as_bytes()
-    }
-
-    fn _lib_reason_match(&self, lib: i32, reason: i32) -> bool {
-        self.e.library_code() == lib && self.e.reason_code() == reason
-    }
-
-    fn __repr__(&self) -> pyo3::PyResult<String> {
-        Ok(format!(
-            "<OpenSSLError(code={}, lib={}, reason={}, reason_text={})>",
-            self.e.code(),
-            self.e.library_code(),
-            self.e.reason_code(),
-            self.e.reason().unwrap_or("")
-        ))
-    }
-}
-
-#[pyo3::prelude::pyfunction]
-fn capture_error_stack(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyList> {
-    let errs = pyo3::types::PyList::empty(py);
-    for e in openssl::error::ErrorStack::get().errors() {
-        errs.append(pyo3::PyCell::new(py, OpenSSLError { e: e.clone() })?)?;
-    }
-    Ok(errs)
-}
-
 #[pyo3::prelude::pyfunction]
 fn is_fips_enabled() -> bool {
     cryptography_openssl::fips::is_enabled()
@@ -138,8 +28,8 @@ fn is_fips_enabled() -> bool {
 
 #[pyo3::prelude::pymodule]
 fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> {
-    m.add_function(pyo3::wrap_pyfunction!(check_pkcs7_padding, m)?)?;
-    m.add_function(pyo3::wrap_pyfunction!(check_ansix923_padding, m)?)?;
+    m.add_function(pyo3::wrap_pyfunction!(padding::check_pkcs7_padding, m)?)?;
+    m.add_function(pyo3::wrap_pyfunction!(padding::check_ansix923_padding, m)?)?;
     m.add_class::<oid::ObjectIdentifier>()?;
     m.add_class::<pool::FixedPool>()?;
 
@@ -165,27 +55,12 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()>
 
     let openssl_mod = pyo3::prelude::PyModule::new(py, "openssl")?;
     openssl_mod.add_function(pyo3::wrap_pyfunction!(openssl_version, m)?)?;
-    openssl_mod.add_function(pyo3::wrap_pyfunction!(raise_openssl_error, m)?)?;
-    openssl_mod.add_function(pyo3::wrap_pyfunction!(capture_error_stack, m)?)?;
+    openssl_mod.add_function(pyo3::wrap_pyfunction!(error::raise_openssl_error, m)?)?;
+    openssl_mod.add_function(pyo3::wrap_pyfunction!(error::capture_error_stack, m)?)?;
     openssl_mod.add_function(pyo3::wrap_pyfunction!(is_fips_enabled, m)?)?;
-    openssl_mod.add_class::<OpenSSLError>()?;
+    openssl_mod.add_class::<error::OpenSSLError>()?;
     crate::backend::add_to_module(openssl_mod)?;
     m.add_submodule(openssl_mod)?;
 
     Ok(())
 }
-
-#[cfg(test)]
-mod tests {
-    use super::constant_time_lt;
-
-    #[test]
-    fn test_constant_time_lt() {
-        for a in 0..=255 {
-            for b in 0..=255 {
-                let expected = if a < b { 0xff } else { 0 };
-                assert_eq!(constant_time_lt(a, b), expected);
-            }
-        }
-    }
-}
diff --git a/src/rust/src/oid.rs b/src/rust/src/oid.rs
index fd7b17cf9183..9dbf63ed46aa 100644
--- a/src/rust/src/oid.rs
+++ b/src/rust/src/oid.rs
@@ -3,6 +3,7 @@
 // for complete details.
 
 use crate::error::CryptographyResult;
+use crate::types;
 use std::collections::hash_map::DefaultHasher;
 use std::hash::{Hash, Hasher};
 
@@ -30,10 +31,9 @@ impl ObjectIdentifier {
         slf: pyo3::PyRef<'_, Self>,
         py: pyo3::Python<'p>,
     ) -> pyo3::PyResult<&'p pyo3::PyAny> {
-        let oid_names = py
-            .import(pyo3::intern!(py, "cryptography.hazmat._oid"))?
-            .getattr(pyo3::intern!(py, "_OID_NAMES"))?;
-        oid_names.call_method1(pyo3::intern!(py, "get"), (slf, "Unknown OID"))
+        types::OID_NAMES
+            .get(py)?
+            .call_method1(pyo3::intern!(py, "get"), (slf, "Unknown OID"))
     }
 
     fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> {
diff --git a/src/rust/src/padding.rs b/src/rust/src/padding.rs
new file mode 100644
index 000000000000..523fe85a5718
--- /dev/null
+++ b/src/rust/src/padding.rs
@@ -0,0 +1,79 @@
+// This file is dual licensed under the terms of the Apache License, Version
+// 2.0, and the BSD License. See the LICENSE file in the root of this repository
+// for complete details.
+
+/// Returns the value of the input with the most-significant-bit copied to all
+/// of the bits.
+fn duplicate_msb_to_all(a: u8) -> u8 {
+    0u8.wrapping_sub(a >> 7)
+}
+
+/// This returns 0xFF if a < b else 0x00, but does so in a constant time
+/// fashion.
+fn constant_time_lt(a: u8, b: u8) -> u8 {
+    // Derived from:
+    // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1i/include/internal/constant_time.h#L120
+    duplicate_msb_to_all(a ^ ((a ^ b) | (a.wrapping_sub(b) ^ b)))
+}
+
+#[pyo3::prelude::pyfunction]
+pub(crate) fn check_pkcs7_padding(data: &[u8]) -> bool {
+    let mut mismatch = 0;
+    let pad_size = *data.last().unwrap();
+    let len: u8 = data.len().try_into().expect("data too long");
+    for (i, b) in (0..len).zip(data.iter().rev()) {
+        let mask = constant_time_lt(i, pad_size);
+        mismatch |= mask & (pad_size ^ b);
+    }
+
+    // Check to make sure the pad_size was within the valid range.
+    mismatch |= !constant_time_lt(0, pad_size);
+    mismatch |= constant_time_lt(len, pad_size);
+
+    // Make sure any bits set are copied to the lowest bit
+    mismatch |= mismatch >> 4;
+    mismatch |= mismatch >> 2;
+    mismatch |= mismatch >> 1;
+
+    // Now check the low bit to see if it's set
+    (mismatch & 1) == 0
+}
+
+#[pyo3::prelude::pyfunction]
+pub(crate) fn check_ansix923_padding(data: &[u8]) -> bool {
+    let mut mismatch = 0;
+    let pad_size = *data.last().unwrap();
+    let len: u8 = data.len().try_into().expect("data too long");
+    // Skip the first one with the pad size
+    for (i, b) in (1..len).zip(data[..data.len() - 1].iter().rev()) {
+        let mask = constant_time_lt(i, pad_size);
+        mismatch |= mask & b;
+    }
+
+    // Check to make sure the pad_size was within the valid range.
+    mismatch |= !constant_time_lt(0, pad_size);
+    mismatch |= constant_time_lt(len, pad_size);
+
+    // Make sure any bits set are copied to the lowest bit
+    mismatch |= mismatch >> 4;
+    mismatch |= mismatch >> 2;
+    mismatch |= mismatch >> 1;
+
+    // Now check the low bit to see if it's set
+    (mismatch & 1) == 0
+}
+
+#[cfg(test)]
+mod tests {
+    use super::constant_time_lt;
+
+    #[test]
+    fn test_constant_time_lt() {
+        for a in 0..=255 {
+            for b in 0..=255 {
+                let expected = if a < b { 0xff } else { 0 };
+                assert_eq!(constant_time_lt(a, b), expected);
+            }
+        }
+    }
+}
diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs
index bc098a9d1367..1acbae457fb3 100644
--- a/src/rust/src/pkcs7.rs
+++ b/src/rust/src/pkcs7.rs
@@ -5,7 +5,7 @@
 use crate::asn1::encode_der_data;
 use crate::buf::CffiBuf;
 use crate::error::CryptographyResult;
-use crate::x509;
+use crate::{types, x509};
 use cryptography_x509::csr::Attribute;
 use cryptography_x509::{common, oid, pkcs7};
 use once_cell::sync::Lazy;
@@ -77,17 +77,10 @@ fn sign_and_serialize<'p>(
     encoding: &'p pyo3::PyAny,
     options: &'p pyo3::types::PyList,
 ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-    let pkcs7_options = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.serialization.pkcs7"
-        ))?
-        .getattr(pyo3::intern!(py, "PKCS7Options"))?;
-
     let raw_data: CffiBuf<'p> = builder.getattr(pyo3::intern!(py, "_data"))?.extract()?;
-    let text_mode = options.contains(pkcs7_options.getattr(pyo3::intern!(py, "Text"))?)?;
+    let text_mode = options.contains(types::PKCS7_TEXT.get(py)?)?;
     let (data_with_header, data_without_header) =
-        if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "Binary"))?)? {
+        if options.contains(types::PKCS7_BINARY.get(py)?)? {
             (
                 Cow::Borrowed(raw_data.as_bytes()),
                 Cow::Borrowed(raw_data.as_bytes()),
@@ -126,7 +119,7 @@ fn sign_and_serialize<'p>(
         .collect::<Vec<_>>();
     for (cert, py_private_key, py_hash_alg) in &py_signers {
         let (authenticated_attrs, signature) = if options
-            .contains(pkcs7_options.getattr(pyo3::intern!(py, "NoAttributes"))?)?
+            .contains(types::PKCS7_NO_ATTRIBUTES.get(py)?)?
         {
             (
                 None,
@@ -165,7 +158,7 @@ fn sign_and_serialize<'p>(
                 ])),
             });
 
-            if !options.contains(pkcs7_options.getattr(pyo3::intern!(py, "NoCapabilities"))?)? {
+            if !options.contains(types::PKCS7_NO_CAPABILITIES.get(py)?)? {
                 authenticated_attrs.push(Attribute {
                     type_id: PKCS7_SMIME_CAP_OID,
                     values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([
@@ -221,13 +214,12 @@ fn sign_and_serialize<'p>(
     }
 
     let data_tlv_bytes;
-    let content =
-        if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "DetachedSignature"))?)? {
-            None
-        } else {
-            data_tlv_bytes = asn1::write_single(&data_with_header.deref())?;
-            Some(asn1::parse_single(&data_tlv_bytes).unwrap())
-        };
+    let content = if options.contains(types::PKCS7_DETACHED_SIGNATURE.get(py)?)? {
+        None
+    } else {
+        data_tlv_bytes = asn1::write_single(&data_with_header.deref())?;
+        Some(asn1::parse_single(&data_tlv_bytes).unwrap())
+    };
 
     let signed_data = pkcs7::SignedData {
         version: 1,
@@ -236,7 +228,7 @@ fn sign_and_serialize<'p>(
             _content_type: asn1::DefinedByMarker::marker(),
             content: pkcs7::Content::Data(content.map(asn1::Explicit::new)),
         },
-        certificates: if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "NoCerts"))?)? {
+        certificates: if options.contains(types::PKCS7_NO_CERTS.get(py)?)? {
             None
         } else {
             Some(asn1::SetOfWriter::new(&certs))
@@ -251,26 +243,14 @@ fn sign_and_serialize<'p>(
     };
     let ci_bytes = asn1::write_single(&content_info)?;
 
-    let encoding_class = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.serialization"
-        ))?
-        .getattr(pyo3::intern!(py, "Encoding"))?;
-
-    if encoding.is(encoding_class.getattr(pyo3::intern!(py, "SMIME"))?) {
+    if encoding.is(types::ENCODING_SMIME.get(py)?) {
         let mic_algs = digest_algs
             .iter()
             .map(|d| OIDS_TO_MIC_NAME[&d.oid()])
             .collect::<Vec<_>>()
             .join(",");
-        let smime_encode = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization.pkcs7"
-            ))?
-            .getattr(pyo3::intern!(py, "_smime_encode"))?;
-        Ok(smime_encode
+        Ok(types::SMIME_ENCODE
+            .get(py)?
             .call1((&*data_without_header, &*ci_bytes, mic_algs, text_mode))?
             .extract()?)
     } else {
diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs
new file mode 100644
index 000000000000..8bfcf905d842
--- /dev/null
+++ b/src/rust/src/types.rs
@@ -0,0 +1,500 @@
+// This file is dual licensed under the terms of the Apache License, Version
+// 2.0, and the BSD License. See the LICENSE file in the root of this repository
+// for complete details.
+
+pub struct LazyPyImport {
+    module: &'static str,
+    names: &'static [&'static str],
+    value: pyo3::once_cell::GILOnceCell<pyo3::PyObject>,
+}
+
+impl LazyPyImport {
+    pub const fn new(module: &'static str, names: &'static [&'static str]) -> LazyPyImport {
+        LazyPyImport {
+            module,
+            names,
+            value: pyo3::once_cell::GILOnceCell::new(),
+        }
+    }
+
+    pub fn get<'p>(&'p self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> {
+        self.value
+            .get_or_try_init(py, || {
+                let mut obj = py.import(self.module)?.as_ref();
+                for name in self.names {
+                    obj = obj.getattr(*name)?;
+                }
+                obj.extract()
+            })
+            .map(|p| p.as_ref(py))
+    }
+}
+
+pub static DATETIME_DATETIME: LazyPyImport = LazyPyImport::new("datetime", &["datetime"]);
+pub static DATETIME_TIMEZONE_UTC: LazyPyImport =
+    LazyPyImport::new("datetime", &["timezone", "utc"]);
+pub static IPADDRESS_IPADDRESS: LazyPyImport = LazyPyImport::new("ipaddress", &["ip_address"]);
+pub static IPADDRESS_IPNETWORK: LazyPyImport = LazyPyImport::new("ipaddress", &["ip_network"]);
+pub static OS_URANDOM: LazyPyImport = LazyPyImport::new("os", &["urandom"]);
+
+pub static DEPRECATED_IN_36: LazyPyImport =
+    LazyPyImport::new("cryptography.utils", &["DeprecatedIn36"]);
+pub static DEPRECATED_IN_41: LazyPyImport =
+    LazyPyImport::new("cryptography.utils", &["DeprecatedIn41"]);
+
+pub static LOAD_DER_PUBLIC_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["load_der_public_key"],
+);
+
+pub static ENCODING: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["Encoding"],
+);
+pub static ENCODING_DER: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["Encoding", "DER"],
+);
+pub static ENCODING_OPENSSH: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["Encoding", "OpenSSH"],
+);
+pub static ENCODING_PEM: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["Encoding", "PEM"],
+);
+pub static ENCODING_RAW: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["Encoding", "Raw"],
+);
+pub static ENCODING_SMIME: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["Encoding", "SMIME"],
+);
+pub static ENCODING_X962: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["Encoding", "X962"],
+);
+
+pub static PRIVATE_FORMAT: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PrivateFormat"],
+);
+pub static PRIVATE_FORMAT_OPENSSH: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PrivateFormat", "OpenSSH"],
+);
+pub static PRIVATE_FORMAT_PKCS8: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PrivateFormat", "PKCS8"],
+);
+pub static PRIVATE_FORMAT_RAW: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PrivateFormat", "Raw"],
+);
+pub static PRIVATE_FORMAT_TRADITIONAL_OPENSSL: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PrivateFormat", "TraditionalOpenSSL"],
+);
+
+pub static PUBLIC_FORMAT: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PublicFormat"],
+);
+pub static PUBLIC_FORMAT_COMPRESSED_POINT: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PublicFormat", "CompressedPoint"],
+);
+pub static PUBLIC_FORMAT_OPENSSH: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PublicFormat", "OpenSSH"],
+);
+pub static PUBLIC_FORMAT_PKCS1: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PublicFormat", "PKCS1"],
+);
+pub static PUBLIC_FORMAT_RAW: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PublicFormat", "Raw"],
+);
+pub static PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PublicFormat", "SubjectPublicKeyInfo"],
+);
+pub static PUBLIC_FORMAT_UNCOMPRESSED_POINT: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["PublicFormat", "UncompressedPoint"],
+);
+
+pub static PARAMETER_FORMAT_PKCS3: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["ParameterFormat", "PKCS3"],
+);
+
+pub static KEY_SERIALIZATION_ENCRYPTION: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["KeySerializationEncryption"],
+);
+pub static NO_ENCRYPTION: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["NoEncryption"],
+);
+pub static BEST_AVAILABLE_ENCRYPTION: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["BestAvailableEncryption"],
+);
+pub static ENCRYPTION_BUILDER: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization",
+    &["_KeySerializationEncryption"],
+);
+
+pub static SERIALIZE_SSH_PRIVATE_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.ssh",
+    &["_serialize_ssh_private_key"],
+);
+pub static SERIALIZE_SSH_PUBLIC_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.ssh",
+    &["serialize_ssh_public_key"],
+);
+
+pub static SIG_OIDS_TO_HASH: LazyPyImport =
+    LazyPyImport::new("cryptography.hazmat._oid", &["_SIG_OIDS_TO_HASH"]);
+pub static OID_NAMES: LazyPyImport = LazyPyImport::new("cryptography.hazmat._oid", &["_OID_NAMES"]);
+
+pub static REASON_FLAGS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["ReasonFlags"]);
+pub static ATTRIBUTE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attribute"]);
+pub static ATTRIBUTES: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attributes"]);
+
+pub static CRL_NUMBER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLNumber"]);
+pub static DELTA_CRL_INDICATOR: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["DeltaCRLIndicator"]);
+pub static ISSUER_ALTERNATIVE_NAME: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["IssuerAlternativeName"]);
+pub static AUTHORITY_INFORMATION_ACCESS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["AuthorityInformationAccess"]);
+pub static ISSUING_DISTRIBUTION_POINT: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["IssuingDistributionPoint"]);
+pub static FRESHEST_CRL: LazyPyImport = LazyPyImport::new("cryptography.x509", &["FreshestCRL"]);
+pub static CRL_REASON: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLReason"]);
+pub static CERTIFICATE_ISSUER: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["CertificateIssuer"]);
+pub static INVALIDITY_DATE: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["InvalidityDate"]);
+pub static OCSP_NONCE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OCSPNonce"]);
+pub static OCSP_ACCEPTABLE_RESPONSES: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["OCSPAcceptableResponses"]);
+pub static SIGNED_CERTIFICATE_TIMESTAMPS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["SignedCertificateTimestamps"]);
+pub static PRECERT_POISON: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["PrecertPoison"]);
+pub static PRECERTIFICATE_SIGNED_CERTIFICATE_TIMESTAMPS: LazyPyImport = LazyPyImport::new(
+    "cryptography.x509",
+    &["PrecertificateSignedCertificateTimestamps"],
+);
+pub static DISTRIBUTION_POINT: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["DistributionPoint"]);
+pub static ACCESS_DESCRIPTION: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["AccessDescription"]);
+pub static AUTHORITY_KEY_IDENTIFIER: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["AuthorityKeyIdentifier"]);
+pub static UNRECOGNIZED_EXTENSION: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["UnrecognizedExtension"]);
+pub static EXTENSION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Extension"]);
+pub static EXTENSIONS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Extensions"]);
+pub static IPADDRESS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["IPAddress"]);
+pub static NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Name"]);
+pub static RELATIVE_DISTINGUISHED_NAME: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["RelativeDistinguishedName"]);
+pub static NAME_ATTRIBUTE: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["NameAttribute"]);
+pub static NAME_CONSTRAINTS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["NameConstraints"]);
+pub static MS_CERTIFICATE_TEMPLATE: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["MSCertificateTemplate"]);
+pub static CRL_DISTRIBUTION_POINTS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["CRLDistributionPoints"]);
+pub static BASIC_CONSTRAINTS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["BasicConstraints"]);
+pub static INHIBIT_ANY_POLICY: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["InhibitAnyPolicy"]);
+pub static OCSP_NO_CHECK: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OCSPNoCheck"]);
+pub static POLICY_CONSTRAINTS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["PolicyConstraints"]);
+pub static CERTIFICATE_POLICIES: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["CertificatePolicies"]);
+pub static SUBJECT_INFORMATION_ACCESS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["SubjectInformationAccess"]);
+pub static KEY_USAGE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["KeyUsage"]);
+pub static EXTENDED_KEY_USAGE: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["ExtendedKeyUsage"]);
+pub static SUBJECT_KEY_IDENTIFIER: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["SubjectKeyIdentifier"]);
+pub static TLS_FEATURE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["TLSFeature"]);
+pub static SUBJECT_ALTERNATIVE_NAME: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["SubjectAlternativeName"]);
+pub static POLICY_INFORMATION: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["PolicyInformation"]);
+pub static USER_NOTICE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["UserNotice"]);
+pub static NOTICE_REFERENCE: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["NoticeReference"]);
+pub static REGISTERED_ID: LazyPyImport = LazyPyImport::new("cryptography.x509", &["RegisteredID"]);
+pub static DIRECTORY_NAME: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["DirectoryName"]);
+pub static UNIFORM_RESOURCE_IDENTIFIER: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["UniformResourceIdentifier"]);
+pub static DNS_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["DNSName"]);
+pub static RFC822_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["RFC822Name"]);
+pub static OTHER_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OtherName"]);
+pub static CERTIFICATE_VERSION_V1: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["Version", "v1"]);
+pub static CERTIFICATE_VERSION_V3: LazyPyImport =
+    LazyPyImport::new("cryptography.x509", &["Version", "v3"]);
+
+pub static CRL_REASON_FLAGS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.extensions", &["_CRLREASONFLAGS"]);
+pub static REASON_BIT_MAPPING: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.extensions", &["_REASON_BIT_MAPPING"]);
+pub static TLS_FEATURE_TYPE_TO_ENUM: LazyPyImport = LazyPyImport::new(
+    "cryptography.x509.extensions",
+    &["_TLS_FEATURE_TYPE_TO_ENUM"],
+);
+
+pub static OCSP_RESPONSE_STATUS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.ocsp", &["OCSPResponseStatus"]);
+pub static OCSP_CERT_STATUS: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus"]);
+pub static OCSP_CERT_STATUS_GOOD: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus", "GOOD"]);
+pub static OCSP_CERT_STATUS_UNKNOWN: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus", "UNKNOWN"]);
+pub static OCSP_RESPONDER_ENCODING_HASH: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.ocsp", &["OCSPResponderEncoding", "HASH"]);
+
+pub static CERTIFICATE_TRANSPARENCY_VERSION_V1: LazyPyImport = LazyPyImport::new(
+    "cryptography.x509.certificate_transparency",
+    &["Version", "v1"],
+);
+pub static SIGNATURE_ALGORITHM: LazyPyImport = LazyPyImport::new(
+    "cryptography.x509.certificate_transparency",
+    &["SignatureAlgorithm"],
+);
+pub static LOG_ENTRY_TYPE_X509_CERTIFICATE: LazyPyImport = LazyPyImport::new(
+    "cryptography.x509.certificate_transparency",
+    &["LogEntryType", "X509_CERTIFICATE"],
+);
+pub static LOG_ENTRY_TYPE_PRE_CERTIFICATE: LazyPyImport = LazyPyImport::new(
+    "cryptography.x509.certificate_transparency",
+    &["LogEntryType", "PRE_CERTIFICATE"],
+);
+
+pub static ASN1_TYPE_TO_ENUM: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.name", &["_ASN1_TYPE_TO_ENUM"]);
+pub static ASN1_TYPE_BIT_STRING: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "BitString"]);
+pub static ASN1_TYPE_BMP_STRING: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "BMPString"]);
+pub static ASN1_TYPE_UNIVERSAL_STRING: LazyPyImport =
+    LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "UniversalString"]);
+
+pub static PKCS7_BINARY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.pkcs7",
+    &["PKCS7Options", "Binary"],
+);
+pub static PKCS7_TEXT: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.pkcs7",
+    &["PKCS7Options", "Text"],
+);
+pub static PKCS7_NO_ATTRIBUTES: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.pkcs7",
+    &["PKCS7Options", "NoAttributes"],
+);
+pub static PKCS7_NO_CAPABILITIES: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.pkcs7",
+    &["PKCS7Options", "NoCapabilities"],
+);
+pub static PKCS7_NO_CERTS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.pkcs7",
+    &["PKCS7Options", "NoCerts"],
+);
+pub static PKCS7_DETACHED_SIGNATURE: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.pkcs7",
+    &["PKCS7Options", "DetachedSignature"],
+);
+
+pub static SMIME_ENCODE: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.serialization.pkcs7",
+    &["_smime_encode"],
+);
+
+pub static HASHES_MODULE: LazyPyImport =
+    LazyPyImport::new("cryptography.hazmat.primitives.hashes", &[]);
+pub static HASH_ALGORITHM: LazyPyImport =
+    LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["HashAlgorithm"]);
+pub static EXTENDABLE_OUTPUT_FUNCTION: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.hashes",
+    &["ExtendableOutputFunction"],
+);
+pub static SHA1: LazyPyImport =
+    LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA1"]);
+
+pub static PREHASHED: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.utils",
+    &["Prehashed"],
+);
+pub static ASYMMETRIC_PADDING: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["AsymmetricPadding"],
+);
+pub static PADDING_AUTO: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["_Auto"],
+);
+pub static PADDING_MAX_LENGTH: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["_MaxLength"],
+);
+pub static PADDING_DIGEST_LENGTH: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["_DigestLength"],
+);
+pub static PKCS1V15: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["PKCS1v15"],
+);
+pub static PSS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["PSS"],
+);
+pub static OAEP: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["OAEP"],
+);
+pub static MGF1: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["MGF1"],
+);
+pub static CALCULATE_MAX_PSS_SALT_LENGTH: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.padding",
+    &["calculate_max_pss_salt_length"],
+);
+
+pub static CRL_ENTRY_REASON_ENUM_TO_CODE: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.backends.openssl.decode_asn1",
+    &["_CRL_ENTRY_REASON_ENUM_TO_CODE"],
+);
+pub static CALCULATE_DIGEST_AND_ALGORITHM: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.backends.openssl.utils",
+    &["_calculate_digest_and_algorithm"],
+);
+
+pub static RSA_PRIVATE_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.rsa",
+    &["RSAPrivateKey"],
+);
+pub static RSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.rsa",
+    &["RSAPublicKey"],
+);
+pub static RSA_PUBLIC_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.rsa",
+    &["RSAPublicNumbers"],
+);
+pub static RSA_PRIVATE_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.rsa",
+    &["RSAPrivateNumbers"],
+);
+
+pub static ELLIPTIC_CURVE_PRIVATE_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ec",
+    &["EllipticCurvePrivateKey"],
+);
+pub static ELLIPTIC_CURVE_PUBLIC_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ec",
+    &["EllipticCurvePublicKey"],
+);
+pub static CURVE_TYPES: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ec",
+    &["_CURVE_TYPES"],
+);
+pub static ECDSA: LazyPyImport =
+    LazyPyImport::new("cryptography.hazmat.primitives.asymmetric.ec", &["ECDSA"]);
+pub static ECDH: LazyPyImport =
+    LazyPyImport::new("cryptography.hazmat.primitives.asymmetric.ec", &["ECDH"]);
+pub static ELLIPTIC_CURVE_PUBLIC_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ec",
+    &["EllipticCurvePublicNumbers"],
+);
+pub static ELLIPTIC_CURVE_PRIVATE_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ec",
+    &["EllipticCurvePrivateNumbers"],
+);
+
+pub static ED25519_PRIVATE_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ed25519",
+    &["Ed25519PrivateKey"],
+);
+pub static ED25519_PUBLIC_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ed25519",
+    &["Ed25519PublicKey"],
+);
+
+pub static ED448_PRIVATE_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ed448",
+    &["Ed448PrivateKey"],
+);
+pub static ED448_PUBLIC_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.ed448",
+    &["Ed448PublicKey"],
+);
+
+pub static DH_PARAMETER_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dh",
+    &["DHParameterNumbers"],
+);
+pub static DH_PUBLIC_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dh",
+    &["DHPublicNumbers"],
+);
+pub static DH_PRIVATE_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dh",
+    &["DHPrivateNumbers"],
+);
+
+pub static DSA_PRIVATE_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dsa",
+    &["DSAPrivateKey"],
+);
+pub static DSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dsa",
+    &["DSAPublicKey"],
+);
+pub static DSA_PARAMETER_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dsa",
+    &["DSAParameterNumbers"],
+);
+pub static DSA_PUBLIC_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dsa",
+    &["DSAPublicNumbers"],
+);
+pub static DSA_PRIVATE_NUMBERS: LazyPyImport = LazyPyImport::new(
+    "cryptography.hazmat.primitives.asymmetric.dsa",
+    &["DSAPrivateNumbers"],
+);
+
+pub static EXTRACT_BUFFER_LENGTH: LazyPyImport =
+    LazyPyImport::new("cryptography.utils", &["_extract_buffer_length"]);
+
+#[cfg(test)]
+mod tests {
+    use super::LazyPyImport;
+
+    #[test]
+    fn test_basic() {
+        pyo3::prepare_freethreaded_python();
+
+        let v = LazyPyImport::new("foo", &["bar"]);
+        pyo3::Python::with_gil(|py| {
+            assert!(v.get(py).is_err());
+        });
+    }
+}
diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs
index 688ed07e8e68..5ebd7a24e002 100644
--- a/src/rust/src/x509/certificate.rs
+++ b/src/rust/src/x509/certificate.rs
@@ -8,7 +8,7 @@ use crate::asn1::{
 use crate::backend::hashes;
 use crate::error::{CryptographyError, CryptographyResult};
 use crate::x509::{extensions, sct, sign};
-use crate::{exceptions, x509};
+use crate::{exceptions, types, x509};
 use cryptography_x509::certificate::Certificate as RawCertificate;
 use cryptography_x509::common::{AlgorithmParameters, Asn1ReadableOrWritable};
 use cryptography_x509::extensions::{
@@ -81,13 +81,7 @@ impl Certificate {
             py,
             &asn1::write_single(&self.raw.borrow_dependent().tbs_cert.spki)?,
         );
-        Ok(py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization"
-            ))?
-            .getattr(pyo3::intern!(py, "load_der_public_key"))?
-            .call1((serialized,))?)
+        Ok(types::LOAD_DER_PUBLIC_KEY.get(py)?.call1((serialized,))?)
     }
 
     fn fingerprint<'p>(
@@ -248,7 +242,6 @@ impl Certificate {
 
     #[getter]
     fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::PyObject> {
-        let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
         x509::parse_and_cache_extensions(
             py,
             &self.cached_extensions,
@@ -256,21 +249,14 @@ impl Certificate {
             |ext| match ext.extn_id {
                 oid::PRECERT_POISON_OID => {
                     ext.value::<()>()?;
-                    Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "PrecertPoison"))?
-                            .call0()?,
-                    ))
+                    Ok(Some(types::PRECERT_POISON.get(py)?.call0()?))
                 }
                 oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID => {
                     let contents = ext.value::<&[u8]>()?;
                     let scts = sct::parse_scts(py, contents, sct::LogEntryType::PreCertificate)?;
                     Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(
-                                py,
-                                "PrecertificateSignedCertificateTimestamps"
-                            ))?
+                        types::PRECERTIFICATE_SIGNED_CERTIFICATE_TIMESTAMPS
+                            .get(py)?
                             .call1((scts,))?,
                     ))
                 }
@@ -311,14 +297,9 @@ impl Certificate {
 }
 
 fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     match version {
-        0 => Ok(x509_module
-            .getattr(pyo3::intern!(py, "Version"))?
-            .get_item(pyo3::intern!(py, "v1"))?),
-        2 => Ok(x509_module
-            .getattr(pyo3::intern!(py, "Version"))?
-            .get_item(pyo3::intern!(py, "v3"))?),
+        0 => Ok(types::CERTIFICATE_VERSION_V1.get(py)?),
+        2 => Ok(types::CERTIFICATE_VERSION_V3.get(py)?),
         _ => Err(CryptographyError::from(
             exceptions::InvalidVersion::new_err((
                 format!("{} is not a valid X509 version", version),
@@ -391,12 +372,10 @@ fn load_der_x509_certificate(
 
 fn warn_if_negative_serial(py: pyo3::Python<'_>, bytes: &'_ [u8]) -> pyo3::PyResult<()> {
     if bytes[0] & 0x80 != 0 {
-        let cryptography_warning = py
-            .import(pyo3::intern!(py, "cryptography.utils"))?
-            .getattr(pyo3::intern!(py, "DeprecatedIn36"))?;
+        let warning_cls = types::DEPRECATED_IN_36.get(py)?;
         pyo3::PyErr::warn(
             py,
-            cryptography_warning,
+            warning_cls,
             "Parsed a negative serial number, which is disallowed by RFC 5280.",
             1,
         )?;
@@ -417,12 +396,10 @@ fn warn_if_invalid_params(
         | AlgorithmParameters::DsaWithSha256(Some(..))
         | AlgorithmParameters::DsaWithSha384(Some(..))
         | AlgorithmParameters::DsaWithSha512(Some(..)) => {
-            let cryptography_warning = py
-                .import(pyo3::intern!(py, "cryptography.utils"))?
-                .getattr(pyo3::intern!(py, "DeprecatedIn41"))?;
+            let warning_cls = types::DEPRECATED_IN_41.get(py)?;
             pyo3::PyErr::warn(
                 py,
-                cryptography_warning,
+                warning_cls,
                 "The parsed certificate contains a NULL parameter value in its signature algorithm parameters. This is invalid and will be rejected in a future version of cryptography. If this certificate was created via Java, please upgrade to JDK21+ or the latest JDK11/17 once a fix is issued. If this certificate was created in some other fashion please report the issue to the cryptography issue tracker. See https://github.com/pyca/cryptography/issues/8996 and https://github.com/pyca/cryptography/issues/9253 for more details.",
                 2,
             )?;
@@ -441,12 +418,10 @@ fn parse_display_text(
         DisplayText::Utf8String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)),
         DisplayText::VisibleString(o) => {
             if asn1::VisibleString::new(o.as_str()).is_none() {
-                let cryptography_warning = py
-                    .import(pyo3::intern!(py, "cryptography.utils"))?
-                    .getattr(pyo3::intern!(py, "DeprecatedIn41"))?;
+                let warning_cls = types::DEPRECATED_IN_41.get(py)?;
                 pyo3::PyErr::warn(
                     py,
-                    cryptography_warning,
+                    warning_cls,
                     "Invalid ASN.1 (UTF-8 characters in a VisibleString) in the explicit text and/or notice reference of the certificate policies extension. In a future version of cryptography, an exception will be raised.",
                     1,
                 )?;
@@ -470,7 +445,6 @@ fn parse_user_notice(
     py: pyo3::Python<'_>,
     un: UserNotice<'_>,
 ) -> Result<pyo3::PyObject, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let et = match un.explicit_text {
         Some(data) => parse_display_text(py, data)?,
         None => py.None(),
@@ -482,15 +456,14 @@ fn parse_user_notice(
             for num in data.notice_numbers.unwrap_read().clone() {
                 numbers.append(big_byte_slice_to_py_int(py, num.as_bytes())?.to_object(py))?;
             }
-            x509_module
-                .call_method1(pyo3::intern!(py, "NoticeReference"), (org, numbers))?
+            types::NOTICE_REFERENCE
+                .get(py)?
+                .call1((org, numbers))?
                 .to_object(py)
         }
         None => py.None(),
     };
-    Ok(x509_module
-        .call_method1(pyo3::intern!(py, "UserNotice"), (nr, et))?
-        .to_object(py))
+    Ok(types::USER_NOTICE.get(py)?.call1((nr, et))?.to_object(py))
 }
 
 fn parse_policy_qualifiers<'a>(
@@ -532,7 +505,6 @@ fn parse_cp(
     ext: &Extension<'_>,
 ) -> Result<pyo3::PyObject, CryptographyError> {
     let cp = ext.value::<asn1::SequenceOf<'_, PolicyInformation<'_>>>()?;
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let certificate_policies = pyo3::types::PyList::empty(py);
     for policyinfo in cp {
         let pi_oid = oid_to_py_oid(py, &policyinfo.policy_identifier)?.to_object(py);
@@ -542,8 +514,9 @@ fn parse_cp(
             }
             None => py.None(),
         };
-        let pi = x509_module
-            .call_method1(pyo3::intern!(py, "PolicyInformation"), (pi_oid, py_pqis))?
+        let pi = types::POLICY_INFORMATION
+            .get(py)?
+            .call1((pi_oid, py_pqis))?
             .to_object(py);
         certificate_policies.append(pi)?;
     }
@@ -590,9 +563,8 @@ fn parse_distribution_point(
         Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?,
         None => py.None(),
     };
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
-    Ok(x509_module
-        .getattr(pyo3::intern!(py, "DistributionPoint"))?
+    Ok(types::DISTRIBUTION_POINT
+        .get(py)?
         .call1((full_name, relative_name, reasons, crl_issuer))?
         .to_object(py))
 }
@@ -614,9 +586,8 @@ pub(crate) fn parse_distribution_point_reasons(
     py: pyo3::Python<'_>,
     reasons: Option<&asn1::BitString<'_>>,
 ) -> Result<pyo3::PyObject, CryptographyError> {
-    let reason_bit_mapping = py
-        .import(pyo3::intern!(py, "cryptography.x509.extensions"))?
-        .getattr(pyo3::intern!(py, "_REASON_BIT_MAPPING"))?;
+    let reason_bit_mapping = types::REASON_BIT_MAPPING.get(py)?;
+
     Ok(match reasons {
         Some(bs) => {
             let mut vec = Vec::new();
@@ -635,9 +606,7 @@ pub(crate) fn encode_distribution_point_reasons(
     py: pyo3::Python<'_>,
     py_reasons: &pyo3::PyAny,
 ) -> pyo3::PyResult<asn1::OwnedBitString> {
-    let reason_flag_mapping = py
-        .import(pyo3::intern!(py, "cryptography.x509.extensions"))?
-        .getattr(pyo3::intern!(py, "_CRLREASONFLAGS"))?;
+    let reason_flag_mapping = types::CRL_REASON_FLAGS.get(py)?;
 
     let mut bits = vec![0, 0];
     for py_reason in py_reasons.iter()? {
@@ -657,7 +626,6 @@ pub(crate) fn parse_authority_key_identifier<'p>(
     py: pyo3::Python<'p>,
     ext: &Extension<'_>,
 ) -> Result<&'p pyo3::PyAny, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let aki = ext.value::<AuthorityKeyIdentifier<'_>>()?;
     let serial = match aki.authority_cert_serial_number {
         Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.to_object(py),
@@ -667,8 +635,8 @@ pub(crate) fn parse_authority_key_identifier<'p>(
         Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?,
         None => py.None(),
     };
-    Ok(x509_module
-        .getattr(pyo3::intern!(py, "AuthorityKeyIdentifier"))?
+    Ok(types::AUTHORITY_KEY_IDENTIFIER
+        .get(py)?
         .call1((aki.key_identifier, issuer, serial))?)
 }
 
@@ -676,14 +644,13 @@ pub(crate) fn parse_access_descriptions(
     py: pyo3::Python<'_>,
     ext: &Extension<'_>,
 ) -> Result<pyo3::PyObject, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let ads = pyo3::types::PyList::empty(py);
     let parsed = ext.value::<SequenceOfAccessDescriptions<'_>>()?;
     for access in parsed.unwrap_read().clone() {
         let py_oid = oid_to_py_oid(py, &access.access_method)?.to_object(py);
         let gn = x509::parse_general_name(py, access.access_location)?;
-        let ad = x509_module
-            .getattr(pyo3::intern!(py, "AccessDescription"))?
+        let ad = types::ACCESS_DESCRIPTION
+            .get(py)?
             .call1((py_oid, gn))?
             .to_object(py);
         ads.append(ad)?;
@@ -695,47 +662,36 @@ pub fn parse_cert_ext<'p>(
     py: pyo3::Python<'p>,
     ext: &Extension<'_>,
 ) -> CryptographyResult<Option<&'p pyo3::PyAny>> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     match ext.extn_id {
         oid::SUBJECT_ALTERNATIVE_NAME_OID => {
             let gn_seq = ext.value::<SubjectAlternativeName<'_>>()?;
             let sans = x509::parse_general_names(py, &gn_seq)?;
             Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "SubjectAlternativeName"))?
-                    .call1((sans,))?,
+                types::SUBJECT_ALTERNATIVE_NAME.get(py)?.call1((sans,))?,
             ))
         }
         oid::ISSUER_ALTERNATIVE_NAME_OID => {
             let gn_seq = ext.value::<IssuerAlternativeName<'_>>()?;
             let ians = x509::parse_general_names(py, &gn_seq)?;
             Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "IssuerAlternativeName"))?
-                    .call1((ians,))?,
+                types::ISSUER_ALTERNATIVE_NAME.get(py)?.call1((ians,))?,
             ))
         }
         oid::TLS_FEATURE_OID => {
-            let tls_feature_type_to_enum = py
-                .import(pyo3::intern!(py, "cryptography.x509.extensions"))?
-                .getattr(pyo3::intern!(py, "_TLS_FEATURE_TYPE_TO_ENUM"))?;
+            let tls_feature_type_to_enum = types::TLS_FEATURE_TYPE_TO_ENUM.get(py)?;
 
             let features = pyo3::types::PyList::empty(py);
             for feature in ext.value::<asn1::SequenceOf<'_, u64>>()? {
                 let py_feature = tls_feature_type_to_enum.get_item(feature.to_object(py))?;
                 features.append(py_feature)?;
             }
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "TLSFeature"))?
-                    .call1((features,))?,
-            ))
+            Ok(Some(types::TLS_FEATURE.get(py)?.call1((features,))?))
         }
         oid::SUBJECT_KEY_IDENTIFIER_OID => {
             let identifier = ext.value::<&[u8]>()?;
             Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "SubjectKeyIdentifier"))?
+                types::SUBJECT_KEY_IDENTIFIER
+                    .get(py)?
                     .call1((identifier,))?,
             ))
         }
@@ -745,101 +701,71 @@ pub fn parse_cert_ext<'p>(
                 let oid_obj = oid_to_py_oid(py, &oid)?;
                 ekus.append(oid_obj)?;
             }
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "ExtendedKeyUsage"))?
-                    .call1((ekus,))?,
-            ))
+            Ok(Some(types::EXTENDED_KEY_USAGE.get(py)?.call1((ekus,))?))
         }
         oid::KEY_USAGE_OID => {
             let kus = ext.value::<KeyUsage<'_>>()?;
 
-            Ok(Some(
-                x509_module.getattr(pyo3::intern!(py, "KeyUsage"))?.call1((
-                    kus.digital_signature(),
-                    kus.content_comitment(),
-                    kus.key_encipherment(),
-                    kus.data_encipherment(),
-                    kus.key_agreement(),
-                    kus.key_cert_sign(),
-                    kus.crl_sign(),
-                    kus.encipher_only(),
-                    kus.decipher_only(),
-                ))?,
-            ))
+            Ok(Some(types::KEY_USAGE.get(py)?.call1((
+                kus.digital_signature(),
+                kus.content_comitment(),
+                kus.key_encipherment(),
+                kus.data_encipherment(),
+                kus.key_agreement(),
+                kus.key_cert_sign(),
+                kus.crl_sign(),
+                kus.encipher_only(),
+                kus.decipher_only(),
+            ))?))
         }
         oid::AUTHORITY_INFORMATION_ACCESS_OID => {
             let ads = parse_access_descriptions(py, ext)?;
             Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))?
-                    .call1((ads,))?,
+                types::AUTHORITY_INFORMATION_ACCESS.get(py)?.call1((ads,))?,
             ))
         }
         oid::SUBJECT_INFORMATION_ACCESS_OID => {
             let ads = parse_access_descriptions(py, ext)?;
             Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "SubjectInformationAccess"))?
-                    .call1((ads,))?,
+                types::SUBJECT_INFORMATION_ACCESS.get(py)?.call1((ads,))?,
             ))
         }
         oid::CERTIFICATE_POLICIES_OID => {
             let cp = parse_cp(py, ext)?;
-            Ok(Some(x509_module.call_method1(
-                pyo3::intern!(py, "CertificatePolicies"),
-                (cp,),
-            )?))
+            Ok(Some(types::CERTIFICATE_POLICIES.get(py)?.call1((cp,))?))
         }
         oid::POLICY_CONSTRAINTS_OID => {
             let pc = ext.value::<PolicyConstraints>()?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "PolicyConstraints"))?
-                    .call1((pc.require_explicit_policy, pc.inhibit_policy_mapping))?,
-            ))
+            Ok(Some(types::POLICY_CONSTRAINTS.get(py)?.call1((
+                pc.require_explicit_policy,
+                pc.inhibit_policy_mapping,
+            ))?))
         }
         oid::OCSP_NO_CHECK_OID => {
             ext.value::<()>()?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "OCSPNoCheck"))?
-                    .call0()?,
-            ))
+            Ok(Some(types::OCSP_NO_CHECK.get(py)?.call0()?))
         }
         oid::INHIBIT_ANY_POLICY_OID => {
             let bignum = ext.value::<asn1::BigUint<'_>>()?;
             let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "InhibitAnyPolicy"))?
-                    .call1((pynum,))?,
-            ))
+            Ok(Some(types::INHIBIT_ANY_POLICY.get(py)?.call1((pynum,))?))
         }
         oid::BASIC_CONSTRAINTS_OID => {
             let bc = ext.value::<BasicConstraints>()?;
             Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "BasicConstraints"))?
+                types::BASIC_CONSTRAINTS
+                    .get(py)?
                     .call1((bc.ca, bc.path_length))?,
             ))
         }
         oid::AUTHORITY_KEY_IDENTIFIER_OID => Ok(Some(parse_authority_key_identifier(py, ext)?)),
         oid::CRL_DISTRIBUTION_POINTS_OID => {
             let dp = parse_distribution_points(py, ext)?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "CRLDistributionPoints"))?
-                    .call1((dp,))?,
-            ))
+            Ok(Some(types::CRL_DISTRIBUTION_POINTS.get(py)?.call1((dp,))?))
         }
         oid::FRESHEST_CRL_OID => {
             let dp = parse_distribution_points(py, ext)?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "FreshestCRL"))?
-                    .call1((dp,))?,
-            ))
+            Ok(Some(types::FRESHEST_CRL.get(py)?.call1((dp,))?))
         }
         oid::NAME_CONSTRAINTS_OID => {
             let nc = ext.value::<NameConstraints<'_>>()?;
@@ -852,19 +778,19 @@ pub fn parse_cert_ext<'p>(
                 None => py.None(),
             };
             Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "NameConstraints"))?
+                types::NAME_CONSTRAINTS
+                    .get(py)?
                     .call1((permitted_subtrees, excluded_subtrees))?,
             ))
         }
         oid::MS_CERTIFICATE_TEMPLATE => {
             let ms_cert_tpl = ext.value::<MSCertificateTemplate>()?;
             let py_oid = oid_to_py_oid(py, &ms_cert_tpl.template_id)?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "MSCertificateTemplate"))?
-                    .call1((py_oid, ms_cert_tpl.major_version, ms_cert_tpl.minor_version))?,
-            ))
+            Ok(Some(types::MS_CERTIFICATE_TEMPLATE.get(py)?.call1((
+                py_oid,
+                ms_cert_tpl.major_version,
+                ms_cert_tpl.minor_version,
+            ))?))
         }
         _ => Ok(None),
     }
@@ -898,23 +824,12 @@ fn create_x509_certificate(
 ) -> CryptographyResult<Certificate> {
     let sigalg =
         x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?;
-    let serialization_mod = py.import(pyo3::intern!(
-        py,
-        "cryptography.hazmat.primitives.serialization"
-    ))?;
-    let der_encoding = serialization_mod
-        .getattr(pyo3::intern!(py, "Encoding"))?
-        .getattr(pyo3::intern!(py, "DER"))?;
-    let spki_format = serialization_mod
-        .getattr(pyo3::intern!(py, "PublicFormat"))?
-        .getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?;
 
+    let der = types::ENCODING_DER.get(py)?;
+    let spki = types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?;
     let spki_bytes = builder
         .getattr(pyo3::intern!(py, "_public_key"))?
-        .call_method1(
-            pyo3::intern!(py, "public_bytes"),
-            (der_encoding, spki_format),
-        )?
+        .call_method1(pyo3::intern!(py, "public_bytes"), (der, spki))?
         .extract::<&[u8]>()?;
 
     let py_serial = builder
diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs
index 3c64b2f6829c..125397c11b0d 100644
--- a/src/rust/src/x509/common.rs
+++ b/src/rust/src/x509/common.rs
@@ -4,7 +4,7 @@
 
 use crate::asn1::{oid_to_py_oid, py_oid_to_oid};
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::{exceptions, x509};
+use crate::{exceptions, types, x509};
 use cryptography_x509::common::{Asn1ReadableOrWritable, AttributeTypeValue, RawTlv};
 use cryptography_x509::extensions::{
     AccessDescription, DuplicateExtensionsError, Extension, Extensions, RawExtensions,
@@ -53,18 +53,14 @@ pub(crate) fn encode_name_entry<'p>(
     py: pyo3::Python<'p>,
     py_name_entry: &'p pyo3::PyAny,
 ) -> CryptographyResult<AttributeTypeValue<'p>> {
-    let asn1_type = py
-        .import(pyo3::intern!(py, "cryptography.x509.name"))?
-        .getattr(pyo3::intern!(py, "_ASN1Type"))?;
-
     let attr_type = py_name_entry.getattr(pyo3::intern!(py, "_type"))?;
     let tag = attr_type
         .getattr(pyo3::intern!(py, "value"))?
         .extract::<u8>()?;
-    let value: &[u8] = if !attr_type.is(asn1_type.getattr(pyo3::intern!(py, "BitString"))?) {
-        let encoding = if attr_type.is(asn1_type.getattr(pyo3::intern!(py, "BMPString"))?) {
+    let value: &[u8] = if !attr_type.is(types::ASN1_TYPE_BIT_STRING.get(py)?) {
+        let encoding = if attr_type.is(types::ASN1_TYPE_BMP_STRING.get(py)?) {
             "utf_16_be"
-        } else if attr_type.is(asn1_type.getattr(pyo3::intern!(py, "UniversalString"))?) {
+        } else if attr_type.is(types::ASN1_TYPE_UNIVERSAL_STRING.get(py)?) {
             "utf_32_be"
         } else {
             "utf8"
@@ -112,21 +108,21 @@ pub(crate) fn encode_general_name<'a>(
     py: pyo3::Python<'a>,
     gn: &'a pyo3::PyAny,
 ) -> Result<GeneralName<'a>, CryptographyError> {
-    let gn_module = py.import(pyo3::intern!(py, "cryptography.x509.general_name"))?;
     let gn_type = gn.get_type().as_ref();
     let gn_value = gn.getattr(pyo3::intern!(py, "value"))?;
-    if gn_type.is(gn_module.getattr(pyo3::intern!(py, "DNSName"))?) {
+
+    if gn_type.is(types::DNS_NAME.get(py)?) {
         Ok(GeneralName::DNSName(UnvalidatedIA5String(
             gn_value.extract::<&str>()?,
         )))
-    } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RFC822Name"))?) {
+    } else if gn_type.is(types::RFC822_NAME.get(py)?) {
         Ok(GeneralName::RFC822Name(UnvalidatedIA5String(
             gn_value.extract::<&str>()?,
         )))
-    } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "DirectoryName"))?) {
+    } else if gn_type.is(types::DIRECTORY_NAME.get(py)?) {
         let name = encode_name(py, gn_value)?;
         Ok(GeneralName::DirectoryName(name))
-    } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "OtherName"))?) {
+    } else if gn_type.is(types::OTHER_NAME.get(py)?) {
         Ok(GeneralName::OtherName(OtherName {
             type_id: py_oid_to_oid(gn.getattr(pyo3::intern!(py, "type_id"))?)?,
             value: asn1::parse_single(gn_value.extract::<&[u8]>()?).map_err(|e| {
@@ -136,16 +132,16 @@ pub(crate) fn encode_general_name<'a>(
                 ))
             })?,
         }))
-    } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "UniformResourceIdentifier"))?) {
+    } else if gn_type.is(types::UNIFORM_RESOURCE_IDENTIFIER.get(py)?) {
         Ok(GeneralName::UniformResourceIdentifier(
             UnvalidatedIA5String(gn_value.extract::<&str>()?),
         ))
-    } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "IPAddress"))?) {
+    } else if gn_type.is(types::IPADDRESS.get(py)?) {
         Ok(GeneralName::IPAddress(
             gn.call_method0(pyo3::intern!(py, "_packed"))?
                 .extract::<&[u8]>()?,
         ))
-    } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RegisteredID"))?) {
+    } else if gn_type.is(types::REGISTERED_ID.get(py)?) {
         let oid = py_oid_to_oid(gn_value)?;
         Ok(GeneralName::RegisteredID(oid))
     } else {
@@ -177,24 +173,19 @@ pub(crate) fn parse_name<'p>(
     py: pyo3::Python<'p>,
     name: &NameReadable<'_>,
 ) -> Result<&'p pyo3::PyAny, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let py_rdns = pyo3::types::PyList::empty(py);
     for rdn in name.clone() {
         let py_rdn = parse_rdn(py, &rdn)?;
         py_rdns.append(py_rdn)?;
     }
-    Ok(x509_module.call_method1(pyo3::intern!(py, "Name"), (py_rdns,))?)
+    Ok(types::NAME.get(py)?.call1((py_rdns,))?)
 }
 
 fn parse_name_attribute(
     py: pyo3::Python<'_>,
     attribute: AttributeTypeValue<'_>,
 ) -> Result<pyo3::PyObject, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let oid = oid_to_py_oid(py, &attribute.type_id)?.to_object(py);
-    let tag_enum = py
-        .import(pyo3::intern!(py, "cryptography.x509.name"))?
-        .getattr(pyo3::intern!(py, "_ASN1_TYPE_TO_ENUM"))?;
     let tag_val = attribute
         .value
         .tag()
@@ -205,7 +196,7 @@ fn parse_name_attribute(
             ))
         })?
         .to_object(py);
-    let py_tag = tag_enum.get_item(tag_val)?;
+    let py_tag = types::ASN1_TYPE_TO_ENUM.get(py)?.get_item(tag_val)?;
     let py_data = match attribute.value.tag().as_u8() {
         // BitString tag value
         Some(3) => pyo3::types::PyBytes::new(py, attribute.value.data()),
@@ -226,12 +217,9 @@ fn parse_name_attribute(
         }
     };
     let kwargs = [("_validate", false)].into_py_dict(py);
-    Ok(x509_module
-        .call_method(
-            pyo3::intern!(py, "NameAttribute"),
-            (oid, py_data, py_tag),
-            Some(kwargs),
-        )?
+    Ok(types::NAME_ATTRIBUTE
+        .get(py)?
+        .call((oid, py_data, py_tag), Some(kwargs))?
         .to_object(py))
 }
 
@@ -239,14 +227,14 @@ pub(crate) fn parse_rdn<'a>(
     py: pyo3::Python<'_>,
     rdn: &asn1::SetOf<'a, AttributeTypeValue<'a>>,
 ) -> Result<pyo3::PyObject, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let py_attrs = pyo3::types::PyList::empty(py);
     for attribute in rdn.clone() {
         let na = parse_name_attribute(py, attribute)?;
         py_attrs.append(na)?;
     }
-    Ok(x509_module
-        .call_method1(pyo3::intern!(py, "RelativeDistinguishedName"), (py_attrs,))?
+    Ok(types::RELATIVE_DISTINGUISHED_NAME
+        .get(py)?
+        .call1((py_attrs,))?
         .to_object(py))
 }
 
@@ -254,44 +242,37 @@ pub(crate) fn parse_general_name(
     py: pyo3::Python<'_>,
     gn: GeneralName<'_>,
 ) -> Result<pyo3::PyObject, CryptographyError> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let py_gn = match gn {
         GeneralName::OtherName(data) => {
             let oid = oid_to_py_oid(py, &data.type_id)?.to_object(py);
-            x509_module
-                .call_method1(
-                    pyo3::intern!(py, "OtherName"),
-                    (oid, data.value.full_data()),
-                )?
+            types::OTHER_NAME
+                .get(py)?
+                .call1((oid, data.value.full_data()))?
                 .to_object(py)
         }
-        GeneralName::RFC822Name(data) => x509_module
-            .getattr(pyo3::intern!(py, "RFC822Name"))?
+        GeneralName::RFC822Name(data) => types::RFC822_NAME
+            .get(py)?
             .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))?
             .to_object(py),
-        GeneralName::DNSName(data) => x509_module
-            .getattr(pyo3::intern!(py, "DNSName"))?
+        GeneralName::DNSName(data) => types::DNS_NAME
+            .get(py)?
             .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))?
             .to_object(py),
         GeneralName::DirectoryName(data) => {
             let py_name = parse_name(py, data.unwrap_read())?;
-            x509_module
-                .call_method1(pyo3::intern!(py, "DirectoryName"), (py_name,))?
+            types::DIRECTORY_NAME
+                .get(py)?
+                .call1((py_name,))?
                 .to_object(py)
         }
-        GeneralName::UniformResourceIdentifier(data) => x509_module
-            .getattr(pyo3::intern!(py, "UniformResourceIdentifier"))?
+        GeneralName::UniformResourceIdentifier(data) => types::UNIFORM_RESOURCE_IDENTIFIER
+            .get(py)?
             .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))?
             .to_object(py),
         GeneralName::IPAddress(data) => {
-            let ip_module = py.import(pyo3::intern!(py, "ipaddress"))?;
             if data.len() == 4 || data.len() == 16 {
-                let addr = ip_module
-                    .call_method1(pyo3::intern!(py, "ip_address"), (data,))?
-                    .to_object(py);
-                x509_module
-                    .call_method1(pyo3::intern!(py, "IPAddress"), (addr,))?
-                    .to_object(py)
+                let addr = types::IPADDRESS_IPADDRESS.get(py)?.call1((data,))?;
+                types::IPADDRESS.get(py)?.call1((addr,))?.to_object(py)
             } else {
                 // if it's not an IPv4 or IPv6 we assume it's an IPNetwork and
                 // verify length in this function.
@@ -300,9 +281,7 @@ pub(crate) fn parse_general_name(
         }
         GeneralName::RegisteredID(data) => {
             let oid = oid_to_py_oid(py, &data)?.to_object(py);
-            x509_module
-                .call_method1(pyo3::intern!(py, "RegisteredID"), (oid,))?
-                .to_object(py)
+            types::REGISTERED_ID.get(py)?.call1((oid,))?.to_object(py)
         }
         _ => {
             return Err(CryptographyError::from(
@@ -331,8 +310,6 @@ fn create_ip_network(
     py: pyo3::Python<'_>,
     data: &[u8],
 ) -> Result<pyo3::PyObject, CryptographyError> {
-    let ip_module = py.import(pyo3::intern!(py, "ipaddress"))?;
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let prefix = match data.len() {
         8 => {
             let num = u32::from_be_bytes(data[4..].try_into().unwrap());
@@ -346,22 +323,17 @@ fn create_ip_network(
             format!("Invalid IPNetwork, must be 8 bytes for IPv4 and 32 bytes for IPv6. Found length: {}", data.len()),
         ))),
     };
-    let base = ip_module.call_method1(
-        "ip_address",
-        (pyo3::types::PyBytes::new(py, &data[..data.len() / 2]),),
-    )?;
+    let base = types::IPADDRESS_IPADDRESS
+        .get(py)?
+        .call1((pyo3::types::PyBytes::new(py, &data[..data.len() / 2]),))?;
     let net = format!(
         "{}/{}",
         base.getattr(pyo3::intern!(py, "exploded"))?
             .extract::<&str>()?,
         prefix?
     );
-    let addr = ip_module
-        .call_method1(pyo3::intern!(py, "ip_network"), (net,))?
-        .to_object(py);
-    Ok(x509_module
-        .call_method1(pyo3::intern!(py, "IPAddress"), (addr,))?
-        .to_object(py))
+    let addr = types::IPADDRESS_IPNETWORK.get(py)?.call1((net,))?;
+    Ok(types::IPADDRESS.get(py)?.call1((addr,))?.to_object(py))
 }
 
 fn ipv4_netmask(num: u32) -> Result<u32, CryptographyError> {
@@ -404,27 +376,23 @@ pub(crate) fn parse_and_cache_extensions<
                 }
             };
 
-            let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
             let exts = pyo3::types::PyList::empty(py);
             for raw_ext in extensions.iter() {
                 let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?;
 
                 let extn_value = match parse_ext(&raw_ext)? {
                     Some(e) => e,
-                    None => x509_module.call_method1(
-                        pyo3::intern!(py, "UnrecognizedExtension"),
-                        (oid_obj, raw_ext.extn_value),
-                    )?,
+                    None => types::UNRECOGNIZED_EXTENSION
+                        .get(py)?
+                        .call1((oid_obj, raw_ext.extn_value))?,
                 };
-                let ext_obj = x509_module.call_method1(
-                    pyo3::intern!(py, "Extension"),
-                    (oid_obj, raw_ext.critical, extn_value),
-                )?;
+                let ext_obj =
+                    types::EXTENSION
+                        .get(py)?
+                        .call1((oid_obj, raw_ext.critical, extn_value))?;
                 exts.append(ext_obj)?;
             }
-            Ok(x509_module
-                .call_method1(pyo3::intern!(py, "Extensions"), (exts,))?
-                .to_object(py))
+            Ok(types::EXTENSIONS.get(py)?.call1((exts,))?.to_object(py))
         })
         .map(|p| p.clone_ref(py))
 }
@@ -441,18 +409,13 @@ pub(crate) fn encode_extensions<
     py_exts: &'p pyo3::PyAny,
     encode_ext: F,
 ) -> pyo3::PyResult<Option<RawExtensions<'p>>> {
-    let unrecognized_extension_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(py, "cryptography.x509"))?
-        .getattr(pyo3::intern!(py, "UnrecognizedExtension"))?
-        .extract()?;
-
     let mut exts = vec![];
     for py_ext in py_exts.iter()? {
         let py_ext = py_ext?;
         let oid = py_oid_to_oid(py_ext.getattr(pyo3::intern!(py, "oid"))?)?;
 
         let ext_val = py_ext.getattr(pyo3::intern!(py, "value"))?;
-        if ext_val.is_instance(unrecognized_extension_type)? {
+        if ext_val.is_instance(types::UNRECOGNIZED_EXTENSION.get(py)?)? {
             exts.push(Extension {
                 extn_id: oid,
                 critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?,
@@ -511,17 +474,14 @@ pub(crate) fn datetime_to_py<'p>(
     py: pyo3::Python<'p>,
     dt: &asn1::DateTime,
 ) -> pyo3::PyResult<&'p pyo3::PyAny> {
-    let datetime_module = py.import(pyo3::intern!(py, "datetime"))?;
-    datetime_module
-        .getattr(pyo3::intern!(py, "datetime"))?
-        .call1((
-            dt.year(),
-            dt.month(),
-            dt.day(),
-            dt.hour(),
-            dt.minute(),
-            dt.second(),
-        ))
+    types::DATETIME_DATETIME.get(py)?.call1((
+        dt.year(),
+        dt.month(),
+        dt.day(),
+        dt.hour(),
+        dt.minute(),
+        dt.second(),
+    ))
 }
 
 pub(crate) fn py_to_datetime(
@@ -540,15 +500,12 @@ pub(crate) fn py_to_datetime(
 }
 
 pub(crate) fn datetime_now(py: pyo3::Python<'_>) -> pyo3::PyResult<asn1::DateTime> {
-    let datetime_module = py.import(pyo3::intern!(py, "datetime"))?;
-    let utc = datetime_module
-        .getattr(pyo3::intern!(py, "timezone"))?
-        .getattr(pyo3::intern!(py, "utc"))?;
+    let utc = types::DATETIME_TIMEZONE_UTC.get(py)?;
 
     py_to_datetime(
         py,
-        datetime_module
-            .getattr(pyo3::intern!(py, "datetime"))?
+        types::DATETIME_DATETIME
+            .get(py)?
             .call_method1(pyo3::intern!(py, "now"), (utc,))?,
     )
 }
diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs
index d1535b31b6cb..e9035b665da7 100644
--- a/src/rust/src/x509/crl.rs
+++ b/src/rust/src/x509/crl.rs
@@ -8,7 +8,7 @@ use crate::asn1::{
 use crate::backend::hashes::Hash;
 use crate::error::{CryptographyError, CryptographyResult};
 use crate::x509::{certificate, extensions, sign};
-use crate::{exceptions, x509};
+use crate::{exceptions, types, x509};
 use cryptography_x509::extensions::{Extension, IssuerAlternativeName};
 use cryptography_x509::{
     common,
@@ -199,11 +199,7 @@ impl CertificateRevocationList {
         py: pyo3::Python<'p>,
     ) -> pyo3::PyResult<&'p pyo3::PyAny> {
         let oid = self.signature_algorithm_oid(py)?;
-        let oid_module = py.import(pyo3::intern!(py, "cryptography.hazmat._oid"))?;
-        match oid_module
-            .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?
-            .get_item(oid)
-        {
+        match types::SIG_OIDS_TO_HASH.get(py)?.get_item(oid) {
             Ok(v) => Ok(v),
             Err(_) => Err(exceptions::UnsupportedAlgorithm::new_err(format!(
                 "Signature algorithm OID: {} not recognized",
@@ -272,7 +268,6 @@ impl CertificateRevocationList {
     fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::PyObject> {
         let tbs_cert_list = &self.owned.borrow_dependent().tbs_cert_list;
 
-        let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
         x509::parse_and_cache_extensions(
             py,
             &self.cached_extensions,
@@ -281,36 +276,24 @@ impl CertificateRevocationList {
                 oid::CRL_NUMBER_OID => {
                     let bignum = ext.value::<asn1::BigUint<'_>>()?;
                     let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?;
-                    Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "CRLNumber"))?
-                            .call1((pynum,))?,
-                    ))
+                    Ok(Some(types::CRL_NUMBER.get(py)?.call1((pynum,))?))
                 }
                 oid::DELTA_CRL_INDICATOR_OID => {
                     let bignum = ext.value::<asn1::BigUint<'_>>()?;
                     let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?;
-                    Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "DeltaCRLIndicator"))?
-                            .call1((pynum,))?,
-                    ))
+                    Ok(Some(types::DELTA_CRL_INDICATOR.get(py)?.call1((pynum,))?))
                 }
                 oid::ISSUER_ALTERNATIVE_NAME_OID => {
                     let gn_seq = ext.value::<IssuerAlternativeName<'_>>()?;
                     let ians = x509::parse_general_names(py, &gn_seq)?;
                     Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "IssuerAlternativeName"))?
-                            .call1((ians,))?,
+                        types::ISSUER_ALTERNATIVE_NAME.get(py)?.call1((ians,))?,
                     ))
                 }
                 oid::AUTHORITY_INFORMATION_ACCESS_OID => {
                     let ads = certificate::parse_access_descriptions(py, ext)?;
                     Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))?
-                            .call1((ads,))?,
+                        types::AUTHORITY_INFORMATION_ACCESS.get(py)?.call1((ads,))?,
                     ))
                 }
                 oid::AUTHORITY_KEY_IDENTIFIER_OID => {
@@ -330,27 +313,19 @@ impl CertificateRevocationList {
                     } else {
                         py.None()
                     };
-                    Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "IssuingDistributionPoint"))?
-                            .call1((
-                                full_name,
-                                relative_name,
-                                idp.only_contains_user_certs,
-                                idp.only_contains_ca_certs,
-                                py_reasons,
-                                idp.indirect_crl,
-                                idp.only_contains_attribute_certs,
-                            ))?,
-                    ))
+                    Ok(Some(types::ISSUING_DISTRIBUTION_POINT.get(py)?.call1((
+                        full_name,
+                        relative_name,
+                        idp.only_contains_user_certs,
+                        idp.only_contains_ca_certs,
+                        py_reasons,
+                        idp.indirect_crl,
+                        idp.only_contains_attribute_certs,
+                    ))?))
                 }
                 oid::FRESHEST_CRL_OID => {
                     let dp = certificate::parse_distribution_points(py, ext)?;
-                    Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "FreshestCRL"))?
-                            .call1((dp,))?,
-                    ))
+                    Ok(Some(types::FRESHEST_CRL.get(py)?.call1((dp,))?))
                 }
                 _ => Ok(None),
             },
@@ -529,7 +504,6 @@ pub(crate) fn parse_crl_reason_flags<'p>(
     py: pyo3::Python<'p>,
     reason: &crl::CRLReason,
 ) -> CryptographyResult<&'p pyo3::PyAny> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     let flag_name = match reason.value() {
         0 => "unspecified",
         1 => "key_compromise",
@@ -550,42 +524,27 @@ pub(crate) fn parse_crl_reason_flags<'p>(
             ))
         }
     };
-    Ok(x509_module
-        .getattr(pyo3::intern!(py, "ReasonFlags"))?
-        .getattr(flag_name)?)
+    Ok(types::REASON_FLAGS.get(py)?.getattr(flag_name)?)
 }
 
 pub fn parse_crl_entry_ext<'p>(
     py: pyo3::Python<'p>,
     ext: &Extension<'_>,
 ) -> CryptographyResult<Option<&'p pyo3::PyAny>> {
-    let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
     match ext.extn_id {
         oid::CRL_REASON_OID => {
             let flags = parse_crl_reason_flags(py, &ext.value::<crl::CRLReason>()?)?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "CRLReason"))?
-                    .call1((flags,))?,
-            ))
+            Ok(Some(types::CRL_REASON.get(py)?.call1((flags,))?))
         }
         oid::CERTIFICATE_ISSUER_OID => {
             let gn_seq = ext.value::<asn1::SequenceOf<'_, name::GeneralName<'_>>>()?;
             let gns = x509::parse_general_names(py, &gn_seq)?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "CertificateIssuer"))?
-                    .call1((gns,))?,
-            ))
+            Ok(Some(types::CERTIFICATE_ISSUER.get(py)?.call1((gns,))?))
         }
         oid::INVALIDITY_DATE_OID => {
             let time = ext.value::<asn1::GeneralizedTime>()?;
             let py_dt = x509::datetime_to_py(py, time.as_datetime())?;
-            Ok(Some(
-                x509_module
-                    .getattr(pyo3::intern!(py, "InvalidityDate"))?
-                    .call1((py_dt,))?,
-            ))
+            Ok(Some(types::INVALIDITY_DATE.get(py)?.call1((py_dt,))?))
         }
         _ => Ok(None),
     }
diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs
index b6718a50385a..cab13b7a1033 100644
--- a/src/rust/src/x509/csr.rs
+++ b/src/rust/src/x509/csr.rs
@@ -5,7 +5,7 @@
 use crate::asn1::{encode_der_data, oid_to_py_oid, py_oid_to_oid};
 use crate::error::{CryptographyError, CryptographyResult};
 use crate::x509::{certificate, sign};
-use crate::{exceptions, x509};
+use crate::{exceptions, types, x509};
 use asn1::SimpleAsn1Readable;
 use cryptography_x509::csr::{check_attribute_length, Attribute, CertificationRequestInfo, Csr};
 use cryptography_x509::{common, oid};
@@ -61,13 +61,7 @@ impl CertificateSigningRequest {
             py,
             &asn1::write_single(&self.raw.borrow_dependent().csr_info.spki)?,
         );
-        Ok(py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization"
-            ))?
-            .getattr(pyo3::intern!(py, "load_der_public_key"))?
-            .call1((serialized,))?)
+        Ok(types::LOAD_DER_PUBLIC_KEY.get(py)?.call1((serialized,))?)
     }
 
     #[getter]
@@ -97,19 +91,7 @@ impl CertificateSigningRequest {
         &self,
         py: pyo3::Python<'p>,
     ) -> Result<&'p pyo3::PyAny, CryptographyError> {
-        let sig_oids_to_hash = py
-            .import(pyo3::intern!(py, "cryptography.hazmat._oid"))?
-            .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?;
-        let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?);
-        match hash_alg {
-            Ok(data) => Ok(data),
-            Err(_) => Err(CryptographyError::from(
-                exceptions::UnsupportedAlgorithm::new_err(format!(
-                    "Signature algorithm OID: {} not recognized",
-                    self.raw.borrow_dependent().signature_alg.oid()
-                )),
-            )),
-        }
+        sign::identify_signature_hash_algorithm(py, &self.raw.borrow_dependent().signature_alg)
     }
 
     #[getter]
@@ -117,6 +99,17 @@ impl CertificateSigningRequest {
         oid_to_py_oid(py, self.raw.borrow_dependent().signature_alg.oid())
     }
 
+    #[getter]
+    fn signature_algorithm_parameters<'p>(
+        &'p self,
+        py: pyo3::Python<'p>,
+    ) -> CryptographyResult<&'p pyo3::PyAny> {
+        sign::identify_signature_algorithm_parameters(
+            py,
+            &self.raw.borrow_dependent().signature_alg,
+        )
+    }
+
     fn public_bytes<'p>(
         &self,
         py: pyo3::Python<'p>,
@@ -132,15 +125,10 @@ impl CertificateSigningRequest {
         py: pyo3::Python<'p>,
         oid: &pyo3::PyAny,
     ) -> pyo3::PyResult<&'p pyo3::PyAny> {
-        let cryptography_warning = py
-            .import(pyo3::intern!(py, "cryptography.utils"))?
-            .getattr(pyo3::intern!(py, "DeprecatedIn36"))?;
-        pyo3::PyErr::warn(
-            py,
-            cryptography_warning,
-            "CertificateSigningRequest.get_attribute_for_oid has been deprecated. Please switch to request.attributes.get_attribute_for_oid.",
-            1,
-        )?;
+        let warning_cls = types::DEPRECATED_IN_36.get(py)?;
+        let warning_msg = "CertificateSigningRequest.get_attribute_for_oid has been deprecated. Please switch to request.attributes.get_attribute_for_oid.";
+        pyo3::PyErr::warn(py, warning_cls, warning_msg, 1)?;
+
         let rust_oid = py_oid_to_oid(oid)?;
         for attribute in self
             .raw
@@ -201,13 +189,10 @@ impl CertificateSigningRequest {
                     "Long-form tags are not supported in CSR attribute values",
                 ))
             })?;
-            let pyattr = py
-                .import(pyo3::intern!(py, "cryptography.x509"))?
-                .call_method1(pyo3::intern!(py, "Attribute"), (oid, serialized, tag))?;
+            let pyattr = types::ATTRIBUTE.get(py)?.call1((oid, serialized, tag))?;
             pyattrs.append(pyattr)?;
         }
-        py.import(pyo3::intern!(py, "cryptography.x509"))?
-            .call_method1(pyo3::intern!(py, "Attributes"), (pyattrs,))
+        types::ATTRIBUTES.get(py)?.call1((pyattrs,))
     }
 
     #[getter]
@@ -292,30 +277,16 @@ fn create_x509_csr(
     builder: &pyo3::PyAny,
     private_key: &pyo3::PyAny,
     hash_algorithm: &pyo3::PyAny,
+    rsa_padding: &pyo3::PyAny,
 ) -> CryptographyResult<CertificateSigningRequest> {
-    let sigalg = x509::sign::compute_signature_algorithm(
-        py,
-        private_key,
-        hash_algorithm,
-        py.None().into_ref(py),
-    )?;
-    let serialization_mod = py.import(pyo3::intern!(
-        py,
-        "cryptography.hazmat.primitives.serialization"
-    ))?;
-    let der_encoding = serialization_mod
-        .getattr(pyo3::intern!(py, "Encoding"))?
-        .getattr(pyo3::intern!(py, "DER"))?;
-    let spki_format = serialization_mod
-        .getattr(pyo3::intern!(py, "PublicFormat"))?
-        .getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?;
+    let sigalg =
+        x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?;
 
+    let der = types::ENCODING_DER.get(py)?;
+    let spki = types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?;
     let spki_bytes = private_key
         .call_method0(pyo3::intern!(py, "public_key"))?
-        .call_method1(
-            pyo3::intern!(py, "public_bytes"),
-            (der_encoding, spki_format),
-        )?
+        .call_method1(pyo3::intern!(py, "public_bytes"), (der, spki))?
         .extract::<&[u8]>()?;
 
     let mut attrs = vec![];
@@ -368,13 +339,8 @@ fn create_x509_csr(
     };
 
     let tbs_bytes = asn1::write_single(&csr_info)?;
-    let signature = x509::sign::sign_data(
-        py,
-        private_key,
-        hash_algorithm,
-        py.None().into_ref(py),
-        &tbs_bytes,
-    )?;
+    let signature =
+        x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?;
     let data = asn1::write_single(&Csr {
         csr_info,
         signature_alg: sigalg,
diff --git a/src/rust/src/x509/extensions.rs b/src/rust/src/x509/extensions.rs
index dcf28833f17f..94dfe8fe8ac2 100644
--- a/src/rust/src/x509/extensions.rs
+++ b/src/rust/src/x509/extensions.rs
@@ -4,8 +4,8 @@
 
 use crate::asn1::{py_oid_to_oid, py_uint_to_big_endian_bytes};
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::x509;
 use crate::x509::{certificate, sct};
+use crate::{types, x509};
 use cryptography_x509::{common, crl, extensions, oid};
 
 fn encode_general_subtrees<'a>(
@@ -462,13 +462,8 @@ pub(crate) fn encode_extension(
             Ok(Some(der))
         }
         &oid::CRL_REASON_OID => {
-            let value = ext
-                .py()
-                .import(pyo3::intern!(
-                    py,
-                    "cryptography.hazmat.backends.openssl.decode_asn1"
-                ))?
-                .getattr(pyo3::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))?
+            let value = types::CRL_ENTRY_REASON_ENUM_TO_CODE
+                .get(ext.py())?
                 .get_item(ext.getattr(pyo3::intern!(py, "reason"))?)?
                 .extract::<u32>()?;
             Ok(Some(asn1::write_single(&asn1::Enumerated::new(value))?))
diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs
index 38704613fa9e..97547097d09e 100644
--- a/src/rust/src/x509/ocsp_req.rs
+++ b/src/rust/src/x509/ocsp_req.rs
@@ -5,7 +5,7 @@
 use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes};
 use crate::error::{CryptographyError, CryptographyResult};
 use crate::x509::{extensions, ocsp};
-use crate::{exceptions, x509};
+use crate::{exceptions, types, x509};
 use cryptography_x509::{
     common,
     ocsp_req::{self, OCSPRequest as RawOCSPRequest},
@@ -89,9 +89,8 @@ impl OCSPRequest {
     ) -> Result<&'p pyo3::PyAny, CryptographyError> {
         let cert_id = self.cert_id();
 
-        let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?;
         match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&cert_id.hash_algorithm.params) {
-            Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?),
+            Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?),
             None => Err(CryptographyError::from(
                 exceptions::UnsupportedAlgorithm::new_err(format!(
                     "Signature algorithm OID: {} not recognized",
@@ -114,7 +113,6 @@ impl OCSPRequest {
     fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::PyObject> {
         let tbs_request = &self.raw.borrow_dependent().tbs_request;
 
-        let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
         x509::parse_and_cache_extensions(
             py,
             &self.cached_extensions,
@@ -129,9 +127,7 @@ impl OCSPRequest {
                         // the nonce. So we try parsing as a TLV and fall back to just using
                         // the raw value.
                         let nonce = ext.value::<&[u8]>().unwrap_or(ext.extn_value);
-                        Ok(Some(
-                            x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?,
-                        ))
+                        Ok(Some(types::OCSP_NONCE.get(py)?.call1((nonce,))?))
                     }
                     oid::ACCEPTABLE_RESPONSES_OID => {
                         let oids = ext.value::<asn1::SequenceOf<'_, asn1::ObjectIdentifier>>()?;
@@ -140,10 +136,11 @@ impl OCSPRequest {
                             py_oids.append(oid_to_py_oid(py, &oid)?)?;
                         }
 
-                        Ok(Some(x509_module.call_method1(
-                            pyo3::intern!(py, "OCSPAcceptableResponses"),
-                            (py_oids,),
-                        )?))
+                        Ok(Some(
+                            types::OCSP_ACCEPTABLE_RESPONSES
+                                .get(py)?
+                                .call1((py_oids,))?,
+                        ))
                     }
                     _ => Ok(None),
                 }
@@ -156,14 +153,7 @@ impl OCSPRequest {
         py: pyo3::Python<'p>,
         encoding: &pyo3::PyAny,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let der = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization"
-            ))?
-            .getattr(pyo3::intern!(py, "Encoding"))?
-            .getattr(pyo3::intern!(py, "DER"))?;
-        if !encoding.is(der) {
+        if !encoding.is(types::ENCODING_DER.get(py)?) {
             return Err(pyo3::exceptions::PyValueError::new_err(
                 "The only allowed encoding value is Encoding.DER",
             )
diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs
index e6e8f77851fe..679dff6e6e09 100644
--- a/src/rust/src/x509/ocsp_resp.rs
+++ b/src/rust/src/x509/ocsp_resp.rs
@@ -5,7 +5,7 @@
 use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid};
 use crate::error::{CryptographyError, CryptographyResult};
 use crate::x509::{certificate, crl, extensions, ocsp, py_to_datetime, sct};
-use crate::{exceptions, x509};
+use crate::{exceptions, types, x509};
 use cryptography_x509::ocsp_resp::SingleResponse;
 use cryptography_x509::{
     common,
@@ -138,9 +138,7 @@ impl OCSPResponse {
             assert_eq!(status, UNAUTHORIZED_RESPONSE);
             "UNAUTHORIZED"
         };
-        py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))?
-            .getattr(pyo3::intern!(py, "OCSPResponseStatus"))?
-            .getattr(attr)
+        types::OCSP_RESPONSE_STATUS.get(py)?.getattr(attr)
     }
 
     #[getter]
@@ -182,10 +180,9 @@ impl OCSPResponse {
         &self,
         py: pyo3::Python<'p>,
     ) -> Result<&'p pyo3::PyAny, CryptographyError> {
-        let sig_oids_to_hash = py
-            .import(pyo3::intern!(py, "cryptography.hazmat._oid"))?
-            .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?;
-        let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?);
+        let hash_alg = types::SIG_OIDS_TO_HASH
+            .get(py)?
+            .get_item(self.signature_algorithm_oid(py)?);
         match hash_alg {
             Ok(data) => Ok(data),
             Err(_) => {
@@ -333,7 +330,6 @@ impl OCSPResponse {
             .get()
             .tbs_response_data;
 
-        let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
         x509::parse_and_cache_extensions(
             py,
             &self.cached_extensions,
@@ -348,9 +344,7 @@ impl OCSPResponse {
                         // the nonce. So we try parsing as a TLV and fall back to just using
                         // the raw value.
                         let nonce = ext.value::<&[u8]>().unwrap_or(ext.extn_value);
-                        Ok(Some(
-                            x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?,
-                        ))
+                        Ok(Some(types::OCSP_NONCE.get(py)?.call1((nonce,))?))
                     }
                     _ => Ok(None),
                 }
@@ -371,7 +365,6 @@ impl OCSPResponse {
                 .get(),
         )?;
 
-        let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
         x509::parse_and_cache_extensions(
             py,
             &self.cached_single_extensions,
@@ -381,8 +374,8 @@ impl OCSPResponse {
                     let contents = ext.value::<&[u8]>()?;
                     let scts = sct::parse_scts(py, contents, sct::LogEntryType::Certificate)?;
                     Ok(Some(
-                        x509_module
-                            .getattr(pyo3::intern!(py, "SignedCertificateTimestamps"))?
+                        types::SIGNED_CERTIFICATE_TIMESTAMPS
+                            .get(py)?
                             .call1((scts,))?,
                     ))
                 }
@@ -396,14 +389,7 @@ impl OCSPResponse {
         py: pyo3::Python<'p>,
         encoding: &pyo3::PyAny,
     ) -> CryptographyResult<&'p pyo3::types::PyBytes> {
-        let der = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.serialization"
-            ))?
-            .getattr(pyo3::intern!(py, "Encoding"))?
-            .getattr(pyo3::intern!(py, "DER"))?;
-        if !encoding.is(der) {
+        if !encoding.is(types::ENCODING_DER.get(py)?) {
             return Err(pyo3::exceptions::PyValueError::new_err(
                 "The only allowed encoding value is Encoding.DER",
             )
@@ -476,18 +462,15 @@ fn singleresp_py_certificate_status<'p>(
         ocsp_resp::CertStatus::Revoked(_) => pyo3::intern!(py, "REVOKED"),
         ocsp_resp::CertStatus::Unknown(_) => pyo3::intern!(py, "UNKNOWN"),
     };
-    py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))?
-        .getattr(pyo3::intern!(py, "OCSPCertStatus"))?
-        .getattr(attr)
+    types::OCSP_CERT_STATUS.get(py)?.getattr(attr)
 }
 
 fn singleresp_py_hash_algorithm<'p>(
     resp: &ocsp_resp::SingleResponse<'_>,
     py: pyo3::Python<'p>,
 ) -> Result<&'p pyo3::PyAny, CryptographyError> {
-    let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?;
     match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&resp.cert_id.hash_algorithm.params) {
-        Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?),
+        Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?),
         None => Err(CryptographyError::from(
             exceptions::UnsupportedAlgorithm::new_err(format!(
                 "Signature algorithm OID: {} not recognized",
@@ -560,8 +543,6 @@ fn create_ocsp_response(
     let borrowed_cert;
     let py_certs: Option<Vec<pyo3::PyRef<'_, x509::certificate::Certificate>>>;
     let response_bytes = if response_status == SUCCESSFUL_RESPONSE {
-        let ocsp_mod = py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))?;
-
         let py_single_resp = builder.getattr(pyo3::intern!(py, "_response"))?;
         py_cert = py_single_resp
             .getattr(pyo3::intern!(py, "_cert"))?
@@ -578,27 +559,17 @@ fn create_ocsp_response(
             .extract()?;
 
         let py_cert_status = py_single_resp.getattr(pyo3::intern!(py, "_cert_status"))?;
-        let cert_status = if py_cert_status.is(ocsp_mod
-            .getattr(pyo3::intern!(py, "OCSPCertStatus"))?
-            .getattr(pyo3::intern!(py, "GOOD"))?)
-        {
+        let cert_status = if py_cert_status.is(types::OCSP_CERT_STATUS_GOOD.get(py)?) {
             ocsp_resp::CertStatus::Good(())
-        } else if py_cert_status.is(ocsp_mod
-            .getattr(pyo3::intern!(py, "OCSPCertStatus"))?
-            .getattr(pyo3::intern!(py, "UNKNOWN"))?)
-        {
+        } else if py_cert_status.is(types::OCSP_CERT_STATUS_UNKNOWN.get(py)?) {
             ocsp_resp::CertStatus::Unknown(())
         } else {
             let revocation_reason = if !py_single_resp
                 .getattr(pyo3::intern!(py, "_revocation_reason"))?
                 .is_none()
             {
-                let value = py
-                    .import(pyo3::intern!(
-                        py,
-                        "cryptography.hazmat.backends.openssl.decode_asn1"
-                    ))?
-                    .getattr(pyo3::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))?
+                let value = types::CRL_ENTRY_REASON_ENUM_TO_CODE
+                    .get(py)?
                     .get_item(py_single_resp.getattr(pyo3::intern!(py, "_revocation_reason"))?)?
                     .extract::<u32>()?;
                 Some(asn1::Enumerated::new(value))
@@ -639,14 +610,8 @@ fn create_ocsp_response(
         }];
 
         borrowed_cert = responder_cert.borrow();
-        let responder_id = if responder_encoding.is(ocsp_mod
-            .getattr(pyo3::intern!(py, "OCSPResponderEncoding"))?
-            .getattr(pyo3::intern!(py, "HASH"))?)
-        {
-            let sha1 = py
-                .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?
-                .getattr(pyo3::intern!(py, "SHA1"))?
-                .call0()?;
+        let responder_id = if responder_encoding.is(types::OCSP_RESPONDER_ENCODING_HASH.get(py)?) {
+            let sha1 = types::SHA1.get(py)?.call0()?;
             ocsp_resp::ResponderId::ByKey(ocsp::hash_data(
                 py,
                 sha1,
diff --git a/src/rust/src/x509/sct.rs b/src/rust/src/x509/sct.rs
index 22eaed817e57..173364cd2a10 100644
--- a/src/rust/src/x509/sct.rs
+++ b/src/rust/src/x509/sct.rs
@@ -3,6 +3,7 @@
 // for complete details.
 
 use crate::error::CryptographyError;
+use crate::types;
 use pyo3::types::IntoPyDict;
 use pyo3::ToPyObject;
 use std::collections::hash_map::DefaultHasher;
@@ -164,12 +165,7 @@ impl Sct {
 
     #[getter]
     fn version<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> {
-        py.import(pyo3::intern!(
-            py,
-            "cryptography.x509.certificate_transparency"
-        ))?
-        .getattr(pyo3::intern!(py, "Version"))?
-        .getattr(pyo3::intern!(py, "v1"))
+        types::CERTIFICATE_TRANSPARENCY_VERSION_V1.get(py)
     }
 
     #[getter]
@@ -179,10 +175,8 @@ impl Sct {
 
     #[getter]
     fn timestamp<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> {
-        let datetime_class = py
-            .import(pyo3::intern!(py, "datetime"))?
-            .getattr(pyo3::intern!(py, "datetime"))?;
-        datetime_class
+        types::DATETIME_DATETIME
+            .get(py)?
             .call_method1(
                 pyo3::intern!(py, "utcfromtimestamp"),
                 (self.timestamp / 1000,),
@@ -196,17 +190,10 @@ impl Sct {
 
     #[getter]
     fn entry_type<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> {
-        let et_class = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.x509.certificate_transparency"
-            ))?
-            .getattr(pyo3::intern!(py, "LogEntryType"))?;
-        let attr_name = match self.entry_type {
-            LogEntryType::Certificate => "X509_CERTIFICATE",
-            LogEntryType::PreCertificate => "PRE_CERTIFICATE",
-        };
-        et_class.getattr(attr_name)
+        Ok(match self.entry_type {
+            LogEntryType::Certificate => types::LOG_ENTRY_TYPE_X509_CERTIFICATE.get(py)?,
+            LogEntryType::PreCertificate => types::LOG_ENTRY_TYPE_PRE_CERTIFICATE.get(py)?,
+        })
     }
 
     #[getter]
@@ -214,19 +201,16 @@ impl Sct {
         &self,
         py: pyo3::Python<'p>,
     ) -> pyo3::PyResult<&'p pyo3::PyAny> {
-        let hashes_mod = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?;
-        hashes_mod.call_method0(self.hash_algorithm.to_attr())
+        types::HASHES_MODULE
+            .get(py)?
+            .call_method0(self.hash_algorithm.to_attr())
     }
 
     #[getter]
     fn signature_algorithm<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> {
-        let sa_class = py
-            .import(pyo3::intern!(
-                py,
-                "cryptography.x509.certificate_transparency"
-            ))?
-            .getattr(pyo3::intern!(py, "SignatureAlgorithm"))?;
-        sa_class.getattr(self.signature_algorithm.to_attr())
+        types::SIGNATURE_ALGORITHM
+            .get(py)?
+            .getattr(self.signature_algorithm.to_attr())
     }
 
     #[getter]
diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs
index 0e3c1bc728b2..47212b555c42 100644
--- a/src/rust/src/x509/sign.rs
+++ b/src/rust/src/x509/sign.rs
@@ -4,7 +4,7 @@
 
 use crate::asn1::oid_to_py_oid;
 use crate::error::{CryptographyError, CryptographyResult};
-use crate::exceptions;
+use crate::{exceptions, types};
 use cryptography_x509::{common, oid};
 use once_cell::sync::Lazy;
 use std::collections::HashMap;
@@ -47,51 +47,15 @@ enum HashType {
 }
 
 fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::PyResult<KeyType> {
-    let rsa_private_key: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.rsa"
-        ))?
-        .getattr(pyo3::intern!(py, "RSAPrivateKey"))?
-        .extract()?;
-    let dsa_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.dsa"
-        ))?
-        .getattr(pyo3::intern!(py, "DSAPrivateKey"))?
-        .extract()?;
-    let ec_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ec"
-        ))?
-        .getattr(pyo3::intern!(py, "EllipticCurvePrivateKey"))?
-        .extract()?;
-    let ed25519_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ed25519"
-        ))?
-        .getattr(pyo3::intern!(py, "Ed25519PrivateKey"))?
-        .extract()?;
-    let ed448_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ed448"
-        ))?
-        .getattr(pyo3::intern!(py, "Ed448PrivateKey"))?
-        .extract()?;
-
-    if private_key.is_instance(rsa_private_key)? {
+    if private_key.is_instance(types::RSA_PRIVATE_KEY.get(py)?)? {
         Ok(KeyType::Rsa)
-    } else if private_key.is_instance(dsa_key_type)? {
+    } else if private_key.is_instance(types::DSA_PRIVATE_KEY.get(py)?)? {
         Ok(KeyType::Dsa)
-    } else if private_key.is_instance(ec_key_type)? {
+    } else if private_key.is_instance(types::ELLIPTIC_CURVE_PRIVATE_KEY.get(py)?)? {
         Ok(KeyType::Ec)
-    } else if private_key.is_instance(ed25519_key_type)? {
+    } else if private_key.is_instance(types::ED25519_PRIVATE_KEY.get(py)?)? {
         Ok(KeyType::Ed25519)
-    } else if private_key.is_instance(ed448_key_type)? {
+    } else if private_key.is_instance(types::ED448_PRIVATE_KEY.get(py)?)? {
         Ok(KeyType::Ed448)
     } else {
         Err(pyo3::exceptions::PyTypeError::new_err(
@@ -108,11 +72,7 @@ fn identify_hash_type(
         return Ok(HashType::None);
     }
 
-    let hash_algorithm_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?
-        .getattr(pyo3::intern!(py, "HashAlgorithm"))?
-        .extract()?;
-    if !hash_algorithm.is_instance(hash_algorithm_type)? {
+    if !hash_algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? {
         return Err(pyo3::exceptions::PyTypeError::new_err(
             "Algorithm must be a registered hash algorithm.",
         ));
@@ -143,23 +103,17 @@ fn compute_pss_salt_length<'p>(
     hash_algorithm: &'p pyo3::PyAny,
     rsa_padding: &'p pyo3::PyAny,
 ) -> pyo3::PyResult<u16> {
-    let padding_mod = py.import(pyo3::intern!(
-        py,
-        "cryptography.hazmat.primitives.asymmetric.padding"
-    ))?;
-    let maxlen = padding_mod.getattr(pyo3::intern!(py, "_MaxLength"))?;
-    let digestlen = padding_mod.getattr(pyo3::intern!(py, "_DigestLength"))?;
     let py_saltlen = rsa_padding.getattr(pyo3::intern!(py, "_salt_length"))?;
-    if py_saltlen.is_instance(maxlen)? {
-        padding_mod
-            .getattr(pyo3::intern!(py, "calculate_max_pss_salt_length"))?
+    if py_saltlen.is_instance(types::PADDING_MAX_LENGTH.get(py)?)? {
+        types::CALCULATE_MAX_PSS_SALT_LENGTH
+            .get(py)?
             .call1((private_key, hash_algorithm))?
             .extract::<u16>()
-    } else if py_saltlen.is_instance(digestlen)? {
+    } else if py_saltlen.is_instance(types::PADDING_DIGEST_LENGTH.get(py)?)? {
         hash_algorithm
             .getattr(pyo3::intern!(py, "digest_size"))?
             .extract::<u16>()
-    } else if py_saltlen.is_instance(py.get_type::<pyo3::types::PyLong>())? {
+    } else if py_saltlen.is_instance_of::<pyo3::types::PyLong>() {
         py_saltlen.extract::<u16>()
     } else {
         Err(pyo3::exceptions::PyTypeError::new_err(
@@ -177,16 +131,9 @@ pub(crate) fn compute_signature_algorithm<'p>(
     let key_type = identify_key_type(py, private_key)?;
     let hash_type = identify_hash_type(py, hash_algorithm)?;
 
-    let pss_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.padding"
-        ))?
-        .getattr(pyo3::intern!(py, "PSS"))?
-        .extract()?;
     // If this is RSA-PSS we need to compute the signature algorithm from the
     // parameters provided in rsa_padding.
-    if !rsa_padding.is_none() && rsa_padding.is_instance(pss_type)? {
+    if !rsa_padding.is_none() && rsa_padding.is_instance(types::PSS.get(py)?)? {
         let hash_alg_params = identify_alg_params_for_hash_type(hash_type)?;
         let hash_algorithm_id = common::AlgorithmIdentifier {
             oid: asn1::DefinedByMarker::marker(),
@@ -340,25 +287,13 @@ pub(crate) fn sign_data<'p>(
             private_key.call_method1(pyo3::intern!(py, "sign"), (data,))?
         }
         KeyType::Ec => {
-            let ec_mod = py.import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.asymmetric.ec"
-            ))?;
-            let ecdsa = ec_mod
-                .getattr(pyo3::intern!(py, "ECDSA"))?
-                .call1((hash_algorithm,))?;
+            let ecdsa = types::ECDSA.get(py)?.call1((hash_algorithm,))?;
             private_key.call_method1(pyo3::intern!(py, "sign"), (data, ecdsa))?
         }
         KeyType::Rsa => {
             let mut padding = rsa_padding;
             if padding.is_none() {
-                let padding_mod = py.import(pyo3::intern!(
-                    py,
-                    "cryptography.hazmat.primitives.asymmetric.padding"
-                ))?;
-                padding = padding_mod
-                    .getattr(pyo3::intern!(py, "PKCS1v15"))?
-                    .call0()?;
+                padding = types::PKCS1V15.get(py)?.call0()?;
             }
             private_key.call_method1(pyo3::intern!(py, "sign"), (data, padding, hash_algorithm))?
         }
@@ -417,51 +352,15 @@ pub(crate) fn identify_public_key_type(
     py: pyo3::Python<'_>,
     public_key: &pyo3::PyAny,
 ) -> pyo3::PyResult<KeyType> {
-    let rsa_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.rsa"
-        ))?
-        .getattr(pyo3::intern!(py, "RSAPublicKey"))?
-        .extract()?;
-    let dsa_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.dsa"
-        ))?
-        .getattr(pyo3::intern!(py, "DSAPublicKey"))?
-        .extract()?;
-    let ec_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ec"
-        ))?
-        .getattr(pyo3::intern!(py, "EllipticCurvePublicKey"))?
-        .extract()?;
-    let ed25519_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ed25519"
-        ))?
-        .getattr(pyo3::intern!(py, "Ed25519PublicKey"))?
-        .extract()?;
-    let ed448_key_type: &pyo3::types::PyType = py
-        .import(pyo3::intern!(
-            py,
-            "cryptography.hazmat.primitives.asymmetric.ed448"
-        ))?
-        .getattr(pyo3::intern!(py, "Ed448PublicKey"))?
-        .extract()?;
-
-    if public_key.is_instance(rsa_key_type)? {
+    if public_key.is_instance(types::RSA_PUBLIC_KEY.get(py)?)? {
         Ok(KeyType::Rsa)
-    } else if public_key.is_instance(dsa_key_type)? {
+    } else if public_key.is_instance(types::DSA_PUBLIC_KEY.get(py)?)? {
         Ok(KeyType::Dsa)
-    } else if public_key.is_instance(ec_key_type)? {
+    } else if public_key.is_instance(types::ELLIPTIC_CURVE_PUBLIC_KEY.get(py)?)? {
         Ok(KeyType::Ec)
-    } else if public_key.is_instance(ed25519_key_type)? {
+    } else if public_key.is_instance(types::ED25519_PUBLIC_KEY.get(py)?)? {
         Ok(KeyType::Ed25519)
-    } else if public_key.is_instance(ed448_key_type)? {
+    } else if public_key.is_instance(types::ED448_PUBLIC_KEY.get(py)?)? {
         Ok(KeyType::Ed448)
     } else {
         Err(pyo3::exceptions::PyTypeError::new_err(
@@ -525,9 +424,8 @@ fn hash_oid_py_hash(
     py: pyo3::Python<'_>,
     oid: asn1::ObjectIdentifier,
 ) -> CryptographyResult<&pyo3::PyAny> {
-    let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?;
     match HASH_OIDS_TO_HASH.get(&oid) {
-        Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?),
+        Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?),
         None => Err(CryptographyError::from(
             exceptions::UnsupportedAlgorithm::new_err(format!(
                 "Signature algorithm OID: {} not recognized",
@@ -541,9 +439,7 @@ pub(crate) fn identify_signature_hash_algorithm<'p>(
     py: pyo3::Python<'p>,
     signature_algorithm: &common::AlgorithmIdentifier<'_>,
 ) -> CryptographyResult<&'p pyo3::PyAny> {
-    let sig_oids_to_hash = py
-        .import(pyo3::intern!(py, "cryptography.hazmat._oid"))?
-        .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?;
+    let sig_oids_to_hash = types::SIG_OIDS_TO_HASH.get(py)?;
     match &signature_algorithm.params {
         common::AlgorithmParameters::RsaPss(opt_pss) => {
             let pss = opt_pss.as_ref().ok_or_else(|| {
@@ -586,16 +482,8 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>(
             }
             let py_mask_gen_hash_alg =
                 hash_oid_py_hash(py, pss.mask_gen_algorithm.params.oid().clone())?;
-            let padding = py.import(pyo3::intern!(
-                py,
-                "cryptography.hazmat.primitives.asymmetric.padding"
-            ))?;
-            let py_mgf = padding
-                .getattr(pyo3::intern!(py, "MGF1"))?
-                .call1((py_mask_gen_hash_alg,))?;
-            Ok(padding
-                .getattr(pyo3::intern!(py, "PSS"))?
-                .call1((py_mgf, pss.salt_length))?)
+            let py_mgf = types::MGF1.get(py)?.call1((py_mask_gen_hash_alg,))?;
+            Ok(types::PSS.get(py)?.call1((py_mgf, pss.salt_length))?)
         }
         common::AlgorithmParameters::RsaWithSha1(_)
         | common::AlgorithmParameters::RsaWithSha1Alt(_)
@@ -607,14 +495,7 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>(
         | common::AlgorithmParameters::RsaWithSha3_256(_)
         | common::AlgorithmParameters::RsaWithSha3_384(_)
         | common::AlgorithmParameters::RsaWithSha3_512(_) => {
-            let pkcs = py
-                .import(pyo3::intern!(
-                    py,
-                    "cryptography.hazmat.primitives.asymmetric.padding"
-                ))?
-                .getattr(pyo3::intern!(py, "PKCS1v15"))?
-                .call0()?;
-            Ok(pkcs)
+            Ok(types::PKCS1V15.get(py)?.call0()?)
         }
         common::AlgorithmParameters::EcDsaWithSha224(_)
         | common::AlgorithmParameters::EcDsaWithSha256(_)
@@ -627,13 +508,7 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>(
             let signature_hash_algorithm =
                 identify_signature_hash_algorithm(py, signature_algorithm)?;
 
-            Ok(py
-                .import(pyo3::intern!(
-                    py,
-                    "cryptography.hazmat.primitives.asymmetric.ec"
-                ))?
-                .getattr(pyo3::intern!(py, "ECDSA"))?
-                .call1((signature_hash_algorithm,))?)
+            Ok(types::ECDSA.get(py)?.call1((signature_hash_algorithm,))?)
         }
         _ => Ok(py.None().into_ref(py)),
     }
diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py
index 7db9607af197..ce90f6892395 100644
--- a/tests/hazmat/primitives/test_aead.py
+++ b/tests/hazmat/primitives/test_aead.py
@@ -681,7 +681,7 @@ def test_no_empty_encryption(self):
         with pytest.raises(ValueError):
             aessiv.encrypt(b"", None)
 
-        with pytest.raises(ValueError):
+        with pytest.raises(InvalidTag):
             aessiv.decrypt(b"", None)
 
     def test_vectors(self, backend, subtests):
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index eda445b8e03e..578bb7886ef4 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -846,6 +846,21 @@ def test_unsupported_hash(self, rsa_key_512: rsa.RSAPrivateKey, backend):
         with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
             private_key.sign(message, pss, hashes.BLAKE2s(32))
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.rsa_padding_supported(
+            padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0)
+        ),
+        skip_message="Does not support PSS.",
+    )
+    def test_unsupported_hash_pss_mgf1(self, rsa_key_2048: rsa.RSAPrivateKey):
+        private_key = rsa_key_2048
+        message = b"my message"
+        pss = padding.PSS(
+            mgf=padding.MGF1(DummyHashAlgorithm()), salt_length=0
+        )
+        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
+            private_key.sign(message, pss, hashes.SHA256())
+
     @pytest.mark.supported(
         only_if=lambda backend: backend.rsa_padding_supported(
             padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0)
@@ -1938,6 +1953,27 @@ def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend):
                 ),
             )
 
+    def test_unsupported_oaep_hash(self, rsa_key_2048: rsa.RSAPrivateKey):
+        private_key = rsa_key_2048
+        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
+            private_key.decrypt(
+                b"0" * 256,
+                padding.OAEP(
+                    mgf=padding.MGF1(DummyHashAlgorithm()),
+                    algorithm=hashes.SHA256(),
+                    label=None,
+                ),
+            )
+        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
+            private_key.decrypt(
+                b"0" * 256,
+                padding.OAEP(
+                    mgf=padding.MGF1(hashes.SHA256()),
+                    algorithm=DummyHashAlgorithm(),
+                    label=None,
+                ),
+            )
+
     def test_unsupported_oaep_mgf(
         self, rsa_key_2048: rsa.RSAPrivateKey, backend
     ):
@@ -2735,6 +2771,8 @@ def test_public_key_equality(self, rsa_key_2048: rsa.RSAPrivateKey):
         assert key1 == key2
         assert key1 != key3
         assert key1 != object()
+        with pytest.raises(TypeError):
+            key1 < key2  # type: ignore[operator]
 
     def test_public_key_copy(self, rsa_key_2048: rsa.RSAPrivateKey):
         key1 = rsa_key_2048.public_key()
diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py
index 48d20f316a1d..996b3cd52c36 100644
--- a/tests/wycheproof/test_rsa.py
+++ b/tests/wycheproof/test_rsa.py
@@ -19,7 +19,8 @@
     "SHA-256": hashes.SHA256(),
     "SHA-384": hashes.SHA384(),
     "SHA-512": hashes.SHA512(),
-    # Not supported by OpenSSL for RSA signing
+    # Not supported by OpenSSL<3 for RSA signing.
+    # Enable these when we require CRYPTOGRAPHY_OPENSSL_300_OR_GREATER
     "SHA-512/224": None,
     "SHA-512/256": None,
     "SHA3-224": hashes.SHA3_224(),
diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py
index 2698c564fc32..a70240a92a2d 100644
--- a/tests/x509/test_x509.py
+++ b/tests/x509/test_x509.py
@@ -4920,6 +4920,104 @@ def test_rsa_key_too_small(self, rsa_key_512: rsa.RSAPrivateKey, backend):
         with pytest.raises(ValueError):
             builder.sign(private_key, hashes.SHA512(), backend)
 
+    @pytest.mark.parametrize(
+        ("alg", "mgf_alg"),
+        [
+            (hashes.SHA512(), hashes.SHA256()),
+            (hashes.SHA3_512(), hashes.SHA3_256()),
+        ],
+    )
+    def test_sign_pss(
+        self, rsa_key_2048: rsa.RSAPrivateKey, alg, mgf_alg, backend
+    ):
+        if not backend.signature_hash_supported(alg):
+            pytest.skip(f"{alg} signature not supported")
+        builder = x509.CertificateSigningRequestBuilder().subject_name(
+            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+        )
+        pss = padding.PSS(
+            mgf=padding.MGF1(mgf_alg), salt_length=alg.digest_size
+        )
+        csr = builder.sign(rsa_key_2048, alg, rsa_padding=pss)
+        pk = csr.public_key()
+        assert isinstance(pk, rsa.RSAPublicKey)
+        assert isinstance(csr.signature_hash_algorithm, type(alg))
+        cert_params = csr.signature_algorithm_parameters
+        assert isinstance(cert_params, padding.PSS)
+        assert cert_params._salt_length == pss._salt_length
+        assert isinstance(cert_params._mgf, padding.MGF1)
+        assert isinstance(cert_params._mgf._algorithm, type(mgf_alg))
+        pk.verify(
+            csr.signature,
+            csr.tbs_certrequest_bytes,
+            cert_params,
+            alg,
+        )
+
+    @pytest.mark.parametrize(
+        ("padding_len", "computed_len"),
+        [
+            (padding.PSS.MAX_LENGTH, 222),
+            (padding.PSS.DIGEST_LENGTH, 32),
+        ],
+    )
+    def test_sign_pss_length_options(
+        self,
+        rsa_key_2048: rsa.RSAPrivateKey,
+        padding_len,
+        computed_len,
+        backend,
+    ):
+        builder = x509.CertificateSigningRequestBuilder().subject_name(
+            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+        )
+        pss = padding.PSS(
+            mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len
+        )
+        csr = builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss)
+        assert isinstance(csr.signature_algorithm_parameters, padding.PSS)
+        assert csr.signature_algorithm_parameters._salt_length == computed_len
+
+    def test_sign_pss_auto_unsupported(
+        self, rsa_key_2048: rsa.RSAPrivateKey, backend
+    ):
+        builder = x509.CertificateSigningRequestBuilder().subject_name(
+            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+        )
+        pss = padding.PSS(
+            mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO
+        )
+        with pytest.raises(TypeError):
+            builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss)
+
+    def test_sign_invalid_padding(
+        self, rsa_key_2048: rsa.RSAPrivateKey, backend
+    ):
+        builder = x509.CertificateSigningRequestBuilder().subject_name(
+            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+        )
+        with pytest.raises(TypeError):
+            builder.sign(
+                rsa_key_2048,
+                hashes.SHA256(),
+                rsa_padding=b"notapadding",  # type: ignore[arg-type]
+            )
+        eckey = ec.generate_private_key(ec.SECP256R1())
+        with pytest.raises(TypeError):
+            builder.sign(
+                eckey, hashes.SHA256(), rsa_padding=padding.PKCS1v15()
+            )
+
+    def test_sign_pss_hash_none(
+        self, rsa_key_2048: rsa.RSAPrivateKey, backend
+    ):
+        builder = x509.CertificateSigningRequestBuilder().subject_name(
+            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+        )
+        pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32)
+        with pytest.raises(TypeError):
+            builder.sign(rsa_key_2048, None, rsa_padding=pss)
+
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.dsa_supported(),