diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 62a0ff5fb763..000000000000 --- a/.coveragerc +++ /dev/null @@ -1,16 +0,0 @@ -[run] -branch = True -source = - cryptography - tests/ - -[paths] -source = - src/cryptography - .tox/*/lib/python*/site-packages/cryptography - .tox/pypy/site-packages/cryptography - -[report] -exclude_lines = - @abc.abstractmethod - @abc.abstractproperty diff --git a/.github/ISSUE_TEMPLATE.rst b/.github/ISSUE_TEMPLATE.rst index 011d571c9f19..ea489382c1a2 100644 --- a/.github/ISSUE_TEMPLATE.rst +++ b/.github/ISSUE_TEMPLATE.rst @@ -1,9 +1,13 @@ If you're filing a bug (as opposed to a feature request), please try the following things: +* Check the FAQ to see if your issue is covered there: + https://cryptography.io/en/latest/faq.html * Upgrade to the latest version of ``setuptools`` and ``pip`` * Make sure you're on a supported version of OpenSSL * Try with the latest version of ``cryptography`` +* Be sure you have the required compilers (both a C compiler and Rust) + installed if you aren't using the binary wheels. If none of that works, please make sure to include the following information in your bug report: @@ -15,4 +19,4 @@ your bug report: Please do not report security issues on Github! Follow the instructions in our documentation for reporting security issues: -https://cryptography.io/en/latest/security/ +https://cryptography.io/en/latest/security.html diff --git a/.github/ISSUE_TEMPLATE/openssl-release.md b/.github/ISSUE_TEMPLATE/openssl-release.md index 336953722fb2..110d06d09c52 100644 --- a/.github/ISSUE_TEMPLATE/openssl-release.md +++ b/.github/ISSUE_TEMPLATE/openssl-release.md @@ -1,7 +1,9 @@ - [ ] Windows, macOS, `manylinux` - - [ ] Send a pull request to `pyca/infra` updating the [version and hash](https://github.com/pyca/infra/blob/master/cryptography-manylinux/openssl-version.sh) + - [ ] Send a pull request to `pyca/infra` updating the [version and hash](https://github.com/pyca/infra/blob/main/cryptography-manylinux/openssl-version.sh) - [ ] Wait for it to be merged - [ ] Wait for the Github Actions job to complete - [ ] Changelog entry - [ ] Release -- [ ] Forward port changelog entry (if releasing from release branch) \ No newline at end of file +- [ ] File Github Security Advisory indicating which releases are impacted (if OpenSSL release is fixing a vulnerability) +- [ ] Send announcement to mailing lists +- [ ] Forward port changelog entry (if releasing from release branch) diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml new file mode 100644 index 000000000000..9b0c9271300d --- /dev/null +++ b/.github/actions/cache/action.yml @@ -0,0 +1,57 @@ +name: Cache rust and pip +description: Caches rust and pip data to speed builds +inputs: + additional-paths: + description: 'Additional paths to add to the cache' + required: false + default: '' + key: + description: 'extra cache key components' + required: false + default: '' +outputs: + cache-hit: + description: 'Was the cache hit?' + value: ${{ steps.cache.outputs.cache-hit }} + + +runs: + using: "composite" + + steps: + - name: Get rust version + id: rust-version + run: echo "version=$(rustc --version | sha256sum | cut -d ' ' -f 1)" >> $GITHUB_OUTPUT + shell: bash + - name: Get pip cache dir + id: pip-cache + run: | + # Determine the path to our Python. It's in venv for our containers + # but just standard $PATH for setup-python pythons. + if [[ -f "/venv/bin/python" ]]; then + echo "dir=$(/venv/bin/python -m pip cache dir)" >> $GITHUB_OUTPUT + elif which python >/dev/null; then + echo "dir=$(python -m pip cache dir)" >> $GITHUB_OUTPUT + fi + shell: bash + - name: Normalize key + id: normalized-key + run: echo "key=$(echo "${{ inputs.key }}" | tr -d ',')" >> $GITHUB_OUTPUT + shell: bash + - uses: actions/cache@v3.3.1 + id: cache + with: + path: | + ${{ steps.pip-cache.outputs.dir }} + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + src/rust/target/ + ${{ inputs.additional-paths }} + key: cargo-pip-${{ runner.os }}-${{ runner.arch }}-${{ steps.normalized-key.outputs.key }}-6-${{ hashFiles('**/Cargo.lock', '**/*.rs') }}-${{ steps.rust-version.version }} + - name: Size of cache items + run: | + du -sh ~/.cargo/registry/index/ + du -sh ~/.cargo/registry/cache/ + du -sh src/rust/target/ + shell: bash + if: ${{ steps.cache.outputs.cache-hit }} diff --git a/.github/actions/mtime-fix/action.yml b/.github/actions/mtime-fix/action.yml new file mode 100644 index 000000000000..42779037ce87 --- /dev/null +++ b/.github/actions/mtime-fix/action.yml @@ -0,0 +1,26 @@ +name: Fix mtime +description: Fixes mtime so cargo will reuse caches more effectively + +runs: + using: "composite" + + steps: + - run: | + GIT_WORKS=$(git rev-parse --is-inside-work-tree 2>/dev/null || true) + if [ "$GIT_WORKS" != "true" ]; then + echo "The git available is probably too old so checkout didn't create a real git clone, skipping mtime fix" + exit 0 + fi + ls -Rla src/rust src/_cffi_src + echo "Verifying commits are monotonic because if they're not caching gets wrecked" + COMMIT_ORDER=$(git log --pretty=format:%cd --date=format-local:%Y%m%d%H%M.%S -5) + SORTED_COMMIT_ORDER=$(git log --pretty=format:%cd --date=format-local:%Y%m%d%H%M.%S -5 | sort -rn) + if [ "$COMMIT_ORDER" != "$SORTED_COMMIT_ORDER" ]; then + echo "Commits are not monotonic, git may have changed how date formatting works" + exit 1 + fi + echo "Setting mtimes for dirs" + for f in $(git ls-tree -t -r --name-only HEAD src/rust src/_cffi_src); do touch -t $(git log --pretty=format:%cd --date=format-local:%Y%m%d%H%M.%S -1 HEAD -- "$f") "$f"; done + echo "Done" + ls -Rla src/rust src/_cffi_src + shell: bash diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml new file mode 100644 index 000000000000..8fa9cca4e630 --- /dev/null +++ b/.github/actions/upload-coverage/action.yml @@ -0,0 +1,22 @@ +name: Upload Coverage +description: Upload coverage files + +runs: + using: "composite" + + steps: + - run: | + COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())") + echo "COVERAGE_UUID=${COVERAGE_UUID}" >> $GITHUB_OUTPUT + if [ -f .coverage ]; then + mv .coverage .coverage.${COVERAGE_UUID} + fi + id: coverage-uuid + shell: bash + - uses: actions/upload-artifact@v3.1.2 + with: + name: coverage-data + path: | + .coverage.* + *.lcov + if-no-files-found: ignore diff --git a/.github/actions/wycheproof/action.yml b/.github/actions/wycheproof/action.yml new file mode 100644 index 000000000000..6ededc54b15d --- /dev/null +++ b/.github/actions/wycheproof/action.yml @@ -0,0 +1,12 @@ +name: Clone wycheproof +description: Clones the wycheproof repository + +runs: + using: "composite" + + steps: + - uses: actions/checkout@v3.5.2 + with: + repository: "google/wycheproof" + path: "wycheproof" + ref: "master" diff --git a/.github.amrom.workers.devpare_benchmarks.py b/.github.amrom.workers.devpare_benchmarks.py new file mode 100644 index 000000000000..54ccd67496a7 --- /dev/null +++ b/.github.amrom.workers.devpare_benchmarks.py @@ -0,0 +1,42 @@ +# 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 json +import sys + + +def bench_data_as_dict(data): + return {d["fullname"]: d["stats"] for d in data["benchmarks"]} + + +def main(base_bench_path, pr_bench_path): + with open(base_bench_path) as f: + base_bench_data = bench_data_as_dict(json.load(f)) + with open(pr_bench_path) as f: + pr_bench_data = bench_data_as_dict(json.load(f)) + + print("| Benchmark | Base | PR | Delta |") + print("| --------- | ---- | -- | ----- |") + for bench_name in sorted(base_bench_data): + # TODO: use better statistics than just comparing medians + base_result = base_bench_data[bench_name]["median"] + pr_result = pr_bench_data[bench_name]["median"] + + if base_result == pr_result: + # PR and base are identical + delta = "--" + elif base_result > pr_result: + # PR is faster than base + delta = f"{100 - round(100 * pr_result / base_result)}% faster" + else: + delta = f"{100 - round(100 * base_result / pr_result)}% slower" + + print( + f"| `{bench_name}` | {round(base_result * 1000 * 1000 * 1000, 2)} " + f"ns | {round(pr_result * 1000 * 1000 * 1000, 2)} ns | {delta} |" + ) + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 123014908beb..273a64e735bc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,35 @@ updates: directory: "/" schedule: interval: "daily" + open-pull-requests-limit: 1024 + + - package-ecosystem: "github-actions" + directory: "/.github/actions/cache/" + schedule: + interval: "daily" + open-pull-requests-limit: 1024 + - package-ecosystem: "github-actions" + directory: "/.github/actions/upload-coverage/" + schedule: + interval: "daily" + open-pull-requests-limit: 1024 + - package-ecosystem: "github-actions" + directory: "/.github/actions/wycheproof/" + schedule: + interval: "daily" + open-pull-requests-limit: 1024 + + - package-ecosystem: cargo + directory: "/src/rust/" + schedule: + interval: daily + allow: + # Also update indirect dependencies + - dependency-type: all + open-pull-requests-limit: 1024 + + - package-ecosystem: pip + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 1024 diff --git a/.travis/downstream.d/aws-encryption-sdk.sh b/.github/downstream.d/aws-encryption-sdk.sh similarity index 85% rename from .travis/downstream.d/aws-encryption-sdk.sh rename to .github/downstream.d/aws-encryption-sdk.sh index 276d47eee559..4992282cbaad 100755 --- a/.travis/downstream.d/aws-encryption-sdk.sh +++ b/.github/downstream.d/aws-encryption-sdk.sh @@ -6,7 +6,7 @@ case "${1}" in cd aws-encryption-sdk-python git rev-parse HEAD pip install -e . - pip install -r test/upstream-requirements-py37.txt + pip install -r test/upstream-requirements-py311.txt ;; run) cd aws-encryption-sdk-python diff --git a/.github/downstream.d/certbot-josepy.sh b/.github/downstream.d/certbot-josepy.sh new file mode 100755 index 000000000000..c27568ffe4f1 --- /dev/null +++ b/.github/downstream.d/certbot-josepy.sh @@ -0,0 +1,19 @@ +#!/bin/bash -ex + +case "${1}" in + install) + git clone --depth=1 https://github.com/certbot/josepy + cd josepy + git rev-parse HEAD + curl -sSL https://install.python-poetry.org | python3 - + "${HOME}/.local/bin/poetry" export -f constraints.txt --dev --without-hashes -o constraints.txt + pip install -e . pytest -c constraints.txt + ;; + run) + cd josepy + pytest tests + ;; + *) + exit 1 + ;; +esac diff --git a/.travis/downstream.d/certbot.sh b/.github/downstream.d/certbot.sh similarity index 73% rename from .travis/downstream.d/certbot.sh rename to .github/downstream.d/certbot.sh index e2890a3a100c..561251d5b1d5 100755 --- a/.travis/downstream.d/certbot.sh +++ b/.github/downstream.d/certbot.sh @@ -5,15 +5,16 @@ case "${1}" in git clone --depth=1 https://github.com/certbot/certbot cd certbot git rev-parse HEAD - tools/pip_install_editable.py ./acme[dev] - tools/pip_install_editable.py ./certbot[dev] + tools/pip_install.py -e ./acme[test] + tools/pip_install.py -e ./certbot[test] + pip install -U pyopenssl ;; run) cd certbot # Ignore some warnings for now since they're now automatically promoted # to errors. We can probably remove this when acme gets split into # its own repo - pytest -Wignore certbot/tests + pytest -Wignore certbot pytest acme ;; *) diff --git a/.travis/downstream.d/dynamodb-encryption-sdk.sh b/.github/downstream.d/dynamodb-encryption-sdk.sh similarity index 70% rename from .travis/downstream.d/dynamodb-encryption-sdk.sh rename to .github/downstream.d/dynamodb-encryption-sdk.sh index 60bbecf36afd..b053e6eb4cc8 100755 --- a/.travis/downstream.d/dynamodb-encryption-sdk.sh +++ b/.github/downstream.d/dynamodb-encryption-sdk.sh @@ -6,11 +6,11 @@ case "${1}" in cd aws-dynamodb-encryption-python git rev-parse HEAD pip install -e . - pip install -r test/upstream-requirements-py37.txt + pip install -r test/upstream-requirements-py311.txt ;; run) cd aws-dynamodb-encryption-python - pytest test/ -m "local and not slow and not veryslow and not nope" + pytest -n auto test/ -m "local and not slow and not veryslow and not nope" ;; *) exit 1 diff --git a/.github/downstream.d/mitmproxy.sh b/.github/downstream.d/mitmproxy.sh new file mode 100755 index 000000000000..df7669aa9336 --- /dev/null +++ b/.github/downstream.d/mitmproxy.sh @@ -0,0 +1,17 @@ +#!/bin/bash -ex + +case "${1}" in + install) + git clone --depth=1 https://github.com/mitmproxy/mitmproxy + cd mitmproxy + git rev-parse HEAD + pip install -e ".[dev]" + ;; + run) + cd mitmproxy + pytest test + ;; + *) + exit 1 + ;; +esac diff --git a/.travis/downstream.d/paramiko.sh b/.github/downstream.d/paramiko.sh similarity index 78% rename from .travis/downstream.d/paramiko.sh rename to .github/downstream.d/paramiko.sh index c994defb0f90..82ab9c1f9748 100755 --- a/.travis/downstream.d/paramiko.sh +++ b/.github/downstream.d/paramiko.sh @@ -10,7 +10,8 @@ case "${1}" in ;; run) cd paramiko - inv test + # https://github.com/paramiko/paramiko/issues/1927 + inv test || inv test ;; *) exit 1 diff --git a/.github/downstream.d/pyopenssl-release.sh b/.github/downstream.d/pyopenssl-release.sh new file mode 100755 index 000000000000..8428892eeb0e --- /dev/null +++ b/.github/downstream.d/pyopenssl-release.sh @@ -0,0 +1,18 @@ +#!/bin/bash -ex + +case "${1}" in + install) + VERSION=$(curl https://pypi.org/pypi/pyOpenSSL/json | jq -r .info.version) + git clone https://github.com/pyca/pyopenssl + cd pyopenssl + git checkout "$VERSION" + pip install -e ".[test]" + ;; + run) + cd pyopenssl + pytest tests + ;; + *) + exit 1 + ;; +esac diff --git a/.travis/downstream.d/pyopenssl.sh b/.github/downstream.d/pyopenssl.sh similarity index 100% rename from .travis/downstream.d/pyopenssl.sh rename to .github/downstream.d/pyopenssl.sh diff --git a/.github/downstream.d/scapy.sh b/.github/downstream.d/scapy.sh new file mode 100755 index 000000000000..ac1b8f820016 --- /dev/null +++ b/.github/downstream.d/scapy.sh @@ -0,0 +1,18 @@ +#!/bin/bash -ex + +case "${1}" in + install) + git clone --depth=1 https://github.com/secdev/scapy + cd scapy + git rev-parse HEAD + pip install tox + ;; + run) + cd scapy + # this tox case uses sitepackages=true to use local cryptography + tox -qe cryptography + ;; + *) + exit 1 + ;; +esac diff --git a/.travis/downstream.d/twisted.sh b/.github/downstream.d/twisted.sh similarity index 85% rename from .travis/downstream.d/twisted.sh rename to .github/downstream.d/twisted.sh index 522e763ec3b7..9fc195ba7552 100755 --- a/.travis/downstream.d/twisted.sh +++ b/.github/downstream.d/twisted.sh @@ -9,7 +9,7 @@ case "${1}" in ;; run) cd twisted - python -m twisted.trial src/twisted + python -m twisted.trial -j4 src/twisted ;; *) exit 1 diff --git a/.github/workflows/auto-close-stale.yml b/.github/workflows/auto-close-stale.yml new file mode 100644 index 000000000000..46b4d3e2a9cf --- /dev/null +++ b/.github/workflows/auto-close-stale.yml @@ -0,0 +1,23 @@ +name: Auto-close stale issues +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +permissions: + issues: "write" + pull-requests: "write" + +jobs: + auto-close: + if: github.repository_owner == 'pyca' + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8.0.0 + with: + only-labels: waiting-on-reporter + days-before-stale: 3 + days-before-close: 5 + stale-issue-message: "This issue has been waiting for a reporter response for 3 days. It will be auto-closed if no activity occurs in the next 5 days." + close-issue-message: "This issue has not received a reporter response and has been auto-closed. If the issue is still relevant please leave a comment and we can reopen it." + close-issue-reason: completed diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 000000000000..1643a283b934 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,57 @@ +name: Benchmark +on: + pull_request: + paths: + - '.github/workflows/benchmark.yml' + - 'src/**' + - 'tests/**' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + +jobs: + benchmark: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + persist-credentials: false + path: "cryptography-pr" + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + repository: "pyca/cryptography" + path: "cryptography-base" + ref: "${{ github.base_ref }}" + + - name: Setup python + id: setup-python + uses: actions/setup-python@v4.6.1 + with: + python-version: "3.11" + + - name: Create virtualenv (base) + run: | + python -m venv .venv-base + .venv-base/bin/pip install -v -c ./cryptography-base/ci-constraints-requirements.txt "./cryptography-base[test]" ./cryptography-base/vectors/ + - name: Create virtualenv (PR) + run: | + python -m venv .venv-pr + .venv-pr/bin/pip install -v -c ./cryptography-pr/ci-constraints-requirements.txt "./cryptography-pr[test]" ./cryptography-pr/vectors/ + + - name: Run benchmarks (base) + run: .venv-base/bin/pytest --benchmark-enable --benchmark-only ./cryptography-pr/tests/bench/ --benchmark-json=bench-base.json + - name: Run benchmarks (PR) + run: .venv-pr/bin/pytest --benchmark-enable --benchmark-only ./cryptography-pr/tests/bench/ --benchmark-json=bench-pr.json + + - name: Compare results + run: python ./cryptography-pr/.github.amrom.workers.devpare_benchmarks.py bench-base.json bench-pr.json | tee -a $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/boring-open-version-bump.yml b/.github/workflows/boring-open-version-bump.yml new file mode 100644 index 000000000000..0c0036e11cac --- /dev/null +++ b/.github/workflows/boring-open-version-bump.yml @@ -0,0 +1,70 @@ +name: Bump BoringSSL and/or OpenSSL +permissions: + contents: read + +on: + workflow_dispatch: + schedule: + # Run daily + - cron: "0 0 * * *" + +jobs: + bump: + if: github.repository_owner == 'pyca' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3.5.2 + - id: check-sha-boring + run: | + SHA=$(git ls-remote https://boringssl.googlesource.com/boringssl refs/heads/master | cut -f1) + LAST_COMMIT=$(grep boringssl .github/workflows/ci.yml | grep TYPE | grep -oE '[a-f0-9]{40}') + if ! grep -q "$SHA" .github/workflows/ci.yml; then + echo "COMMIT_SHA=${SHA}" >> $GITHUB_OUTPUT + echo "COMMIT_MSG<> $GITHUB_OUTPUT + echo -e "## BoringSSL\n[Commit: ${SHA}](https://boringssl.googlesource.com/boringssl/+/${SHA})\n\n[Diff](https://boringssl.googlesource.com/boringssl/+/${LAST_COMMIT}..${SHA}) between the last commit hash merged to this repository and the new commit." >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + fi + - id: check-sha-openssl + run: | + SHA=$(git ls-remote https://github.com/openssl/openssl refs/heads/master | cut -f1) + LAST_COMMIT=$(grep openssl .github/workflows/ci.yml | grep TYPE | grep -oE '[a-f0-9]{40}') + if ! grep -q "$SHA" .github/workflows/ci.yml; then + echo "COMMIT_SHA=${SHA}" >> $GITHUB_OUTPUT + echo "COMMIT_MSG<> $GITHUB_OUTPUT + echo -e "## OpenSSL\n[Commit: ${SHA}](https://github.com/openssl/openssl/commit/${SHA})\n\n[Diff](https://github.com/openssl/openssl/compare/${LAST_COMMIT}...${SHA}) between the last commit hash merged to this repository and the new commit." >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + - name: Update boring + run: | + set -xe + CURRENT_DATE=$(date "+%b %d, %Y") + sed -E -i "s/Latest commit on the BoringSSL master branch.*/Latest commit on the BoringSSL master branch, as of ${CURRENT_DATE}./" .github/workflows/ci.yml + sed -E -i "s/TYPE: \"boringssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"boringssl\", VERSION: \"${{ steps.check-sha-boring.outputs.COMMIT_SHA }}\"/" .github/workflows/ci.yml + git status + if: steps.check-sha-boring.outputs.COMMIT_SHA + - name: Update OpenSSL + run: | + set -xe + CURRENT_DATE=$(date "+%b %d, %Y") + sed -E -i "s/Latest commit on the OpenSSL master branch.*/Latest commit on the OpenSSL master branch, as of ${CURRENT_DATE}./" .github/workflows/ci.yml + 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 + id: generate-token + with: + app_id: ${{ secrets.BORINGBOT_APP_ID }} + private_key: ${{ secrets.BORINGBOT_PRIVATE_KEY }} + if: steps.check-sha-boring.outputs.COMMIT_SHA || steps.check-sha-openssl.outputs.COMMIT_SHA + - name: Create Pull Request + uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 + with: + commit-message: "Bump BoringSSL and/or OpenSSL in CI" + title: "Bump BoringSSL and/or OpenSSL in CI" + author: "pyca-boringbot[bot] " + body: | + ${{ steps.check-sha-boring.outputs.COMMIT_MSG }} + ${{ steps.check-sha-openssl.outputs.COMMIT_MSG }} + token: ${{ steps.generate-token.outputs.token }} + if: steps.check-sha-boring.outputs.COMMIT_SHA || steps.check-sha-openssl.outputs.COMMIT_SHA diff --git a/.github/workflows/build_openssl.sh b/.github/workflows/build_openssl.sh new file mode 100755 index 000000000000..42357abae9fc --- /dev/null +++ b/.github/workflows/build_openssl.sh @@ -0,0 +1,84 @@ +#!/bin/bash +set -e +set -x + +shlib_sed() { + # modify the shlib version to a unique one to make sure the dynamic + # linker doesn't load the system one. + sed -i "s/^SHLIB_MAJOR=.*/SHLIB_MAJOR=100/" Makefile + sed -i "s/^SHLIB_MINOR=.*/SHLIB_MINOR=0.0/" Makefile + sed -i "s/^SHLIB_VERSION_NUMBER=.*/SHLIB_VERSION_NUMBER=100.0.0/" Makefile +} +shlib_sed_3() { + # OpenSSL 3 changes how it does the shlib versioning + sed -i "s/^SHLIB_VERSION=.*/SHLIB_VERSION=100/" VERSION.dat +} + +if [[ "${TYPE}" == "openssl" ]]; then + if [[ "${VERSION}" =~ ^[0-9a-f]{40}$ ]]; then + git clone https://github.com/openssl/openssl + pushd openssl + git checkout "${VERSION}" + else + curl -O "https://www.openssl.org/source/openssl-${VERSION}.tar.gz" + tar zxf "openssl-${VERSION}.tar.gz" + pushd "openssl-${VERSION}" + fi + # For OpenSSL 3 we need to call this before config + if [[ "${VERSION}" =~ ^3. ]] || [[ "${VERSION}" =~ ^[0-9a-f]{40}$ ]]; then + shlib_sed_3 + fi + + # CONFIG_FLAGS is a global coming from a previous step + ./config ${CONFIG_FLAGS} -fPIC --prefix="${OSSL_PATH}" + + # For OpenSSL 1 we need to call this after config + if [[ "${VERSION}" =~ ^1. ]]; then + shlib_sed + fi + make depend + make -j"$(nproc)" + # avoid installing the docs (for performance) + # https://github.com/openssl/openssl/issues/6685#issuecomment-403838728 + make install_sw install_ssldirs + # delete binaries we don't need + rm -rf "${OSSL_PATH}/bin" + # For OpenSSL 3.0.0 set up the FIPS config. This does not activate it by + # default, but allows programmatic activation at runtime + if [[ "${VERSION}" =~ ^3. && "${CONFIG_FLAGS}" =~ enable-fips ]]; then + # As of alpha16 we have to install it separately and enable it in the config flags + make -j"$(nproc)" install_fips + pushd "${OSSL_PATH}" + # include the conf file generated as part of install_fips + sed -i "s:# .include fipsmodule.cnf:.include $(pwd)/ssl/fipsmodule.cnf:" ssl/openssl.cnf + # uncomment the FIPS section + sed -i 's:# fips = fips_sect:fips = fips_sect:' ssl/openssl.cnf + popd + fi + popd +elif [[ "${TYPE}" == "libressl" ]]; then + curl -O "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${VERSION}.tar.gz" + tar zxf "libressl-${VERSION}.tar.gz" + pushd "libressl-${VERSION}" + ./config -Wl -Wl,-Bsymbolic-functions -fPIC shared --prefix="${OSSL_PATH}" + shlib_sed + make -j"$(nproc)" install + # delete binaries, libtls, and docs we don't need. can't skip install/compile sadly + rm -rf "${OSSL_PATH}/bin" + rm -rf "${OSSL_PATH}/share" + rm -rf "${OSSL_PATH}/lib/libtls*" + popd +elif [[ "${TYPE}" == "boringssl" ]]; then + git clone https://boringssl.googlesource.com/boringssl + pushd boringssl + git checkout "${VERSION}" + mkdir build + pushd build + cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX="${OSSL_PATH}" + make -j"$(nproc)" + make install + # delete binaries we don't need + rm -rf "${OSSL_PATH}/bin" + popd + popd +fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b55266721e95..0f385e3421e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,100 +3,487 @@ on: pull_request: {} push: branches: - - master + - main - '*.*.x' tags: - '*.*' - '*.*.*' +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + jobs: - macos: - runs-on: macos-latest + linux: + runs-on: ubuntu-latest strategy: + fail-fast: false matrix: PYTHON: - - {VERSION: "2.7", TOXENV: "py27", EXTRA_CFLAGS: ""} - - {VERSION: "3.5", TOXENV: "py35", EXTRA_CFLAGS: ""} - - {VERSION: "3.9", TOXENV: "py39", EXTRA_CFLAGS: "-DUSE_OSRANDOM_RNG_FOR_TESTING"} - name: "Python ${{ matrix.PYTHON.VERSION }} on macOS" + - {VERSION: "3.11", NOXSESSION: "flake"} + - {VERSION: "3.11", NOXSESSION: "rust"} + - {VERSION: "3.11", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.1.3"}} + - {VERSION: "pypy-3.8", NOXSESSION: "tests-nocoverage"} + - {VERSION: "pypy-3.9", NOXSESSION: "tests-nocoverage"} + - {VERSION: "pypy-3.10", NOXSESSION: "tests-nocoverage"} + - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1v"}} + - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.0.11"}} + - {VERSION: "3.11", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.1.3"}} + - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.3", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} + - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.3", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} + - {VERSION: "3.11", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.1.3"}} + - {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-randomorder"} + - {VERSION: "3.12-dev", NOXSESSION: "tests"} + # Latest commit on the BoringSSL master branch, as of May 27, 2023. + - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "b0a026f8541c551854efd617021bb276f1fe5c23"}} + # Latest commit on the OpenSSL master branch, as of May 30, 2023. + - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "36424806d699233b9a90a3a97fff3011828e2548"}} + # Builds with various Rust versions. Includes MSRV and potential + # future MSRV: + # 1.60 - pem 2.0.1 + - {VERSION: "3.11", NOXSESSION: "tests-nocoverage", RUST: "1.56.0"} + - {VERSION: "3.11", NOXSESSION: "rust,tests", RUST: "1.60.0"} + - {VERSION: "3.11", NOXSESSION: "rust,tests", RUST: "beta"} + - {VERSION: "3.11", NOXSESSION: "rust,tests", RUST: "nightly"} + timeout-minutes: 15 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + persist-credentials: false + fetch-depth: 0 + - name: set mtimes for rust dirs + uses: ./.github/actions/mtime-fix - name: Setup python - uses: actions/setup-python@v2 + id: setup-python + uses: actions/setup-python@v4.6.1 with: python-version: ${{ matrix.PYTHON.VERSION }} + - name: Setup rust + uses: dtolnay/rust-toolchain@52e69531e6f69a396bc9d1226284493a5db969ff + with: + toolchain: ${{ matrix.PYTHON.RUST }} + components: rustfmt,clippy + if: matrix.PYTHON.RUST - - run: python -m pip install tox requests coverage - - - run: git clone https://github.com/google/wycheproof + - run: rustup component add llvm-tools-preview + - name: Clone wycheproof + timeout-minutes: 2 + uses: ./.github/actions/wycheproof + - name: Compute config hash and set config vars + run: | + DEFAULT_CONFIG_FLAGS="shared no-ssl2 no-ssl3" + CONFIG_FLAGS="$DEFAULT_CONFIG_FLAGS $CONFIG_FLAGS" + OPENSSL_HASH=$(echo "${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-$CONFIG_FLAGS" | sha1sum | sed 's/ .*$//') + echo "CONFIG_FLAGS=${CONFIG_FLAGS}" >> $GITHUB_ENV + echo "OPENSSL_HASH=${OPENSSL_HASH}" >> $GITHUB_ENV + echo "OSSL_INFO=${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${CONFIG_FLAGS}" >> $GITHUB_ENV + echo "OSSL_PATH=${{ github.workspace }}/osslcache/${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${OPENSSL_HASH}" >> $GITHUB_ENV + env: + CONFIG_FLAGS: ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }} + if: matrix.PYTHON.OPENSSL + - name: Load OpenSSL cache + uses: actions/cache@v3.3.1 + id: ossl-cache + timeout-minutes: 2 + with: + path: ${{ github.workspace }}/osslcache + # When altering the openssl build process you may need to increment the value on the end of this cache key + # so that you can prevent it from fetching the cache and skipping the build step. + key: ${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.OPENSSL_HASH }}-8 + if: matrix.PYTHON.OPENSSL + - name: Build custom OpenSSL/LibreSSL + run: .github/workflows/build_openssl.sh + env: + TYPE: ${{ matrix.PYTHON.OPENSSL.TYPE }} + VERSION: ${{ matrix.PYTHON.OPENSSL.VERSION }} + if: matrix.PYTHON.OPENSSL && steps.ossl-cache.outputs.cache-hit != 'true' + - name: Set CFLAGS/LDFLAGS + run: | + echo "OPENSSL_DIR=${OSSL_PATH}" >> $GITHUB_ENV + echo "CFLAGS=${CFLAGS} -Werror=implicit-function-declaration" >> $GITHUB_ENV + echo "RUSTFLAGS=-Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib -Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib64" >> $GITHUB_ENV + if: matrix.PYTHON.OPENSSL + - name: Cache rust and pip + uses: ./.github/actions/cache + timeout-minutes: 2 + with: + # We have both the Python version from the matrix and from the + # setup-python step because the latter doesn't distinguish + # pypy3-3.8 and pypy3-3.9 -- both of them show up as 7.3.11. + key: ${{ matrix.PYTHON.VERSION }}-${{ steps.setup-python.outputs.python-version }}-${{ matrix.PYTHON.NOXSESSION }}-${{ env.OPENSSL_HASH }} - - name: Download OpenSSL + - run: python -m pip install -c ci-constraints-requirements.txt 'nox' + - name: Create nox environment run: | - python .github/workflows/download_openssl.py macos openssl-macos + nox -v --install-only env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - name: Tests run: | - CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 \ - LDFLAGS="${HOME}/openssl-macos/lib/libcrypto.a ${HOME}/openssl-macos/lib/libssl.a" \ - CFLAGS="-I${HOME}/openssl-macos/include -Werror -Wno-error=deprecated-declarations -Wno-error=incompatible-pointer-types-discards-qualifiers -Wno-error=unused-function -Wno-error=unused-command-line-argument -mmacosx-version-min=10.10 -march=core2 $EXTRA_CFLAGS" \ - tox -r -- --color=yes --wycheproof-root=wycheproof + nox --no-install -- --color=yes --wycheproof-root=wycheproof ${{ matrix.PYTHON.NOXARGS }} env: - TOXENV: ${{ matrix.PYTHON.TOXENV }} - EXTRA_CFLAGS: ${{ matrix.PYTHON.EXTRA_CFLAGS }} + NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} + COLUMNS: 80 + CRYPTOGRAPHY_OPENSSL_NO_LEGACY: ${{ matrix.PYTHON.OPENSSL.NO_LEGACY }} + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - - name: Upload coverage + - uses: ./.github/actions/upload-coverage + + distros: + runs-on: ${{ matrix.IMAGE.RUNNER }} + container: ghcr.io/pyca/cryptography-runner-${{ matrix.IMAGE.IMAGE }} + strategy: + fail-fast: false + matrix: + IMAGE: + - {IMAGE: "rhel8", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "rhel8-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} + - {IMAGE: "buster", NOXSESSION: "tests-nocoverage", RUNNER: "ubuntu-latest"} + - {IMAGE: "bullseye", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "bookworm", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "sid", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "ubuntu-focal", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "ubuntu-jammy", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "ubuntu-rolling", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "fedora", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "alpine", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "centos-stream9", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "centos-stream9-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} + + - {IMAGE: "ubuntu-jammy:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} + - {IMAGE: "alpine:aarch64", NOXSESSION: "tests-nocoverage", RUNNER: [self-hosted, Linux, ARM64]} + timeout-minutes: 15 + env: + RUSTUP_HOME: /root/.rustup + steps: + - name: Ridiculous alpine workaround for actions support on arm64 run: | - curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash - bash codecov.sh -n "Python ${{ matrix.PYTHON.VERSION }} on macOS" + # This modifies /etc/os-release so the JS actions + # from GH can't detect that it's on alpine:aarch64. It will + # then use a glibc nodejs, which works fine when gcompat + # is installed in the container (which it is) + sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release + if: matrix.IMAGE.IMAGE == 'alpine:aarch64' + + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + persist-credentials: false + fetch-depth: 0 + - name: git config shenanigans + run: | + git config --global --add safe.directory $(pwd) # needed for the mtime fix since git doesn't think it owns the files due to being in containers + - name: set mtimes for rust dirs + uses: ./.github/actions/mtime-fix + - name: Cache rust and pip + uses: ./.github/actions/cache + timeout-minutes: 2 + with: + key: ${{ matrix.IMAGE.IMAGE }} + - name: Clone wycheproof + timeout-minutes: 2 + uses: ./.github/actions/wycheproof + # When run in a docker container the home directory doesn't have the same owner as the + # apparent user so pip refuses to create a cache dir + - name: create pip cache dir + run: mkdir -p "${HOME}/.cache/pip" + - run: | + echo "OPENSSL_FORCE_FIPS_MODE=1" >> $GITHUB_ENV + if: matrix.IMAGE.FIPS + - run: /venv/bin/python -m pip install -c ci-constraints-requirements.txt 'nox' + - run: '/venv/bin/nox -v --install-only' + env: + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + # OPENSSL_ENABLE_SHA1_SIGNATURES is for CentOS 9 Stream + OPENSSL_ENABLE_SHA1_SIGNATURES: 1 + NOXSESSION: ${{ matrix.IMAGE.NOXSESSION }} + - run: '/venv/bin/nox --no-install -- --color=yes --wycheproof-root="wycheproof"' + env: + COLUMNS: 80 + # OPENSSL_ENABLE_SHA1_SIGNATURES is for CentOS 9 Stream + OPENSSL_ENABLE_SHA1_SIGNATURES: 1 + NOXSESSION: ${{ matrix.IMAGE.NOXSESSION }} + - uses: ./.github/actions/upload-coverage + + macos: + runs-on: ${{ matrix.RUNNER.OS }} + strategy: + fail-fast: false + matrix: + RUNNER: + - {OS: 'macos-12', ARCH: 'x86_64'} + - {OS: [self-hosted, macos, ARM64, tart], ARCH: 'arm64'} + PYTHON: + - {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} + - {VERSION: "3.11", NOXSESSION: "tests"} + exclude: + # We only test latest Python on arm64. py37 won't work since there's no universal2 binary + - PYTHON: {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} + RUNNER: {OS: [self-hosted, macos, ARM64, tart], ARCH: 'arm64'} + timeout-minutes: 15 + steps: + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + persist-credentials: false + fetch-depth: 0 + - name: set mtimes for rust dirs + uses: ./.github/actions/mtime-fix + - name: Cache rust and pip + uses: ./.github/actions/cache + timeout-minutes: 2 + with: + key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.PYTHON.VERSION }} + + - name: Setup python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ matrix.PYTHON.VERSION }} + architecture: 'x64' # we force this right now so that it will install the universal2 on arm64 + - run: rustup component add llvm-tools-preview + + - run: python -m pip install -c ci-constraints-requirements.txt 'nox' + + - name: Clone wycheproof + timeout-minutes: 2 + uses: ./.github/actions/wycheproof + + - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + with: + repo: pyca/infra + workflow: build-macos-openssl.yml + branch: main + workflow_conclusion: success + name: openssl-macos-universal2 + path: "../openssl-macos-universal2/" + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Build nox environment + run: | + OPENSSL_DIR=$(readlink -f ../openssl-macos-universal2/) \ + OPENSSL_STATIC=1 \ + CFLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=incompatible-pointer-types-discards-qualifiers -Wno-error=unused-function -mmacosx-version-min=10.12" \ + nox -v --install-only + env: + NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + - name: Tests + run: nox --no-install -- --color=yes --wycheproof-root=wycheproof + env: + NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} + COLUMNS: 80 + + - uses: ./.github/actions/upload-coverage windows: runs-on: windows-latest strategy: + fail-fast: false matrix: WINDOWS: - {ARCH: 'x86', WINDOWS: 'win32'} - {ARCH: 'x64', WINDOWS: 'win64'} PYTHON: - - {VERSION: "2.7", TOXENV: "py27", MSVC_VERSION: "2010", CL_FLAGS: ""} - - {VERSION: "3.5", TOXENV: "py35", MSVC_VERSION: "2019", CL_FLAGS: ""} - - {VERSION: "3.6", TOXENV: "py36", MSVC_VERSION: "2019", CL_FLAGS: ""} - - {VERSION: "3.7", TOXENV: "py37", MSVC_VERSION: "2019", CL_FLAGS: ""} - - {VERSION: "3.8", TOXENV: "py38", MSVC_VERSION: "2019", CL_FLAGS: ""} - - {VERSION: "3.9", TOXENV: "py39", MSVC_VERSION: "2019", CL_FLAGS: "/D USE_OSRANDOM_RNG_FOR_TESTING"} - name: "Python ${{ matrix.PYTHON.VERSION }} on ${{ matrix.WINDOWS.WINDOWS }}" + - {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} + - {VERSION: "3.11", NOXSESSION: "tests"} + timeout-minutes: 15 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + persist-credentials: false + fetch-depth: 0 + - name: set mtimes for rust dirs + uses: ./.github/actions/mtime-fix - name: Setup python - uses: actions/setup-python@v2 + id: setup-python + uses: actions/setup-python@v4.6.1 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} + - run: rustup component add llvm-tools-preview + - name: Cache rust and pip + uses: ./.github/actions/cache + timeout-minutes: 2 + with: + key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.WINDOWS.ARCH }}-${{ steps.setup-python.outputs.python-version }} + - run: python -m pip install -c ci-constraints-requirements.txt "nox" - - name: Install MSVC for Python 2.7 - run: | - Invoke-WebRequest -Uri https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi -OutFile VCForPython27.msi - Start-Process msiexec -Wait -ArgumentList @('/i', 'VCForPython27.msi', '/qn', 'ALLUSERS=1') - Remove-Item VCForPython27.msi -Force - shell: powershell - if: matrix.PYTHON.VERSION == '2.7' - - run: python -m pip install tox requests coverage - - name: Download OpenSSL + - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + with: + repo: pyca/infra + workflow: build-windows-openssl.yml + branch: main + workflow_conclusion: success + name: "openssl-${{ matrix.WINDOWS.WINDOWS }}" + path: "C:/openssl-${{ matrix.WINDOWS.WINDOWS }}/" + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Configure run: | - python .github/workflows/download_openssl.py windows openssl-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.MSVC_VERSION }} - echo "INCLUDE=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.MSVC_VERSION }}/include;$INCLUDE" >> $GITHUB_ENV - echo "LIB=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.MSVC_VERSION }}/lib;$LIB" >> $GITHUB_ENV - echo "CL=${{ matrix.PYTHON.CL_FLAGS }}" >> $GITHUB_ENV - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV shell: bash - - run: git clone https://github.com/google/wycheproof - - run: tox -r -- --color=yes --wycheproof-root=wycheproof + - name: Clone wycheproof + timeout-minutes: 2 + uses: ./.github/actions/wycheproof + + - name: Build nox environment + run: nox -v --install-only + env: + NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + - name: Tests + run: nox --no-install -- --color=yes --wycheproof-root=wycheproof + env: + NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} + COLUMNS: 80 + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + + - uses: ./.github/actions/upload-coverage + + linux-downstream: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + DOWNSTREAM: + - paramiko + - pyopenssl + - pyopenssl-release + - twisted + - aws-encryption-sdk + - dynamodb-encryption-sdk + - certbot + - certbot-josepy + - mitmproxy + - scapy + PYTHON: + - '3.11' + name: "Downstream tests for ${{ matrix.DOWNSTREAM }}" + timeout-minutes: 15 + steps: + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + persist-credentials: false + fetch-depth: 0 + - name: set mtimes for rust dirs + uses: ./.github/actions/mtime-fix + - name: Cache rust and pip + uses: ./.github/actions/cache + timeout-minutes: 2 + - name: Setup python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ matrix.PYTHON }} + - run: ./.github/downstream.d/${{ matrix.DOWNSTREAM }}.sh install + - run: pip install . env: - TOXENV: ${{ matrix.PYTHON.TOXENV }} + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + # cryptography main has a version of "(X+1).0.0.dev1" where X is the + # most recently released major version. A package used by a downstream + # may depend on cryptography <=X. If you use entrypoints stuff, this can + # lead to runtime errors due to version incompatibilities. Rename the + # dist-info directory to pretend to be an older version to "solve" this. + - run: | + import json + import pkg_resources + import shutil + import urllib.request + + d = pkg_resources.get_distribution("cryptography") + with urllib.request.urlopen("https://pypi.org/pypi/cryptography/json") as r: + latest_version = json.load(r)["info"]["version"] + new_path = d.egg_info.replace(d.version, latest_version) + shutil.move(d.egg_info, new_path) + shell: python + - run: ./.github/downstream.d/${{ matrix.DOWNSTREAM }}.sh run - - name: Upload coverage + all-green: + # https://github.amrom.workers.devmunity/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert + runs-on: ubuntu-latest + needs: [linux, distros, macos, windows, linux-downstream] + if: ${{ always() }} + steps: + - uses: actions/checkout@v3.5.2 + timeout-minutes: 3 + with: + persist-credentials: false + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe + with: + jobs: ${{ toJSON(needs) }} + - name: Setup python + if: ${{ always() }} + uses: actions/setup-python@v4.6.1 + with: + python-version: '3.11' + - run: pip install -c ci-constraints-requirements.txt coverage[toml] + if: ${{ always() }} + - name: Download coverage data + if: ${{ always() }} + uses: actions/download-artifact@v3.0.2 + with: + name: coverage-data + - name: Combine coverage and fail if it's <100%. + if: ${{ always() }} + id: combinecoverage + run: | + set +e + python -m coverage combine + echo "## Python Coverage" >> $GITHUB_STEP_SUMMARY + python -m coverage report -m --fail-under=100 > COV_REPORT + COV_EXIT_CODE=$? + cat COV_REPORT + if [ $COV_EXIT_CODE -ne 0 ]; then + echo "🚨 Python Coverage failed. Under 100" | tee -a $GITHUB_STEP_SUMMARY + fi + echo '```' >> $GITHUB_STEP_SUMMARY + cat COV_REPORT >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + exit $COV_EXIT_CODE + - name: Combine rust coverage and fail if it's <100%. + if: ${{ always() }} + id: combinerustcoverage run: | - curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash - bash codecov.sh -n "Python ${{ matrix.PYTHON.VERSION }} on ${{ matrix.WINDOWS.WINDOWS }}" + set +e + sudo apt-get install -y lcov + RUST_COVERAGE_OUTPUT=$(lcov $(for f in *.lcov; do echo --add-tracefile "$f"; done) -o combined.lcov | grep lines) + echo "## Rust Coverage" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo $RUST_COVERAGE_OUTPUT >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + if ! echo "$RUST_COVERAGE_OUTPUT" | grep "100.0%"; then + echo "🚨 Rust Coverage failed. Under 100" | tee -a $GITHUB_STEP_SUMMARY + exit 1 + fi + - name: Create rust coverage HTML + run: genhtml combined.lcov -o rust-coverage + if: ${{ failure() && steps.combinerustcoverage.outcome == 'failure' }} + - name: Create coverage HTML + run: python -m coverage html + if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} + - name: Upload HTML report. + uses: actions/upload-artifact@v3.1.2 + with: + name: _html-report + path: htmlcov + if-no-files-found: ignore + if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} + - name: Upload rust HTML report. + uses: actions/upload-artifact@v3.1.2 + with: + name: _html-rust-report + path: rust-coverage + if-no-files-found: ignore + if: ${{ failure() && steps.combinerustcoverage.outcome == 'failure' }} diff --git a/.github/workflows/download_openssl.py b/.github/workflows/download_openssl.py deleted file mode 100644 index 1d5e3bfb471a..000000000000 --- a/.github/workflows/download_openssl.py +++ /dev/null @@ -1,73 +0,0 @@ -import io -import os -import sys -import time -import zipfile - -import requests - -from urllib3.util.retry import Retry - - -def get_response(session, url, token): - # Retry on non-502s - for i in range(5): - response = session.get( - url, headers={"Authorization": "token " + token} - ) - if response.status_code != 200: - print( - "HTTP error ({}) fetching {}, retrying".format( - response.status_code, url - ) - ) - time.sleep(2) - continue - return response - response = session.get(url, headers={"Authorization": "token " + token}) - if response.status_code != 200: - raise ValueError( - "Got HTTP {} fetching {}: ".format(response.status_code, url) - ) - return response - - -def main(platform, target): - if platform == "windows": - workflow = "build-windows-openssl.yml" - path = "C:/" - elif platform == "macos": - workflow = "build-macos-openssl.yml" - path = os.environ["HOME"] - else: - raise ValueError("Invalid platform") - - session = requests.Session() - adapter = requests.adapters.HTTPAdapter(max_retries=Retry()) - session.mount("https://", adapter) - session.mount("http://", adapter) - - token = os.environ["GITHUB_TOKEN"] - print("Looking for: {}".format(target)) - runs_url = ( - "https://api.github.com/repos/pyca/infra/actions/workflows/" - "{}/runs?branch=master&status=success".format(workflow) - ) - - response = get_response(session, runs_url, token).json() - artifacts_url = response["workflow_runs"][0]["artifacts_url"] - response = get_response(session, artifacts_url, token).json() - for artifact in response["artifacts"]: - if artifact["name"] == target: - print("Found artifact") - response = get_response( - session, artifact["archive_download_url"], token - ) - zipfile.ZipFile(io.BytesIO(response.content)).extractall( - os.path.join(path, artifact["name"]) - ) - return - - -if __name__ == "__main__": - main(sys.argv[1], sys.argv[2]) diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml new file mode 100644 index 000000000000..9a11f2a9fc70 --- /dev/null +++ b/.github/workflows/linkcheck.yml @@ -0,0 +1,46 @@ +name: linkcheck +on: + pull_request: {} + push: + branches: + - main + +permissions: + contents: read + +env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + +jobs: + docs-linkcheck: + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && contains(github.event.pull_request.title, 'linkcheck')) + runs-on: ubuntu-latest + name: "linkcheck" + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3.5.2 + with: + persist-credentials: false + fetch-depth: 0 + - name: set mtimes for rust dirs + uses: ./.github/actions/mtime-fix + - name: Setup python + id: setup-python + uses: actions/setup-python@v4.6.1 + with: + python-version: 3.11 + - name: Cache rust and pip + uses: ./.github/actions/cache + timeout-minutes: 2 + with: + # This creates the same key as the docs job (as long as they have the same + # python version) + key: 3.11-${{ steps.setup-python.outputs.python-version }} + - run: python -m pip install -c ci-constraints-requirements.txt nox + - name: Build nox environment + run: | + nox -v --install-only -s docs-linkcheck + env: + CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + - name: linkcheck + run: nox --no-install -s docs-linkcheck -- --color=yes \ No newline at end of file diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 7356bd4af3f9..951b70546066 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -1,14 +1,19 @@ name: Lock Issues on: + workflow_dispatch: schedule: - cron: '0 0 * * *' +permissions: + issues: "write" + jobs: lock: + if: github.repository_owner == 'pyca' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@c1b35aecc5cdb1a34539d14196df55838bb2f836 with: github-token: ${{ secrets.GITHUB_TOKEN }} - issue-lock-inactive-days: 90 - pr-lock-inactive-days: 90 + issue-inactive-days: 90 + pr-inactive-days: 90 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 000000000000..eed42830ecc7 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,76 @@ +name: Publish to PyPI + +on: + workflow_dispatch: + inputs: + run_id: + description: The run of wheel-builder to use for finding artifacts. + required: true + environment: + description: Which PyPI environment to upload to + required: true + type: choice + options: ["testpypi", "pypi"] + workflow_run: + workflows: ["Wheel Builder"] + types: [completed] + +jobs: + publish: + runs-on: ubuntu-latest + # We're not actually verifying that the triggering push event was for a + # tag, because github doesn't expose enough information to do so. + # wheel-builder.yml currently only has push events for tags. + if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.event == 'push' && github.event.workflow_run.conclusion == 'success') + permissions: + id-token: "write" + steps: + - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + with: + path: dist/ + run_id: ${{ github.event.inputs.run_id || github.event.workflow_run.id }} + - run: pip install twine requests sigstore + + - run: | + echo "OIDC_AUDIENCE=pypi" >> $GITHUB_ENV + echo "PYPI_DOMAIN=pypi.org" >> $GITHUB_ENV + echo "TWINE_REPOSITORY=pypi" >> $GITHUB_ENV + echo "TWINE_USERNAME=__token__" >> $GITHUB_ENV + if: github.event_name == 'workflow_run' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'pypi') + - run: | + echo "OIDC_AUDIENCE=testpypi" >> $GITHUB_ENV + echo "PYPI_DOMAIN=test.pypi.org" >> $GITHUB_ENV + echo "TWINE_REPOSITORY=testpypi" >> $GITHUB_ENV + echo "TWINE_USERNAME=__token__" >> $GITHUB_ENV + if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'testpypi' + + - run: | + import os + + import requests + + response = requests.get( + os.environ["ACTIONS_ID_TOKEN_REQUEST_URL"], + params={"audience": os.environ["OIDC_AUDIENCE"]}, + headers={"Authorization": f"bearer {os.environ['ACTIONS_ID_TOKEN_REQUEST_TOKEN']}"} + ) + response.raise_for_status() + token = response.json()["value"] + + response = requests.post(f"https://{os.environ['PYPI_DOMAIN']}/_/oidc/github/mint-token", json={"token": token}) + response.raise_for_status() + pypi_token = response.json()["token"] + + with open(os.environ["GITHUB_ENV"], "a") as f: + print(f"::add-mask::{pypi_token}") + f.write(f"TWINE_PASSWORD={pypi_token}\n") + shell: python + + - run: twine upload --skip-existing $(find dist/ -type f -name 'cryptography*') + + # Do not perform sigstore signatures for things for TestPyPI. This is + # because there's nothing that would prevent a malicious PyPI from + # serving a signed TestPyPI asset in place of a release intended for + # PyPI. + - run: sigstore sign $(find dist/ -type f -name 'cryptography*') + if: env.TWINE_REPOSITORY == 'pypi' diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index 74012f09b53a..b03df812b8d7 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -1,42 +1,135 @@ name: Wheel Builder +permissions: + contents: read on: workflow_dispatch: inputs: version: - required: true + description: The version to build + # Do not add any non-tag push events without updating pypi-publish.yml. If + # you do, it'll upload wheels to PyPI. + push: + tags: + - '*.*' + - '*.*.*' + pull_request: + paths: + - .github/workflows/wheel-builder.yml + - setup.py + - pyproject.toml + - vectors/pyproject.toml + +env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse jobs: - manylinux: + sdist: runs-on: ubuntu-latest - container: ${{ matrix.MANYLINUX.CONTAINER }} + name: sdists + steps: + - uses: actions/checkout@v3.5.2 + with: + # The tag to build or the tag received by the tag event + ref: ${{ github.event.inputs.version || github.ref }} + persist-credentials: false + + - run: python -m venv .venv + - name: Install Python dependencies + run: .venv/bin/pip install -U pip build + - name: Make sdist (cryptography) + run: .venv/bin/python -m build --sdist + - name: Make sdist and wheel (vectors) + run: cd vectors/ && ../.venv/bin/python -m build + - uses: actions/upload-artifact@v3.1.2 + with: + name: "cryptography-sdist" + path: dist/cryptography* + - uses: actions/upload-artifact@v3.1.2 + with: + name: "vectors-sdist-wheel" + path: vectors/dist/cryptography* + + manylinux: + needs: [sdist] + runs-on: ${{ matrix.MANYLINUX.RUNNER }} + container: ghcr.io/pyca/${{ matrix.MANYLINUX.CONTAINER }} strategy: + fail-fast: false matrix: - PYTHON: ["cp27-cp27m", "cp27-cp27mu", "cp35-cp35m"] + PYTHON: + - { VERSION: "cp37-cp37m", ABI_VERSION: 'cp37' } + - { VERSION: "pp38-pypy38_pp73" } + - { VERSION: "pp39-pypy39_pp73" } + - { VERSION: "pp310-pypy310_pp73" } MANYLINUX: - - NAME: manylinux1_x86_64 - CONTAINER: "pyca/cryptography-manylinux1:x86_64" - - NAME: manylinux2010_x86_64 - CONTAINER: "pyca/cryptography-manylinux2010:x86_64" - name: "${{ matrix.PYTHON }} for ${{ matrix.MANYLINUX.NAME }}" + - { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest" } + - { NAME: "manylinux_2_28_x86_64", CONTAINER: "cryptography-manylinux_2_28:x86_64", RUNNER: "ubuntu-latest"} + - { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} + + - { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64] } + - { NAME: "manylinux_2_28_aarch64", CONTAINER: "cryptography-manylinux_2_28:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + exclude: + # There are no readily available musllinux PyPy distributions + - PYTHON: { VERSION: "pp38-pypy38_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp39-pypy39_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp38-pypy38_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp39-pypy39_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + # We also don't build pypy wheels for anything except the latest manylinux + - PYTHON: { VERSION: "pp38-pypy38_pp73" } + MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp39-pypy39_pp73" } + MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp38-pypy38_pp73" } + MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp39-pypy39_pp73" } + MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64]} + name: "${{ matrix.PYTHON.VERSION }} for ${{ matrix.MANYLINUX.NAME }}" steps: - - run: /opt/python/${{ matrix.PYTHON }}/bin/python -m virtualenv .venv + - name: Ridiculous alpine workaround for actions support on arm64 + run: | + # This modifies /etc/os-release so the JS actions + # from GH can't detect that it's on alpine:aarch64. It will + # then use a glibc nodejs, which works fine when gcompat + # is installed in the container (which it is) + sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release + if: matrix.MANYLINUX.NAME == 'musllinux_1_1_aarch64' + + - uses: actions/download-artifact@v3.0.2 + with: + name: cryptography-sdist + + - run: /opt/python/${{ matrix.PYTHON.VERSION }}/bin/python -m venv .venv - name: Install Python dependencies - run: .venv/bin/pip install -U pip wheel cffi six ipaddress "enum34; python_version < '3'" - - run: .venv/bin/pip download cryptography==${{ github.event.inputs.version }} --no-deps --no-binary cryptography && tar zxvf cryptography*.tar.gz && mkdir tmpwheelhouse - - run: | - REGEX="cp3([0-9])*" - if [[ "${{ matrix.PYTHON }}" =~ $REGEX ]]; then - PY_LIMITED_API="--py-limited-api=cp3${BASH_REMATCH[1]}" + run: .venv/bin/pip install -U pip wheel cffi setuptools-rust + - run: mkdir tmpwheelhouse + - name: Build the wheel + run: | + if [ -n "${{ matrix.PYTHON.ABI_VERSION }}" ]; then + PY_LIMITED_API="--config-settings=--build-option=--py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} --no-build-isolation" fi - cd cryptography* - LDFLAGS="-L/opt/pyca/cryptography/openssl/lib" \ - CFLAGS="-I/opt/pyca/cryptography/openssl/include -Wl,--exclude-libs,ALL" \ - ../.venv/bin/python setup.py bdist_wheel $PY_LIMITED_API && mv dist/cryptography*.whl ../tmpwheelhouse + OPENSSL_DIR="/opt/pyca/cryptography/openssl" \ + OPENSSL_STATIC=1 \ + .venv/bin/python -m pip wheel -v $PY_LIMITED_API cryptograph*.tar.gz -w dist/ && mv dist/cryptography*.whl tmpwheelhouse + env: + RUSTUP_HOME: /root/.rustup - run: auditwheel repair --plat ${{ matrix.MANYLINUX.NAME }} tmpwheelhouse/cryptograph*.whl -w wheelhouse/ - run: unzip wheelhouse/*.whl -d execstack.check - run: | - results=$(execstack execstack.check/cryptography/hazmat/bindings/*.so) - count=$(echo "$results" | grep -c '^X' || true) + results=$(readelf -lW execstack.check/cryptography/hazmat/bindings/*.so) + count=$(echo "$results" | grep -c 'GNU_STACK.*[R ][W ]E' || true) if [ "$count" -ne 0 ]; then exit 1 else @@ -47,111 +140,183 @@ jobs: .venv/bin/python -c "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" - run: mkdir cryptography-wheelhouse - run: mv wheelhouse/cryptography*.whl cryptography-wheelhouse/ - - uses: actions/upload-artifact@v2.2.0 + - uses: actions/upload-artifact@v3.1.2 with: - name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.MANYLINUX.NAME }}-${{ matrix.PYTHON }}" + name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.MANYLINUX.NAME }}-${{ matrix.PYTHON.VERSION }}" path: cryptography-wheelhouse/ macos: - runs-on: macos-latest + needs: [sdist] + runs-on: macos-12 strategy: + fail-fast: false matrix: PYTHON: - - VERSION: '2.7' - ABI_VERSION: '2.7' - DOWNLOAD_URL: 'https://www.python.org/ftp/python/2.7.17/python-2.7.17-macosx10.9.pkg' - BIN_PATH: '/Library/Frameworks/Python.framework/Versions/2.7/bin/python' - - VERSION: '3.8' - ABI_VERSION: '3.5' - DOWNLOAD_URL: 'https://www.python.org/ftp/python/3.8.2/python-3.8.2-macosx10.9.pkg' - BIN_PATH: '/Library/Frameworks/Python.framework/Versions/3.8/bin/python3' - name: "${{ matrix.PYTHON.VERSION }} ABI ${{ matrix.PYTHON.ABI_VERSION }} macOS" + - VERSION: '3.11' + ABI_VERSION: 'cp37' + # Despite the name, this is built for the macOS 11 SDK on arm64 and 10.9+ on intel + DOWNLOAD_URL: 'https://www.python.org/ftp/python/3.11.3/python-3.11.3-macos11.pkg' + BIN_PATH: '/Library/Frameworks/Python.framework/Versions/3.11/bin/python3' + DEPLOYMENT_TARGET: '10.12' + # This archflags is default, but let's be explicit + ARCHFLAGS: '-arch x86_64 -arch arm64' + # See https://github.com/pypa/cibuildwheel/blob/c8876b5c54a6c6b08de5d4b1586906b56203bd9e/cibuildwheel/macos.py#L257-L269 + # This will change in the future as we change the base Python we + # build against + _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' + - VERSION: '3.11' + ABI_VERSION: 'cp37' + DOWNLOAD_URL: 'https://www.python.org/ftp/python/3.11.3/python-3.11.3-macos11.pkg' + BIN_PATH: '/Library/Frameworks/Python.framework/Versions/3.11/bin/python3' + DEPLOYMENT_TARGET: '10.12' + # We continue to build a non-universal2 for a bit to see metrics on + # download counts (this is a proxy for pip version since universal2 + # requires a 21.x pip) + ARCHFLAGS: '-arch x86_64' + _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' + - VERSION: 'pypy-3.8' + BIN_PATH: 'pypy3' + DEPLOYMENT_TARGET: '10.12' + _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' + ARCHFLAGS: '-arch x86_64' + - VERSION: 'pypy-3.9' + BIN_PATH: 'pypy3' + DEPLOYMENT_TARGET: '10.12' + _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' + ARCHFLAGS: '-arch x86_64' + - VERSION: 'pypy-3.10' + BIN_PATH: 'pypy3' + DEPLOYMENT_TARGET: '10.12' + _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' + ARCHFLAGS: '-arch x86_64' + name: "${{ matrix.PYTHON.VERSION }} ABI ${{ matrix.PYTHON.ABI_VERSION }} macOS ${{ matrix.PYTHON.ARCHFLAGS }}" steps: - - uses: actions/checkout@v2 - - run: | + - uses: actions/download-artifact@v3.0.2 + with: + name: cryptography-sdist + + - name: Setup python + run: | curl "$PYTHON_DOWNLOAD_URL" -o python.pkg sudo installer -pkg python.pkg -target / env: PYTHON_DOWNLOAD_URL: ${{ matrix.PYTHON.DOWNLOAD_URL }} - - run: ${{ matrix.PYTHON.BIN_PATH }} -m pip install -U virtualenv requests - - name: Download OpenSSL - run: | - ${{ matrix.PYTHON.BIN_PATH }} .github/workflows/download_openssl.py macos openssl-macos - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: contains(matrix.PYTHON.VERSION, 'pypy') == false + - name: Setup pypy + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ matrix.PYTHON.VERSION }} + if: contains(matrix.PYTHON.VERSION, 'pypy') + - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + with: + repo: pyca/infra + workflow: build-macos-openssl.yml + branch: main + workflow_conclusion: success + name: openssl-macos-universal2 + path: "../openssl-macos-universal2/" + github_token: ${{ secrets.GITHUB_TOKEN }} + - uses: dtolnay/rust-toolchain@52e69531e6f69a396bc9d1226284493a5db969ff + with: + toolchain: stable + # Add the arm64 target in addition to the native arch (x86_64) + target: aarch64-apple-darwin - - run: ${{ matrix.PYTHON.BIN_PATH }} -m virtualenv venv - - run: venv/bin/pip install -U pip wheel cffi six ipaddress "enum34; python_version < '3'" - - run: venv/bin/pip download cryptography==${{ github.event.inputs.version }} --no-deps --no-binary cryptography && tar zxvf cryptography*.tar.gz && mkdir wheelhouse + - run: ${{ matrix.PYTHON.BIN_PATH }} -m venv venv + - run: venv/bin/pip install -U pip wheel cffi setuptools-rust + - run: mkdir wheelhouse - name: Build the wheel run: | - REGEX="3\.([0-9])*" - if [[ "${{ matrix.PYTHON.ABI_VERSION }}" =~ $REGEX ]]; then - PY_LIMITED_API="--py-limited-api=cp3${BASH_REMATCH[1]}" + if [ -n "${{ matrix.PYTHON.ABI_VERSION }}" ]; then + PY_LIMITED_API="--config-settings=--build-option=--py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} --no-build-isolation" fi - cd cryptography* - CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS="1" \ - LDFLAGS="${HOME}/openssl-macos/lib/libcrypto.a ${HOME}/openssl-macos/lib/libssl.a" \ - CFLAGS="-I${HOME}/openssl-macos/include -mmacosx-version-min=10.10 -march=core2" \ - ../venv/bin/python setup.py bdist_wheel $PY_LIMITED_API && mv dist/cryptography*.whl ../wheelhouse - - run: venv/bin/pip install -f wheelhouse --no-index cryptography + OPENSSL_DIR="$(readlink -f ../openssl-macos-universal2/)" \ + OPENSSL_STATIC=1 \ + venv/bin/python -m pip wheel -v $PY_LIMITED_API cryptograph*.tar.gz -w dist/ && mv dist/cryptography*.whl wheelhouse + env: + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.PYTHON.DEPLOYMENT_TARGET }} + ARCHFLAGS: ${{ matrix.PYTHON.ARCHFLAGS }} + _PYTHON_HOST_PLATFORM: ${{ matrix.PYTHON._PYTHON_HOST_PLATFORM }} + - run: venv/bin/pip install -f wheelhouse/ --no-index cryptography + - name: Show the wheel's minimum macOS SDK and architectures + run: | + find venv/lib/*/site-packages/cryptography/hazmat/bindings -name '*.so' -exec vtool -show {} \; - run: | venv/bin/python -c "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" - run: mkdir cryptography-wheelhouse - run: mv wheelhouse/cryptography*.whl cryptography-wheelhouse/ - - uses: actions/upload-artifact@v2.2.0 + - run: | + echo "CRYPTOGRAPHY_WHEEL_NAME=$(basename $(ls cryptography-wheelhouse/cryptography*.whl))" >> $GITHUB_ENV + - uses: actions/upload-artifact@v3.1.2 with: - name: "cryptography-${{ github.event.inputs.version }}-macOS-${{ matrix.PYTHON.ABI_VERSION }}" + name: "${{ env.CRYPTOGRAPHY_WHEEL_NAME }}" path: cryptography-wheelhouse/ windows: + needs: [sdist] runs-on: windows-latest strategy: + fail-fast: false matrix: WINDOWS: - - {ARCH: 'x86', WINDOWS: 'win32'} - - {ARCH: 'x64', WINDOWS: 'win64'} + - {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} + - {ARCH: 'x64', WINDOWS: 'win64', RUST_TRIPLE: 'x86_64-pc-windows-msvc'} PYTHON: - - {VERSION: "2.7", MSVC_VERSION: "2010"} - - {VERSION: "3.5", MSVC_VERSION: "2019"} - - {VERSION: "3.6", MSVC_VERSION: "2019"} - - {VERSION: "3.7", MSVC_VERSION: "2019"} - - {VERSION: "3.8", MSVC_VERSION: "2019"} - - {VERSION: "3.8", MSVC_VERSION: "2019", "USE_ABI3": "true", "ABI_VERSION": "cp36"} + - {VERSION: "3.11", "ABI_VERSION": "cp37"} + - {VERSION: "pypy-3.8"} + - {VERSION: "pypy-3.9"} + - {VERSION: "pypy-3.10"} + exclude: + # We need to exclude the below configuration because there is no 32-bit pypy3 + - WINDOWS: {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} + PYTHON: {VERSION: "pypy-3.8"} + - WINDOWS: {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} + PYTHON: {VERSION: "pypy-3.9"} + - WINDOWS: {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} + PYTHON: {VERSION: "pypy-3.10"} name: "${{ matrix.PYTHON.VERSION }} ${{ matrix.WINDOWS.WINDOWS }} ${{ matrix.PYTHON.ABI_VERSION }}" steps: - - uses: actions/checkout@v2 + - uses: actions/download-artifact@v3.0.2 + with: + name: cryptography-sdist + - name: Setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4.6.1 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} - - name: Install MSVC for Python 2.7 - run: | - Invoke-WebRequest -Uri https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi -OutFile VCForPython27.msi - Start-Process msiexec -Wait -ArgumentList @('/i', 'VCForPython27.msi', '/qn', 'ALLUSERS=1') - Remove-Item VCForPython27.msi -Force - shell: powershell - if: matrix.PYTHON.VERSION == '2.7' - - run: pip install requests - - name: Download OpenSSL + - uses: dtolnay/rust-toolchain@52e69531e6f69a396bc9d1226284493a5db969ff + with: + toolchain: stable + target: ${{ matrix.WINDOWS.RUST_TRIPLE }} + + - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + with: + repo: pyca/infra + workflow: build-windows-openssl.yml + branch: main + workflow_conclusion: success + name: "openssl-${{ matrix.WINDOWS.WINDOWS }}" + path: "C:/openssl-${{ matrix.WINDOWS.WINDOWS }}/" + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Configure OpenSSL run: | - python .github/workflows/download_openssl.py windows openssl-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.MSVC_VERSION }} - echo "INCLUDE=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.MSVC_VERSION }}/include;$INCLUDE" >> $GITHUB_ENV - echo "LIB=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.MSVC_VERSION }}/lib;$LIB" >> $GITHUB_ENV - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV + echo "OPENSSL_STATIC=1" >> $GITHUB_ENV shell: bash - - run: python -m pip install -U pip wheel cffi six ipaddress "enum34; python_version < '3'" - - run: pip download cryptography==${{ github.event.inputs.version }} --no-deps --no-binary cryptography && tar zxvf cryptography*.tar.gz && mkdir wheelhouse + - run: python -m pip install -U pip wheel + - run: python -m pip install cffi setuptools-rust + - run: mkdir wheelhouse + - run: | + if [ -n "${{ matrix.PYTHON.ABI_VERSION }}" ]; then + PY_LIMITED_API="--config-settings=--build-option=--py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} --no-build-isolation" + fi + + python -m pip wheel -v cryptography*.tar.gz $PY_LIMITED_API -w dist/ && mv dist/cryptography*.whl wheelhouse/ shell: bash - - run: cd cryptography* && python setup.py bdist_wheel && mv dist/cryptography*.whl ../wheelhouse - if: matrix.PYTHON.USE_ABI3 != 'true' - - run: cd cryptography* && python setup.py bdist_wheel --py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} && mv dist/cryptography*.whl ../wheelhouse - if: matrix.PYTHON.USE_ABI3 == 'true' - run: pip install -f wheelhouse --no-index cryptography - name: Print the OpenSSL we built and linked against run: | @@ -159,7 +324,7 @@ jobs: - run: mkdir cryptography-wheelhouse - run: move wheelhouse\cryptography*.whl cryptography-wheelhouse\ - - uses: actions/upload-artifact@v2.2.0 + - uses: actions/upload-artifact@v3.1.2 with: name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION}}" path: cryptography-wheelhouse\ diff --git a/.gitignore b/.gitignore index cdc4b6ad762e..035b15ccd025 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ htmlcov/ .eggs/ *.py[cdo] .hypothesis/ +target/ +.rust-cov/ \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000000..95b3c4f46e7c --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,22 @@ +# https://docs.readthedocs.io/en/stable/config-file/v2.html#supported-settings + +version: 2 + +sphinx: + # The config file overrides the UI settings: + # https://github.com/pyca/cryptography/issues/5863#issuecomment-817828152 + builder: dirhtml + +build: + # readdocs master now includes a rust toolchain + os: "ubuntu-22.04" + tools: + python: "3.11" + rust: "1.64" + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b0f0fe78d6e6..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,155 +0,0 @@ -sudo: true -dist: focal - -language: python - -cache: - directories: - - $HOME/.cache/pip - - $HOME/ossl-2/ - -# Only build master, the version branches (e.g. 1.7.x), and -# version tags (which are apparently considered branches by travis) -branches: - only: - - master - - /^\d+\.\d+\.x$/ - - /^\d+\.\d+(\.\d+)?$/ - -matrix: - include: - - python: 3.8 - env: TOXENV=pep8,packaging - # Setting 'python' is just to make travis's UI a bit prettier - - python: 3.6 - env: TOXENV=py36 - - python: 3.9-dev - env: TOXENV=py39 - # Travis lists available Pythons (including PyPy) by arch and distro here: - # https://docs.travis-ci.com/user/languages/python/#python-versions - - python: pypy2.7-7.3.1 - env: TOXENV=pypy-nocoverage - - python: pypy3.6-7.3.1 - env: TOXENV=pypy3-nocoverage - - python: 3.8 - env: TOXENV=py38 OPENSSL=1.0.2u - - python: 2.7 - env: TOXENV=py27 OPENSSL=1.1.0l - - python: 2.7 - env: TOXENV=py27-ssh OPENSSL=1.1.0l - - python: 3.8 - env: TOXENV=py38 OPENSSL=1.1.0l - - python: 2.7 - env: TOXENV=py27 OPENSSL=1.1.1h - - python: 3.8 - env: TOXENV=py38 OPENSSL=1.1.1h - - python: 3.8 - env: TOXENV=py38 OPENSSL=1.1.1h OPENSSL_CONFIG_FLAGS="no-engine no-rc2 no-srtp no-ct" - - python: 3.8 - env: TOXENV=py38-ssh OPENSSL=1.1.1h - - python: 3.8 - env: TOXENV=py38 LIBRESSL=2.9.2 - - python: 3.8 - env: TOXENV=py38 LIBRESSL=3.0.2 - - python: 3.8 - env: TOXENV=py38 LIBRESSL=3.1.4 - - python: 3.8 - env: TOXENV=py38 LIBRESSL=3.2.2 - - - python: 2.7 - services: docker - env: TOXENV=py27 DOCKER=pyca/cryptography-runner-centos7 - - python: 2.7 - services: docker - env: TOXENV=py27 DOCKER=pyca/cryptography-runner-centos8 - - python: 3.6 - services: docker - env: TOXENV=py36 DOCKER=pyca/cryptography-runner-centos8 - - python: 3.6 - services: docker - env: TOXENV=py36 OPENSSL_FORCE_FIPS_MODE=1 DOCKER=pyca/cryptography-runner-centos8-fips - - python: 2.7 - services: docker - env: TOXENV=py27 DOCKER=pyca/cryptography-runner-stretch - - python: 3.5 - services: docker - env: TOXENV=py35 DOCKER=pyca/cryptography-runner-stretch - - python: 3.7 - services: docker - env: TOXENV=py37 DOCKER=pyca/cryptography-runner-buster - - python: 3.8 - services: docker - env: TOXENV=py38 DOCKER=pyca/cryptography-runner-bullseye - - python: 3.8 - services: docker - env: TOXENV=py38 DOCKER=pyca/cryptography-runner-sid - - python: 3.6 - services: docker - env: TOXENV=py36 DOCKER=pyca/cryptography-runner-ubuntu-bionic - - python: 3.8 - services: docker - env: TOXENV=py38 DOCKER=pyca/cryptography-runner-ubuntu-focal - - python: 2.7 - services: docker - env: TOXENV=py27 DOCKER=pyca/cryptography-runner-ubuntu-rolling - - python: 3.8 - services: docker - env: TOXENV=py38 DOCKER=pyca/cryptography-runner-ubuntu-rolling - - python: 3.8 - services: docker - env: TOXENV=py38-randomorder DOCKER=pyca/cryptography-runner-ubuntu-rolling - - python: 3.8 - services: docker - env: TOXENV=py38 DOCKER=pyca/cryptography-runner-fedora - - python: 3.8 - services: docker - env: TOXENV=py38 DOCKER=pyca/cryptography-runner-alpine:latest - - - python: 3.8 - env: TOXENV=docs OPENSSL=1.1.1h - addons: - apt: - packages: - - libenchant-dev - - python: 3.8 - services: docker - env: TOXENV=docs-linkcheck DOCKER=pyca/cryptography-runner-buster - if: (branch = master AND type != pull_request) OR commit_message =~ /linkcheck/ - - - python: 3.8 - env: DOWNSTREAM=pyopenssl - - python: 3.7 - env: DOWNSTREAM=twisted OPENSSL=1.1.1h - - python: 3.7 - env: DOWNSTREAM=paramiko - - python: 3.7 - env: DOWNSTREAM=aws-encryption-sdk - - python: 3.7 - # BOTO_CONFIG works around this boto issue on travis: - # https://github.com/boto/boto/issues/3717 - env: DOWNSTREAM=dynamodb-encryption-sdk BOTO_CONFIG=/dev/null - - python: 3.8 - env: DOWNSTREAM=certbot - - python: 3.8 - env: DOWNSTREAM=certbot-josepy - -install: - - ./.travis/install.sh - -script: - - ./.travis/run.sh - -after_success: - - ./.travis/upload_coverage.sh - -notifications: - irc: - channels: - # This is set to a secure variable to prevent forks from notifying the - # IRC channel whenever they fail a build. This can be removed when travis - # implements https://github.com/travis-ci/travis-ci/issues/1094. - # The value encrypted here was created via - # travis encrypt "irc.freenode.org#cryptography-dev" - - secure: "A93qvTOlwlMK5WoEvZQ5jQ8Z4Hd0JpeO53WYt8iIJ3s/L6AubkfiN7gwhThRtPnPx7DVMenoKRMlcRg76/ICvXEViVnGgXFjsypF0CzVcIay9pPdjpZjZHP735yLfX512RtxYEdEGwi5r25Z2CEFaydhhxNwfuMxGBtLUjusix4=" - use_notice: true - skip_join: true diff --git a/.travis/downstream.d/README.rst b/.travis/downstream.d/README.rst deleted file mode 100644 index 1553448c327f..000000000000 --- a/.travis/downstream.d/README.rst +++ /dev/null @@ -1,13 +0,0 @@ -To add downstream tests to be run in CI: - -1. Create a test handler for the downstream consumer that you want to test. - - * The test handler should be a single file in the ``.travis/downstream.d/`` directory. - * The file name should be ``{downstream name}.sh`` where ``{downstream name}`` - is the name that you wish to use to identify the consumer. - * The test handler should accept a single argument that can be either ``install`` or ``run``. - These should be used to separate installation of the downstream consumer and - any dependencies from the actual running of the tests. - -2. Add an entry to the test matrix in ``.travis.yml`` that sets the ``DOWNSTREAM`` - environment variable to the downstream name that you selected. diff --git a/.travis/downstream.d/certbot-josepy.sh b/.travis/downstream.d/certbot-josepy.sh deleted file mode 100755 index 2253edc1e9a6..000000000000 --- a/.travis/downstream.d/certbot-josepy.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -ex - -case "${1}" in - install) - git clone --depth=1 https://github.com/certbot/josepy - cd josepy - git rev-parse HEAD - pip install -e ".[tests]" -c constraints.txt - ;; - run) - cd josepy - pytest src - ;; - *) - exit 1 - ;; -esac diff --git a/.travis/install.sh b/.travis/install.sh deleted file mode 100755 index 91df42285cdb..000000000000 --- a/.travis/install.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") - -shlib_sed() { - # modify the shlib version to a unique one to make sure the dynamic - # linker doesn't load the system one. - sed -i "s/^SHLIB_MAJOR=.*/SHLIB_MAJOR=100/" Makefile - sed -i "s/^SHLIB_MINOR=.*/SHLIB_MINOR=0.0/" Makefile - sed -i "s/^SHLIB_VERSION_NUMBER=.*/SHLIB_VERSION_NUMBER=100.0.0/" Makefile -} - -# download, compile, and install if it's not already present via travis -# cache -if [ -n "${OPENSSL}" ]; then - . "$SCRIPT_DIR/openssl_config.sh" - if [[ ! -f "$HOME/$OPENSSL_DIR/bin/openssl" ]]; then - curl -O "https://www.openssl.org/source/openssl-${OPENSSL}.tar.gz" - tar zxf "openssl-${OPENSSL}.tar.gz" - pushd "openssl-${OPENSSL}" - ./config $OPENSSL_CONFIG_FLAGS -fPIC --prefix="$HOME/$OPENSSL_DIR" - shlib_sed - make depend - make -j"$(nproc)" - # CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 - if [ "${OPENSSL}" == "1.0.2u" ]; then - make install - else - # avoid installing the docs on versions of OpenSSL that aren't ancient. - # https://github.com/openssl/openssl/issues/6685#issuecomment-403838728 - make install_sw install_ssldirs - fi - popd - fi -elif [ -n "${LIBRESSL}" ]; then - LIBRESSL_DIR="ossl-2/${LIBRESSL}" - if [[ ! -f "$HOME/$LIBRESSL_DIR/bin/openssl" ]]; then - curl -O "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL}.tar.gz" - tar zxf "libressl-${LIBRESSL}.tar.gz" - pushd "libressl-${LIBRESSL}" - ./config -Wl -Wl,-Bsymbolic-functions -fPIC shared --prefix="$HOME/$LIBRESSL_DIR" - shlib_sed - make -j"$(nproc)" install - popd - fi -fi - -if [ -n "${DOCKER}" ]; then - if [ -n "${OPENSSL}" ] || [ -n "${LIBRESSL}" ]; then - echo "OPENSSL and LIBRESSL are not allowed when DOCKER is set." - exit 1 - fi - docker pull "$DOCKER" || docker pull "$DOCKER" || docker pull "$DOCKER" -fi - -if [ -z "${DOWNSTREAM}" ]; then - git clone --depth=1 https://github.com/google/wycheproof "$HOME/wycheproof" -fi - -pip install -U pip -pip install virtualenv - -python -m virtualenv ~/.venv -source ~/.venv/bin/activate -# If we pin coverage it must be kept in sync with tox.ini and .github/workflows/ci.yml -pip install tox coverage diff --git a/.travis/openssl_config.sh b/.travis/openssl_config.sh deleted file mode 100755 index 83f16d2bfea8..000000000000 --- a/.travis/openssl_config.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e -set -x - -DEFAULT_CONFIG_FLAGS="shared no-ssl2 no-ssl3" -if [ -n "${OPENSSL_CONFIG_FLAGS}" ]; then - OPENSSL_CONFIG_FLAGS="$DEFAULT_CONFIG_FLAGS $OPENSSL_CONFIG_FLAGS" -else - OPENSSL_CONFIG_FLAGS=$DEFAULT_CONFIG_FLAGS -fi -CONFIG_HASH=$(echo "$OPENSSL_CONFIG_FLAGS" | sha1sum | sed 's/ .*$//') -OPENSSL_DIR="ossl-2/${OPENSSL}${CONFIG_HASH}" diff --git a/.travis/run.sh b/.travis/run.sh deleted file mode 100755 index 01605c2717d4..000000000000 --- a/.travis/run.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -ex - -SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") - -if [ -n "${LIBRESSL}" ]; then - LIBRESSL_DIR="ossl-2/${LIBRESSL}" - export CFLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=discarded-qualifiers -Wno-error=unused-function -I$HOME/$LIBRESSL_DIR/include" - export PATH="$HOME/$LIBRESSL_DIR/bin:$PATH" - export LDFLAGS="-L$HOME/$LIBRESSL_DIR/lib -Wl,-rpath=$HOME/$LIBRESSL_DIR/lib" -fi - -if [ -n "${OPENSSL}" ]; then - . "$SCRIPT_DIR/openssl_config.sh" - export PATH="$HOME/$OPENSSL_DIR/bin:$PATH" - export CFLAGS="${CFLAGS} -I$HOME/$OPENSSL_DIR/include" - # rpath on linux will cause it to use an absolute path so we don't need to - # do LD_LIBRARY_PATH - export LDFLAGS="-L$HOME/$OPENSSL_DIR/lib -Wl,-rpath=$HOME/$OPENSSL_DIR/lib" -fi - -source ~/.venv/bin/activate - -if [ -n "${DOCKER}" ]; then - docker run --rm \ - -v "${TRAVIS_BUILD_DIR}":"${TRAVIS_BUILD_DIR}" \ - -v "${HOME}/wycheproof":/wycheproof \ - -w "${TRAVIS_BUILD_DIR}" \ - -e OPENSSL_FORCE_FIPS_MODE \ - -e TOXENV "${DOCKER}" \ - /bin/sh -c "tox -- --wycheproof-root='/wycheproof'" -elif [ -n "${TOXENV}" ]; then - tox -- --wycheproof-root="$HOME/wycheproof" -else - downstream_script="${TRAVIS_BUILD_DIR}/.travis/downstream.d/${DOWNSTREAM}.sh" - if [ ! -x "$downstream_script" ]; then - exit 1 - fi - $downstream_script install - pip install . - $downstream_script run -fi diff --git a/.travis/upload_coverage.sh b/.travis/upload_coverage.sh deleted file mode 100755 index 2999bb7e6b25..000000000000 --- a/.travis/upload_coverage.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -e -set -x - -if [ -n "${TOXENV}" ]; then - case "${TOXENV}" in - pypy-nocoverage);; - pypy3-nocoverage);; - pep8);; - py3pep8);; - docs);; - *) - source ~/.venv/bin/activate - curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash - - bash codecov.sh -Z -e TRAVIS_OS_NAME,TOXENV,OPENSSL,DOCKER,OPENSSL_FORCE_FIPS_MODE || \ - bash codecov.sh -Z -e TRAVIS_OS_NAME,TOXENV,OPENSSL,DOCKER,OPENSSL_FORCE_FIPS_MODE - ;; - esac -fi diff --git a/.zuul.d/jobs.yaml b/.zuul.d/jobs.yaml deleted file mode 100644 index 1430b0c31510..000000000000 --- a/.zuul.d/jobs.yaml +++ /dev/null @@ -1,68 +0,0 @@ -- job: - name: pyca-cryptography-base - abstract: true - description: Run pyca/cryptography unit testing - run: .zuul.playbooks/playbooks/tox/main.yaml - -- job: - name: pyca-cryptography-ubuntu-focal-py38-arm64 - parent: pyca-cryptography-base - nodeset: ubuntu-focal-arm64 - vars: - tox_envlist: py38 - -- job: - name: pyca-cryptography-ubuntu-bionic-py36-arm64 - parent: pyca-cryptography-base - nodeset: ubuntu-bionic-arm64 - vars: - tox_envlist: py36 - -- job: - name: pyca-cryptography-centos-8-py36-arm64 - parent: pyca-cryptography-base - nodeset: centos-8-arm64 - vars: - tox_envlist: py36 - -- job: - name: pyca-cryptography-centos-8-py27-arm64 - parent: pyca-cryptography-base - nodeset: centos-8-arm64 - vars: - tox_envlist: py27 - -- job: - name: pyca-cryptography-build-wheel - abstract: true - run: .zuul.playbooks/playbooks/wheel/main.yaml - -- job: - name: pyca-cryptography-build-wheel-arm64 - parent: pyca-cryptography-build-wheel - nodeset: ubuntu-bionic-arm64 - vars: - wheel_builds: - - platform: manylinux2014_aarch64 - image: pyca/cryptography-manylinux2014_aarch64 - pythons: - - cp35-cp35m - -- job: - name: pyca-cryptography-build-wheel-x86_64 - parent: pyca-cryptography-build-wheel - nodeset: ubuntu-bionic - vars: - wheel_builds: - - platform: manylinux1_x86_64 - image: pyca/cryptography-manylinux1:x86_64 - pythons: - - cp27-cp27m - - cp27-cp27mu - - cp35-cp35m - - platform: manylinux2010_x86_64 - image: pyca/cryptography-manylinux2010:x86_64 - pythons: - - cp27-cp27m - - cp27-cp27mu - - cp35-cp35m diff --git a/.zuul.d/project.yaml b/.zuul.d/project.yaml deleted file mode 100644 index 3cda2ff87d00..000000000000 --- a/.zuul.d/project.yaml +++ /dev/null @@ -1,13 +0,0 @@ -- project: - check: - jobs: - - pyca-cryptography-build-wheel-arm64 - - pyca-cryptography-build-wheel-x86_64 - - pyca-cryptography-ubuntu-focal-py38-arm64 - - pyca-cryptography-ubuntu-bionic-py36-arm64 - - pyca-cryptography-centos-8-py36-arm64 - - pyca-cryptography-centos-8-py27-arm64 - release: - jobs: - - pyca-cryptography-build-wheel-arm64 - - pyca-cryptography-build-wheel-x86_64 diff --git a/.zuul.playbooks/playbooks/tox/main.yaml b/.zuul.playbooks/playbooks/tox/main.yaml deleted file mode 100644 index fc92398ffa62..000000000000 --- a/.zuul.playbooks/playbooks/tox/main.yaml +++ /dev/null @@ -1,41 +0,0 @@ -- hosts: all - tasks: - - - name: Install tox - include_role: - name: ensure-tox - - - name: Install required packages - package: - name: - - build-essential - - libssl-dev - - libffi-dev - - python3-dev - become: yes - when: ansible_distribution in ['Debian', 'Ubuntu'] - - - name: Install required packages - package: - name: - - redhat-rpm-config - - gcc - - libffi-devel - - openssl-devel - - python3-devel - - python2-devel - become: yes - when: ansible_distribution == 'CentOS' - - - name: Clone wycheproof - git: - repo: https://github.com/google/wycheproof - dest: "{{ ansible_facts.env['HOME'] }}/wycheproof" - depth: 1 - - - name: Run tox - include_role: - name: tox - vars: - tox_extra_args: "-- --wycheproof-root={{ ansible_facts.env['HOME'] }}/wycheproof/" - diff --git a/.zuul.playbooks/playbooks/wheel/main.yaml b/.zuul.playbooks/playbooks/wheel/main.yaml deleted file mode 100644 index 7fcdd82efe2b..000000000000 --- a/.zuul.playbooks/playbooks/wheel/main.yaml +++ /dev/null @@ -1,6 +0,0 @@ -- hosts: all - tasks: - - - name: Build wheel - include_role: - name: build-wheel-manylinux diff --git a/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/README.rst b/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/README.rst deleted file mode 100644 index 13c22d2cbaca..000000000000 --- a/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/README.rst +++ /dev/null @@ -1 +0,0 @@ -Build manylinux wheels for cryptography diff --git a/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/files/build-wheels.sh b/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/files/build-wheels.sh deleted file mode 100644 index 65a8201823ca..000000000000 --- a/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/files/build-wheels.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -ex - -# Compile wheels -cd /io - -mkdir -p wheelhouse.final - -for P in ${PYTHONS}; do - - PYBIN=/opt/python/${P}/bin - - "${PYBIN}"/python -m virtualenv .venv - - .venv/bin/pip install cffi six ipaddress "enum34; python_version < '3'" - - REGEX="cp3([0-9])*" - if [[ "${PYBIN}" =~ $REGEX ]]; then - PY_LIMITED_API="--py-limited-api=cp3${BASH_REMATCH[1]}" - fi - - LDFLAGS="-L/opt/pyca/cryptography/openssl/lib" \ - CFLAGS="-I/opt/pyca/cryptography/openssl/include -Wl,--exclude-libs,ALL" \ - .venv/bin/python setup.py bdist_wheel $PY_LIMITED_API - - auditwheel repair --plat ${PLAT} -w wheelhouse/ dist/cryptography*.whl - - # Sanity checks - # NOTE(ianw) : no execstack on aarch64, comes from - # prelink, which was never supported. CentOS 8 does - # have it separate, skip for now. - if [[ "${PLAT}" != "manylinux2014_aarch64" ]]; then - for f in wheelhouse/*.whl; do - unzip $f -d execstack.check - - results=$(execstack execstack.check/cryptography/hazmat/bindings/*.so) - count=$(echo "$results" | grep -c '^X' || true) - if [ "$count" -ne 0 ]; then - exit 1 - fi - rm -rf execstack.check - done - fi - - .venv/bin/pip install cryptography --no-index -f wheelhouse/ - .venv/bin/python -c "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" - - # Cleanup - mv wheelhouse/* wheelhouse.final - rm -rf .venv dist wheelhouse - -done diff --git a/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/tasks/main.yaml b/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/tasks/main.yaml deleted file mode 100644 index aebf7d6b7fbb..000000000000 --- a/.zuul.playbooks/playbooks/wheel/roles/build-wheel-manylinux/tasks/main.yaml +++ /dev/null @@ -1,145 +0,0 @@ -# Wheel builds is a list of dicts, with keys -# -# platform: the manylinux platform name -# image: the docker image to build in -# pythons: list of pythons in the image to build wheels for -- name: Sanity check build list - assert: - that: wheel_builds is defined - -- name: Ensure pip installed - include_role: - name: ensure-pip - -- name: Run ensure-docker - include_role: - name: ensure-docker - -- name: Workaround Linaro aarch64 cloud MTU issues - # NOTE(ianw) : Docker default networking, the Linaro NAT setup and - # *insert random things here* cause PMTU issues, resulting in hung - # connections, particularly to fastly CDN (particularly annoying - # because pypi and pythonhosted live behind that). Can remove after - # upstream changes merge, or we otherwise find a solution in the - # upstream cloud. - # https://review.opendev.org/747062 - # https://review.opendev.org/746833 - # https://review.opendev.org/747064 - when: ansible_architecture == 'aarch64' - block: - - name: Install jq - package: - name: jq - state: present - become: yes - - - name: Reset docker MTU - shell: | - jq --arg mtu 1400 '. + {mtu: $mtu|tonumber}' /etc/docker/daemon.json > /etc/docker/daemon.json.new - cat /etc/docker/daemon.json.new - mv /etc/docker/daemon.json.new /etc/docker/daemon.json - service docker restart - become: yes - -# We build an sdist of the checkout, and then build wheels from the -# sdist. This ensures that nothing is left out of the sdist. -- name: Install sdist required packages - package: - name: - - build-essential - - libssl-dev - - libffi-dev - - python3-dev - become: yes - when: ansible_distribution in ['Debian', 'Ubuntu'] - -- name: Create sdist - command: | - python3 setup.py sdist - args: - chdir: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}' - -- name: Find output file - find: - paths: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/dist' - file_type: file - patterns: "*.tar.gz" - register: _sdist - -- assert: - that: - - _sdist.matched == 1 - -- name: Create a build area - file: - path: '{{ ansible_user_dir }}/build' - state: directory - -- name: Create build area from sdist - unarchive: - src: '{{ _sdist.files[0].path }}' - dest: '{{ ansible_user_dir }}/build' - remote_src: yes - -- name: Find cryptography subdir from sdist build dir - set_fact: - _build_dir: "{{ ansible_user_dir }}/build/{{ _sdist.files[0].path | basename | replace('.tar.gz', '') }}" - -- name: Show _build_dir - debug: - var: _build_dir - -- name: Install build script - copy: - src: build-wheels.sh - dest: '{{ _build_dir }}' - mode: 0755 - -- name: Pre-pull containers - command: >- - docker pull {{ item.image }} - become: yes - loop: '{{ wheel_builds }}' - -- name: Run builds - command: | - docker run --rm \ - -e PLAT={{ item.platform }} \ - -e PYTHONS="{{ item.pythons | join(' ') }}" \ - -v {{ _build_dir }}:/io \ - {{ item.image }} \ - /io/build-wheels.sh - become: yes - loop: '{{ wheel_builds }}' - -- name: Copy sdist to output - synchronize: - src: '{{ _sdist.files[0].path }}' - dest: '{{ zuul.executor.log_root }}' - mode: pull - -- name: Return sdist artifact - zuul_return: - data: - zuul: - artifacts: - - name: '{{ _sdist.files[0].path | basename }}' - url: 'sdist/{{ _sdist.files[0].path }}' - metadata: - type: sdist - -- name: Copy wheels to output - synchronize: - src: '{{ _build_dir }}/wheelhouse.final/' - dest: '{{ zuul.executor.log_root }}/wheelhouse' - mode: pull - -- name: Return wheelhouse artifact - zuul_return: - data: - zuul: - artifacts: - - name: "Wheelhouse" - url: "wheelhouse" - metadata: - type: wheelhouse diff --git a/AUTHORS.rst b/AUTHORS.rst deleted file mode 100644 index 8ba7e0ed32e9..000000000000 --- a/AUTHORS.rst +++ /dev/null @@ -1,44 +0,0 @@ -AUTHORS -======= - -PGP key fingerprints are enclosed in parentheses. - -* Alex Gaynor (E27D 4AA0 1651 72CB C5D2 AF2B 125F 5C67 DFE9 4084) -* Hynek Schlawack (C2A0 4F86 ACE2 8ADC F817 DBB7 AE25 3622 7F69 F181) -* Donald Stufft -* Laurens Van Houtven <_@lvh.io> (D9DC 4315 772F 8E91 DD22 B153 DFD1 3DF7 A8DD 569B) -* Christian Heimes -* Paul Kehrer (05FD 9FA1 6CF7 5735 0D91 A560 235A E5F1 29F9 ED98) -* Jarret Raim -* Alex Stapleton (A1C7 E50B 66DE 39ED C847 9665 8E3C 20D1 9BD9 5C4C) -* David Reid (0F83 CC87 B32F 482B C726 B58A 9FBF D8F4 DA89 6D74) -* Matthew Lefkowitz (06AB F638 E878 CD29 1264 18AB 7EC2 8125 0FBC 4A07) -* Konstantinos Koukopoulos (D6BD 52B6 8C99 A91C E2C8 934D 3300 566B 3A46 726E) -* Stephen Holsapple -* Terry Chia -* Matthew Iversen (2F04 3DCC D6E6 D5AC D262 2E0B C046 E8A8 7452 2973) -* Mohammed Attia -* Michael Hart -* Mark Adams (A18A 7DD3 283C CF2A B0CE FE0E C7A0 5E3F C972 098C) -* Gregory Haynes (6FB6 44BF 9FD0 EBA2 1CE9 471F B08F 42F9 0DC6 599F) -* Chelsea Winfree -* Steven Buss (1FB9 2EC1 CF93 DFD6 B47F F583 B1A5 6C22 290D A4C3) -* Andre Caron -* Jiangge Zhang (BBEC 782B 015F 71B1 5FF7 EACA 1A8C AA98 255F 5000) -* Major Hayden (1BF9 9264 9596 0033 698C 252B 7370 51E0 C101 1FB1) -* Phoebe Queen (10D4 7741 AB65 50F4 B264 3888 DA40 201A 072B C1FA) -* Google Inc. -* Amaury Forgeot d'Arc -* Dirkjan Ochtman (25BB BAC1 13C1 BFD5 AA59 4A4C 9F96 B929 3038 0381) -* Maximilian Hils -* Simo Sorce -* Thomas Sileo -* Fraser Tweedale -* Ofek Lev (FFB6 B92B 30B1 7848 546E 9912 972F E913 DAD5 A46E) -* Erik Daguerre -* Aviv Palivoda -* Chris Wolfe -* Jeremy Lainé -* Denis Gladkikh -* John Pacific (2CF6 0381 B5EF 29B7 D48C 2020 7BB9 71A0 E891 44D9) -* Marti Raudsepp diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 007f802006d1..e84012428aeb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,710 @@ Changelog ========= +.. _v41-0-4: + +41.0.4 - 2023-09-19 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.3. + +.. _v41-0-3: + +41.0.3 - 2023-08-01 +~~~~~~~~~~~~~~~~~~~ + +* Fixed performance regression loading DH public keys. +* Fixed a memory leak when using + :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`. +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.2. + +.. _v41-0-2: + +41.0.2 - 2023-07-10 +~~~~~~~~~~~~~~~~~~~ + +* Fixed bugs in creating and parsing SSH certificates where critical options + with values were handled incorrectly. Certificates are now created correctly + and parsing accepts correct values as well as the previously generated + invalid forms with a warning. In the next release, support for parsing these + invalid forms will be removed. + +.. _v41-0-1: + +41.0.1 - 2023-06-01 +~~~~~~~~~~~~~~~~~~~ + +* Temporarily allow invalid ECDSA signature algorithm parameters in X.509 + certificates, which are generated by older versions of Java. +* Allow null bytes in pass phrases when serializing private keys. + +.. _v41-0-0: + +41.0.0 - 2023-05-30 +~~~~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** Support for OpenSSL less than 1.1.1d has been + removed. Users on older version of OpenSSL will need to upgrade. +* **BACKWARDS INCOMPATIBLE:** Support for Python 3.6 has been removed. +* **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.6. +* Updated the minimum supported Rust version (MSRV) to 1.56.0, from 1.48.0. +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.1. +* Added support for the :class:`~cryptography.x509.OCSPAcceptableResponses` + OCSP extension. +* Added support for the :class:`~cryptography.x509.MSCertificateTemplate` + proprietary Microsoft certificate extension. +* Implemented support for equality checks on all asymmetric public key types. +* Added support for ``aes256-gcm@openssh.com`` encrypted keys in + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key`. +* Added support for obtaining X.509 certificate signature algorithm parameters + (including PSS) via + :meth:`~cryptography.x509.Certificate.signature_algorithm_parameters`. +* Support signing :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + X.509 certificates via the new keyword-only argument ``rsa_padding`` on + :meth:`~cryptography.x509.CertificateBuilder.sign`. +* Added support for + :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` + on BoringSSL. + +.. _v40-0-2: + +40.0.2 - 2023-04-14 +~~~~~~~~~~~~~~~~~~~ + +* Fixed compilation when using LibreSSL 3.7.2. +* Added some functions to support an upcoming ``pyOpenSSL`` release. + +.. _v40-0-1: + +40.0.1 - 2023-03-24 +~~~~~~~~~~~~~~~~~~~ + +* Fixed a bug where certain operations would fail if an object happened to be + in the top-half of the memory-space. This only impacted 32-bit systems. + +.. _v40-0-0: + +40.0.0 - 2023-03-24 +~~~~~~~~~~~~~~~~~~~ + + +* **BACKWARDS INCOMPATIBLE:** As announced in the 39.0.0 changelog, the way + ``cryptography`` links OpenSSL has changed. This only impacts users who + build ``cryptography`` from source (i.e., not from a ``wheel``), and + specify their own version of OpenSSL. For those users, the ``CFLAGS``, + ``LDFLAGS``, ``INCLUDE``, ``LIB``, and ``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS`` + environment variables are no longer valid. Instead, users need to configure + their builds `as documented here`_. +* Support for Python 3.6 is deprecated and will be removed in the next + release. +* Deprecated the current minimum supported Rust version (MSRV) of 1.48.0. + In the next release we will raise MSRV to 1.56.0. Users with the latest + ``pip`` will typically get a wheel and not need Rust installed, but check + :doc:`/installation` for documentation on installing a newer ``rustc`` if + required. +* Deprecated support for OpenSSL less than 1.1.1d. The next release of + ``cryptography`` will drop support for older versions. +* Deprecated support for DSA keys in + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` + and + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key`. +* Deprecated support for OpenSSH serialization in + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. +* The minimum supported version of PyPy3 is now 7.3.10. +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.0. +* Added support for parsing SSH certificates in addition to public keys with + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_identity`. + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` + continues to support only public keys. +* Added support for generating SSH certificates with + :class:`~cryptography.hazmat.primitives.serialization.SSHCertificateBuilder`. +* Added :meth:`~cryptography.x509.Certificate.verify_directly_issued_by` to + :class:`~cryptography.x509.Certificate`. +* Added a check to :class:`~cryptography.x509.NameConstraints` to ensure that + :class:`~cryptography.x509.DNSName` constraints do not contain any ``*`` + wildcards. +* Removed many unused CFFI OpenSSL bindings. This will not impact you unless + you are using ``cryptography`` to directly invoke OpenSSL's C API. Note that + these have never been considered a stable, supported, public API by + ``cryptography``, this note is included as a courtesy. +* The X.509 builder classes now raise ``UnsupportedAlgorithm`` instead of + ``ValueError`` if an unsupported hash algorithm is passed. +* Added public union type aliases for type hinting: + + * Asymmetric types: + :const:`~cryptography.hazmat.primitives.asymmetric.types.PublicKeyTypes`, + :const:`~cryptography.hazmat.primitives.asymmetric.types.PrivateKeyTypes`, + :const:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`, + :const:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPublicKeyTypes`, + :const:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. + * SSH keys: + :const:`~cryptography.hazmat.primitives.serialization.SSHPublicKeyTypes`, + :const:`~cryptography.hazmat.primitives.serialization.SSHPrivateKeyTypes`, + :const:`~cryptography.hazmat.primitives.serialization.SSHCertPublicKeyTypes`, + :const:`~cryptography.hazmat.primitives.serialization.SSHCertPrivateKeyTypes`. + * PKCS12: + :const:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12PrivateKeyTypes` + * PKCS7: + :const:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7HashTypes`, + :const:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7PrivateKeyTypes`. + * Two-factor: + :const:`~cryptography.hazmat.primitives.twofactor.hotp.HOTPHashTypes` + +* Deprecated previously undocumented but not private type aliases in the + ``cryptography.hazmat.primitives.asymmetric.types`` module in favor of new + ones above. + + +.. _v39-0-2: + + +39.0.2 - 2023-03-02 +~~~~~~~~~~~~~~~~~~~ + +* Fixed a bug where the content type header was not properly encoded for + PKCS7 signatures when using the ``Text`` option and ``SMIME`` encoding. + + +.. _v39-0-1: + +39.0.1 - 2023-02-07 +~~~~~~~~~~~~~~~~~~~ + +* **SECURITY ISSUE** - Fixed a bug where ``Cipher.update_into`` accepted Python + buffer protocol objects, but allowed immutable buffers. **CVE-2023-23931** +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.8. + +.. _v39-0-0: + +39.0.0 - 2023-01-01 +~~~~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** Support for OpenSSL 1.1.0 has been removed. + Users on older version of OpenSSL will need to upgrade. +* **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.5. The new + minimum LibreSSL version is 3.5.0. Going forward our policy is to support + versions of LibreSSL that are available in versions of OpenBSD that are + still receiving security support. +* **BACKWARDS INCOMPATIBLE:** Removed the ``encode_point`` and + ``from_encoded_point`` methods on + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers`, + which had been deprecated for several years. + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point` + should be used instead. +* **BACKWARDS INCOMPATIBLE:** Support for using MD5 or SHA1 in + :class:`~cryptography.x509.CertificateBuilder`, other X.509 builders, and + PKCS7 has been removed. +* **BACKWARDS INCOMPATIBLE:** Dropped support for macOS 10.10 and 10.11, macOS + users must upgrade to 10.12 or newer. +* **ANNOUNCEMENT:** The next version of ``cryptography`` (40.0) will change + the way we link OpenSSL. This will only impact users who build + ``cryptography`` from source (i.e., not from a ``wheel``), and specify their + own version of OpenSSL. For those users, the ``CFLAGS``, ``LDFLAGS``, + ``INCLUDE``, ``LIB``, and ``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS`` environment + variables will no longer be respected. Instead, users will need to + configure their builds `as documented here`_. +* Added support for + :ref:`disabling the legacy provider in OpenSSL 3.0.x`. +* Added support for disabling RSA key validation checks when loading RSA + keys via + :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`, + :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`, + and + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers.private_key`. + This speeds up key loading but is :term:`unsafe` if you are loading potentially + attacker supplied keys. +* Significantly improved performance for + :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` + when repeatedly calling ``encrypt`` or ``decrypt`` with the same key. +* Added support for creating OCSP requests with precomputed hashes using + :meth:`~cryptography.x509.ocsp.OCSPRequestBuilder.add_certificate_by_hash`. +* Added support for loading multiple PEM-encoded X.509 certificates from + a single input via :func:`~cryptography.x509.load_pem_x509_certificates`. + +.. _v38-0-4: + +38.0.4 - 2022-11-27 +~~~~~~~~~~~~~~~~~~~ + +* Fixed compilation when using LibreSSL 3.6.0. +* Fixed error when using ``py2app`` to build an application with a + ``cryptography`` dependency. + +.. _v38-0-3: + +38.0.3 - 2022-11-01 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.7, + which resolves *CVE-2022-3602* and *CVE-2022-3786*. + +.. _v38-0-2: + +38.0.2 - 2022-10-11 (YANKED) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attention:: + + This release was subsequently yanked from PyPI due to a regression in OpenSSL. + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.6. + + +.. _v38-0-1: + +38.0.1 - 2022-09-07 +~~~~~~~~~~~~~~~~~~~ + +* Fixed parsing TLVs in ASN.1 with length greater than 65535 bytes (typically + seen in large CRLs). + +.. _v38-0-0: + +38.0.0 - 2022-09-06 +~~~~~~~~~~~~~~~~~~~ + +* Final deprecation of OpenSSL 1.1.0. The next release of ``cryptography`` + will drop support. +* We no longer ship ``manylinux2010`` wheels. Users should upgrade to the + latest ``pip`` to ensure this doesn't cause issues downloading wheels on + their platform. We now ship ``manylinux_2_28`` wheels for users on new + enough platforms. +* Updated the minimum supported Rust version (MSRV) to 1.48.0, from 1.41.0. + Users with the latest ``pip`` will typically get a wheel and not need Rust + installed, but check :doc:`/installation` for documentation on installing a + newer ``rustc`` if required. +* :meth:`~cryptography.fernet.Fernet.decrypt` and related methods now accept + both ``str`` and ``bytes`` tokens. +* Parsing ``CertificateSigningRequest`` restores the behavior of enforcing + that the ``Extension`` ``critical`` field must be correctly encoded DER. See + `the issue `_ for complete + details. +* Added two new OpenSSL functions to the bindings to support an upcoming + ``pyOpenSSL`` release. +* When parsing :class:`~cryptography.x509.CertificateRevocationList` and + :class:`~cryptography.x509.CertificateSigningRequest` values, it is now + enforced that the ``version`` value in the input must be valid according to + the rules of :rfc:`2986` and :rfc:`5280`. +* Using MD5 or SHA1 in :class:`~cryptography.x509.CertificateBuilder` and + other X.509 builders is deprecated and support will be removed in the next + version. +* Added additional APIs to + :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp`, including + :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.signature_hash_algorithm`, + :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.signature_algorithm`, + :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.signature`, and + :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.extension_bytes`. +* Added :attr:`~cryptography.x509.Certificate.tbs_precertificate_bytes`, allowing + users to access the to-be-signed pre-certificate data needed for signed + certificate timestamp verification. +* :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFHMAC` and + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFCMAC` now support + :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed` + counter location. +* Fixed :rfc:`4514` name parsing to reverse the order of the RDNs according + to the section 2.1 of the RFC, affecting method + :meth:`~cryptography.x509.Name.from_rfc4514_string`. +* It is now possible to customize some aspects of encryption when serializing + private keys, using + :meth:`~cryptography.hazmat.primitives.serialization.PrivateFormat.encryption_builder`. +* Removed several legacy symbols from our OpenSSL bindings. Users of pyOpenSSL + versions older than 22.0 will need to upgrade. +* Added + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES128` and + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES256` classes. + These classes do not replace + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` (which + allows all AES key lengths), but are intended for applications where + developers want to be explicit about key length. + +.. _v37-0-4: + +37.0.4 - 2022-07-05 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.5. + +.. _v37-0-3: + +37.0.3 - 2022-06-21 (YANKED) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attention:: + + This release was subsequently yanked from PyPI due to a regression in OpenSSL. + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.4. + +.. _v37-0-2: + +37.0.2 - 2022-05-03 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.3. +* Added a constant needed for an upcoming pyOpenSSL release. + +.. _v37-0-1: + +37.0.1 - 2022-04-27 +~~~~~~~~~~~~~~~~~~~ + +* Fixed an issue where parsing an encrypted private key with the public + loader functions would hang waiting for console input on OpenSSL 3.0.x rather + than raising an error. +* Restored some legacy symbols for older ``pyOpenSSL`` users. These will be + removed again in the future, so ``pyOpenSSL`` users should still upgrade + to the latest version of that package when they upgrade ``cryptography``. + +.. _v37-0-0: + +37.0.0 - 2022-04-26 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.2. +* **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL 2.9.x and 3.0.x. + The new minimum LibreSSL version is 3.1+. +* **BACKWARDS INCOMPATIBLE:** Removed ``signer`` and ``verifier`` methods + from the public key and private key classes. These methods were originally + deprecated in version 2.0, but had an extended deprecation timeline due + to usage. Any remaining users should transition to ``sign`` and ``verify``. +* Deprecated OpenSSL 1.1.0 support. OpenSSL 1.1.0 is no longer supported by + the OpenSSL project. The next release of ``cryptography`` will be the last + to support compiling with OpenSSL 1.1.0. +* Deprecated Python 3.6 support. Python 3.6 is no longer supported by the + Python core team. Support for Python 3.6 will be removed in a future + ``cryptography`` release. +* Deprecated the current minimum supported Rust version (MSRV) of 1.41.0. + In the next release we will raise MSRV to 1.48.0. Users with the latest + ``pip`` will typically get a wheel and not need Rust installed, but check + :doc:`/installation` for documentation on installing a newer ``rustc`` if + required. +* Deprecated + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5`, + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SEED`, + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA`, and + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.Blowfish` because + they are legacy algorithms with extremely low usage. These will be removed + in a future version of ``cryptography``. +* Added limited support for distinguished names containing a bit string. +* We now ship ``universal2`` wheels on macOS, which contain both ``arm64`` + and ``x86_64`` architectures. Users on macOS should upgrade to the latest + ``pip`` to ensure they can use this wheel, although we will continue to + ship ``x86_64`` specific wheels for now to ease the transition. +* This will be the final release for which we ship ``manylinux2010`` wheels. + Going forward the minimum supported ``manylinux`` ABI for our wheels will + be ``manylinux2014``. The vast majority of users will continue to receive + ``manylinux`` wheels provided they have an up to date ``pip``. For PyPy + wheels this release already requires ``manylinux2014`` for compatibility + with binaries distributed by upstream. +* Added support for multiple + :class:`~cryptography.x509.ocsp.OCSPSingleResponse` in a + :class:`~cryptography.x509.ocsp.OCSPResponse`. +* Restored support for signing certificates and other structures in + :doc:`/x509/index` with SHA3 hash algorithms. +* :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` is + disabled in FIPS mode. +* Added support for serialization of PKCS#12 CA friendly names/aliases in + :func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates` +* Added support for 12-15 byte (96 to 120 bit) nonces to + :class:`~cryptography.hazmat.primitives.ciphers.aead.AESOCB3`. This class + previously supported only 12 byte (96 bit). +* Added support for + :class:`~cryptography.hazmat.primitives.ciphers.aead.AESSIV` when using + OpenSSL 3.0.0+. +* Added support for serializing PKCS7 structures from a list of + certificates with + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.serialize_certificates`. +* Added support for parsing :rfc:`4514` strings with + :meth:`~cryptography.x509.Name.from_rfc4514_string`. +* Added :attr:`~cryptography.hazmat.primitives.asymmetric.padding.PSS.AUTO` to + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. This can + be used to verify a signature where the salt length is not already known. +* Added :attr:`~cryptography.hazmat.primitives.asymmetric.padding.PSS.DIGEST_LENGTH` + to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. This + constant will set the salt length to the same length as the ``PSS`` hash + algorithm. +* Added support for loading RSA-PSS key types with + :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key` + and + :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`. + This functionality is limited to OpenSSL 1.1.1e+ and loads the key as a + normal RSA private key, discarding the PSS constraint information. + +.. _v36-0-2: + +36.0.2 - 2022-03-15 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 1.1.1n. + +.. _v36-0-1: + +36.0.1 - 2021-12-14 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 1.1.1m. + +.. _v36-0-0: + +36.0.0 - 2021-11-21 +~~~~~~~~~~~~~~~~~~~ + +* **FINAL DEPRECATION** Support for ``verifier`` and ``signer`` on our + asymmetric key classes was deprecated in version 2.0. These functions had an + extended deprecation due to usage, however the next version of + ``cryptography`` will drop support. Users should migrate to ``sign`` and + ``verify``. +* The entire :doc:`/x509/index` layer is now written in Rust. This allows + alternate asymmetric key implementations that can support cloud key + management services or hardware security modules provided they implement + the necessary interface (for example: + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`). +* :ref:`Deprecated the backend argument` for all + functions. +* Added support for + :class:`~cryptography.hazmat.primitives.ciphers.aead.AESOCB3`. +* Added support for iterating over arbitrary request + :attr:`~cryptography.x509.CertificateSigningRequest.attributes`. +* Deprecated the ``get_attribute_for_oid`` method on + :class:`~cryptography.x509.CertificateSigningRequest` in favor of + :meth:`~cryptography.x509.Attributes.get_attribute_for_oid` on the new + :class:`~cryptography.x509.Attributes` object. +* Fixed handling of PEM files to allow loading when certificate and key are + in the same file. +* Fixed parsing of :class:`~cryptography.x509.CertificatePolicies` extensions + containing legacy ``BMPString`` values in their ``explicitText``. +* Allow parsing of negative serial numbers in certificates. Negative serial + numbers are prohibited by :rfc:`5280` so a deprecation warning will be + raised whenever they are encountered. A future version of ``cryptography`` + will drop support for parsing them. +* Added support for parsing PKCS12 files with friendly names for all + certificates with + :func:`~cryptography.hazmat.primitives.serialization.pkcs12.load_pkcs12`, + which will return an object of type + :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates`. +* :meth:`~cryptography.x509.Name.rfc4514_string` and related methods now have + an optional ``attr_name_overrides`` parameter to supply custom OID to name + mappings, which can be used to match vendor-specific extensions. +* **BACKWARDS INCOMPATIBLE:** Reverted the nonstandard formatting of + email address fields as ``E`` in + :meth:`~cryptography.x509.Name.rfc4514_string` methods from version 35.0. + + The previous behavior can be restored with: + ``name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"})`` +* Allow + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey` + and + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey` to + be used as public keys when parsing certificates or creating them with + :class:`~cryptography.x509.CertificateBuilder`. These key types must be + signed with a different signing algorithm as ``X25519`` and ``X448`` do + not support signing. +* Extension values can now be serialized to a DER byte string by calling + :func:`~cryptography.x509.ExtensionType.public_bytes`. +* Added experimental support for compiling against BoringSSL. As BoringSSL + does not commit to a stable API, ``cryptography`` tests against the + latest commit only. Please note that several features are not available + when building against BoringSSL. +* Parsing ``CertificateSigningRequest`` from DER and PEM now, for a limited + time period, allows the ``Extension`` ``critical`` field to be incorrectly + encoded. See `the issue `_ + for complete details. This will be reverted in a future ``cryptography`` + release. +* When :class:`~cryptography.x509.OCSPNonce` are parsed and generated their + value is now correctly wrapped in an ASN.1 ``OCTET STRING``. This conforms + to :rfc:`6960` but conflicts with the original behavior specified in + :rfc:`2560`. For a temporary period for backwards compatibility, we will + also parse values that are encoded as specified in :rfc:`2560` but this + behavior will be removed in a future release. + +.. _v35-0-0: + +35.0.0 - 2021-09-29 +~~~~~~~~~~~~~~~~~~~ + +* Changed the :ref:`version scheme `. This will + result in us incrementing the major version more frequently, but does not + change our existing backwards compatibility policy. +* **BACKWARDS INCOMPATIBLE:** The :doc:`/x509/index` PEM parsers now require + that the PEM string passed have PEM delimiters of the correct type. For + example, parsing a private key PEM concatenated with a certificate PEM will + no longer be accepted by the PEM certificate parser. +* **BACKWARDS INCOMPATIBLE:** The X.509 certificate parser no longer allows + negative serial numbers. :rfc:`5280` has always prohibited these. +* **BACKWARDS INCOMPATIBLE:** Additional forms of invalid ASN.1 found during + :doc:`/x509/index` parsing will raise an error on initial parse rather than + when the malformed field is accessed. +* Rust is now required for building ``cryptography``, the + ``CRYPTOGRAPHY_DONT_BUILD_RUST`` environment variable is no longer + respected. +* Parsers for :doc:`/x509/index` no longer use OpenSSL and have been + rewritten in Rust. This should be backwards compatible (modulo the items + listed above) and improve both security and performance. +* Added support for OpenSSL 3.0.0 as a compilation target. +* Added support for + :class:`~cryptography.hazmat.primitives.hashes.SM3` and + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SM4`, + when using OpenSSL 1.1.1. These algorithms are provided for compatibility + in regions where they may be required, and are not generally recommended. +* We now ship ``manylinux_2_24`` and ``musllinux_1_1`` wheels, in addition to + our ``manylinux2010`` and ``manylinux2014`` wheels. Users on distributions + like Alpine Linux should ensure they upgrade to the latest ``pip`` to + correctly receive wheels. +* Added ``rfc4514_attribute_name`` attribute to :attr:`x509.NameAttribute + `. +* Added :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFCMAC`. + +.. _v3-4-8: + +3.4.8 - 2021-08-24 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and ``manylinux`` wheels to be compiled with + OpenSSL 1.1.1l. + +.. _v3-4-7: + +3.4.7 - 2021-03-25 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and ``manylinux`` wheels to be compiled with + OpenSSL 1.1.1k. + +.. _v3-4-6: + +3.4.6 - 2021-02-16 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and ``manylinux`` wheels to be compiled with + OpenSSL 1.1.1j. + +.. _v3-4-5: + +3.4.5 - 2021-02-13 +~~~~~~~~~~~~~~~~~~ + +* Various improvements to type hints. +* Lower the minimum supported Rust version (MSRV) to >=1.41.0. This change + improves compatibility with system-provided Rust on several Linux + distributions. +* ``cryptography`` will be switching to a new versioning scheme with its next + feature release. More information is available in our + :doc:`/api-stability` documentation. + +.. _v3-4-4: + +3.4.4 - 2021-02-09 +~~~~~~~~~~~~~~~~~~ + +* Added a ``py.typed`` file so that ``mypy`` will know to use our type + annotations. +* Fixed an import cycle that could be triggered by certain import sequences. + +.. _v3-4-3: + +3.4.3 - 2021-02-08 +~~~~~~~~~~~~~~~~~~ + +* Specify our supported Rust version (>=1.45.0) in our ``setup.py`` so users + on older versions will get a clear error message. + +.. _v3-4-2: + +3.4.2 - 2021-02-08 +~~~~~~~~~~~~~~~~~~ + +* Improvements to make the rust transition a bit easier. This includes some + better error messages and small dependency fixes. If you experience + installation problems **Be sure to update pip** first, then check the + :doc:`FAQ `. + +.. _v3-4-1: + +3.4.1 - 2021-02-07 +~~~~~~~~~~~~~~~~~~ + +* Fixed a circular import issue. +* Added additional debug output to assist users seeing installation errors + due to outdated ``pip`` or missing ``rustc``. + +.. _v3-4: + +3.4 - 2021-02-07 +~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** Support for Python 2 has been removed. +* We now ship ``manylinux2014`` wheels and no longer ship ``manylinux1`` + wheels. Users should upgrade to the latest ``pip`` to ensure this doesn't + cause issues downloading wheels on their platform. +* ``cryptography`` now incorporates Rust code. Users building ``cryptography`` + themselves will need to have the Rust toolchain installed. Users who use an + officially produced wheel will not need to make any changes. The minimum + supported Rust version is 1.45.0. +* ``cryptography`` now has :pep:`484` type hints on nearly all of of its public + APIs. Users can begin using them to type check their code with ``mypy``. + +.. _v3-3-2: + +3.3.2 - 2021-02-07 +~~~~~~~~~~~~~~~~~~ + +* **SECURITY ISSUE:** Fixed a bug where certain sequences of ``update()`` calls + when symmetrically encrypting very large payloads (>2GB) could result in an + integer overflow, leading to buffer overflows. *CVE-2020-36242* **Update:** + This fix is a workaround for *CVE-2021-23840* in OpenSSL, fixed in OpenSSL + 1.1.1j. + +.. _v3-3-1: + +3.3.1 - 2020-12-09 +~~~~~~~~~~~~~~~~~~ + +* Re-added a legacy symbol causing problems for older ``pyOpenSSL`` users. + +.. _v3-3: + +3.3 - 2020-12-08 +~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** Support for Python 3.5 has been removed due to + low usage and maintenance burden. +* **BACKWARDS INCOMPATIBLE:** The + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` and + :class:`~cryptography.hazmat.primitives.ciphers.aead.AESGCM` now require + 64-bit to 1024-bit (8 byte to 128 byte) initialization vectors. This change + is to conform with an upcoming OpenSSL release that will no longer support + sizes outside this window. +* **BACKWARDS INCOMPATIBLE:** When deserializing asymmetric keys we now + raise ``ValueError`` rather than ``UnsupportedAlgorithm`` when an + unsupported cipher is used. This change is to conform with an upcoming + OpenSSL release that will no longer distinguish between error types. +* **BACKWARDS INCOMPATIBLE:** We no longer allow loading of finite field + Diffie-Hellman parameters of less than 512 bits in length. This change is to + conform with an upcoming OpenSSL release that no longer supports smaller + sizes. These keys were already wildly insecure and should not have been used + in any application outside of testing. +* Updated Windows, macOS, and ``manylinux`` wheels to be compiled with + OpenSSL 1.1.1i. +* Python 2 support is deprecated in ``cryptography``. This is the last release + that will support Python 2. +* Added the + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.recover_data_from_signature` + function to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + for recovering the signed data from an RSA signature. + +.. _v3-2-1: + +3.2.1 - 2020-10-27 +~~~~~~~~~~~~~~~~~~ + +* Disable blinding on RSA public keys to address an error with some versions + of OpenSSL. + .. _v3-2: 3.2 - 2020-10-25 @@ -94,7 +798,7 @@ Changelog * Added support for parsing :class:`~cryptography.x509.SignedCertificateTimestamps` in OCSP responses. * Added support for parsing attributes in certificate signing requests via - :meth:`~cryptography.x509.CertificateSigningRequest.get_attribute_for_oid`. + ``CertificateSigningRequest.get_attribute_for_oid``. * Added support for encoding attributes in certificate signing requests via :meth:`~cryptography.x509.CertificateSigningRequestBuilder.add_attribute`. * On OpenSSL 1.1.1d and higher ``cryptography`` now uses OpenSSL's @@ -289,7 +993,7 @@ Changelog with no arguments has been deprecated. * Added support for encoding compressed and uncompressed points via :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes`. Deprecated the previous method - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point`. + ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point``. .. _v2-4-2: @@ -631,9 +1335,9 @@ Changelog :meth:`~cryptography.hazmat.primitives.ciphers.CipherContext.update_into` on :class:`~cryptography.hazmat.primitives.ciphers.CipherContext`. * Added - :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization.private_bytes` + :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey.private_bytes` to - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey.public_bytes` to @@ -988,7 +1692,7 @@ Changelog * Added a ``__hash__`` method to :class:`~cryptography.x509.Name`. * Add support for encoding and decoding elliptic curve points to a byte string form using - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point` + ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point`` and :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point`. * Added :meth:`~cryptography.x509.Extensions.get_extension_for_class`. @@ -1135,16 +1839,16 @@ Changelog ``no-comp`` (``OPENSSL_NO_COMP``) option. * Support :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` serialization of public keys using the ``public_bytes`` method of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, and - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. * Support :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` serialization of private keys using the ``private_bytes`` method of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, and - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. * Add support for parsing X.509 certificate signing requests (CSRs) with :func:`~cryptography.x509.load_pem_x509_csr` and :func:`~cryptography.x509.load_der_x509_csr`. @@ -1217,42 +1921,32 @@ Changelog and :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` now support PKCS1 RSA public keys (in addition to the previous support for SubjectPublicKeyInfo format for RSA, EC, and DSA). +* Added ``EllipticCurvePrivateKeyWithSerialization`` and deprecated + ``EllipticCurvePrivateKeyWithNumbers``. * Added - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - and deprecated ``EllipticCurvePrivateKeyWithNumbers``. -* Added - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization.private_bytes` + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.private_bytes` to - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` - and deprecated ``RSAPrivateKeyWithNumbers``. + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. +* Added ``RSAPrivateKeyWithSerialization`` and deprecated ``RSAPrivateKeyWithNumbers``. * Added - :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes` + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.private_bytes` to - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. +* Added ``DSAPrivateKeyWithSerialization`` and deprecated ``DSAPrivateKeyWithNumbers``. * Added - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` - and deprecated ``DSAPrivateKeyWithNumbers``. -* Added - :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization.private_bytes` + :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.private_bytes` to - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` - and deprecated ``RSAPublicKeyWithNumbers``. + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. +* Added ``RSAPublicKeyWithSerialization`` and deprecated ``RSAPublicKeyWithNumbers``. * Added ``public_bytes`` to - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization` - and deprecated ``EllipticCurvePublicKeyWithNumbers``. + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. +* Added ``EllipticCurvePublicKeyWithSerialization`` and deprecated + ``EllipticCurvePublicKeyWithNumbers``. * Added ``public_bytes`` to - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization` - and deprecated ``DSAPublicKeyWithNumbers``. + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. +* Added ``DSAPublicKeyWithSerialization`` and deprecated ``DSAPublicKeyWithNumbers``. * Added ``public_bytes`` to - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. * :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` and :class:`~cryptography.hazmat.primitives.hashes.HashContext` were moved from ``cryptography.hazmat.primitives.interfaces`` to @@ -1281,7 +1975,7 @@ Changelog were moved from ``cryptography.hazmat.primitives.interfaces`` to ``cryptography.hazmat.primitives.asymmetric``. * :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParametersWithNumbers`, + ``DSAParametersWithNumbers``, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, ``DSAPrivateKeyWithNumbers``, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` and @@ -1381,8 +2075,7 @@ Changelog * Deprecated ``elliptic_curve_private_key_from_numbers`` and ``elliptic_curve_public_key_from_numbers`` in favor of ``load_elliptic_curve_private_numbers`` and - ``load_elliptic_curve_public_numbers`` on - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. + ``load_elliptic_curve_public_numbers`` on ``EllipticCurveBackend``. * Added ``EllipticCurvePrivateKeyWithNumbers`` and ``EllipticCurvePublicKeyWithNumbers`` support. * Work around three GCM related bugs in CommonCrypto and OpenSSL. @@ -1452,17 +2145,15 @@ Changelog * Added :class:`~cryptography.hazmat.primitives.ciphers.modes.CFB8` support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on - ``commoncrypto`` and :doc:`/hazmat/backends/openssl`. + ``commoncrypto`` and ``openssl``. * Added ``AES`` :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR` support to the OpenSSL backend when linked against 0.9.8. * Added ``PKCS8SerializationBackend`` and - ``TraditionalOpenSSLSerializationBackend`` support to the - :doc:`/hazmat/backends/openssl`. -* Added :doc:`/hazmat/primitives/asymmetric/ec` and - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. + ``TraditionalOpenSSLSerializationBackend`` support to ``openssl``. +* Added :doc:`/hazmat/primitives/asymmetric/ec` and ``EllipticCurveBackend``. * Added :class:`~cryptography.hazmat.primitives.ciphers.modes.ECB` support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on - ``commoncrypto`` and :doc:`/hazmat/backends/openssl`. + ``commoncrypto`` and ``openssl``. * Deprecated the concrete ``RSAPrivateKey`` class in favor of backend specific providers of the :class:`cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` @@ -1484,10 +2175,9 @@ Changelog :class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters` interface. * Deprecated ``encrypt_rsa``, ``decrypt_rsa``, ``create_rsa_signature_ctx`` and - ``create_rsa_verification_ctx`` on - :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. + ``create_rsa_verification_ctx`` on ``RSABackend``. * Deprecated ``create_dsa_signature_ctx`` and ``create_dsa_verification_ctx`` - on :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + on ``DSABackend``. .. _v0-4: @@ -1554,8 +2244,7 @@ Changelog * Added ``commoncrypto``. * Added initial ``commoncrypto``. -* Removed ``register_cipher_adapter`` method from - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`. +* Removed ``register_cipher_adapter`` method from ``CipherBackend``. * Added support for the OpenSSL backend under Windows. * Improved thread-safety for the OpenSSL backend. * Fixed compilation on systems where OpenSSL's ``ec.h`` header is not @@ -1563,8 +2252,7 @@ Changelog * Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`. * Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. * Added ``multibackend``. -* Set default random for the :doc:`/hazmat/backends/openssl` to the OS - random engine. +* Set default random for ``openssl`` to the OS random engine. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5` (CAST-128) support. @@ -1576,5 +2264,6 @@ Changelog * Initial release. -.. _`master`: https://github.com/pyca/cryptography/ +.. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic +.. _`main`: https://github.com/pyca/cryptography/ .. _`cffi`: https://cffi.readthedocs.io/ diff --git a/LICENSE b/LICENSE index fe5af51408c1..b11f379efe15 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,3 @@ This software is made available under the terms of *either* of the licenses found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made under the terms of *both* these licenses. - -The code used in the OpenSSL locking callback and OS random engine is derived -from CPython, and is licensed under the terms of the PSF License Agreement. diff --git a/LICENSE.PSF b/LICENSE.PSF deleted file mode 100644 index 4d3a4f57dea9..000000000000 --- a/LICENSE.PSF +++ /dev/null @@ -1,41 +0,0 @@ -1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and - the Individual or Organization ("Licensee") accessing and otherwise using Python - 2.7.12 software in source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby - grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, - analyze, test, perform and/or display publicly, prepare derivative works, - distribute, and otherwise use Python 2.7.12 alone or in any derivative - version, provided, however, that PSF's License Agreement and PSF's notice of - copyright, i.e., "Copyright © 2001-2016 Python Software Foundation; All Rights - Reserved" are retained in Python 2.7.12 alone or in any derivative version - prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on or - incorporates Python 2.7.12 or any part thereof, and wants to make the - derivative work available to others as provided herein, then Licensee hereby - agrees to include in any such work a brief summary of the changes made to Python - 2.7.12. - -4. PSF is making Python 2.7.12 available to Licensee on an "AS IS" basis. - PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF - EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR - WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE - USE OF PYTHON 2.7.12 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.7.12 - FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF - MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.7.12, OR ANY DERIVATIVE - THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material breach of - its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any relationship - of agency, partnership, or joint venture between PSF and Licensee. This License - Agreement does not grant permission to use PSF trademarks or trade name in a - trademark sense to endorse or promote products or services of Licensee, or any - third party. - -8. By copying, installing or otherwise using Python 2.7.12, Licensee agrees - to be bound by the terms and conditions of this License Agreement. diff --git a/MANIFEST.in b/MANIFEST.in index 7e97167a1b63..dcffd6024d1c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,26 +1,24 @@ -include AUTHORS.rst include CHANGELOG.rst include CONTRIBUTING.rst include LICENSE include LICENSE.APACHE include LICENSE.BSD -include LICENSE.PSF include README.rst +include noxfile.py include pyproject.toml +recursive-include src py.typed *.pyi recursive-include docs * recursive-include src/_cffi_src *.py *.c *.h +recursive-include src/rust Cargo.toml Cargo.lock *.rs prune docs/_build recursive-include tests *.py exclude vectors recursive-exclude vectors * +exclude src/rust/target +recursive-exclude src/rust/target * -exclude .travis.yml .travis -recursive-exclude .travis * recursive-exclude .github * -exclude release.py .coveragerc codecov.yml dev-requirements.txt rtd-requirements.txt tox.ini - -recursive-exclude .zuul.d * -recursive-exclude .zuul.playbooks * +exclude release.py .readthedocs.yml ci-constraints-requirements.txt mypy.ini diff --git a/README.rst b/README.rst index fddde9878581..d71765b8dba3 100644 --- a/README.rst +++ b/README.rst @@ -9,19 +9,13 @@ pyca/cryptography :target: https://cryptography.io :alt: Latest Docs -.. image:: https://travis-ci.org/pyca/cryptography.svg?branch=master - :target: https://travis-ci.org/pyca/cryptography - -.. image:: https://github.com/pyca/cryptography/workflows/CI/badge.svg?branch=master - :target: https://github.com/pyca/cryptography/actions?query=workflow%3ACI+branch%3Amaster - -.. image:: https://codecov.io/github/pyca/cryptography/coverage.svg?branch=master - :target: https://codecov.io/github/pyca/cryptography?branch=master +.. image:: https://github.com/pyca/cryptography/workflows/CI/badge.svg?branch=main + :target: https://github.com/pyca/cryptography/actions?query=workflow%3ACI+branch%3Amain ``cryptography`` is a package which provides cryptographic recipes and -primitives to Python developers. Our goal is for it to be your "cryptographic -standard library". It supports Python 2.7, Python 3.5+, and PyPy 5.4+. +primitives to Python developers. Our goal is for it to be your "cryptographic +standard library". It supports Python 3.7+ and PyPy3 7.3.10+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and @@ -36,9 +30,9 @@ key derivation functions. For example, to encrypt something with >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token - '...' + b'...' >>> f.decrypt(token) - 'A really secret message. Not for prying eyes.' + b'A really secret message. Not for prying eyes.' You can find more information in the `documentation`_. @@ -57,7 +51,7 @@ If you run into bugs, you can file them in our `issue tracker`_. We maintain a `cryptography-dev`_ mailing list for development discussion. -You can also join ``#cryptography-dev`` on Freenode to ask questions or get +You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get involved. Security diff --git a/ci-constraints-requirements.txt b/ci-constraints-requirements.txt new file mode 100644 index 000000000000..009faa5e0bdc --- /dev/null +++ b/ci-constraints-requirements.txt @@ -0,0 +1,197 @@ +# This is named ambigiously, but it's a pip constraints file, named like a +# requirements file so dependabot will update the pins. +# It was originally generated with; +# pip-compile --extra=docs --extra=docstest --extra=pep8test --extra=test --extra=test-randomorder --extra=nox --extra=sdist --resolver=backtracking --strip-extras --unsafe-package=cffi --unsafe-package=pycparser --unsafe-package=setuptools pyproject.toml +# and then manually massaged to add version specifiers to packages whose +# versions vary by Python version + +alabaster==0.7.13 + # via sphinx +argcomplete==3.0.8 + # via nox +babel==2.12.1 + # via sphinx +black==23.3.0 + # via cryptography (pyproject.toml) +bleach==6.0.0 + # via readme-renderer +build==0.10.0 + # via + # check-sdist + # cryptography (pyproject.toml) +certifi==2023.5.7 + # via requests +charset-normalizer==3.1.0 + # via requests +check-sdist==0.1.2 + # via cryptography (pyproject.toml) +click==8.1.3 + # via black +colorlog==6.7.0 + # via nox +coverage==7.2.7 + # via pytest-cov +distlib==0.3.6 + # via virtualenv +docutils==0.18.1 + # via + # readme-renderer + # sphinx + # sphinx-rtd-theme +exceptiongroup==1.1.1 + # via pytest +execnet==1.9.0 + # via pytest-xdist +filelock==3.12.0 + # via virtualenv +idna==3.4 + # via requests +imagesize==1.4.1 + # via sphinx +importlib-metadata==6.6.0 + # via + # keyring + # twine +iniconfig==2.0.0 + # via pytest +jaraco-classes==3.2.3 + # via keyring +jinja2==3.1.2 + # via sphinx +keyring==23.13.1 + # via twine +markdown-it-py==2.2.0 + # via rich +markupsafe==2.1.2 + # via jinja2 +mdurl==0.1.2 + # via markdown-it-py +more-itertools==9.1.0 + # via jaraco-classes +mypy==1.3.0 + # via cryptography (pyproject.toml) +mypy-extensions==1.0.0 + # via + # black + # mypy +nox==2023.4.22 + # via cryptography (pyproject.toml) +packaging==23.1 + # via + # black + # build + # nox + # pytest + # sphinx +pathspec==0.11.1 + # via + # black + # check-sdist +pkginfo==1.9.6 + # via twine +platformdirs==3.5.1 + # via + # black + # virtualenv +pluggy==1.0.0 + # via pytest +pretend==1.0.9 + # via cryptography (pyproject.toml) +py-cpuinfo==9.0.0 + # via pytest-benchmark +pyenchant==3.2.2 + # via + # cryptography (pyproject.toml) + # sphinxcontrib-spelling +pygments==2.15.1 + # via + # readme-renderer + # rich + # sphinx +pyproject-hooks==1.0.0 + # via build +pytest==7.3.1 + # via + # cryptography (pyproject.toml) + # pytest-benchmark + # pytest-cov + # pytest-randomly + # pytest-xdist +pytest-benchmark==4.0.0 + # via cryptography (pyproject.toml) +pytest-cov==4.1.0 + # via cryptography (pyproject.toml) +pytest-randomly==3.12.0 + # via cryptography (pyproject.toml) +pytest-xdist==3.3.1 + # via cryptography (pyproject.toml) +readme-renderer==37.3 + # via twine +requests==2.31.0 + # via + # requests-toolbelt + # sphinx + # twine +requests-toolbelt==1.0.0 + # via twine +rfc3986==2.0.0 + # via twine +rich==13.3.5 + # via twine +ruff==0.0.270 + # via cryptography (pyproject.toml) +six==1.16.0 + # via bleach +snowballstemmer==2.2.0 + # via sphinx +sphinx==6.2.1 + # via + # cryptography (pyproject.toml) + # sphinx-rtd-theme + # sphinxcontrib-jquery + # sphinxcontrib-spelling +sphinx-rtd-theme==1.2.1 + # via cryptography (pyproject.toml) +sphinxcontrib-applehelp==1.0.4 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.1 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +sphinxcontrib-spelling==8.0.0 + # via cryptography (pyproject.toml) +tomli==2.0.1 + # via + # black + # build + # check-manifest + # coverage + # mypy + # pyproject-hooks + # pytest +twine==4.0.2 + # via cryptography (pyproject.toml) +typing-extensions==4.6.2 + # via mypy +urllib3==2.0.2 + # via + # requests + # twine +virtualenv==20.23.0 + # via nox +webencodings==0.5.1 + # via bleach +zipp==3.15.0 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +# cffi +# pycparser diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 2bfc815b8319..000000000000 --- a/codecov.yml +++ /dev/null @@ -1,9 +0,0 @@ -comment: false -coverage: - status: - patch: - default: - target: '100' - project: - default: - target: '100' diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index 333faaddf8fe..000000000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -click -coverage -tox >= 2.4.1 -twine >= 1.8.0 --e .[test,docs,docstest,pep8test] --e vectors diff --git a/docs/cryptography-docs.py b/docs/_ext/cryptography-docs.py similarity index 96% rename from docs/cryptography-docs.py rename to docs/_ext/cryptography-docs.py index 923ec6f5b2c3..43a9c6cb8ea1 100644 --- a/docs/cryptography-docs.py +++ b/docs/_ext/cryptography-docs.py @@ -2,12 +2,9 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - from docutils import nodes from docutils.parsers.rst import Directive - DANGER_MESSAGE = """ This is a "Hazardous Materials" module. You should **ONLY** use it if you're 100% absolutely sure that you know what you're doing because this module is diff --git a/docs/_ext/linkcode_res.py b/docs/_ext/linkcode_res.py new file mode 100644 index 000000000000..9b6f427d4e88 --- /dev/null +++ b/docs/_ext/linkcode_res.py @@ -0,0 +1,106 @@ +import importlib +import inspect +import os +import sys + +import cryptography + +# -- Linkcode resolver ----------------------------------------------------- + +# This is HEAVILY inspired by numpy's +# https://github.com/numpy/numpy/blob/73fe877ff967f279d470b81ad447b9f3056c1335/doc/source/conf.py#L390 + +# Copyright (c) 2005-2020, NumPy Developers. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# * Neither the name of the NumPy Developers nor the names of any +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +def linkcode_resolve(domain, info): + """ + Determine the url corresponding to Python object + """ + if domain != "py": + return None + + modname = info["module"] + fullname = info["fullname"] + + try: + importlib.import_module(modname) + except Exception: + return None + submod = sys.modules.get(modname) + if submod is None: + return None + + obj = submod + for part in fullname.split("."): + try: + obj = getattr(obj, part) + except Exception: + return None + + # strip decorators, which would resolve to the source of the decorator + # possibly an upstream bug in getsourcefile, bpo-1764286 + try: + unwrap = inspect.unwrap + except AttributeError: + pass + else: + obj = unwrap(obj) + + fn = None + lineno = None + + try: + fn = inspect.getsourcefile(obj) + except Exception: + fn = None + if not fn: + return None + + try: + source, lineno = inspect.getsourcelines(obj) + except Exception: + lineno = None + + fn = os.path.relpath(fn, start=os.path.dirname(cryptography.__file__)) + + if lineno: + linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) + else: + linespec = "" + + url = "https://github.com/pyca/cryptography/blob/%s/src/cryptography/%s%s" + if "dev" in cryptography.__version__: + return url % ("main", fn, linespec) + else: + version = ".".join(cryptography.__version__.split(".")[:2] + ["x"]) + return url % (version, fn, linespec) diff --git a/docs/api-stability.rst b/docs/api-stability.rst index 205b18447b05..eafbd1d9506e 100644 --- a/docs/api-stability.rst +++ b/docs/api-stability.rst @@ -1,7 +1,7 @@ API stability ============= -From its first release, ``cryptography`` will have a strong API stability +From its first release, ``cryptography`` has had a strong API stability policy. What does this policy cover? @@ -24,6 +24,9 @@ What doesn't this policy cover? contents of ``obj.__dict__`` may change. * Objects are not guaranteed to be pickleable, and pickled objects from one version of ``cryptography`` may not be loadable in future versions. +* Unless otherwise documented, types in ``cryptography`` are not intended to + be sub-classed, and we do not guarantee that behavior with respect to + sub-classes will be stable. * Development versions of ``cryptography``. Before a feature is in a release, it is not covered by this policy and may change. @@ -37,18 +40,23 @@ policy as necessary in order to resolve a security issue or harden Versioning ---------- -This project uses a custom versioning scheme as described below. +Version 35.0.0+ +~~~~~~~~~~~~~~~ -Given a version ``cryptography X.Y.Z``, +Beginning with release 35.0.0 ``cryptography`` uses a Firefox-inspired version +scheme. -* ``X.Y`` is a decimal number that is incremented for - potentially-backwards-incompatible releases. +Given a version ``cryptography X.Y.Z``, - * This increases like a standard decimal. - In other words, 0.9 is the ninth release, and 1.0 is the tenth (not 0.10). - The dividing decimal point can effectively be ignored. +* ``X`` indicates the major version number. This is incremented on any feature + release. +* ``Y`` is always ``0``. +* ``Z`` is an integer that is incremented for minor backward-compatible + releases (such as fixing security issues or correcting regressions in a major + release). -* ``Z`` is an integer that is incremented for backward-compatible releases. +This scheme is compatible with `SemVer`_, though many major releases will +**not** include any backwards-incompatible changes. Deprecation ~~~~~~~~~~~ @@ -56,16 +64,36 @@ Deprecation From time to time we will want to change the behavior of an API or remove it entirely. In that case, here's how the process will work: -* In ``cryptography X.Y`` the feature exists. -* In ``cryptography X.Y + 0.1`` using that feature will emit a +* In ``cryptography X.0.0`` the feature exists. +* In ``cryptography (X + 1).0.0`` using that feature will emit a ``UserWarning``. -* In ``cryptography X.Y + 0.2`` using that feature will emit a +* In ``cryptography (X + 2).0.0`` using that feature will emit a ``UserWarning``. -* In ``cryptography X.Y + 0.3`` the feature will be removed or changed. +* In ``cryptography (X + 3).0.0`` the feature will be removed or changed. In short, code that runs without warnings will always continue to work for a -period of two releases. +period of two major releases. From time to time, we may decide to deprecate an API that is particularly widely used. In these cases, we may decide to provide an extended deprecation period, at our discretion. + +Previous Scheme +~~~~~~~~~~~~~~~ + +Before version 35.0.0 this project uses a custom versioning scheme as described +below. + +Given a version ``cryptography X.Y.Z``, + +* ``X.Y`` is a decimal number that is incremented for + potentially-backwards-incompatible releases. + + * This increases like a standard decimal. + In other words, 0.9 is the ninth release, and 1.0 is the tenth (not 0.10). + The dividing decimal point can effectively be ignored. + +* ``Z`` is an integer that is incremented for backward-compatible releases. + + +.. _`SemVer`: https://semver.org/ diff --git a/docs/community.rst b/docs/community.rst index da6376531ab3..7feb476e61be 100644 --- a/docs/community.rst +++ b/docs/community.rst @@ -7,7 +7,7 @@ You can find ``cryptography`` all over the web: * `Source code`_ * `Issue tracker`_ * `Documentation`_ -* IRC: ``#cryptography-dev`` on ``irc.freenode.net`` +* IRC: ``#pyca`` on ``irc.libera.chat`` Wherever we interact, we adhere to the `Python Community Code of Conduct`_. diff --git a/docs/conf.py b/docs/conf.py index 87e2b5869c6c..4cbbde37b7ce 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # 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. @@ -16,8 +14,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -from __future__ import absolute_import, division, print_function - import os import sys @@ -35,7 +31,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath(".")) +sys.path.insert(0, os.path.abspath("_ext")) # -- General configuration ---------------------------------------------------- @@ -46,15 +42,20 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", + "sphinx.ext.autosectionlabel", "sphinx.ext.doctest", "sphinx.ext.intersphinx", - "sphinx.ext.viewcode", + "sphinx.ext.linkcode", "cryptography-docs", + "sphinx_rtd_theme", ] if spelling is not None: extensions.append("sphinxcontrib.spelling") +# Linkcode resolver +from linkcode_res import linkcode_resolve # noqa: E402, F401 + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -71,7 +72,7 @@ # General information about the project. project = "Cryptography" -copyright = "2013-2020, Individual Contributors" +copyright = "2013-2023, Individual Contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -81,7 +82,7 @@ base_dir = os.path.join(os.path.dirname(__file__), os.pardir) about = {} with open(os.path.join(base_dir, "src", "cryptography", "__about__.py")) as f: - exec (f.read(), about) + exec(f.read(), about) version = release = about["__version__"] @@ -123,7 +124,6 @@ if sphinx_rtd_theme: html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] else: html_theme = "default" @@ -183,8 +183,7 @@ ), ] -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"https://docs.python.org/3": None} +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} epub_theme = "epub" @@ -195,12 +194,14 @@ linkcheck_timeout = 5 linkcheck_ignore = [ - # Small DH key results in a TLS failure on modern OpenSSL + # Insecure renegotiation settings r"https://info.isl.ntt.co.jp/crypt/eng/camellia/", # Inconsistent small DH params they seem incapable of fixing r"https://www.secg.org/sec1-v2.pdf", - # 403ing from Travis - r"https://devblogs.microsoft.com/oldnewthing/\?p=4223", # Incomplete cert chain - r"https://cveform.mitre.org/", + r"https://www.oscca.gov.cn", + # Cloudflare returns 403s for all non-browser requests + r"https://speakerdeck.com", ] + +autosectionlabel_prefix_document = True diff --git a/docs/development/c-bindings.rst b/docs/development/c-bindings.rst index efc21ca0a8f5..e53e0bae7f65 100644 --- a/docs/development/c-bindings.rst +++ b/docs/development/c-bindings.rst @@ -189,9 +189,9 @@ Caveats Sometimes, a set of loosely related features are added in the same version, and it's impractical to create ``#ifdef`` statements for each one. In that case, it may make sense to either check for a particular -version. For example, to check for OpenSSL 1.1.0 or newer:: +version. For example, to check for OpenSSL 1.1.1 or newer:: - #if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER + #if CRYPTOGRAPHY_OPENSSL_111_OR_GREATER Sometimes, the version of a library on a particular platform will have features that you thought it wouldn't, based on its version. diff --git a/docs/development/custom-vectors/arc4/generate_arc4.py b/docs/development/custom-vectors/arc4/generate_arc4.py index 2ca85c98dbe7..2ca919b40858 100644 --- a/docs/development/custom-vectors/arc4/generate_arc4.py +++ b/docs/development/custom-vectors/arc4/generate_arc4.py @@ -2,15 +2,11 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - import binascii -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import algorithms - _RFC6229_KEY_MATERIALS = [ ( True, @@ -67,7 +63,6 @@ def _build_vectors(): cipher = ciphers.Cipher( algorithms.ARC4(binascii.unhexlify(key)), None, - default_backend(), ) encryptor = cipher.encryptor() current_offset = 0 @@ -81,13 +76,11 @@ def _build_vectors(): while current_offset < offset: encryptor.update(plaintext) current_offset += len(plaintext) - output.append("\nCOUNT = {}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 - output.append("KEY = {}".format(key)) - output.append("OFFSET = {}".format(offset)) - output.append( - "PLAINTEXT = {}".format(binascii.hexlify(plaintext)) - ) + output.append(f"KEY = {key}") + output.append(f"OFFSET = {offset}") + output.append(f"PLAINTEXT = {binascii.hexlify(plaintext)}") output.append( "CIPHERTEXT = {}".format( binascii.hexlify(encryptor.update(plaintext)) diff --git a/docs/development/custom-vectors/cast5/generate_cast5.py b/docs/development/custom-vectors/cast5/generate_cast5.py index 5208b90d8440..27fb4634e295 100644 --- a/docs/development/custom-vectors/cast5/generate_cast5.py +++ b/docs/development/custom-vectors/cast5/generate_cast5.py @@ -2,11 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - import binascii -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import algorithms, base, modes @@ -14,7 +11,6 @@ def encrypt(mode, key, iv, plaintext): cipher = base.Cipher( algorithms.CAST5(binascii.unhexlify(key)), mode(binascii.unhexlify(iv)), - default_backend(), ) encryptor = cipher.encryptor() ct = encryptor.update(binascii.unhexlify(plaintext)) @@ -29,7 +25,7 @@ def build_vectors(mode, filename): iv = None plaintext = None - with open(filename, "r") as vector_file: + with open(filename) as vector_file: for line in vector_file: line = line.strip() if line.startswith("KEY"): @@ -39,20 +35,18 @@ def build_vectors(mode, filename): encrypt(mode, key, iv, plaintext) ) ) - output.append("\nCOUNT = {}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 name, key = line.split(" = ") - output.append("KEY = {}".format(key)) + output.append(f"KEY = {key}") elif line.startswith("IV"): name, iv = line.split(" = ") iv = iv[0:16] - output.append("IV = {}".format(iv)) + output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): name, plaintext = line.split(" = ") - output.append("PLAINTEXT = {}".format(plaintext)) - output.append( - "CIPHERTEXT = {}".format(encrypt(mode, key, iv, plaintext)) - ) + output.append(f"PLAINTEXT = {plaintext}") + output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) diff --git a/docs/development/custom-vectors/hkdf/generate_hkdf.py b/docs/development/custom-vectors/hkdf/generate_hkdf.py index aa2fc274f9ae..97ad2fae70d5 100644 --- a/docs/development/custom-vectors/hkdf/generate_hkdf.py +++ b/docs/development/custom-vectors/hkdf/generate_hkdf.py @@ -2,11 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - import binascii -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF @@ -17,7 +14,6 @@ length=L, salt=None, info=None, - backend=default_backend(), ).derive(IKM) @@ -28,7 +24,7 @@ def _build_vectors(): "IKM = " + binascii.hexlify(IKM).decode("ascii"), "salt = ", "info = ", - "L = {}".format(L), + f"L = {L}", "OKM = " + binascii.hexlify(OKM).decode("ascii"), ] return "\n".join(output) diff --git a/docs/development/custom-vectors/idea/generate_idea.py b/docs/development/custom-vectors/idea/generate_idea.py index 00309567b959..c0e93ee52a48 100644 --- a/docs/development/custom-vectors/idea/generate_idea.py +++ b/docs/development/custom-vectors/idea/generate_idea.py @@ -1,6 +1,5 @@ import binascii -from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.primitives.ciphers import algorithms, base, modes @@ -8,7 +7,6 @@ def encrypt(mode, key, iv, plaintext): cipher = base.Cipher( algorithms.IDEA(binascii.unhexlify(key)), mode(binascii.unhexlify(iv)), - backend, ) encryptor = cipher.encryptor() ct = encryptor.update(binascii.unhexlify(plaintext)) @@ -17,7 +15,7 @@ def encrypt(mode, key, iv, plaintext): def build_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() count = 0 @@ -30,23 +28,21 @@ def build_vectors(mode, filename): if line.startswith("KEY"): if count != 0: output.append( - "CIPHERTEXT = {0}".format( - encrypt(mode, key, iv, plaintext) - ) + f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) - output.append("\nCOUNT = {0}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 name, key = line.split(" = ") - output.append("KEY = {0}".format(key)) + output.append(f"KEY = {key}") elif line.startswith("IV"): name, iv = line.split(" = ") iv = iv[0:16] - output.append("IV = {0}".format(iv)) + output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): name, plaintext = line.split(" = ") - output.append("PLAINTEXT = {0}".format(plaintext)) + output.append(f"PLAINTEXT = {plaintext}") - output.append("CIPHERTEXT = {0}".format(encrypt(mode, key, iv, plaintext))) + output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) diff --git a/docs/development/custom-vectors/idea/verify_idea.py b/docs/development/custom-vectors/idea/verify_idea.py index d356de0ba7f3..52e5f73f4e5f 100644 --- a/docs/development/custom-vectors/idea/verify_idea.py +++ b/docs/development/custom-vectors/idea/verify_idea.py @@ -9,7 +9,7 @@ def encrypt(mode, key, iv, plaintext): encryptor = botan.Cipher( - "IDEA/{0}/NoPadding".format(mode), "encrypt", binascii.unhexlify(key) + f"IDEA/{mode}/NoPadding", "encrypt", binascii.unhexlify(key) ) cipher_text = encryptor.cipher( @@ -19,7 +19,7 @@ def encrypt(mode, key, iv, plaintext): def verify_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() vectors = load_nist_vectors(vector_file) diff --git a/docs/development/custom-vectors/rsa-oaep-sha2.rst b/docs/development/custom-vectors/rsa-oaep-sha2.rst index 36f256d7c68e..30c3e273505a 100644 --- a/docs/development/custom-vectors/rsa-oaep-sha2.rst +++ b/docs/development/custom-vectors/rsa-oaep-sha2.rst @@ -33,7 +33,7 @@ Download link: :download:`VerifyRSAOAEPSHA2.java Using the Verifier ------------------ -Download and install the `Java 8 SDK`_. Initial verification was performed +Download and install the `Java SDK`_. Initial verification was performed using ``jdk-8u77-macosx-x64.dmg``. Download the latest `Bouncy Castle`_ JAR. Initial verification was performed @@ -53,4 +53,4 @@ Finally, run the program with the path to the SHA-2 vectors: $ java -classpath ~/Downloads/bcprov-jdk15on-154.jar:./ VerifyRSAOAEPSHA2 .. _`Bouncy Castle`: https://www.bouncycastle.org/ -.. _`Java 8 SDK`: https://www.oracle.com/technetwork/java/javase/downloads/index.html +.. _`Java SDK`: https://www.oracle.com/java/technologies/javase-downloads.html diff --git a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py index a43e1506d53d..f9e79122686e 100644 --- a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py +++ b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py @@ -2,16 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - import binascii import itertools import os -from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding, rsa - from tests.utils import load_pkcs1_vectors, load_vectors_from_file @@ -26,32 +22,32 @@ def build_vectors(mgf1alg, hashalg, filename): # small. Instead we parse the vectors for the test cases, then # generate our own 2048-bit keys for each. private, _ = vector - skey = rsa.generate_private_key(65537, 2048, backend) + skey = rsa.generate_private_key(65537, 2048) pn = skey.private_numbers() examples = private["examples"] - output.append(b"# =============================================") - output.append(b"# Example") - output.append(b"# Public key") - output.append(b"# Modulus:") + output.append("# =============================================") + output.append("# Example") + output.append("# Public key") + output.append("# Modulus:") output.append(format(pn.public_numbers.n, "x")) - output.append(b"# Exponent:") + output.append("# Exponent:") output.append(format(pn.public_numbers.e, "x")) - output.append(b"# Private key") - output.append(b"# Modulus:") + output.append("# Private key") + output.append("# Modulus:") output.append(format(pn.public_numbers.n, "x")) - output.append(b"# Public exponent:") + output.append("# Public exponent:") output.append(format(pn.public_numbers.e, "x")) - output.append(b"# Exponent:") + output.append("# Exponent:") output.append(format(pn.d, "x")) - output.append(b"# Prime 1:") + output.append("# Prime 1:") output.append(format(pn.p, "x")) - output.append(b"# Prime 2:") + output.append("# Prime 2:") output.append(format(pn.q, "x")) - output.append(b"# Prime exponent 1:") + output.append("# Prime exponent 1:") output.append(format(pn.dmp1, "x")) - output.append(b"# Prime exponent 2:") + output.append("# Prime exponent 2:") output.append(format(pn.dmq1, "x")) - output.append(b"# Coefficient:") + output.append("# Coefficient:") output.append(format(pn.iqmp, "x")) pkey = skey.public_key() vectorkey = rsa.RSAPrivateNumbers( @@ -64,7 +60,7 @@ def build_vectors(mgf1alg, hashalg, filename): public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), - ).private_key(backend) + ).private_key() count = 1 for example in examples: @@ -86,17 +82,17 @@ def build_vectors(mgf1alg, hashalg, filename): ), ) output.append( - b"# OAEP Example {0} alg={1} mgf1={2}".format( + "# OAEP Example {} alg={} mgf1={}".format( count, hashalg.name, mgf1alg.name ) ) count += 1 - output.append(b"# Message:") - output.append(example["message"]) - output.append(b"# Encryption:") - output.append(binascii.hexlify(ct)) + output.append("# Message:") + output.append(example["message"].decode("utf-8")) + output.append("# Encryption:") + output.append(binascii.hexlify(ct).decode("utf-8")) - return b"\n".join(output) + return "\n".join(output) def write_file(data, filename): @@ -122,5 +118,5 @@ def write_file(data, filename): write_file( build_vectors(hashtuple[0], hashtuple[1], oaep_path), - "oaep-{0}-{1}.txt".format(hashtuple[0].name, hashtuple[1].name), + f"oaep-{hashtuple[0].name}-{hashtuple[1].name}.txt", ) diff --git a/docs/development/custom-vectors/secp256k1/generate_secp256k1.py b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py index bfb150ba6fcc..545b25af756c 100644 --- a/docs/development/custom-vectors/secp256k1/generate_secp256k1.py +++ b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function - import hashlib import os from binascii import hexlify @@ -9,7 +7,6 @@ from ecdsa.util import sigdecode_der, sigencode_der from cryptography_vectors import open_vector_file - from tests.utils import load_fips_ecdsa_signing_vectors, load_vectors_from_file HASHLIB_HASH_TYPES = { @@ -21,7 +18,7 @@ } -class TruncatedHash(object): +class TruncatedHash: def __init__(self, hasher): self.hasher = hasher @@ -43,7 +40,7 @@ def build_vectors(fips_vectors): continue yield "" - yield "[K-256,{0}]".format(digest_algorithm) + yield f"[K-256,{digest_algorithm}]" yield "" for message in messages: @@ -59,12 +56,12 @@ def build_vectors(fips_vectors): r, s = sigdecode_der(signature, None) - yield "Msg = {0}".format(hexlify(message)) - yield "d = {0:x}".format(secret_key.privkey.secret_multiplier) - yield "Qx = {0:x}".format(public_key.pubkey.point.x()) - yield "Qy = {0:x}".format(public_key.pubkey.point.y()) - yield "R = {0:x}".format(r) - yield "S = {0:x}".format(s) + yield f"Msg = {hexlify(message)}" + yield f"d = {secret_key.privkey.secret_multiplier:x}" + yield f"Qx = {public_key.pubkey.point.x():x}" + yield f"Qy = {public_key.pubkey.point.y():x}" + yield f"R = {r:x}" + yield f"S = {s:x}" yield "" diff --git a/docs/development/custom-vectors/secp256k1/verify_secp256k1.py b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py index f721b0001213..7949a74ee9c7 100644 --- a/docs/development/custom-vectors/secp256k1/verify_secp256k1.py +++ b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py @@ -1,14 +1,10 @@ -from __future__ import absolute_import, print_function - import os -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( encode_dss_signature, ) - from tests.utils import load_fips_ecdsa_signing_vectors, load_vectors_from_file CRYPTOGRAPHY_HASH_TYPES = { @@ -29,7 +25,7 @@ def verify_one_vector(vector): numbers = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256K1()) - key = numbers.public_key(default_backend()) + key = numbers.public_key() verifier = key.verifier( signature, ec.ECDSA(CRYPTOGRAPHY_HASH_TYPES[digest_algorithm]()) diff --git a/docs/development/custom-vectors/seed/generate_seed.py b/docs/development/custom-vectors/seed/generate_seed.py index 046fcfb87b48..c2ebf4b2b2b9 100644 --- a/docs/development/custom-vectors/seed/generate_seed.py +++ b/docs/development/custom-vectors/seed/generate_seed.py @@ -1,6 +1,5 @@ import binascii -from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.primitives.ciphers import algorithms, base, modes @@ -8,7 +7,6 @@ def encrypt(mode, key, iv, plaintext): cipher = base.Cipher( algorithms.SEED(binascii.unhexlify(key)), mode(binascii.unhexlify(iv)), - backend, ) encryptor = cipher.encryptor() ct = encryptor.update(binascii.unhexlify(plaintext)) @@ -17,7 +15,7 @@ def encrypt(mode, key, iv, plaintext): def build_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() count = 0 @@ -30,22 +28,20 @@ def build_vectors(mode, filename): if line.startswith("KEY"): if count != 0: output.append( - "CIPHERTEXT = {0}".format( - encrypt(mode, key, iv, plaintext) - ) + f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) - output.append("\nCOUNT = {0}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 name, key = line.split(" = ") - output.append("KEY = {0}".format(key)) + output.append(f"KEY = {key}") elif line.startswith("IV"): name, iv = line.split(" = ") - output.append("IV = {0}".format(iv)) + output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): name, plaintext = line.split(" = ") - output.append("PLAINTEXT = {0}".format(plaintext)) + output.append(f"PLAINTEXT = {plaintext}") - output.append("CIPHERTEXT = {0}".format(encrypt(mode, key, iv, plaintext))) + output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) diff --git a/docs/development/custom-vectors/seed/verify_seed.py b/docs/development/custom-vectors/seed/verify_seed.py index 252088d083e1..c28ed1e2fbef 100644 --- a/docs/development/custom-vectors/seed/verify_seed.py +++ b/docs/development/custom-vectors/seed/verify_seed.py @@ -7,7 +7,7 @@ def encrypt(mode, key, iv, plaintext): encryptor = botan.Cipher( - "SEED/{0}/NoPadding".format(mode), "encrypt", binascii.unhexlify(key) + f"SEED/{mode}/NoPadding", "encrypt", binascii.unhexlify(key) ) cipher_text = encryptor.cipher( @@ -17,7 +17,7 @@ def encrypt(mode, key, iv, plaintext): def verify_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() vectors = load_nist_vectors(vector_file) diff --git a/docs/development/getting-started.rst b/docs/development/getting-started.rst index 1d939a9c3786..a4283469b5cc 100644 --- a/docs/development/getting-started.rst +++ b/docs/development/getting-started.rst @@ -3,91 +3,39 @@ Getting started Development dependencies ------------------------ + Working on ``cryptography`` requires the installation of a small number of development dependencies in addition to the dependencies for -:doc:`/installation`. These are listed in ``dev-requirements.txt`` and they can -be installed in a `virtualenv`_ using `pip`_. Before you install them, follow -the **build** instructions in :doc:`/installation` (be sure to stop before -actually installing ``cryptography``). Once you've done that, install the -development dependencies, and then install ``cryptography`` in ``editable`` -mode. For example: +:doc:`/installation`. These are handled by the use of ``nox``, which can be +installed with ``pip``. .. code-block:: console $ # Create a virtualenv and activate it $ # Set up your cryptography build environment - $ pip install --requirement dev-requirements.txt - $ pip install --editable . - -Make sure that ``pip install --requirement ...`` has installed the Python -package ``vectors/`` and packages on ``tests/`` . If it didn't, you may -install them manually by using ``pip`` on each directory. - -You will also need to install ``enchant`` using your system's package manager -to check spelling in the documentation. - -.. note:: - There is an upstream bug in ``enchant`` that prevents its installation on - Windows with 64-bit Python. See `this Github issue`_ for more information. - The easiest workaround is to use 32-bit Python for ``cryptography`` - development, even on 64-bit Windows. - -You are now ready to run the tests and build the documentation. + $ pip install nox + $ # Specify your Python version here. + $ nox -e tests -p py310 OpenSSL on macOS ~~~~~~~~~~~~~~~~ -You must have installed `OpenSSL`_ via `Homebrew`_ or `MacPorts`_ and must set -``CFLAGS`` and ``LDFLAGS`` environment variables before installing the -``dev-requirements.txt`` otherwise pip will fail with include errors. - -For example, with `Homebrew`_: - -.. code-block:: console - - $ env LDFLAGS="-L$(brew --prefix openssl@1.1)/lib" \ - CFLAGS="-I$(brew --prefix openssl@1.1)/include" \ - pip install --requirement ./dev-requirements.txt - -Alternatively for a static build you can specify -``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1`` and ensure ``LDFLAGS`` points to the -absolute path for the `OpenSSL`_ libraries before calling pip. - -.. tip:: - You will also need to set these values when `Building documentation`_. +You must have installed `OpenSSL`_ (via `Homebrew`_ , `MacPorts`_, or a custom +build) and must configure the build `as documented here`_ before calling +``nox`` or else pip will fail to compile. Running tests ------------- ``cryptography`` unit tests are found in the ``tests/`` directory and are -designed to be run using `pytest`_. `pytest`_ will discover the tests -automatically, so all you have to do is: +designed to be run using `pytest`_. ``nox`` automatically invokes ``pytest``: .. code-block:: console - $ pytest + $ nox -e tests -p py310 ... 62746 passed in 220.43 seconds -This runs the tests with the default Python interpreter. - -You can also verify that the tests pass on other supported Python interpreters. -For this we use `tox`_, which will automatically create a `virtualenv`_ for -each supported Python version and run the tests. For example: - -.. code-block:: console - - $ tox - ... - py27: commands succeeded - ERROR: pypy: InterpreterNotFound: pypy - py38: commands succeeded - docs: commands succeeded - pep8: commands succeeded - -You may not have all the required Python versions installed, in which case you -will see one or more ``InterpreterNotFound`` errors. - Building documentation ---------------------- @@ -95,14 +43,13 @@ Building documentation ``cryptography`` documentation is stored in the ``docs/`` directory. It is written in `reStructured Text`_ and rendered using `Sphinx`_. -Use `tox`_ to build the documentation. For example: +Use `nox`_ to build the documentation. For example: .. code-block:: console - $ tox -e docs + $ nox -e docs ... - docs: commands succeeded - congratulations :) + nox > Session docs was successful. The HTML documentation index can now be found at ``docs/_build/html/index.html``. @@ -111,9 +58,9 @@ The HTML documentation index can now be found at .. _`MacPorts`: https://www.macports.org .. _`OpenSSL`: https://www.openssl.org .. _`pytest`: https://pypi.org/project/pytest/ -.. _`tox`: https://pypi.org/project/tox/ +.. _`nox`: https://pypi.org/project/nox/ .. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _`pip`: https://pypi.org/project/pip/ .. _`sphinx`: https://pypi.org/project/Sphinx/ .. _`reStructured Text`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html -.. _`this Github issue`: https://github.com/rfk/pyenchant/issues/42 +.. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic \ No newline at end of file diff --git a/docs/development/reviewing-patches.rst b/docs/development/reviewing-patches.rst index 084461830be3..98df23d901f4 100644 --- a/docs/development/reviewing-patches.rst +++ b/docs/development/reviewing-patches.rst @@ -16,7 +16,7 @@ Intent Architecture ------------ -* Is the proposed change being made in the correct place? Is it a fix in a +* Is the proposed change being made in the correct place? Is it a fix in the backend when it should be in the primitives? Implementation @@ -41,15 +41,15 @@ Merge requirements Because cryptography is so complex, and the implications of getting it wrong so devastating, ``cryptography`` has a strict merge policy for committers: -* Patches must *never* be pushed directly to ``master``, all changes (even the +* Patches must *never* be pushed directly to ``main``, all changes (even the most trivial typo fixes!) must be submitted as a pull request. * A committer may *never* merge their own pull request, a second party must merge their changes. If multiple people work on a pull request, it must be merged by someone who did not work on it. * A patch that breaks tests, or introduces regressions by changing or removing existing tests should not be merged. Tests must always be passing on - ``master``. -* If somehow the tests get into a failing state on ``master`` (such as by a + ``main``. +* If somehow the tests get into a failing state on ``main`` (such as by a backwards incompatible release of a dependency) no pull requests may be merged until this is rectified. * All merged patches must have 100% test coverage. diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst index b4ed175e6adb..6148419ce134 100644 --- a/docs/development/submitting-patches.rst +++ b/docs/development/submitting-patches.rst @@ -20,9 +20,9 @@ Code When in doubt, refer to :pep:`8` for Python code. You can check if your code meets our automated requirements by formatting it with ``black`` and running -``flake8`` against it. If you've installed the development requirements this -will automatically use our configuration. You can also run the ``tox`` job with -``tox -e pep8``. +``ruff`` against it. If you've installed the development requirements this +will automatically use our configuration. You can also run the ``nox`` job with +``nox -e flake``. `Write comments as complete sentences.`_ @@ -37,12 +37,6 @@ Every code file must start with the boilerplate licensing notice: # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -Additionally, every Python code file must contain - -.. code-block:: python - - from __future__ import absolute_import, division, print_function - API considerations ~~~~~~~~~~~~~~~~~~ @@ -81,10 +75,6 @@ Every recipe should include a version or algorithmic marker of some sort in its output in order to allow transparent upgrading of the algorithms in use, as the algorithms or parameters needed to achieve a given security margin evolve. -APIs at the :doc:`/hazmat/primitives/index` and recipes layer should -automatically use the :func:`~cryptography.hazmat.backends.default_backend`, -but optionally allow specifying a different backend. - C bindings ~~~~~~~~~~ @@ -105,7 +95,7 @@ Documentation ------------- All features should be documented with prose in the ``docs`` section. To ensure -it builds and passes `doc8`_ style checks you can run ``tox -e docs``. +it builds you can run ``nox -e docs``. Because of the inherent challenges in implementing correct cryptographic systems, we want to make our documentation point people in the right directions @@ -158,4 +148,3 @@ So, specifically: .. _`syntax`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists .. _`Studies have shown`: https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/ .. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev -.. _`doc8`: https://github.com/openstack/doc8 diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 6146c9c139b9..cfab7edcca69 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -1,12 +1,11 @@ Test vectors ============ -Testing the correctness of the primitives implemented in each ``cryptography`` -backend requires trusted test vectors. Where possible these vectors are +Testing the correctness of the primitives implemented in ``cryptography`` +requires trusted test vectors. Where possible these vectors are obtained from official sources such as `NIST`_ or `IETF`_ RFCs. When this is not possible ``cryptography`` has chosen to create a set of custom vectors -using an official vector file as input to verify consistency between -implemented backends. +using an official vector file as input. Vectors are kept in the ``cryptography_vectors`` package rather than within our main test suite. @@ -23,7 +22,7 @@ for various cryptographic algorithms. These are not included in the repository continuous integration environments. We have ensured all test vectors are used as of commit -``2196000605e45d91097147c9c71f26b72af58003``. +``b063b4aedae951c69df014cd25fa6d69ae9e8cb9``. Asymmetric ciphers ~~~~~~~~~~~~~~~~~~ @@ -37,6 +36,7 @@ Asymmetric ciphers * Ed25519 test vectors from the `Ed25519 website_`. * OpenSSL PEM RSA serialization vectors from the `OpenSSL example key`_ and `GnuTLS key parsing tests`_. +* ``asymmetric/PEM_Serialization/rsa-bad-1025-q-is-2.pem`` from `badkeys`_. * OpenSSL PEM DSA serialization vectors from the `GnuTLS example keys`_. * PKCS #8 PEM serialization vectors from @@ -141,6 +141,19 @@ Custom asymmetric vectors ``asymmetric/Ed448/ed448-pkcs8.der`` contain an unencrypted Ed448 key. * ``asymmetric/Ed448/ed448-pub.pem`` and ``asymmetric/Ed448/ed448-pub.der`` contain an Ed448 public key. +* ``asymmetric/PKCS8/rsa_pss_2048.pem`` - A 2048-bit RSA PSS key with no + explicit parameters set. +* ``asymmetric/PKCS8/rsa_pss_2048_pub.der`` - The public key corresponding to + ``asymmetric/PKCS8/rsa_pss_2048.pem``. +* ``asymmetric/PKCS8/rsa_pss_2048_hash.pem`` - A 2048-bit RSA PSS key with the + hash algorithm PSS parameter set to SHA256. +* ``asymmetric/PKCS8/rsa_pss_2048_hash_mask.pem`` - A 2048-bit RSA PSS key with + with the hash (SHA256) and mask algorithm (SHA256) PSS parameters set. +* ``asymmetric/PKCS8/rsa_pss_2048_hash_mask_diff.pem`` - A 2048-bit RSA PSS key + with the hash (SHA256) and mask algorithm (SHA512) PSS parameters set. +* ``asymmetric/PKCS8/rsa_pss_2048_hash_mask_salt.pem`` - A 2048-bit RSA PSS key + with the hash (SHA256), mask algorithm (SHA256), and salt length (32) + PSS parameters set. Key exchange @@ -184,6 +197,8 @@ Key exchange ``vectors/cryptography_vectors/asymmetric/DH/dhkey_rfc5114_2.der`` and ``vectors/cryptography_vectors/asymmetric/DH/dhpub_rfc5114_2.der`` contains are the above parameters and keys in DER format. +* ``vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem`` contains + a PEM PKCS8 encoded DH key with a 256-bit key size. * ``vectors/cryptoraphy_vectors/asymmetric/ECDH/brainpool.txt`` contains Brainpool vectors from :rfc:`7027`. @@ -200,6 +215,17 @@ X.509 tree. * ``cryptography.io.pem`` - A leaf certificate issued by RapidSSL for the cryptography website. +* ``cryptography.io.old_header.pem`` - A leaf certificate issued by RapidSSL + for the cryptography website. This certificate uses the ``X509 CERTIFICATE`` + legacy PEM header format. +* ``cryptography.io.chain.pem`` - The same as ``cryptography.io.pem``, + but ``rapidssl_sha256_ca_g3.pem`` is concatenated to the end. +* ``cryptography.io.with_headers.pem`` - The same as ``cryptography.io.pem``, + but with an unrelated (encrypted) private key concatenated to the end. +* ``cryptography.io.chain_with_garbage.pem`` - The same as + ``cryptography.io.chain.pem``, but with other sections and text around it. +* ``cryptography.io.with_garbage.pem`` - The same as ``cryptography.io.pem``, + but with other sections and text around it. * ``rapidssl_sha256_ca_g3.pem`` - The intermediate CA that issued the ``cryptography.io.pem`` certificate. * ``cryptography.io.precert.pem`` - A pre-certificate with the CT poison @@ -218,11 +244,15 @@ X.509 * ``e-trust.ru.der`` - A certificate from a `Russian CA`_ signed using the GOST cipher and containing numerous unusual encodings such as NUMERICSTRING in the subject DN. -* ``alternate-rsa-sha1-oid.pem`` - A certificate from an - `unknown signature OID`_ Mozilla bug that uses an alternate signature OID for - RSA with SHA1. +* ``alternate-rsa-sha1-oid.der`` - A certificate that uses an alternate + signature OID for RSA with SHA1. This certificate has an invalid signature. * ``badssl-sct.pem`` - A certificate with the certificate transparency signed certificate timestamp extension. +* ``badssl-sct-none-hash.der`` - The same as ``badssl-sct.pem``, but DER-encoded + and with the SCT's signature hash manually changed to "none" (``0x00``). +* ``badssl-sct-anonymous-sig.der`` - The same as ``badssl-sct.pem``, but + DER-encoded and with the SCT's signature algorithm manually changed to + "anonymous" (``0x00``). * ``bigoid.pem`` - A certificate with a rather long OID in the Certificate Policies extension. We need to make sure we can parse long OIDs. @@ -251,6 +281,16 @@ X.509 * ``server-ed448-cert.pem`` - An ``ed448`` server certificate (RSA signature with ``ed448`` public key) from the OpenSSL test suite. (`server-ed448-cert.pem`_) +* ``accvraiz1.pem`` - An RSA root certificate that contains an + ``explicitText`` entry with a ``BMPString`` type. +* ``scottishpower-bitstring-dn.pem`` - An ECDSA certificate that contains + a subject DN with a bit string type. +* ``cryptography-scts-tbs-precert.der`` - The "to-be-signed" pre-certificate + bytes from ``cryptography-scts.pem``, with the SCT list extension removed. +* ``belgian-eid-invalid-visiblestring.pem`` - A certificate with UTF-8 + bytes in a ``VisibleString`` type. +* ``ee-pss-sha1-cert.pem`` - An RSA PSS certificate using a SHA1 signature and + SHA1 for MGF1 from the OpenSSL test suite. Custom X.509 Vectors ~~~~~~~~~~~~~~~~~~~~ @@ -271,6 +311,8 @@ Custom X.509 Vectors * ``utf8_common_name.pem`` - An RSA 2048 bit self-signed CA certificate generated using OpenSSL that contains a UTF8String common name with the value "We heart UTF8!™". +* ``invalid_utf8_common_name.pem`` - A certificate that contains a + ``UTF8String`` common name with an invalid UTF-8 byte sequence. * ``two_basic_constraints.pem`` - An RSA 2048 bit self-signed certificate containing two basic constraints extensions. * ``basic_constraints_not_critical.pem`` - An RSA 2048 bit self-signed @@ -372,9 +414,17 @@ Custom X.509 Vectors * ``nc_invalid_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with a permitted element that has an ``IPv6`` IP and an invalid network mask. +* ``nc_invalid_ip4_netmask.der`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with a permitted element that has an + ``IPv4`` IP and an invalid network mask. The signature on this certificate + is invalid. * ``nc_single_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with a permitted element that has two IPs with ``/32`` and ``/128`` network masks. +* ``nc_ip_invalid_length.pem`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with a permitted element that has an + invalid length (33 bytes instead of 32) for an ``IPv6`` address with + network mask. The signature on this certificate is invalid. * ``cp_user_notice_with_notice_reference.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with a notice reference in the user notice. @@ -387,7 +437,12 @@ Custom X.509 Vectors certificate containing a certificate policies extension with a user notice with no explicit text. * ``cp_invalid.pem`` - An RSA 2048 bit self-signed certificate containing a - certificate policies extension with invalid data. + certificate policies extension with invalid data. The ``policyQualifierId`` + is for ``id-qt-unotice`` but the value is an ``id-qt-cps`` ASN.1 structure. +* ``cp_invalid2.der`` - An RSA 2048 bit self-signed certificate containing a + certificate policies extension with invalid data. The ``policyQualifierId`` + is for ``id-qt-cps`` but the value is an ``id-qt-unotice`` ASN.1 structure. + The signature on this certificate is invalid. * ``ian_uri.pem`` - An RSA 2048 bit certificate containing an issuer alternative name extension with a ``URI`` general name. * ``ocsp_nocheck.pem`` - An RSA 2048 bit self-signed certificate containing @@ -418,6 +473,34 @@ Custom X.509 Vectors using ``ed448-pkcs8.pem`` as key. * ``ca/rsa_ca.pem`` - A self-signed RSA certificate with ``basicConstraints`` set to true. Its private key is ``ca/rsa_key.pem``. +* ``invalid-sct-version.der`` - A certificate with an SCT with an unknown + version. +* ``invalid-sct-length.der`` - A certificate with an SCT with an internal + length greater than the amount of data. +* ``bad_country.pem`` - A certificate with country name and jurisdiction + country name values in its subject and issuer distinguished names which + are longer than 2 characters. +* ``rsa_pss_cert.pem`` - A self-signed certificate with an RSA PSS signature + with ``asymmetric/PKCS8/rsa_pss_2048.pem`` as its key. +* ``rsa_pss_cert_invalid_mgf.der`` - A self-signed certificate with an invalid + RSA PSS signature that has a non-MGF1 OID for its mask generation function in the + signature algorithm. +* ``rsa_pss_cert_no_sig_params.der`` - A self-signed certificate with an invalid + RSA PSS signature algorithm that is missing signature parameters for PSS. +* ``rsa_pss_cert_unsupported_mgf_hash.der`` - A self-signed certificate with an + unsupported MGF1 hash algorithm in the signature algorithm. +* ``long-form-name-attribute.pem`` - A certificate with ``subject`` and ``issuer`` + names containing attributes whose value's tag is encoded in long-form. +* ``mismatch_inner_outer_sig_algorithm.der`` - A leaf certificate derived from + ``x509/cryptography.io.pem`` but modifying the ``tbs_cert.signature_algorithm`` + OID to not match the outer signature algorithm OID. +* ``ms-certificate-template.pem`` - A certificate with a ``msCertificateTemplate`` + extension. +* ``rsa_pss_sha256_no_null.pem`` - A certificate with an RSA PSS signature + with no encoded ``NULL`` for the PSS hash algorithm parameters. This certificate + was generated by LibreSSL. +* ``ecdsa_null_alg.pem`` - A certificate with an ECDSA signature with ``NULL`` + algorithm parameters. This encoding is invalid, but was generated by Java 11. Custom X.509 Request Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -432,22 +515,25 @@ Custom X.509 Request Vectors using 2048 bit RSA and SHA256 generated using OpenSSL. * ``ec_sha256.pem`` and ``ec_sha256.der`` - Contain a certificate request using EC (``secp384r1``) and SHA256 generated using OpenSSL. +* ``ec_sha256_old_header.pem`` - Identical to ``ec_sha256.pem``, but uses + the ``-----BEGIN NEW CERTIFICATE REQUEST-----`` legacy PEM header format. * ``san_rsa_sha1.pem`` and ``san_rsa_sha1.der`` - Contain a certificate request using RSA and SHA1 with a subject alternative name extension generated using OpenSSL. * ``two_basic_constraints.pem`` - A certificate signing request - for an RSA 2048 bit key containing two basic constraints extensions. + for an RSA 2048 bit key containing two basic constraints extensions. The + signature on this CSR is invalid. * ``unsupported_extension.pem`` - A certificate signing request for an RSA 2048 bit key containing containing an unsupported extension type. The OID was encoded as "1.2.3.4" with an - ``extnValue`` of "value". + ``extnValue`` of "value". The signature on this CSR is invalid. * ``unsupported_extension_critical.pem`` - A certificate signing request for an RSA 2048 bit key containing containing an unsupported extension type marked critical. The OID was encoded as "1.2.3.4" - with an ``extnValue`` of "value". + with an ``extnValue`` of "value". The signature on this CSR is invalid. * ``basic_constraints.pem`` - A certificate signing request for an RSA 2048 bit key containing a basic constraints extension marked as - critical. + critical. The signature on this CSR is invalid. * ``invalid_signature.pem`` - A certificate signing request for an RSA 1024 bit key containing an invalid signature with correct padding. * ``challenge.pem`` - A certificate signing request for an RSA 2048 bit key @@ -458,6 +544,15 @@ Custom X.509 Request Vectors * ``challenge-unstructured.pem`` - A certificate signing request for an RSA 2048 bit key containing a challenge password attribute and an unstructured name attribute. +* ``challenge-multi-valued.der`` - A certificate signing request for an RSA + 2048 bit key containing a challenge password attribute with two values + inside the ASN.1 set. The signature on this request is invalid. +* ``freeipa-bad-critical.pem`` - A certificate signing request where the + extensions value has a ``critical`` value of ``False`` explicitly encoded. +* ``bad-version.pem`` - A certificate signing request where the version is + invalid. +* ``long-form-attribute.pem`` - A certificate signing request containing an + attribute whose value's tag is encoded in the long form. Custom X.509 Certificate Revocation List Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -466,25 +561,33 @@ Custom X.509 Certificate Revocation List Vectors serials match their list position. It includes one revocation without any entry extensions, 10 revocations with every supported reason code and one revocation with an unsupported, non-critical entry extension with the OID - value set to "1.2.3.4". + value set to "1.2.3.4". The signature on this CRL is invalid. * ``crl_dup_entry_ext.pem`` - Contains a CRL with one revocation which has a - duplicate entry extension. + duplicate entry extension. The signature on this CRL is invalid. * ``crl_md2_unknown_crit_entry_ext.pem`` - Contains a CRL with one revocation which contains an unsupported critical entry extension with the OID value set - to "1.2.3.4". The CRL uses an unsupported MD2 signature algorithm. + to "1.2.3.4". The CRL uses an unsupported MD2 signature algorithm, and the + signature on this CRL is invalid. * ``crl_unsupported_reason.pem`` - Contains a CRL with one revocation which has - an unsupported reason code. + an unsupported reason code. The signature on this CRL is invalid. * ``crl_inval_cert_issuer_entry_ext.pem`` - Contains a CRL with one revocation - which has one entry extension for certificate issuer with an empty value. + which has one entry extension for certificate issuer with an empty value. The + signature on this CRL is invalid. * ``crl_empty.pem`` - Contains a CRL with no revoked certificates. +* ``crl_empty_no_sequence.der`` - Contains a CRL with no revoked certificates + and the optional ASN.1 sequence for revoked certificates is omitted. * ``crl_ian_aia_aki.pem`` - Contains a CRL with ``IssuerAlternativeName``, ``AuthorityInformationAccess``, ``AuthorityKeyIdentifier`` and ``CRLNumber`` extensions. -* ``valid_signature.pem`` - Contains a CRL with the public key which was used - to generate it. -* ``invalid_signature.pem`` - Contains a CRL with the last signature byte - incremented by 1 to produce an invalid signature, and the public key which - was used to generate it. +* ``valid_signature_crl.pem`` - Contains a CRL with a valid signature. +* ``valid_signature_cert.pem`` - Contains a cert whose public key corresponds + to the private key that produced the signature for + ``valid_signature_crl.pem``. +* ``invalid_signature_crl.pem`` - Contains a CRL with the last signature byte + incremented by 1 to produce an invalid signature. +* ``invalid_signature_cert.pem`` - Contains a cert whose public key corresponds + to the private key that produced the signature for + ``invalid_signature_crl.pem``. * ``crl_delta_crl_indicator.pem`` - Contains a CRL with the ``DeltaCRLIndicator`` extension. * ``crl_idp_fullname_only.pem`` - Contains a CRL with an @@ -511,6 +614,18 @@ Custom X.509 Certificate Revocation List Vectors * ``crl_idp_relativename_only.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension with only a ``relativename`` for the distribution point. +* ``crl_unrecognized_extension.der`` - Contains a CRL containing an + unsupported extension type. The OID was encoded as "1.2.3.4.5" with an + ``extnValue`` of ``abcdef``. +* ``crl_invalid_time.der`` - Contains a CRL with an invalid ``UTCTime`` + value in ``thisUpdate``. The signature on this CRL is invalid. +* ``crl_no_next_time.pem`` - Contains a CRL with no ``nextUpdate`` value. The + signature on this CRL is invalid. +* ``crl_bad_version.pem`` - Contains a CRL with an invalid version. +* ``crl_almost_10k.pem`` - Contains a CRL with 9,999 entries. +* ``crl_inner_outer_mismatch.der`` - A CRL created from + ``valid_signature_crl.pem`` but with a mismatched inner and + outer signature algorithm. The signature on this CRL is invalid. X.509 OCSP Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~ @@ -538,6 +653,12 @@ X.509 OCSP Test Vectors ``CT Certificate SCTs`` single extension, from the SwissSign OCSP responder. * ``x509/ocsp/ocsp-army.deps.mil-resp.der`` - An OCSP response containing multiple ``SINGLERESP`` values. +* ``x509/ocsp/resp-response-type-unknown-oid.der`` - An OCSP response with + an unknown OID for response type. The signature on this response is invalid. +* ``x509/ocsp/resp-successful-no-response-bytes.der`` - An OCSP request with + a successful response type but the response bytes are missing. +* ``x509/ocsp/resp-unknown-response-status.der`` - An OCSP response with an + unknown response status. Custom X.509 OCSP Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -549,6 +670,16 @@ Custom X.509 OCSP Test Vectors invalid hash algorithm OID. * ``x509/ocsp/req-ext-nonce.der`` - An OCSP request containing a nonce extension. +* ``x509/ocsp/req-ext-unknown-oid.der`` - An OCSP request containing an + extension with an unknown OID. +* ``x509/ocsp/req-duplicate-ext.der`` - An OCSP request with duplicate + extensions. +* ``x509/ocsp/resp-unknown-extension.der`` - An OCSP response containing an + extension with an unknown OID. +* ``x509/ocsp/resp-unknown-hash-alg.der`` - An OCSP response containing an + invalid hash algorithm OID. +* ``x509/ocsp/req-acceptable-responses.der`` - An OCSP request containing an + acceptable responses extension. Custom PKCS12 Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -572,18 +703,126 @@ Custom PKCS12 Test Vectors * ``pkcs12/cert-aes256cbc-no-key.p12`` - A PKCS12 file containing a cert (``x509/custom/ca/ca.pem``) encrypted via AES 256 CBC with the password ``cryptography`` and no private key. +* ``pkcs12/no-name-no-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``). +* ``pkcs12/name-all-no-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + with friendly name ``name``, as well as two additional certificates + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``name2`` and ``name3``, respectively. +* ``pkcs12/name-1-no-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + with friendly name ``name``, as well as two additional certificates + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``). +* ``pkcs12/name-2-3-no-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and + ``name3``, respectively. +* ``pkcs12/name-2-no-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``), the first having friendly name ``name2``. +* ``pkcs12/name-3-no-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``), the latter having friendly name ``name3``. +* ``pkcs12/name-unicode-no-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + with friendly name ``☺``, as well as two additional certificates + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``ä`` and ``ç``, respectively. +* ``pkcs12/no-name-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``), + encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/name-all-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + with friendly name ``name``, as well as two additional certificates + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``name2`` and ``name3`` respectively, + encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/name-1-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + with friendly name ``name``, as well as two additional certificates + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), + encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/name-2-3-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``) with friendly names ``name2` and + ``name3`` respectively, encrypted via AES 256 CBC with the password + ``cryptography``. +* ``pkcs12/name-2-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``), the first having friendly name ``name2``, + encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/name-3-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + as well as two additional certificates (``x509/cryptography.io.pem`` + and ``x509/letsencryptx3.pem``), the latter having friendly name ``name2``, + encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/name-unicode-pwd.p12`` - A PKCS12 file containing a cert + (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + with friendly name ``☺``, as well as two additional certificates + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``ä`` and ``ç`` respectively, encrypted via + AES 256 CBC with the password ``cryptography``. +* ``pkcs12/no-cert-no-name-no-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``). +* ``pkcs12/no-cert-name-all-no-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``name2`` and ``name3``, respectively. +* ``pkcs12/no-cert-name-2-no-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), + the first having friendly name ``name2``. +* ``pkcs12/no-cert-name-3-no-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), + the second having friendly name ``name3``. +* ``pkcs12/no-cert-name-unicode-no-pwd.p12`` - A PKCS12 file containing two + certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``☹`` and ``ï``, respectively. +* ``pkcs12/no-cert-no-name-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), + encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/no-cert-name-all-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``name2`` and ``name3``, respectively, + encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/no-cert-name-2-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), + the first with friendly name ``name2``, encrypted via AES 256 CBC with + the password ``cryptography``. +* ``pkcs12/no-cert-name-3-pwd.p12`` - A PKCS12 file containing two certs + (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), + the second with friendly name ``name3``, encrypted via AES 256 CBC with + the password ``cryptography``. +* ``pkcs12/no-cert-name-unicode-pwd.p12`` - A PKCS12 file containing two + certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) + with friendly names ``☹`` and ``ï``, respectively, encrypted via + AES 256 CBC with the password ``cryptography``. Custom PKCS7 Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~ * ``pkcs7/isrg.pem`` - A PEM encoded PKCS7 file containing the ISRG X1 root CA. -* ``pkcs7/amazon-roots.p7b`` - A DER encoded PCKS7 file containing Amazon Root - CA 2 and 3. +* ``pkcs7/amazon-roots.p7b`` - A BER encoded PCKS7 file containing Amazon Root + CA 2 and 3 generated by Apple Keychain. +* ``pkcs7/amazon-roots.der`` - A DER encoded PCKS7 file containing Amazon Root + CA 2 and 3 generated by OpenSSL. * ``pkcs7/enveloped.pem`` - A PEM encoded PKCS7 file with enveloped data. Custom OpenSSH Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* ``ed25519-aesgcm-psw.key`` and ``ed25519-aesgcm-psw.key.pub`` generated by + exporting an Ed25519 key from ``1password 8`` with the password "password". + This key is encrypted using the ``aes256-gcm@openssh.com`` algorithm. + Generated by ``asymmetric/OpenSSH/gen.sh`` using command-line tools from OpenSSH_7.6p1 package. @@ -616,6 +855,43 @@ using command-line tools from OpenSSH_7.6p1 package. Password-protected RSA-2048 private key and corresponding public key. Password is "password". +Custom OpenSSH Certificate Test Vectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* ``p256-p256-duplicate-extension.pub`` - A certificate with a duplicate + extension. +* ``p256-p256-non-lexical-extensions.pub`` - A certificate with extensions + in non-lexical order. +* ``p256-p256-duplicate-crit-opts.pub`` - A certificate with a duplicate + critical option. +* ``p256-p256-non-lexical-crit-opts.pub`` - A certificate with critical + options in non-lexical order. +* ``p256-ed25519-non-singular-crit-opt-val.pub`` - A certificate with + a critical option that contains more than one value. +* ``p256-ed25519-non-singular-ext-val.pub`` - A certificate with + an extension that contains more than one value. +* ``dsa-p256.pub`` - A certificate with a DSA public key signed by a P256 + CA. +* ``p256-dsa.pub`` - A certificate with a P256 public key signed by a DSA + CA. +* ``p256-p256-broken-signature-key-type.pub`` - A certificate with a P256 + public key signed by a P256 CA, but the signature key type is set to + ``rsa-sha2-512``. +* ``p256-p256-empty-principals.pub`` - A certificate with a P256 public + key signed by a P256 CA with an empty valid principals list. +* ``p256-p256-invalid-cert-type.pub`` - A certificate with a P256 public + key signed by a P256 CA with an invalid certificate type. +* ``p256-p384.pub`` - A certificate with a P256 public key signed by a P384 + CA. +* ``p256-p521.pub`` - A certificate with a P256 public key signed by a P521 + CA. +* ``p256-rsa-sha1.pub`` - A certificate with a P256 public key signed by a + RSA CA using SHA1. +* ``p256-rsa-sha256.pub`` - A certificate with a P256 public key signed by + a RSA CA using SHA256. +* ``p256-rsa-sha512.pub`` - A certificate with a P256 public key signed by + a RSA CA using SHA512. + Hashes ~~~~~~ @@ -661,6 +937,8 @@ Symmetric ciphers * AES (CBC, CFB, ECB, GCM, OFB, CCM) from `NIST CAVP`_. * AES CTR from :rfc:`3686`. +* AES OCB3 from :rfc:`7253`, `dkg's additional OCB3 vectors`_, and `OpenSSL's OCB vectors`_. +* AES SIV from OpenSSL's `evpciph_aes_siv.txt`_. * 3DES (CBC, CFB, ECB, OFB) from `NIST CAVP`_. * ARC4 (KEY-LENGTH: 40, 56, 64, 80, 128, 192, 256) from :rfc:`6229`. * ARC4 (KEY-LENGTH: 160) generated by this project. @@ -747,13 +1025,13 @@ header format (substituting the correct information): .. _`Specification repository`: https://github.com/fernet/spec .. _`errata`: https://www.rfc-editor.org/errata_search.php?rfc=6238 .. _`OpenSSL example key`: https://github.com/openssl/openssl/blob/d02b48c63a58ea4367a0e905979f140b7d090f86/test/testrsa.pem -.. _`GnuTLS key parsing tests`: https://gitlab.com/gnutls/gnutls/commit/f16ef39ef0303b02d7fa590a37820440c466ce8d +.. _`GnuTLS key parsing tests`: https://gitlab.com/gnutls/gnutls/-/commit/f16ef39ef0303b02d7fa590a37820440c466ce8d .. _`enc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/encpkcs8.pem .. _`enc2-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/enc2pkcs8.pem .. _`unenc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/unencpkcs8.pem .. _`pkcs12_s2k_pem.c`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs12_s2k_pem.c .. _`Botan's ECC private keys`: https://github.com/randombit/botan/tree/4917f26a2b154e841cd27c1bcecdd41d2bdeb6ce/src/tests/data/ecc -.. _`GnuTLS example keys`: https://gitlab.com/gnutls/gnutls/commit/ad2061deafdd7db78fd405f9d143b0a7c579da7b +.. _`GnuTLS example keys`: https://gitlab.com/gnutls/gnutls/-/commit/ad2061deafdd7db78fd405f9d143b0a7c579da7b .. _`NESSIE IDEA vectors`: https://www.cosic.esat.kuleuven.be/nessie/testvectors/bc/idea/Idea-128-64.verified.test-vectors .. _`NESSIE`: https://en.wikipedia.org/wiki/NESSIE .. _`Ed25519 website`: https://ed25519.cr.yp.to/software.html @@ -773,3 +1051,7 @@ header format (substituting the correct information): .. _`root-ed25519.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/root-ed25519.pem .. _`server-ed25519-cert.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/server-ed25519-cert.pem .. _`server-ed448-cert.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/server-ed448-cert.pem +.. _`evpciph_aes_siv.txt`: https://github.com/openssl/openssl/blob/d830526c711074fdcd82c70c24c31444366a1ed8/test/recipes/30-test_evp_data/evpciph_aes_siv.txt +.. _`dkg's additional OCB3 vectors`: https://gitlab.com/dkg/ocb-test-vectors +.. _`OpenSSL's OCB vectors`: https://github.com/openssl/openssl/commit/2f19ab18a29cf9c82cdd68bc8c7e5be5061b19be +.. _`badkeys`: https://github.com/vcsjones/badkeys/tree/50f1cc5f8d13bf3a2046d689f6452decb15d9c3c diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index 043d52d28da6..c7e82ffb4df2 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -13,7 +13,11 @@ security vulnerability, you should also include the following steps: included in the :doc:`changelog`. Ideally you should request the CVE before starting the release process so that the CVE is available at the time of the release. -* Ensure that the :doc:`changelog` entry credits whoever reported the issue. +* Document the CVE in the git commit that fixes the issue. +* Ensure that the :doc:`changelog` entry credits whoever reported the issue and + contains the assigned CVE. +* Publish a GitHub Security Advisory on the repository with all relevant + information. * The release should be announced on the `oss-security`_ mailing list, in addition to the regular announcement lists. @@ -21,10 +25,9 @@ Verifying OpenSSL version ------------------------- The release process creates wheels bundling OpenSSL for Windows, macOS, and -Linux. Check that the Windows, macOS, and Linux builders (both -``pyca/cryptography-manylinux1`` and ``pyca/cryptography-manylinux2010``) have -the latest OpenSSL. If anything is out of date follow the instructions for -upgrading OpenSSL. +Linux. Check that the Windows, macOS, and Linux builders (the ``manylinux`` +containers) have the latest OpenSSL. If anything is out of date follow the +instructions for upgrading OpenSSL. Upgrading OpenSSL ----------------- @@ -37,8 +40,7 @@ Bumping the version number The next step in doing a release is bumping the version number in the software. -* Update the version number in ``src/cryptography/__about__.py``. -* Update the version number in ``vectors/cryptography_vectors/__about__.py``. +* Run ``python release.py bump-version {new_version}`` * Set the release date in the :doc:`/changelog`. * Do a commit indicating this. * Send a pull request with this. @@ -51,7 +53,7 @@ The commit that merged the version number bump is now the official release commit for this release. You will need to have ``gpg`` installed and a ``gpg`` key in order to do a release. Once this has happened: -* Run ``python release.py {version}``. +* Run ``python release.py release {version}``. The release should now be available on PyPI and a tag should be available in the repository. @@ -79,18 +81,19 @@ the expected OpenSSL version. Post-release tasks ------------------ -* Update the version number to the next major (e.g. ``0.5.dev1``) in - ``src/cryptography/__about__.py`` and - ``vectors/cryptography_vectors/__about__.py``. +* Send an email to the `mailing list`_ and `python-announce`_ announcing the + release. * Close the `milestone`_ for the previous release on GitHub. +* For major version releases, send a pull request to pyOpenSSL increasing the + maximum ``cryptography`` version pin and perform a pyOpenSSL release. +* Update the version number to the next major (e.g. ``0.5.dev1``) with + ``python release.py bump-version {new_version}``. * Add new :doc:`/changelog` entry with next version and note that it is under active development * Send a pull request with these items * Check for any outstanding code undergoing a deprecation cycle by looking in ``cryptography.utils`` for ``DeprecatedIn**`` definitions. If any exist open a ticket to increment them for the next release. -* Send an email to the `mailing list`_ and `python-announce`_ announcing the - release. .. _`CVE from MITRE`: https://cveform.mitre.org/ .. _`oss-security`: https://www.openwall.com/lists/oss-security/ diff --git a/docs/faq.rst b/docs/faq.rst index 33c5417d12db..ac7f4152c731 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -1,6 +1,22 @@ Frequently asked questions ========================== +What issues can you help with in your issue tracker? +---------------------------------------------------- + +The primary purpose of our issue tracker is to enable us to identify and +resolve bugs and feature requests in ``cryptography``, so any time a user +files a bug, we start by asking: Is this a ``cryptography`` bug, or is it a +bug somewhere else? + +That said, we do our best to help users to debug issues that are in their code +or environments. Please note, however, that there's a limit to our ability to +assist users in resolving problems that are specific to their environments, +particularly when we have no way to reproduce the issue. + +Lastly, we're not able to provide support for general Python or Python +packaging issues. + .. _faq-howto-handle-deprecation-warning: I cannot suppress the deprecation warning that ``cryptography`` emits on import @@ -16,7 +32,7 @@ If your pytest setup follows the best practices of failing on emitted warnings (``filterwarnings = error``), you may ignore it by adding the following line at the end of the list:: - ignore:Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in a future release.:UserWarning:cryptography + ignore:Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in a future release.:UserWarning **Note:** Using ``cryptography.utils.CryptographyDeprecationWarning`` is not possible here because specifying it triggers @@ -67,21 +83,30 @@ legacy libraries: * Lack of high level APIs. * Lack of PyPy and Python 3 support. * Absence of algorithms such as - :class:`AES-GCM ` and + :class:`AES-GCM ` and :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. -Compiling ``cryptography`` on macOS produces a ``fatal error: 'openssl/aes.h' file not found`` error ----------------------------------------------------------------------------------------------------- +Why does ``cryptography`` require Rust? +--------------------------------------- -This happens because macOS 10.11 no longer includes a copy of OpenSSL. -``cryptography`` now provides wheels which include a statically linked copy of -OpenSSL. You're seeing this error because your copy of pip is too old to find -our wheel files. Upgrade your copy of pip with ``pip install -U pip`` and then -try install ``cryptography`` again. +``cryptography`` uses OpenSSL (see: :doc:`/openssl`) for its cryptographic operations. OpenSSL is +the de facto standard for cryptographic libraries and provides high performance +along with various certifications that may be relevant to developers. However, +it is written in C and lacks `memory safety`_. We want ``cryptography`` to be +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. -If you are using PyPy, we do not currently ship ``cryptography`` wheels for -PyPy. You will need to install your own copy of OpenSSL -- we recommend using -Homebrew. +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? ------------------------------------------------------------------------- @@ -93,57 +118,50 @@ If you have no other libraries using OpenSSL in your process, or they do not appear to be at fault, it's possible that this is a bug in ``cryptography``. Please file an `issue`_ with instructions on how to reproduce it. -error: ``-Werror=sign-conversion``: No option ``-Wsign-conversion`` during installation ---------------------------------------------------------------------------------------- - -The compiler you are using is too old and not supported by ``cryptography``. -Please upgrade to a more recent version. If you are running OpenBSD 6.1 or -earlier the default compiler is extremely old. Use ``pkg_add`` to install a -newer ``gcc`` and then install ``cryptography`` using -``CC=/path/to/newer/gcc pip install cryptography``. - -Installing ``cryptography`` fails with ``Invalid environment marker: python_version < '3'`` -------------------------------------------------------------------------------------------- +Installing cryptography with OpenSSL 0.9.8, 1.0.0, 1.0.1, 1.0.2, 1.1.0 fails +---------------------------------------------------------------------------- -Your ``pip`` and/or ``setuptools`` are outdated. Please upgrade to the latest -versions with ``pip install -U pip setuptools`` (or on Windows -``python -m pip install -U pip setuptools``). +The OpenSSL project has dropped support for the 0.9.8, 1.0.0, 1.0.1, 1.0.2, +and 1.1.0 release series. Since they are no longer receiving security patches +from upstream, ``cryptography`` is also dropping support for them. To fix this +issue you should upgrade to a newer version of OpenSSL (1.1.1 or later). This +may require you to upgrade to a newer operating system. -Importing cryptography causes a ``RuntimeError`` about OpenSSL 1.0.2 --------------------------------------------------------------------- +Installing ``cryptography`` fails with ``error: Can not find Rust compiler`` +---------------------------------------------------------------------------- -The OpenSSL project has dropped support for the 1.0.2 release series. Since it -is no longer receiving security patches from upstream, ``cryptography`` is also -dropping support for it. To fix this issue you should upgrade to a newer -version of OpenSSL (1.1.0 or later). This may require you to upgrade to a newer -operating system. +Building ``cryptography`` from source requires you have :ref:`Rust installed +and available` on your ``PATH``. You may be able to fix this +by upgrading to a newer version of ``pip`` which will install a pre-compiled +``cryptography`` wheel. If not, you'll need to install Rust. Follow the +:ref:`instructions` to ensure you install a recent Rust +version. -For the 3.2 release, you can set the ``CRYPTOGRAPHY_ALLOW_OPENSSL_102`` -environment variable. Please note that this is *temporary* and will be removed -in ``cryptography`` 3.3. +Rust is only required during the build phase of ``cryptography``, you do not +need to have Rust installed after you've built ``cryptography``. This is the +same as the C compiler toolchain which is also required to build +``cryptography``, but not afterwards. -Installing cryptography with OpenSSL 0.9.8, 1.0.0, 1.0.1 fails --------------------------------------------------------------- +I'm getting errors installing or importing ``cryptography`` on AWS Lambda +------------------------------------------------------------------------- -The OpenSSL project has dropped support for the 0.9.8, 1.0.0, and 1.0.1 release -series. Since they are no longer receiving security patches from upstream, -``cryptography`` is also dropping support for them. To fix this issue you -should upgrade to a newer version of OpenSSL (1.0.2 or later). This may require -you to upgrade to a newer operating system. +Make sure you're following AWS's documentation either for +`building .zip archives for Lambda`_ or +`building container images for Lambda`_. -Why are there no wheels for Python 3.6+ on Linux or macOS? ----------------------------------------------------------- +Why are there no wheels for my Python3.x version? +------------------------------------------------- -Our Python3 wheels, for macOS and Linux, are ``abi3`` wheels. This means they -support multiple versions of Python. The Python 3.5 ``abi3`` wheel can be used -with any version of Python greater than or equal to 3.5. Recent versions of -``pip`` will automatically install ``abi3`` wheels. +Our Python3 wheels are ``abi3`` wheels. This means they support multiple +versions of Python. The ``abi3`` wheel can be used with any version of Python +greater than or equal to the version it specifies. Recent versions of ``pip`` +will automatically install ``abi3`` wheels. Why can't I import my PEM file? ------------------------------- PEM is a format (defined by several RFCs, but originally :rfc:`1421`) for -encoding keys, certificates and others cryptographic data into a regular form. +encoding keys, certificates, and others cryptographic data into a regular form. The data is encoded as base64 and wrapped with a header and footer. If you are having trouble importing PEM files, make sure your file fits @@ -170,8 +188,42 @@ For example, this is a PEM file for a RSA Public Key: :: 2QIDAQAB -----END PUBLIC KEY----- +.. _faq-missing-backend: + +What happened to the backend argument? +-------------------------------------- + +``cryptography`` stopped requiring the use of ``backend`` arguments in +version 3.1 and deprecated their use in version 36.0. If you are on an older +version that requires these arguments please view the appropriate documentation +version or upgrade to the latest release. + +Note that for forward compatibility ``backend`` is still silently accepted by +functions that previously required it, but it is ignored and no longer +documented. + +Will you upload wheels for my non-x86 non-ARM64 CPU architecture? +----------------------------------------------------------------- + +Maybe! But there's some pre-requisites. For us to build wheels and upload them +to PyPI, we consider it necessary to run our tests for that architecture as a +part of our CI (i.e. for every commit). If we don't run the tests, it's hard +to have confidence that everything works -- particularly with cryptography, +which frequently employs per-architecture assembly code. + +For us to add something to CI we need a provider which offers builds on that +architecture, which integrate into our workflows, has sufficient capacity, and +performs well enough not to regress the contributor experience. We don't think +this is an insurmountable bar, but it's also not one that can be cleared +lightly. + +If you are interested in helping support a new CPU architecture, we encourage +you to reach out, discuss, and contribute that support. We will attempt to be +supportive, but we cannot commit to doing the work ourselves. .. _`NaCl`: https://nacl.cr.yp.to/ .. _`PyNaCl`: https://pynacl.readthedocs.io -.. _`WSGIApplicationGroup`: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIApplicationGroup.html .. _`issue`: https://github.com/pyca/cryptography/issues +.. _`memory safety`: https://alexgaynor.net/2019/aug/12/introduction-to-memory-unsafety-for-vps-of-engineering/ +.. _`building .zip archives for Lambda`: https://docs.aws.amazon.com/lambda/latest/dg/python-package.html +.. _`building container images for Lambda`: https://docs.aws.amazon.com/lambda/latest/dg/python-image.html diff --git a/docs/fernet.rst b/docs/fernet.rst index 960f47137850..b55ecea3206a 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -83,8 +83,8 @@ has support for implementing key rotation via :class:`MultiFernet`. raised. It is safe to use this data immediately as Fernet verifies that the data has not been tampered with prior to returning it. - :param bytes token: The Fernet token. This is the result of calling - :meth:`encrypt`. + :param bytes or str token: The Fernet token. This is the result of + calling :meth:`encrypt`. :param int ttl: Optionally, the number of seconds old a message may be for it to be valid. If the message is older than ``ttl`` seconds (from the time it was originally @@ -101,7 +101,7 @@ has support for implementing key rotation via :class:`MultiFernet`. it does not have a valid signature. :raises TypeError: This exception is raised if ``token`` is not - ``bytes``. + ``bytes`` or ``str``. .. method:: decrypt_at_time(token, ttl, current_time) @@ -127,14 +127,14 @@ has support for implementing key rotation via :class:`MultiFernet`. Returns the timestamp for the token. The caller can then decide if the token is about to expire and, for example, issue a new token. - :param bytes token: The Fernet token. This is the result of calling - :meth:`encrypt`. - :returns int: The UNIX timestamp of the token. + :param bytes or str token: The Fernet token. This is the result of + calling :meth:`encrypt`. + :returns int: The Unix timestamp of the token. :raises cryptography.fernet.InvalidToken: If the ``token``'s signature is invalid this exception is raised. :raises TypeError: This exception is raised if ``token`` is not - ``bytes``. + ``bytes`` or ``str``. .. class:: MultiFernet(fernets) @@ -201,14 +201,14 @@ has support for implementing key rotation via :class:`MultiFernet`. >>> f2.decrypt(rotated) b'Secret message!' - :param bytes msg: The token to re-encrypt. + :param bytes or str msg: The token to re-encrypt. :returns bytes: A secure message that cannot be read or altered without the key. This is URL-safe base64-encoded. This is referred to as a "Fernet token". :raises cryptography.fernet.InvalidToken: If a ``token`` is in any way invalid this exception is raised. :raises TypeError: This exception is raised if the ``msg`` is not - ``bytes``. + ``bytes`` or ``str``. .. class:: InvalidToken @@ -237,7 +237,7 @@ password through a key derivation function such as ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=100000, + ... iterations=480000, ... ) >>> key = base64.urlsafe_b64encode(kdf.derive(password)) >>> f = Fernet(key) @@ -251,8 +251,8 @@ In this scheme, the salt has to be stored in a retrievable location in order to derive the same key from the password in the future. The iteration count used should be adjusted to be as high as your server can -tolerate. A good default is at least 100,000 iterations which is what Django -recommended in 2014. +tolerate. A good default is at least 480,000 iterations, which is what `Django +recommends as of December 2022`_. Implementation -------------- @@ -274,9 +274,11 @@ Limitations ----------- Fernet is ideal for encrypting data that easily fits in memory. As a design -feature it does not expose unauthenticated bytes. Unfortunately, this makes it -generally unsuitable for very large files at this time. +feature it does not expose unauthenticated bytes. This means that the complete +message contents must be available in memory, making Fernet generally +unsuitable for very large files at this time. .. _`Fernet`: https://github.com/fernet/spec/ +.. _`Django recommends as of December 2022`: https://github.com/django/django/blob/main/django/contrib/auth/hashers.py .. _`specification`: https://github.com/fernet/spec/blob/master/Spec.md diff --git a/docs/glossary.rst b/docs/glossary.rst index 95b893c8fee6..86718cc0d675 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -61,10 +61,6 @@ Glossary aren't distinguishable without knowing the encryption key. This is considered a basic, necessary property for a working encryption system. - text - This type corresponds to ``unicode`` on Python 2 and ``str`` on Python - 3. This is equivalent to ``six.text_type``. - nonce A nonce is a **n**\ umber used **once**. Nonces are used in many cryptographic protocols. Generally, a nonce does not have to be secret @@ -97,13 +93,22 @@ Glossary bytes-like A bytes-like object contains binary data and supports the `buffer protocol`_. This includes ``bytes``, ``bytearray``, and - ``memoryview`` objects. + ``memoryview`` objects. It is :term:`unsafe` to pass a mutable object + (e.g., a ``bytearray`` or other implementor of the buffer protocol) + and to `mutate it concurrently`_ with the operation it has been + provided for. U-label The presentational unicode form of an internationalized domain name. U-labels use unicode characters outside the ASCII range and are encoded as A-labels when stored in certificates. + unsafe + This is a term used to describe an operation where the user must + ensure that the input is correct. Failure to do so can result in + crashes, hangs, and other security issues. + .. _`hardware security module`: https://en.wikipedia.org/wiki/Hardware_security_module .. _`idna`: https://pypi.org/project/idna/ .. _`buffer protocol`: https://docs.python.org/3/c-api/buffer.html +.. _`mutate it concurrently`: https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ diff --git a/docs/hazmat/backends/index.rst b/docs/hazmat/backends/index.rst deleted file mode 100644 index 97dbc869b8ce..000000000000 --- a/docs/hazmat/backends/index.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. hazmat:: - -Backends -======== - -Getting a backend ------------------ - -.. currentmodule:: cryptography.hazmat.backends - -``cryptography`` was designed to support multiple cryptographic backends, but -consumers rarely need this flexibility. Starting with version 3.1 ``backend`` -arguments are optional and the default backend will automatically be selected -if none is specified. - -On older versions you can get the default backend by calling -:func:`~default_backend`. - - -.. function:: default_backend() - - :returns: An object that provides at least - :class:`~interfaces.CipherBackend`, :class:`~interfaces.HashBackend`, and - :class:`~interfaces.HMACBackend`. - -Individual backends -------------------- - -.. toctree:: - :maxdepth: 1 - - openssl - interfaces diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst deleted file mode 100644 index 36dd3a7a5a1e..000000000000 --- a/docs/hazmat/backends/interfaces.rst +++ /dev/null @@ -1,729 +0,0 @@ -.. hazmat:: - -Backend interfaces -================== - -.. currentmodule:: cryptography.hazmat.backends.interfaces - - -Backend implementations may provide a number of interfaces to support -operations such as :doc:`/hazmat/primitives/symmetric-encryption`, -:doc:`/hazmat/primitives/cryptographic-hashes`, and -:doc:`/hazmat/primitives/mac/hmac`. - -A specific ``backend`` may provide one or more of these interfaces. - - -.. class:: CipherBackend - - A backend that provides methods for using ciphers for encryption - and decryption. - - The following backends implement this interface: - - * :doc:`/hazmat/backends/openssl` - - .. method:: cipher_supported(cipher, mode) - - Check if a ``cipher`` and ``mode`` combination is supported by - this backend. - - :param cipher: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`. - - :param mode: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`. - - :returns: ``True`` if the specified ``cipher`` and ``mode`` combination - is supported by this backend, otherwise ``False`` - - - .. method:: create_symmetric_encryption_ctx(cipher, mode) - - Create a - :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` that - can be used for encrypting data with the symmetric ``cipher`` using - the given ``mode``. - - :param cipher: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`. - - :param mode: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`. - - :returns: - :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` - - :raises ValueError: When tag is not None in an AEAD mode - - - .. method:: create_symmetric_decryption_ctx(cipher, mode) - - Create a - :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` that - can be used for decrypting data with the symmetric ``cipher`` using - the given ``mode``. - - :param cipher: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`. - - :param mode: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`. - - :returns: - :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` - - :raises ValueError: When tag is None in an AEAD mode - - -.. class:: HashBackend - - A backend with methods for using cryptographic hash functions. - - The following backends implement this interface: - - * :doc:`/hazmat/backends/openssl` - - .. method:: hash_supported(algorithm) - - Check if the specified ``algorithm`` is supported by this backend. - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. - - :returns: ``True`` if the specified ``algorithm`` is supported by this - backend, otherwise ``False``. - - - .. method:: create_hash_ctx(algorithm) - - Create a - :class:`~cryptography.hazmat.primitives.hashes.HashContext` that - uses the specified ``algorithm`` to calculate a message digest. - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. - - :returns: - :class:`~cryptography.hazmat.primitives.hashes.HashContext` - - -.. class:: HMACBackend - - A backend with methods for using cryptographic hash functions as message - authentication codes. - - The following backends implement this interface: - - * :doc:`/hazmat/backends/openssl` - - .. method:: hmac_supported(algorithm) - - Check if the specified ``algorithm`` is supported by this backend. - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. - - :returns: ``True`` if the specified ``algorithm`` is supported for HMAC - by this backend, otherwise ``False``. - - .. method:: create_hmac_ctx(key, algorithm) - - Create a - :class:`~cryptography.hazmat.primitives.hashes.HashContext` that - uses the specified ``algorithm`` to calculate a hash-based message - authentication code. - - :param bytes key: Secret key as ``bytes``. - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. - - :returns: - :class:`~cryptography.hazmat.primitives.hashes.HashContext` - - -.. class:: CMACBackend - - .. versionadded:: 0.4 - - A backend with methods for using CMAC - - .. method:: cmac_algorithm_supported(algorithm) - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. - - :return: Returns True if the block cipher is supported for CMAC by this backend - - .. method:: create_cmac_ctx(algorithm) - - Create a - context that - uses the specified ``algorithm`` to calculate a message authentication code. - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. - - :returns: CMAC object. - - -.. class:: PBKDF2HMACBackend - - .. versionadded:: 0.2 - - A backend with methods for using PBKDF2 using HMAC as a PRF. - - The following backends implement this interface: - - * :doc:`/hazmat/backends/openssl` - - .. method:: pbkdf2_hmac_supported(algorithm) - - Check if the specified ``algorithm`` is supported by this backend. - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. - - :returns: ``True`` if the specified ``algorithm`` is supported for - PBKDF2 HMAC by this backend, otherwise ``False``. - - .. method:: derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, key_material) - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. - - :param int length: The desired length of the derived key. Maximum is - (2\ :sup:`32` - 1) * ``algorithm.digest_size`` - - :param bytes salt: A salt. - - :param int iterations: The number of iterations to perform of the hash - function. This can be used to control the length of time the - operation takes. Higher numbers help mitigate brute force attacks - against derived keys. - - :param bytes key_material: The key material to use as a basis for - the derived key. This is typically a password. - - :return bytes: Derived key. - - -.. class:: RSABackend - - .. versionadded:: 0.2 - - A backend with methods for using RSA. - - .. method:: generate_rsa_private_key(public_exponent, key_size) - - :param int public_exponent: The public exponent of the new key. - Often one of the small Fermat primes 3, 5, 17, 257 or 65537. - - :param int key_size: The length in bits of the modulus. Should be - at least 2048. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. - - :raises ValueError: If the public_exponent is not valid. - - .. method:: rsa_padding_supported(padding) - - Check if the specified ``padding`` is supported by the backend. - - :param padding: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. - - :returns: ``True`` if the specified ``padding`` is supported by this - backend, otherwise ``False``. - - .. method:: generate_rsa_parameters_supported(public_exponent, key_size) - - Check if the specified parameters are supported for key generation by - the backend. - - :param int public_exponent: The public exponent. - - :param int key_size: The bit length of the generated modulus. - - .. method:: load_rsa_private_numbers(numbers) - - :param numbers: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. - - :raises ValueError: This is raised when the values of ``p``, ``q``, - ``private_exponent``, ``public_exponent``, or ``modulus`` do not - match the bounds specified in :rfc:`3447`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - .. method:: load_rsa_public_numbers(numbers) - - :param numbers: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. - - :raises ValueError: This is raised when the values of - ``public_exponent`` or ``modulus`` do not match the bounds - specified in :rfc:`3447`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - -.. class:: DSABackend - - .. versionadded:: 0.4 - - A backend with methods for using DSA. - - .. method:: generate_dsa_parameters(key_size) - - :param int key_size: The length of the modulus in bits. It should be - either 1024, 2048 or 3072. For keys generated in 2015 this should - be at least 2048. - Note that some applications (such as SSH) have not yet gained - support for larger key sizes specified in FIPS 186-3 and are still - restricted to only the 1024-bit keys specified in FIPS 186-2. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. - - .. method:: generate_dsa_private_key(parameters) - - :param parameters: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. - - :raises ValueError: This is raised if the key size is not one of 1024, - 2048, or 3072. - - .. method:: generate_dsa_private_key_and_parameters(key_size) - - :param int key_size: The length of the modulus in bits. It should be - either 1024, 2048 or 3072. For keys generated in 2015 this should - be at least 2048. - Note that some applications (such as SSH) have not yet gained - support for larger key sizes specified in FIPS 186-3 and are still - restricted to only the 1024-bit keys specified in FIPS 186-2. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. - - :raises ValueError: This is raised if the key size is not supported - by the backend. - - .. method:: dsa_hash_supported(algorithm) - - :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. - - :returns: ``True`` if the specified ``algorithm`` is supported by this - backend, otherwise ``False``. - - .. method:: dsa_parameters_supported(p, q, g) - - :param int p: The p value of a DSA key. - - :param int q: The q value of a DSA key. - - :param int g: The g value of a DSA key. - - :returns: ``True`` if the given values of ``p``, ``q``, and ``g`` are - supported by this backend, otherwise ``False``. - - .. method:: load_dsa_parameter_numbers(numbers) - - :param numbers: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - .. method:: load_dsa_private_numbers(numbers) - - :param numbers: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - .. method:: load_dsa_public_numbers(numbers) - - :param numbers: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - -.. class:: EllipticCurveBackend - - .. versionadded:: 0.5 - - .. method:: elliptic_curve_supported(curve) - - :param curve: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. - - :returns: True if the elliptic curve is supported by this backend. - - .. method:: elliptic_curve_signature_algorithm_supported(signature_algorithm, curve) - - :param signature_algorithm: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurveSignatureAlgorithm`. - - :param curve: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. - - :returns: True if the signature algorithm and curve are supported by this backend. - - .. method:: generate_elliptic_curve_private_key(curve) - - :param curve: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. - - .. method:: load_elliptic_curve_private_numbers(numbers) - - :param numbers: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateNumbers`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. - - .. method:: load_elliptic_curve_public_numbers(numbers) - - :param numbers: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. - - .. method:: derive_elliptic_curve_private_key(private_value, curve) - - :param private_value: A secret scalar value. - - :param curve: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. - - :returns: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. - -.. class:: PEMSerializationBackend - - .. versionadded:: 0.6 - - A backend with methods for working with any PEM encoded keys. - - .. method:: load_pem_private_key(data, password) - - :param bytes data: PEM data to load. - :param bytes password: The password to use if the data is encrypted. - Should be ``None`` if the data is not encrypted. - :return: A new instance of the appropriate type of private key that the - serialized data contains. - :raises ValueError: If the data could not be deserialized. - :raises cryptography.exceptions.UnsupportedAlgorithm: If the data is - encrypted with an unsupported algorithm. - - .. method:: load_pem_public_key(data) - - :param bytes data: PEM data to load. - :return: A new instance of the appropriate type of public key - serialized data contains. - :raises ValueError: If the data could not be deserialized. - - .. method:: load_pem_parameters(data) - - .. versionadded:: 2.0 - - :param bytes data: PEM data to load. - :return: A new instance of the appropriate type of asymmetric - parameters the serialized data contains. - :raises ValueError: If the data could not be deserialized. - -.. class:: DERSerializationBackend - - .. versionadded:: 0.8 - - A backend with methods for working with DER encoded keys. - - .. method:: load_der_private_key(data, password) - - :param bytes data: DER data to load. - :param bytes password: The password to use if the data is encrypted. - Should be ``None`` if the data is not encrypted. - :return: A new instance of the appropriate type of private key that the - serialized data contains. - :raises ValueError: If the data could not be deserialized. - :raises cryptography.exceptions.UnsupportedAlgorithm: If the data is - encrypted with an unsupported algorithm. - - .. method:: load_der_public_key(data) - - :param bytes data: DER data to load. - :return: A new instance of the appropriate type of public key - serialized data contains. - :raises ValueError: If the data could not be deserialized. - - .. method:: load_der_parameters(data) - - .. versionadded:: 2.0 - - :param bytes data: DER data to load. - :return: A new instance of the appropriate type of asymmetric - parameters the serialized data contains. - :raises ValueError: If the data could not be deserialized. - - -.. class:: X509Backend - - .. versionadded:: 0.7 - - A backend with methods for working with X.509 objects. - - .. method:: load_pem_x509_certificate(data) - - :param bytes data: PEM formatted certificate data. - - :returns: An instance of :class:`~cryptography.x509.Certificate`. - - .. method:: load_der_x509_certificate(data) - - :param bytes data: DER formatted certificate data. - - :returns: An instance of :class:`~cryptography.x509.Certificate`. - - .. method:: load_pem_x509_csr(data) - - .. versionadded:: 0.9 - - :param bytes data: PEM formatted certificate signing request data. - - :returns: An instance of - :class:`~cryptography.x509.CertificateSigningRequest`. - - .. method:: load_der_x509_csr(data) - - .. versionadded:: 0.9 - - :param bytes data: DER formatted certificate signing request data. - - :returns: An instance of - :class:`~cryptography.x509.CertificateSigningRequest`. - - .. method:: create_x509_csr(builder, private_key, algorithm) - - .. versionadded:: 1.0 - - :param builder: An instance of - :class:`~cryptography.x509.CertificateSigningRequestBuilder`. - - :param private_key: The - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` - that will be used to sign the request. When the request is - signed by a certificate authority, the private key's associated - public key will be stored in the resulting certificate. - - :param algorithm: The - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` - that will be used to generate the request signature. - - :returns: A new instance of - :class:`~cryptography.x509.CertificateSigningRequest`. - - .. method:: create_x509_certificate(builder, private_key, algorithm) - - .. versionadded:: 1.0 - - :param builder: An instance of - :class:`~cryptography.x509.CertificateBuilder`. - - :param private_key: The - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` - that will be used to sign the certificate. - - :param algorithm: The - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` - that will be used to generate the certificate signature. - - :returns: A new instance of :class:`~cryptography.x509.Certificate`. - - .. method:: create_x509_crl(builder, private_key, algorithm) - - .. versionadded:: 1.2 - - :param builder: An instance of - :class:`~cryptography.x509.CertificateRevocationListBuilder`. - - :param private_key: The - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` - that will be used to sign the CRL. - - :param algorithm: The - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` - that will be used to generate the CRL signature. - - :returns: A new instance of - :class:`~cryptography.x509.CertificateRevocationList`. - - .. method:: create_x509_revoked_certificate(builder) - - .. versionadded:: 1.2 - - :param builder: An instance of RevokedCertificateBuilder. - - :returns: A new instance of - :class:`~cryptography.x509.RevokedCertificate`. - - .. method:: x509_name_bytes(name) - - .. versionadded:: 1.6 - - :param name: An instance of :class:`~cryptography.x509.Name`. - - :return bytes: The DER encoded bytes. - -.. class:: DHBackend - - .. versionadded:: 0.9 - - A backend with methods for doing Diffie-Hellman key exchange. - - .. method:: generate_dh_parameters(generator, key_size) - - :param int generator: The generator to use. Often 2 or 5. - - :param int key_size: The bit length of the prime modulus to generate. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. - - :raises ValueError: If ``key_size`` is not at least 512. - - .. method:: generate_dh_private_key(parameters) - - :param parameters: An instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. - - .. method:: generate_dh_private_key_and_parameters(generator, key_size) - - :param int generator: The generator to use. Often 2 or 5. - - :param int key_size: The bit length of the prime modulus to generate. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. - - :raises ValueError: If ``key_size`` is not at least 512. - - .. method:: load_dh_private_numbers(numbers) - - :param numbers: A - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateNumbers` - instance. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - .. method:: load_dh_public_numbers(numbers) - - :param numbers: A - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers` - instance. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - .. method:: load_dh_parameter_numbers(numbers) - - :param numbers: A - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers` - instance. - - :return: A new instance of - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - when any backend specific criteria are not met. - - .. method:: dh_parameters_supported(p, g, q=None) - - :param int p: The p value of the DH key. - - :param int g: The g value of the DH key. - - :param int q: The q value of the DH key. - - :returns: ``True`` if the given values of ``p``, ``g`` and ``q`` - are supported by this backend, otherwise ``False``. - - .. versionadded:: 1.8 - - .. method:: dh_x942_serialization_supported() - - :returns: True if serialization of DH objects with - subgroup order (q) is supported by this backend. - - -.. class:: ScryptBackend - - .. versionadded:: 1.6 - - A backend with methods for using Scrypt. - - The following backends implement this interface: - - * :doc:`/hazmat/backends/openssl` - - .. method:: derive_scrypt(self, key_material, salt, length, n, r, p) - - :param bytes key_material: The key material to use as a basis for - the derived key. This is typically a password. - - :param bytes salt: A salt. - - :param int length: The desired length of the derived key. - - :param int n: CPU/Memory cost parameter. It must be larger than 1 and be a - power of 2. - - :param int r: Block size parameter. - - :param int p: Parallelization parameter. - - :return bytes: Derived key. - diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst deleted file mode 100644 index 0e695279dbe4..000000000000 --- a/docs/hazmat/backends/openssl.rst +++ /dev/null @@ -1,132 +0,0 @@ -.. hazmat:: - -OpenSSL backend -=============== - -The `OpenSSL`_ C library. Cryptography supports OpenSSL version 1.0.2 and -greater. - -.. data:: cryptography.hazmat.backends.openssl.backend - - This is the exposed API for the OpenSSL backend. - - It implements the following interfaces: - - * :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` - * :class:`~cryptography.hazmat.backends.interfaces.CMACBackend` - * :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend` - * :class:`~cryptography.hazmat.backends.interfaces.DHBackend` - * :class:`~cryptography.hazmat.backends.interfaces.DSABackend` - * :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend` - * :class:`~cryptography.hazmat.backends.interfaces.HashBackend` - * :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` - * :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` - * :class:`~cryptography.hazmat.backends.interfaces.RSABackend` - * :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend` - * :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - - It also implements the following interface for OpenSSL versions ``1.1.0`` - and above. - - * :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend` - - It also exposes the following: - - .. attribute:: name - - The string name of this backend: ``"openssl"`` - - .. method:: openssl_version_text() - - :return text: The friendly string name of the loaded OpenSSL library. - This is not necessarily the same version as it was compiled against. - - .. method:: openssl_version_number() - - .. versionadded:: 1.8 - - :return int: The integer version of the loaded OpenSSL library. This is - defined in ``opensslv.h`` as ``OPENSSL_VERSION_NUMBER`` and is - typically shown in hexadecimal (e.g. ``0x1010003f``). This is - not necessarily the same version as it was compiled against. - - .. method:: activate_osrandom_engine() - - Activates the OS random engine. This will effectively disable OpenSSL's - default CSPRNG. - - .. method:: osrandom_engine_implementation() - - .. versionadded:: 1.7 - - Returns the implementation of OS random engine. - - .. method:: activate_builtin_random() - - This will activate the default OpenSSL CSPRNG. - -OS random engine ----------------- - -.. note:: - - As of OpenSSL 1.1.1d its CSPRNG is fork-safe by default. - ``cryptography`` does not compile or load the custom engine on - these versions. - -By default OpenSSL uses a user-space CSPRNG that is seeded from system random ( -``/dev/urandom`` or ``CryptGenRandom``). This CSPRNG is not reseeded -automatically when a process calls ``fork()``. This can result in situations -where two different processes can return similar or identical keys and -compromise the security of the system. - -The approach this project has chosen to mitigate this vulnerability is to -include an engine that replaces the OpenSSL default CSPRNG with one that -sources its entropy from ``/dev/urandom`` on UNIX-like operating systems and -uses ``CryptGenRandom`` on Windows. This method of pulling from the system pool -allows us to avoid potential issues with `initializing the RNG`_ as well as -protecting us from the ``fork()`` weakness. - -This engine is **active** by default when importing the OpenSSL backend. When -active this engine will be used to generate all the random data OpenSSL -requests. - -When importing only the binding it is added to the engine list but -**not activated**. - - -OS random sources ------------------ - -On macOS and FreeBSD ``/dev/urandom`` is an alias for ``/dev/random``. The -implementation on macOS uses the `Yarrow`_ algorithm. FreeBSD uses the -`Fortuna`_ algorithm. - -On Windows the implementation of ``CryptGenRandom`` depends on which version of -the operation system you are using. See the `Microsoft documentation`_ for more -details. - -Linux uses its own PRNG design. ``/dev/urandom`` is a non-blocking source -seeded from the same pool as ``/dev/random``. - -+------------------------------------------+------------------------------+ -| Windows | ``CryptGenRandom()`` | -+------------------------------------------+------------------------------+ -| Linux >= 3.17 with working | ``getrandom()`` | -| ``SYS_getrandom`` syscall | | -+------------------------------------------+------------------------------+ -| OpenBSD >= 5.6 | ``getentropy()`` | -+------------------------------------------+------------------------------+ -| BSD family (including macOS 10.12+) with | ``getentropy()`` | -| ``SYS_getentropy`` in ``sys/syscall.h`` | | -+------------------------------------------+------------------------------+ -| fallback | ``/dev/urandom`` with | -| | cached file descriptor | -+------------------------------------------+------------------------------+ - - -.. _`OpenSSL`: https://www.openssl.org/ -.. _`initializing the RNG`: https://en.wikipedia.org/wiki/OpenSSL#Predictable_private_keys_.28Debian-specific.29 -.. _`Fortuna`: https://en.wikipedia.org/wiki/Fortuna_(PRNG) -.. _`Yarrow`: https://en.wikipedia.org/wiki/Yarrow_algorithm -.. _`Microsoft documentation`: https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/nf-wincrypt-cryptgenrandom diff --git a/docs/hazmat/primitives/aead.rst b/docs/hazmat/primitives/aead.rst index d318367bc4ba..db9ef96d1ab7 100644 --- a/docs/hazmat/primitives/aead.rst +++ b/docs/hazmat/primitives/aead.rst @@ -56,13 +56,15 @@ also support providing integrity for associated data which is not encrypted. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to encrypt. - :param bytes associated_data: Additional data that should be + :param data: The data to encrypt. + :type data: :term:`bytes-like` + :param associated_data: Additional data that should be authenticated with the key, but does not need to be encrypted. Can be ``None``. + :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the 16 byte tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger - than 2\ :sup:`32` bytes. + than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) @@ -73,9 +75,11 @@ also support providing integrity for associated data which is not encrypted. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to decrypt (with tag appended). - :param bytes associated_data: Additional data to authenticate. Can be + :param data: The data to decrypt (with tag appended). + :type data: :term:`bytes-like` + :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. + :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur @@ -130,12 +134,14 @@ also support providing integrity for associated data which is not encrypted. performance but it can be up to 2\ :sup:`64` - 1 :term:`bits`. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to encrypt. - :param bytes associated_data: Additional data that should be + :param data: The data to encrypt. + :type data: :term:`bytes-like` + :param associated_data: Additional data that should be authenticated with the key, but is not encrypted. Can be ``None``. + :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the 16 byte tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger - than 2\ :sup:`32` bytes. + than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) @@ -147,15 +153,180 @@ also support providing integrity for associated data which is not encrypted. performance but it can be up to 2\ :sup:`64` - 1 :term:`bits`. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to decrypt (with tag appended). - :param bytes associated_data: Additional data to authenticate. Can be + :param data: The data to decrypt (with tag appended). + :type data: :term:`bytes-like` + :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. + :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. +.. class:: AESOCB3(key) + + .. versionadded:: 36.0.0 + + The OCB3 construction is defined in :rfc:`7253`. It is an AEAD mode + that offers strong integrity guarantees and good performance. + + :param key: A 128, 192, or 256-bit key. This **must** be kept secret. + :type key: :term:`bytes-like` + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of + OpenSSL does not support AES-OCB3. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives.ciphers.aead import AESOCB3 + >>> data = b"a secret message" + >>> aad = b"authenticated but unencrypted data" + >>> key = AESOCB3.generate_key(bit_length=128) + >>> aesocb = AESOCB3(key) + >>> nonce = os.urandom(12) + >>> ct = aesocb.encrypt(nonce, data, aad) + >>> aesocb.decrypt(nonce, ct, aad) + b'a secret message' + + .. classmethod:: generate_key(bit_length) + + Securely generates a random AES-OCB3 key. + + :param bit_length: The bit length of the key to generate. Must be + 128, 192, or 256. + + :returns bytes: The generated key. + + .. method:: encrypt(nonce, data, associated_data) + + .. warning:: + + Reuse of a ``nonce`` with a given ``key`` compromises the security + of any message with that ``nonce`` and ``key`` pair. + + Encrypts and authenticates the ``data`` provided as well as + authenticating the ``associated_data``. The output of this can be + passed directly to the ``decrypt`` method. + + :param nonce: A 12-15 byte value. **NEVER REUSE A NONCE** with a key. + :type nonce: :term:`bytes-like` + :param data: The data to encrypt. + :type data: :term:`bytes-like` + :param associated_data: Additional data that should be + authenticated with the key, but is not encrypted. Can be ``None``. + :type associated_data: :term:`bytes-like` + :returns bytes: The ciphertext bytes with the 16 byte tag appended. + :raises OverflowError: If ``data`` or ``associated_data`` is larger + than 2\ :sup:`31` - 1 bytes. + + .. method:: decrypt(nonce, data, associated_data) + + Decrypts the ``data`` and authenticates the ``associated_data``. If you + called encrypt with ``associated_data`` you must pass the same + ``associated_data`` in decrypt or the integrity check will fail. + + :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. + :type nonce: :term:`bytes-like` + :param data: The data to decrypt (with tag appended). + :type data: :term:`bytes-like` + :param associated_data: Additional data to authenticate. Can be + ``None`` if none was passed during encryption. + :type associated_data: :term:`bytes-like` + :returns bytes: The original plaintext. + :raises cryptography.exceptions.InvalidTag: If the authentication tag + doesn't validate this exception will be raised. This will occur + when the ciphertext has been changed, but will also occur when the + key, nonce, or associated data are wrong. + +.. class:: AESSIV(key) + + .. versionadded:: 37.0.0 + + The SIV (synthetic initialization vector) construction is defined in + :rfc:`5297`. Depending on how it is used, SIV allows either + deterministic authenticated encryption or nonce-based, + misuse-resistant authenticated encryption. + + :param key: A 256, 384, or 512-bit key (double sized from typical AES). + This **must** be kept secret. + :type key: :term:`bytes-like` + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of + OpenSSL does not support AES-SIV. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives.ciphers.aead import AESSIV + >>> data = b"a secret message" + >>> nonce = os.urandom(16) + >>> aad = [b"authenticated but unencrypted data", nonce] + >>> key = AESSIV.generate_key(bit_length=512) # AES256 requires 512-bit keys for SIV + >>> aessiv = AESSIV(key) + >>> ct = aessiv.encrypt(data, aad) + >>> aessiv.decrypt(ct, aad) + b'a secret message' + + .. classmethod:: generate_key(bit_length) + + Securely generates a random AES-SIV key. + + :param bit_length: The bit length of the key to generate. Must be + 256, 384, or 512. AES-SIV splits the key into an encryption and + MAC key, so these lengths correspond to AES 128, 192, and 256. + + :returns bytes: The generated key. + + .. method:: encrypt(data, associated_data) + + .. note:: + + SIV performs nonce-based authenticated encryption when a component of + the associated data is a nonce. The final associated data in the + list is used for the nonce. + + Random nonces should have at least 128-bits of entropy. If a nonce is + reused with SIV authenticity is retained and confidentiality is only + compromised to the extent that an attacker can determine that the + same plaintext (and same associated data) was protected with the same + nonce and key. + + If you do not supply a nonce encryption is deterministic and the same + (plaintext, key) pair will always produce the same ciphertext. + + Encrypts and authenticates the ``data`` provided as well as + authenticating the ``associated_data``. The output of this can be + passed directly to the ``decrypt`` method. + + :param data: The data to encrypt. + :type data: :term:`bytes-like` + :param list associated_data: An optional ``list`` of ``bytes-like objects``. This + is additional data that should be authenticated with the key, but + is not encrypted. Can be ``None``. In SIV mode the final element + of this list is treated as a ``nonce``. + :returns bytes: The ciphertext bytes with the 16 byte tag **prepended**. + :raises OverflowError: If ``data`` or an ``associated_data`` element + is larger than 2\ :sup:`31` - 1 bytes. + + .. method:: decrypt(data, associated_data) + + Decrypts the ``data`` and authenticates the ``associated_data``. If you + called encrypt with ``associated_data`` you must pass the same + ``associated_data`` in decrypt or the integrity check will fail. + + :param bytes data: The data to decrypt (with tag **prepended**). + :param list associated_data: An optional ``list`` of ``bytes-like objects``. This + is additional data that should be authenticated with the key, but + is not encrypted. Can be ``None`` if none was used during + encryption. + :returns bytes: The original plaintext. + :raises cryptography.exceptions.InvalidTag: If the authentication tag + doesn't validate this exception will be raised. This will occur + when the ciphertext has been changed, but will also occur when the + key or associated data are wrong. + .. class:: AESCCM(key, tag_length=16) .. versionadded:: 2.0 @@ -219,12 +390,14 @@ also support providing integrity for associated data which is not encrypted. ``len(data) < 2 ** (8 * (15 - len(nonce)))`` **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to encrypt. - :param bytes associated_data: Additional data that should be + :param data: The data to encrypt. + :type data: :term:`bytes-like` + :param associated_data: Additional data that should be authenticated with the key, but is not encrypted. Can be ``None``. + :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger - than 2\ :sup:`32` bytes. + than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) @@ -236,9 +409,11 @@ also support providing integrity for associated data which is not encrypted. is the same value used when you originally called encrypt. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to decrypt (with tag appended). - :param bytes associated_data: Additional data to authenticate. Can be + :param data: The data to decrypt (with tag appended). + :type data: :term:`bytes-like` + :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. + :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur diff --git a/docs/hazmat/primitives/asymmetric/dh.rst b/docs/hazmat/primitives/asymmetric/dh.rst index 6b47da089378..361aa6dff82b 100644 --- a/docs/hazmat/primitives/asymmetric/dh.rst +++ b/docs/hazmat/primitives/asymmetric/dh.rst @@ -115,27 +115,20 @@ peer:: peer_public_key = peer_public_numbers.public_key() -See also the :class:`~cryptography.hazmat.backends.interfaces.DHBackend` -API for additional functionality. - Group parameters ~~~~~~~~~~~~~~~~ -.. function:: generate_parameters(generator, key_size, backend=None) +.. function:: generate_parameters(generator, key_size) .. versionadded:: 1.7 - Generate a new DH parameter group for use with ``backend``. + Generate a new DH parameter group. :param generator: The :class:`int` to use as a generator. Must be 2 or 5. :param key_size: The bit length of the prime modulus to generate. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.DHBackend` - instance. - :returns: DH parameters as a new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. @@ -181,13 +174,6 @@ Group parameters :return bytes: Serialized parameters. -.. class:: DHParametersWithSerialization - - .. versionadded:: 1.7 - - Alias for :class:`DHParameters`. - - Key interfaces ~~~~~~~~~~~~~~ @@ -195,9 +181,6 @@ Key interfaces .. versionadded:: 1.7 - A DH private key that is not an :term:`opaque key` also implements - :class:`DHPrivateKeyWithSerialization` to provide serialization methods. - .. attribute:: key_size The bit length of the prime modulus. @@ -223,15 +206,6 @@ Key interfaces :return bytes: The agreed key. The bytes are ordered in 'big' endian. - -.. class:: DHPrivateKeyWithSerialization - - .. versionadded:: 1.7 - - This interface contains additional methods relating to serialization. - Any object with this interface also has all the methods from - :class:`DHPrivateKey`. - .. method:: private_numbers() Return the numbers that make up this private key. @@ -305,13 +279,6 @@ Key interfaces :return bytes: Serialized key. -.. class:: DHPublicKeyWithSerialization - - .. versionadded:: 1.7 - - Alias for :class:`DHPublicKey`. - - Numbers ~~~~~~~ @@ -341,13 +308,10 @@ Numbers p subgroup order value. - .. method:: parameters(backend=None) + .. method:: parameters() .. versionadded:: 1.7 - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DHBackend`. - :returns: A new instance of :class:`DHParameters`. .. class:: DHPrivateNumbers(x, public_numbers) @@ -369,13 +333,10 @@ Numbers The private value. - .. method:: private_key(backend=None) + .. method:: private_key() .. versionadded:: 1.7 - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DHBackend`. - :returns: A new instance of :class:`DHPrivateKey`. @@ -397,13 +358,10 @@ Numbers The public value. - .. method:: public_key(backend=None) + .. method:: public_key() .. versionadded:: 1.7 - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DHBackend`. - :returns: A new instance of :class:`DHPublicKey`. diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 788e4270b886..5df80149bb9b 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -17,7 +17,7 @@ DSA Generation ~~~~~~~~~~ -.. function:: generate_private_key(key_size, backend=None) +.. function:: generate_private_key(key_size) .. versionadded:: 0.5 @@ -34,17 +34,10 @@ Generation be either 1024, 2048, 3072, or 4096. For keys generated in 2015 this should be `at least 2048`_ (See page 41). - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. - :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if - the provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.DSABackend` - -.. function:: generate_parameters(key_size, backend=None) +.. function:: generate_parameters(key_size) .. versionadded:: 0.5 @@ -54,22 +47,15 @@ Generation continue to use DSA despite the wider cryptographic community's `ongoing protestations`_. - Generate DSA parameters using the provided ``backend``. + Generate DSA parameters. - :param int key_size: The length of :attr:`~DSAParameterNumbers.q`. It + :param int key_size: The length of :attr:`~DSAParameterNumbers.p`. It should be either 1024, 2048, 3072, or 4096. For keys generated in 2015 this should be `at least 2048`_ (See page 41). - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. - :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if - the provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.DSABackend` - Signing ~~~~~~~ @@ -181,10 +167,7 @@ Numbers The generator. - .. method:: parameters(backend=None) - - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + .. method:: parameters() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. @@ -208,10 +191,7 @@ Numbers The :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` associated with the public key. - .. method:: public_key(backend=None) - - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + .. method:: public_key() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. @@ -240,10 +220,7 @@ Numbers The :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` associated with the private key. - .. method:: private_key(backend=None) - - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + .. method:: private_key() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. @@ -267,13 +244,6 @@ Key interfaces :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. - -.. class:: DSAParametersWithNumbers - - .. versionadded:: 0.5 - - Extends :class:`DSAParameters`. - .. method:: parameter_numbers() Create a @@ -289,9 +259,7 @@ Key interfaces .. versionadded:: 0.3 - A `DSA`_ private key. A DSA private key that is not an - :term:`opaque key` also implements :class:`DSAPrivateKeyWithSerialization` - to provide serialization methods. + A `DSA`_ private key. .. method:: public_key() @@ -330,15 +298,6 @@ Key interfaces :return bytes: Signature. - -.. class:: DSAPrivateKeyWithSerialization - - .. versionadded:: 0.8 - - This interface contains additional methods relating to serialization. - Any object with this interface also has all the methods from - :class:`DSAPrivateKey`. - .. method:: private_numbers() Create a @@ -356,7 +315,6 @@ Key interfaces :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`), format ( :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL`, - :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.OpenSSH` or :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`) and encryption algorithm (such as @@ -442,17 +400,11 @@ Key interfaces :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` if the ``data`` you want to sign has already been hashed. + :returns: None :raises cryptography.exceptions.InvalidSignature: If the signature does not validate. -.. class:: DSAPublicKeyWithSerialization - - .. versionadded:: 0.8 - - Alias for :class:`DSAPublicKey`. - - .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography .. _`FIPS 186-4`: https://csrc.nist.gov/publications/detail/fips/186/4/final diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 5691560f3c76..5842e9ca1667 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -6,34 +6,27 @@ Elliptic curve cryptography .. module:: cryptography.hazmat.primitives.asymmetric.ec -.. function:: generate_private_key(curve, backend=None) +.. function:: generate_private_key(curve) .. versionadded:: 0.5 - Generate a new private key on ``curve`` for use with ``backend``. + Generate a new private key on ``curve``. :param curve: An instance of :class:`EllipticCurve`. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. - :returns: A new instance of :class:`EllipticCurvePrivateKey`. -.. function:: derive_private_key(private_value, curve, backend=None) +.. function:: derive_private_key(private_value, curve) .. versionadded:: 1.6 - Derive a private key from ``private_value`` on ``curve`` for use with - ``backend``. + Derive a private key from ``private_value`` on ``curve``. :param int private_value: The secret scalar value. :param curve: An instance of :class:`EllipticCurve`. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. - :returns: A new instance of :class:`EllipticCurvePrivateKey`. @@ -147,14 +140,11 @@ Elliptic Curve Signature Algorithms The private value. - .. method:: private_key(backend=None) + .. method:: private_key() Convert a collection of numbers into a private key suitable for doing actual cryptographic operations. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. - :returns: A new instance of :class:`EllipticCurvePrivateKey`. @@ -189,33 +179,14 @@ Elliptic Curve Signature Algorithms The affine y component of the public point used for verifying. - .. method:: public_key(backend=None) + .. method:: public_key() Convert a collection of numbers into a public key suitable for doing actual cryptographic operations. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. - :raises ValueError: Raised if the point is invalid for the curve. :returns: A new instance of :class:`EllipticCurvePublicKey`. - .. method:: encode_point() - - .. warning:: - - This method is deprecated as of version 2.5. Callers should migrate - to using - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes`. - - .. versionadded:: 1.1 - - Encodes an elliptic curve point to a byte string as described in - `SEC 1 v2.0`_ section 2.3.3. This method only supports uncompressed - points. - - :return bytes: The encoded point. - .. classmethod:: from_encoded_point(curve, data) .. versionadded:: 1.1 @@ -563,11 +534,7 @@ Key Interfaces .. versionadded:: 0.5 - An elliptic curve private key for use with an algorithm such as `ECDSA`_ or - `EdDSA`_. An elliptic curve private key that is not an - :term:`opaque key` also implements - :class:`EllipticCurvePrivateKeyWithSerialization` to provide serialization - methods. + An elliptic curve private key for use with an algorithm such as `ECDSA`_. .. method:: exchange(algorithm, peer_public_key) @@ -627,15 +594,6 @@ Key Interfaces Size (in :term:`bits`) of a secret scalar for the curve (as generated by :func:`generate_private_key`). - -.. class:: EllipticCurvePrivateKeyWithSerialization - - .. versionadded:: 0.8 - - This interface contains additional methods relating to serialization. - Any object with this interface also has all the methods from - :class:`EllipticCurvePrivateKey`. - .. method:: private_numbers() Create a :class:`EllipticCurvePrivateNumbers` object. @@ -730,6 +688,7 @@ Key Interfaces :param signature_algorithm: An instance of :class:`EllipticCurveSignatureAlgorithm`. + :returns: None :raises cryptography.exceptions.InvalidSignature: If the signature does not validate. @@ -764,13 +723,6 @@ Key Interfaces :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. -.. class:: EllipticCurvePublicKeyWithSerialization - - .. versionadded:: 0.6 - - Alias for :class:`EllipticCurvePublicKey`. - - Serialization ~~~~~~~~~~~~~ @@ -967,7 +919,6 @@ Elliptic Curve Object Identifiers .. _`minimize the number of security concerns for elliptic-curve cryptography`: https://cr.yp.to/ecdh/curve25519-20060209.pdf .. _`SafeCurves`: https://safecurves.cr.yp.to/ .. _`ECDSA`: https://en.wikipedia.org/wiki/ECDSA -.. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA .. _`forward secrecy`: https://en.wikipedia.org/wiki/Forward_secrecy .. _`SEC 1 v2.0`: https://www.secg.org/sec1-v2.pdf .. _`bad cryptographic practice`: https://crypto.stackexchange.com/a/3313 diff --git a/docs/hazmat/primitives/asymmetric/ed25519.rst b/docs/hazmat/primitives/asymmetric/ed25519.rst index 47d95ec1b9da..1ca06fc1b9f2 100644 --- a/docs/hazmat/primitives/asymmetric/ed25519.rst +++ b/docs/hazmat/primitives/asymmetric/ed25519.rst @@ -43,6 +43,11 @@ Key interfaces :returns: :class:`Ed25519PrivateKey` + :raises ValueError: This is raised if the private key is not 32 bytes long. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If Ed25519 is not + supported by the OpenSSL version ``cryptography`` is using. + .. doctest:: >>> from cryptography.hazmat.primitives import serialization @@ -98,6 +103,20 @@ Key interfaces :return bytes: Serialized key. + .. method:: private_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`private_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding, + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` + format, and + :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. + + :return bytes: Raw key. + .. class:: Ed25519PublicKey .. versionadded:: 2.6 @@ -108,6 +127,11 @@ Key interfaces :returns: :class:`Ed25519PublicKey` + :raises ValueError: This is raised if the public key is not 32 bytes long. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If Ed25519 is not + supported by the OpenSSL version ``cryptography`` is using. + .. doctest:: >>> from cryptography.hazmat.primitives import serialization @@ -153,12 +177,26 @@ Key interfaces :returns bytes: The public key bytes. + .. method:: public_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`public_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding and + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` + format. + + :return bytes: Raw key. + .. method:: verify(signature, data) :param bytes signature: The signature to verify. :param bytes data: The data to verify. + :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the signature cannot be verified. diff --git a/docs/hazmat/primitives/asymmetric/ed448.rst b/docs/hazmat/primitives/asymmetric/ed448.rst index fb79dcb61ba3..efe245d568e9 100644 --- a/docs/hazmat/primitives/asymmetric/ed448.rst +++ b/docs/hazmat/primitives/asymmetric/ed448.rst @@ -81,6 +81,20 @@ Key interfaces :return bytes: Serialized key. + .. method:: private_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`private_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding, + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` + format, and + :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. + + :return bytes: Raw key. + .. class:: Ed448PublicKey .. versionadded:: 2.6 @@ -117,12 +131,26 @@ Key interfaces :returns bytes: The public key bytes. + .. method:: public_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`public_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding and + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` + format. + + :return bytes: Raw key. + .. method:: verify(signature, data) :param bytes signature: The signature to verify. :param bytes data: The data to verify. + :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the signature cannot be verified. diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst index c27e1781e46e..136dd324b57e 100644 --- a/docs/hazmat/primitives/asymmetric/index.rst +++ b/docs/hazmat/primitives/asymmetric/index.rst @@ -36,3 +36,83 @@ private key is able to decrypt it. .. _`proof of identity`: https://en.wikipedia.org/wiki/Public-key_infrastructure + +Common types +~~~~~~~~~~~~ + +Asymmetric key types do not inherit from a common base class. The following +union type aliases can be used instead to reference a multitude of key types. + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.types + +.. data:: PublicKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of all public key types supported: + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. + +.. data:: PrivateKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of all private key types supported: + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`. + +.. data:: CertificatePublicKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of all public key types supported for X.509 + certificates: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. + +.. data:: CertificateIssuerPublicKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of all public key types that can sign other X.509 + certificates as an issuer. x448/x25519 can be a public key, but cannot be + used in signing, so they are not allowed in these contexts. + + Allowed: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + +.. data:: CertificateIssuerPrivateKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of all private key types that can sign other X.509 + certificates as an issuer. x448/x25519 can be a public key, but cannot be + used in signing, so they are not allowed in these contexts. + + Allowed: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`. diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index b8060e4740fd..23401f52793a 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -14,7 +14,7 @@ Unlike symmetric cryptography, where the key is typically just a random series of bytes, RSA keys have a complex internal structure with `specific mathematical properties`_. -.. function:: generate_private_key(public_exponent, key_size, backend=None) +.. function:: generate_private_key(public_exponent, key_size) .. versionadded:: 0.5 @@ -22,7 +22,7 @@ mathematical properties`_. Tightened restrictions on ``public_exponent``. - Generates a new RSA private key using the provided ``backend``. + Generates a new RSA private key. ``key_size`` describes how many :term:`bits` long the key should be. Larger keys provide more security; currently ``1024`` and below are considered breakable while ``2048`` or ``4096`` are reasonable default key sizes for @@ -45,18 +45,10 @@ mathematical properties`_. :param int key_size: The length of the modulus in :term:`bits`. For keys generated in 2015 it is strongly recommended to be `at least 2048`_ (See page 41). It must not be less than 512. - Some backends may have additional limitations. - - :param backend: An optional backend which implements - :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if - the provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.RSABackend` - Key loading ~~~~~~~~~~~ @@ -85,10 +77,8 @@ There is also support for :func:`loading public keys in the SSH format Key serialization ~~~~~~~~~~~~~~~~~ -If you have a private key that you've loaded or generated which implements the -:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` -interface you can use -:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes` +If you have a private key that you've loaded you can use +:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.private_bytes` to serialize the key. .. doctest:: @@ -305,13 +295,28 @@ Padding supported MGF is :class:`MGF1`. :param int salt_length: The length of the salt. It is recommended that this - be set to ``PSS.MAX_LENGTH``. + be set to ``PSS.DIGEST_LENGTH`` or ``PSS.MAX_LENGTH``. .. attribute:: MAX_LENGTH Pass this attribute to ``salt_length`` to get the maximum salt length available. + .. attribute:: DIGEST_LENGTH + + .. versionadded:: 37.0.0 + + Pass this attribute to ``salt_length`` to set the salt length to the + byte length of the digest passed when calling ``sign``. Note that this + is **not** the length of the digest passed to ``MGF1``. + + .. attribute:: AUTO + + .. versionadded:: 37.0.0 + + Pass this attribute to ``salt_length`` to automatically determine the + salt length when verifying. Raises ``ValueError`` if used when signing. + .. class:: OAEP(mgf, algorithm, label) .. versionadded:: 0.4 @@ -342,6 +347,11 @@ Padding :class:`OAEP` should be preferred for encryption and :class:`PSS` should be preferred for signatures. + .. warning:: + + Our implementation of PKCS1 v1.5 decryption is not constant time. See + :doc:`/limitations` for details. + .. function:: calculate_max_pss_salt_length(key, hash_algorithm) @@ -399,10 +409,7 @@ is unavailable. The public exponent. - .. method:: public_key(backend=None) - - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. + .. method:: public_key() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. @@ -466,10 +473,21 @@ is unavailable. A `Chinese remainder theorem`_ coefficient used to speed up RSA operations. Calculated as: q\ :sup:`-1` mod p - .. method:: private_key(backend=None) + .. method:: private_key(*, unsafe_skip_rsa_key_validation=False) + + :param unsafe_skip_rsa_key_validation: - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. + .. versionadded:: 39.0.0 + + A keyword-only argument that defaults to ``False``. If ``True`` + RSA private keys will not be validated. This significantly speeds up + loading the keys, but is :term:`unsafe` unless you are certain + the key is valid. User supplied keys should never be loaded with + this parameter set to ``True``. If you do load an invalid key this + way and attempt to use it OpenSSL may hang, crash, or otherwise + misbehave. + + :type unsafe_skip_rsa_key_validation: bool :returns: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. @@ -531,14 +549,17 @@ Key interfaces .. versionadded:: 0.2 - An `RSA`_ private key. An RSA private key that is not an - :term:`opaque key` also implements :class:`RSAPrivateKeyWithSerialization` - to provide serialization methods. + An `RSA`_ private key. .. method:: decrypt(ciphertext, padding) .. versionadded:: 0.4 + .. warning:: + + Our implementation of PKCS1 v1.5 decryption is not constant time. See + :doc:`/limitations` for details. + Decrypt data that was encrypted with the public key. :param bytes ciphertext: The ciphertext to decrypt. @@ -582,15 +603,6 @@ Key interfaces :return bytes: Signature. - -.. class:: RSAPrivateKeyWithSerialization - - .. versionadded:: 0.8 - - This interface contains additional methods relating to serialization. - Any object with this interface also has all the methods from - :class:`RSAPrivateKey`. - .. method:: private_numbers() Create a @@ -649,6 +661,10 @@ Key interfaces :return bytes: Encrypted data. + :raises ValueError: The data could not be encrypted. One possible cause + is if ``data`` is too large; RSA keys can only encrypt data that + is smaller than the key size. + .. attribute:: key_size :type: int @@ -706,16 +722,59 @@ Key interfaces :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` if the ``data`` you want to verify has already been hashed. + :returns: None :raises cryptography.exceptions.InvalidSignature: If the signature does not validate. + .. method:: recover_data_from_signature(signature, padding, algorithm) -.. class:: RSAPublicKeyWithSerialization + .. versionadded:: 3.3 - .. versionadded:: 0.8 + Recovers the signed data from the signature. The data typically contains + the digest of the original message string. The ``padding`` and + ``algorithm`` parameters must match the ones used when the signature + was created for the recovery to succeed. + + The ``algorithm`` parameter can also be set to ``None`` to recover all + the data present in the signature, without regard to its format or the + hash algorithm used for its creation. + + For + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` + padding, this method returns the data after removing the padding layer. + For standard signatures the data contains the full ``DigestInfo`` + structure. For non-standard signatures, any data can be returned, + including zero-length data. + + Normally you should use the + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.verify` + function to validate the signature. But for some non-standard signature + formats you may need to explicitly recover and validate the signed + data. The following are some examples: + + - Some old Thawte and Verisign timestamp certificates without ``DigestInfo``. + - Signed MD5/SHA1 hashes in TLS 1.1 or earlier (:rfc:`4346`, section 4.7). + - IKE version 1 signatures without ``DigestInfo`` (:rfc:`2409`, section 5.1). + + :param bytes signature: The signature. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + Recovery is only supported with some of the padding types. (Currently + only with + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`). + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + Can be ``None`` to return the all the data present in the signature. + + :return bytes: The signed data. - Alias for :class:`RSAPublicKey`. + :raises cryptography.exceptions.InvalidSignature: If the signature is + invalid. + :raises cryptography.exceptions.UnsupportedAlgorithm: If signature + data recovery is not supported with the provided ``padding`` type. .. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem) .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography @@ -726,4 +785,4 @@ Key interfaces .. _`Chinese Remainder Theorem`: https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29#Using_the_Chinese_remainder_algorithm .. _`security proof`: https://eprint.iacr.org/2001/062.pdf .. _`recommended padding algorithm`: https://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html -.. _`proven secure`: https://cseweb.ucsd.edu/~mihir/papers/oae.pdf +.. _`proven secure`: https://cseweb.ucsd.edu/~mihir/papers/oaep.pdf diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index 6b2e858db9af..c60accca6b40 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -125,10 +125,14 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END extract the public key with :meth:`Certificate.public_key `. -.. function:: load_pem_private_key(data, password, backend=None) +.. function:: load_pem_private_key(data, password, *, unsafe_skip_rsa_key_validation=False) .. versionadded:: 0.6 + .. note:: + SSH private keys are a different format and must be loaded with + :func:`load_ssh_private_key`. + Deserialize a private key from PEM encoded data to one of the supported asymmetric private key types. @@ -137,12 +141,26 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END :param password: The password to use to decrypt the data. Should be ``None`` if the private key is not encrypted. - :type data: :term:`bytes-like` + :type password: :term:`bytes-like` + + :param unsafe_skip_rsa_key_validation: - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`. + .. versionadded:: 39.0.0 + + A keyword-only argument that defaults to ``False``. If ``True`` + RSA private keys will not be validated. This significantly speeds up + loading the keys, but is :term:`unsafe` unless you are certain the + key is valid. User supplied keys should never be loaded with this + parameter set to ``True``. If you do load an invalid key this way and + attempt to use it OpenSSL may hang, crash, or otherwise misbehave. + + :type unsafe_skip_rsa_key_validation: bool :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, @@ -158,10 +176,9 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END password was supplied. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key - is of a type that is not supported by the backend or if the key is - encrypted with a symmetric cipher that is not supported by the backend. + type is not supported by the OpenSSL version ``cryptography`` is using. -.. function:: load_pem_public_key(data, backend=None) +.. function:: load_pem_public_key(data) .. versionadded:: 0.6 @@ -178,11 +195,11 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END :param bytes data: The PEM encoded key data. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`. - - :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, @@ -194,10 +211,9 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END successfully. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key - is of a type that is not supported by the backend. - -.. function:: load_pem_parameters(data, backend=None) + type is not supported by the OpenSSL version ``cryptography`` is using. +.. function:: load_pem_parameters(data) .. versionadded:: 2.0 Deserialize parameters from PEM encoded data to one of the supported @@ -213,10 +229,6 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END :param bytes data: The PEM encoded parameters data. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`. - - :returns: Currently only :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters` supported. @@ -224,8 +236,8 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END :raises ValueError: If the PEM data's structure could not be decoded successfully. - :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized parameters - is of a type that is not supported by the backend. + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key + type is not supported by the OpenSSL version ``cryptography`` is using. DER ~~~ @@ -235,7 +247,7 @@ data is binary. DER keys may be in a variety of formats, but as long as you know whether it is a public or private key the loading functions will handle the rest. -.. function:: load_der_private_key(data, password, backend=None) +.. function:: load_der_private_key(data, password, *, unsafe_skip_rsa_key_validation=False) .. versionadded:: 0.8 @@ -249,10 +261,24 @@ the rest. be ``None`` if the private key is not encrypted. :type password: :term:`bytes-like` - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`. + :param unsafe_skip_rsa_key_validation: + + .. versionadded:: 39.0.0 + + A keyword-only argument that defaults to ``False``. If ``True`` + RSA private keys will not be validated. This significantly speeds up + loading the keys, but is :term:`unsafe` unless you are certain the + key is valid. User supplied keys should never be loaded with this + parameter set to ``True``. If you do load an invalid key this way and + attempt to use it OpenSSL may hang, crash, or otherwise misbehave. + + :type unsafe_skip_rsa_key_validation: bool :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, @@ -267,9 +293,8 @@ the rest. not encrypted. Or if the key was encrypted but no password was supplied. - :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that - is not supported by the backend or if the key is encrypted with a - symmetric cipher that is not supported by the backend. + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key + type is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: @@ -279,7 +304,7 @@ the rest. >>> isinstance(key, rsa.RSAPrivateKey) True -.. function:: load_der_public_key(data, backend=None) +.. function:: load_der_public_key(data) .. versionadded:: 0.8 @@ -289,10 +314,11 @@ the rest. :param bytes data: The DER encoded key data. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`. - :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, @@ -303,8 +329,8 @@ the rest. :raises ValueError: If the DER data's structure could not be decoded successfully. - :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that - is not supported by the backend. + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key + type is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: @@ -314,7 +340,7 @@ the rest. >>> isinstance(key, rsa.RSAPublicKey) True -.. function:: load_der_parameters(data, backend=None) +.. function:: load_der_parameters(data) .. versionadded:: 2.0 @@ -323,9 +349,6 @@ the rest. :param bytes data: The DER encoded parameters data. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`. - :returns: Currently only :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters` supported. @@ -333,8 +356,8 @@ the rest. :raises ValueError: If the DER data's structure could not be decoded successfully. - :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that - is not supported by the backend. + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key + type is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: @@ -365,30 +388,37 @@ DSA keys look almost identical but begin with ``ssh-dss`` rather than ``ssh-rsa``. ECDSA keys have a slightly different format, they begin with ``ecdsa-sha2-{curve}``. -.. function:: load_ssh_public_key(data, backend=None) + +.. data:: SSHPublicKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of public key types accepted for SSH: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + , or + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`. + + +.. function:: load_ssh_public_key(data) .. versionadded:: 0.7 + .. note:: + + SSH DSA key support is deprecated and will be removed in a future + release. + Deserialize a public key from OpenSSH (:rfc:`4253` and `PROTOCOL.certkeys`_) encoded data to an - instance of the public key type for the specified backend. + instance of the public key type. :param data: The OpenSSH encoded key data. :type data: :term:`bytes-like` - :param backend: An optional backend which implements - :class:`~cryptography.hazmat.backends.interfaces.RSABackend`, - :class:`~cryptography.hazmat.backends.interfaces.DSABackend`, or - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend` - depending on the key's type. - - :returns: One of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` - , or - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, - depending on the contents of ``data``. + :returns: One of :data:`SSHPublicKeyTypes` depending on the contents of + ``data``. :raises ValueError: If the OpenSSH data could not be properly decoded or if the key is not in the proper format. @@ -414,12 +444,29 @@ An example ECDSA key in OpenSSH format:: BAUGBw== -----END OPENSSH PRIVATE KEY----- -.. function:: load_ssh_private_key(data, password, backend=None) +.. data:: SSHPrivateKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of private key types accepted for SSH: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + or + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`. + + +.. function:: load_ssh_private_key(data, password) .. versionadded:: 3.0 + .. note:: + + SSH DSA key support is deprecated and will be removed in a future + release. + Deserialize a private key from OpenSSH encoded data to an - instance of the private key type for the specified backend. + instance of the private key type. :param data: The PEM encoded OpenSSH private key data. :type data: :term:`bytes-like` @@ -427,19 +474,8 @@ An example ECDSA key in OpenSSH format:: :param bytes password: Password bytes to use to decrypt password-protected key. Or ``None`` if not needed. - :param backend: An optional backend which implements - :class:`~cryptography.hazmat.backends.interfaces.RSABackend`, - :class:`~cryptography.hazmat.backends.interfaces.DSABackend`, or - :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend` - depending on the key's type. - - :returns: One of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` - or - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, - depending on the contents of ``data``. + :returns: One of :data:`SSHPrivateKeyTypes` depending on the contents of + ``data``. :raises ValueError: If the OpenSSH data could not be properly decoded, if the key is not in the proper format or the incorrect password @@ -448,6 +484,303 @@ An example ECDSA key in OpenSSH format:: :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that is not supported. + +OpenSSH Certificate +~~~~~~~~~~~~~~~~~~~ + +The format used by OpenSSH for certificates, as specified in +`PROTOCOL.certkeys`_. + +.. data:: SSHCertPublicKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of public key types supported for SSH + certificates: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + or + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` + +.. data:: SSHCertPrivateKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of private key types supported for SSH + certificates: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + or + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` + +.. function:: load_ssh_public_identity(data) + + .. versionadded:: 40.0.0 + + .. note:: + + This function does not support parsing certificates with DSA public + keys or signatures from DSA certificate authorities. DSA is a + deprecated algorithm and should not be used. + + Deserialize an OpenSSH encoded identity to an instance of + :class:`SSHCertificate` or the appropriate public key type. + Parsing a certificate does not verify anything. It is up to the caller to + perform any necessary verification. + + :param data: The OpenSSH encoded data. + :type data: bytes + + :returns: :class:`SSHCertificate` or one of :data:`SSHCertPublicKeyTypes`. + + :raises ValueError: If the OpenSSH data could not be properly decoded. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the data contains + a public key type that is not supported. + + +.. class:: SSHCertificate + + .. versionadded:: 40.0.0 + + .. attribute:: nonce + + :type: bytes + + The nonce field is a CA-provided random value of arbitrary length + (but typically 16 or 32 bytes) included to make attacks that depend on + inducing collisions in the signature hash infeasible. + + .. method:: public_key() + + The public key contained in the certificate, one of + :data:`SSHCertPublicKeyTypes`. + + .. attribute:: serial + + :type: int + + Serial is an optional certificate serial number set by the CA to + provide an abbreviated way to refer to certificates from that CA. + If a CA does not wish to number its certificates, it must set this + field to zero. + + .. attribute:: type + + :type: :class:`SSHCertificateType` + + Type specifies whether this certificate is for identification of a user + or a host. + + .. attribute:: key_id + + :type: bytes + + This is a free-form text field that is filled in by the CA at the time + of signing; the intention is that the contents of this field are used to + identify the identity principal in log messages. + + .. attribute:: valid_principals + + :type: list[bytes] + + "valid principals" is a list containing one or more principals as + byte strings. These principals list the names for which this + certificate is valid; hostnames for host certificates and + usernames for user certificates. As a special case, an + empty list means the certificate is valid for any principal of + the specified type. + + .. attribute:: valid_after + + :type: int + + An integer representing the Unix timestamp (in UTC) after which the + certificate is valid. **This time is inclusive.** + + .. attribute:: valid_before + + :type: int + + An integer representing the Unix timestamp (in UTC) before which the + certificate is valid. **This time is not inclusive.** + + .. attribute:: critical_options + + :type: dict[bytes, bytes] + + Critical options is a dict of zero or more options that are + critical for the certificate to be considered valid. If + any of these options are not supported by the implementation, the + certificate must be rejected. + + .. attribute:: extensions + + :type: dict[bytes, bytes] + + Extensions is a dict of zero or more options that are + non-critical for the certificate to be considered valid. If any of + these options are not supported by the implementation, the + implementation may safely ignore them. + + .. method:: signature_key() + + The public key used to sign the certificate, one of + :data:`SSHCertPublicKeyTypes`. + + .. method:: verify_cert_signature() + + .. warning:: + + This method does not validate anything about whether the + signing key is trusted! Callers are responsible for validating + trust in the signer. + + Validates that the signature on the certificate was created by + the private key associated with the certificate's signature key + and that the certificate has not been changed since signing. + + :return: None + :raises: :class:`~cryptography.exceptions.InvalidSignature` if the + signature is invalid. + + .. method:: public_bytes() + + :return: The serialized certificate in OpenSSH format. + :rtype: bytes + + +.. class:: SSHCertificateType + + .. versionadded:: 40.0.0 + + An enumeration of the types of SSH certificates. + + .. attribute:: USER + + The cert is intended for identification of a user. Corresponds to the + value ``1``. + + .. attribute:: HOST + + The cert is intended for identification of a host. Corresponds to the + value ``2``. + +SSH Certificate Builder +~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: SSHCertificateBuilder + + .. versionadded:: 40.0.0 + + .. note:: + + This builder does not support generating certificates with DSA public + keys or creating signatures with DSA certificate authorities. DSA is a + deprecated algorithm and should not be used. + + .. doctest:: + + >>> import datetime + >>> from cryptography.hazmat.primitives.asymmetric import ec + >>> from cryptography.hazmat.primitives.serialization import ( + ... SSHCertificateType, SSHCertificateBuilder + ... ) + >>> signing_key = ec.generate_private_key(ec.SECP256R1()) + >>> public_key = ec.generate_private_key(ec.SECP256R1()).public_key() + >>> valid_after = datetime.datetime( + ... 2023, 1, 1, 1, tzinfo=datetime.timezone.utc + ... ).timestamp() + >>> valid_before = datetime.datetime( + ... 2023, 7, 1, 1, tzinfo=datetime.timezone.utc + ... ).timestamp() + >>> key_id = b"a_key_id" + >>> valid_principals = [b"eve", b"alice"] + >>> builder = ( + ... SSHCertificateBuilder() + ... .public_key(public_key) + ... .type(SSHCertificateType.USER) + ... .valid_before(valid_before) + ... .valid_after(valid_after) + ... .key_id(b"a_key_id") + ... .valid_principals(valid_principals) + ... .add_extension(b"no-touch-required", b"") + ... ) + >>> builder.sign(signing_key).public_bytes() + b'...' + + .. method:: public_key(public_key) + + :param public_key: The public key to be included in the certificate. + This value is required. + :type public_key: :data:`SSHCertPublicKeyTypes` + + .. method:: serial(serial) + + :param int serial: The serial number to be included in the certificate. + This is not a required value and will be set to zero if not + provided. Value must be between 0 and 2:sup:`64` - 1, inclusive. + + .. method:: type(type) + + :param type: The type of the certificate. There are two options, + user or host. + :type type: :class:`SSHCertificateType` + + .. method:: key_id(key_id) + + :param key_id: The key ID to be included in the certificate. This is + not a required value. + :type key_id: bytes + + .. method:: valid_principals(valid_principals) + + :param valid_principals: A list of principals that the certificate is + valid for. This is a required value unless + :meth:`valid_for_all_principals` has been called. + :type valid_principals: list[bytes] + + .. method:: valid_for_all_principals() + + Marks the certificate as valid for all principals. This cannot be + set if principals have been added via :meth:`valid_principals`. + + .. method:: valid_after(valid_after) + + :param int valid_after: The Unix timestamp (in UTC) that marks the + activation time for the certificate. This is a required value. + + .. method:: valid_before(valid_before) + + :param int valid_before: The Unix timestamp (in UTC) that marks the + expiration time for the certificate. This is a required value. + + .. method:: add_critical_option(name, value) + + :param name: The name of the critical option to add. No duplicates + are allowed. + :type name: bytes + :param value: The value of the critical option to add. This is + commonly an empty byte string. + :type value: bytes + + .. method:: add_extension(name, value) + + :param name: The name of the extension to add. No duplicates are + allowed. + :type name: bytes + :param value: The value of the extension to add. + :type value: bytes + + .. method:: sign(private_key) + + :param private_key: The private key that will be used to sign the + certificate. + :type private_key: :data:`SSHCertPrivateKeyTypes` + + :return: The signed certificate. + :rtype: :class:`SSHCertificate` + PKCS12 ~~~~~~ @@ -462,7 +795,24 @@ file suffix. ``cryptography`` only supports a single private key and associated certificates when parsing PKCS12 files at this time. -.. function:: load_key_and_certificates(data, password, backend=None) + +.. data:: PKCS12PrivateKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of private key types supported for PKCS12 + serialization: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` + or + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. + +.. function:: load_key_and_certificates(data, password) .. versionadded:: 2.5 @@ -475,8 +825,6 @@ file suffix. if the PKCS12 is not encrypted. :type password: :term:`bytes-like` - :param backend: An optional backend instance. - :returns: A tuple of ``(private_key, certificate, additional_certificates)``. ``private_key`` is a private key type or ``None``, ``certificate`` @@ -485,45 +833,165 @@ file suffix. ``additional_certificates`` is a list of all other :class:`~cryptography.x509.Certificate` instances in the PKCS12 object. +.. function:: load_pkcs12(data, password) + + .. versionadded:: 36.0.0 + + Deserialize a PKCS12 blob, and return a + :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates` + instance. + + :param data: The binary data. + :type data: :term:`bytes-like` + + :param password: The password to use to decrypt the data. ``None`` + if the PKCS12 is not encrypted. + :type password: :term:`bytes-like` + + :returns: A + :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates` + instance. + .. function:: serialize_key_and_certificates(name, key, cert, cas, encryption_algorithm) .. versionadded:: 3.0 + .. note:: + With OpenSSL 3.0.0+ the defaults for encryption when serializing PKCS12 + have changed and some versions of Windows and macOS will not be able to + read the new format. Maximum compatibility can be achieved by using + ``SHA1`` for MAC algorithm and + :attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC` + for encryption algorithm as seen in the example below. However, users + should avoid this unless required for compatibility. + .. warning:: - PKCS12 encryption is not secure and should not be used as a security - mechanism. Wrap a PKCS12 blob in a more secure envelope if you need - to store or send it safely. Encryption is provided for compatibility - reasons only. + PKCS12 encryption is typically not secure and should not be used as a + security mechanism. Wrap a PKCS12 blob in a more secure envelope if you + need to store or send it safely. Serialize a PKCS12 blob. + .. note:: + + Due to `a bug in Firefox`_ it's not possible to load unencrypted PKCS12 + blobs in Firefox. + :param name: The friendly name to use for the supplied certificate and key. :type name: bytes :param key: The private key to include in the structure. - :type key: An - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` - , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - , or - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` - object. + :type key: :data:`PKCS12PrivateKeyTypes` :param cert: The certificate associated with the private key. :type cert: :class:`~cryptography.x509.Certificate` or ``None`` :param cas: An optional set of certificates to also include in the structure. - :type cas: list of :class:`~cryptography.x509.Certificate` or ``None`` + If a :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` + is given, its friendly name will be serialized. + :type cas: ``None``, or list of + :class:`~cryptography.x509.Certificate` + or + :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` :param encryption_algorithm: The encryption algorithm that should be used for the key and certificate. An instance of an object conforming to the :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` - interface. PKCS12 encryption is **very weak** and should not be used - as a security boundary. + interface. PKCS12 encryption is typically **very weak** and should not + be used as a security boundary. :return bytes: Serialized PKCS12. + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, load_pem_private_key, pkcs12 + >>> cert = x509.load_pem_x509_certificate(ca_cert) + >>> key = load_pem_private_key(ca_key, None) + >>> p12 = pkcs12.serialize_key_and_certificates( + ... b"friendlyname", key, cert, None, BestAvailableEncryption(b"password") + ... ) + + This example uses an ``encryption_builder()`` to create a PKCS12 with more + compatible, but substantially less secure, encryption. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.serialization import PrivateFormat, load_pem_private_key, pkcs12 + >>> encryption = ( + ... PrivateFormat.PKCS12.encryption_builder(). + ... kdf_rounds(50000). + ... key_cert_algorithm(pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC). + ... hmac_hash(hashes.SHA1()).build(b"my password") + ... ) + >>> cert = x509.load_pem_x509_certificate(ca_cert) + >>> key = load_pem_private_key(ca_key, None) + >>> p12 = pkcs12.serialize_key_and_certificates( + ... b"friendlyname", key, cert, None, encryption + ... ) + +.. class:: PKCS12Certificate + + .. versionadded:: 36.0.0 + + Represents additional data provided for a certificate in a PKCS12 file. + + .. attribute:: certificate + + A :class:`~cryptography.x509.Certificate` instance. + + .. attribute:: friendly_name + + :type: bytes or None + + An optional byte string containing the friendly name of the certificate. + +.. class:: PKCS12KeyAndCertificates + + .. versionadded:: 36.0.0 + + A simplified representation of a PKCS12 file. + + .. attribute:: key + + An optional private key belonging to + :attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates.cert` + (see :data:`PKCS12PrivateKeyTypes`). + + .. attribute:: cert + + An optional + :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` + instance belonging to the private key + :attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates.key`. + + .. attribute:: additional_certs + + A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` + instances. + +.. class:: PBES + :canonical: cryptography.hazmat.primitives._serialization.PBES + + .. versionadded:: 38.0.0 + + An enumeration of password-based encryption schemes used in PKCS12. These + values are used with + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryptionBuilder`. + + .. attribute:: PBESv1SHA1And3KeyTripleDESCBC + + PBESv1 using SHA1 as the KDF PRF and 3-key triple DES-CBC as the cipher. + + .. attribute:: PBESv2SHA256AndAES256CBC + + PBESv2 using SHA256 as the KDF PRF and AES256-CBC as the cipher. This + is only supported on OpenSSL 3.0.0 or newer. + + PKCS7 ~~~~~ @@ -538,6 +1006,25 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, ``cryptography`` only supports parsing certificates from PKCS7 files at this time. +.. data:: PKCS7HashTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of hash types supported for PKCS7 serialization: + :class:`~cryptography.hazmat.primitives.hashes.SHA1`, + :class:`~cryptography.hazmat.primitives.hashes.SHA224`, + :class:`~cryptography.hazmat.primitives.hashes.SHA256`, + :class:`~cryptography.hazmat.primitives.hashes.SHA384`, or + :class:`~cryptography.hazmat.primitives.hashes.SHA512`. + +.. data:: PKCS7PrivateKeyTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of private key types supported for PKCS7 serialization: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + .. function:: load_pem_pkcs7_certificates(data) .. versionadded:: 3.1 @@ -574,6 +1061,17 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :raises cryptography.exceptions.UnsupportedAlgorithm: If the PKCS7 data is of a type that is not supported. +.. function:: serialize_certificates(certs, encoding) + + .. versionadded:: 37.0.0 + + Serialize a list of certificates to a PKCS7 structure. + + :param certs: A list of :class:`~cryptography.x509.Certificate`. + :param encoding: :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` + or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`. + :returns bytes: The serialized PKCS7 data. + .. testsetup:: ca_key = b""" @@ -636,16 +1134,13 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` - associated with the certificate provided. + associated with the certificate provided + (matches :data:`PKCS7PrivateKeyTypes`). :param hash_algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that - will be used to generate the signature. This must be an instance of - :class:`~cryptography.hazmat.primitives.hashes.SHA1`, - :class:`~cryptography.hazmat.primitives.hashes.SHA224`, - :class:`~cryptography.hazmat.primitives.hashes.SHA256`, - :class:`~cryptography.hazmat.primitives.hashes.SHA384`, or - :class:`~cryptography.hazmat.primitives.hashes.SHA512`. + will be used to generate the signature. This must be one of the + types in :data:`PKCS7HashTypes`. .. method:: add_certificate(certificate) @@ -655,7 +1150,7 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :param certificate: The :class:`~cryptography.x509.Certificate` to add. - .. method:: sign(encoding, options, backend=None) + .. method:: sign(encoding, options) :param encoding: :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, @@ -664,9 +1159,7 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :param options: A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. - :return bytes: The signed PKCS7 message. - - :param backend: An optional backend. + :returns bytes: The signed PKCS7 message. .. class:: PKCS7Options @@ -720,17 +1213,18 @@ Serialization Formats .. currentmodule:: cryptography.hazmat.primitives.serialization .. class:: PrivateFormat + :canonical: cryptography.hazmat.primitives._serialization.PrivateFormat .. versionadded:: 0.8 An enumeration for private key formats. Used with the ``private_bytes`` method available on - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey` and - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. .. attribute:: TraditionalOpenSSL @@ -777,6 +1271,54 @@ Serialization Formats ... -----END OPENSSH PRIVATE KEY----- + .. attribute:: PKCS12 + + .. versionadded:: 38.0.0 + + The PKCS#12 format is a binary format used to store private keys and + certificates. This attribute is used in conjunction with + ``encryption_builder()`` to allow control of the encryption algorithm + and parameters. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.serialization import PrivateFormat, pkcs12 + >>> encryption = ( + ... PrivateFormat.PKCS12.encryption_builder(). + ... kdf_rounds(50000). + ... key_cert_algorithm(pkcs12.PBES.PBESv2SHA256AndAES256CBC). + ... hmac_hash(hashes.SHA256()).build(b"my password") + ... ) + >>> p12 = pkcs12.serialize_key_and_certificates( + ... b"friendlyname", key, None, None, encryption + ... ) + + .. method:: encryption_builder() + + .. versionadded:: 38.0.0 + + Returns a builder for configuring how values are encrypted with this + format. You must call this method on an element of the enumeration. + For example, ``PrivateFormat.OpenSSH.encryption_builder()``. + + For most use cases, :class:`BestAvailableEncryption` is preferred. + + :returns: A new instance of :class:`KeySerializationEncryptionBuilder` + + .. doctest:: + + >>> from cryptography.hazmat.primitives import serialization + >>> encryption = ( + ... serialization.PrivateFormat.OpenSSH.encryption_builder().kdf_rounds(30).build(b"my password") + ... ) + >>> key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.OpenSSH, + ... encryption_algorithm=encryption + ... ) + b'-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n' + .. class:: PublicFormat @@ -784,12 +1326,12 @@ Serialization Formats An enumeration for public key formats. Used with the ``public_bytes`` method available on - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey` , and - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. .. attribute:: SubjectPublicKeyInfo @@ -848,7 +1390,7 @@ Serialization Formats An enumeration for parameters formats. Used with the ``parameter_bytes`` method available on - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParametersWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. .. attribute:: PKCS3 @@ -858,14 +1400,15 @@ Serialization Encodings ~~~~~~~~~~~~~~~~~~~~~~~ .. class:: Encoding + :canonical: cryptography.hazmat.primitives._serialization.Encoding An enumeration for encoding types. Used with the ``private_bytes`` method available on - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, and :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey` as well as ``public_bytes`` on @@ -918,32 +1461,76 @@ Serialization Encryption Types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: KeySerializationEncryption + :canonical: cryptography.hazmat.primitives._serialization.KeySerializationEncryption Objects with this interface are usable as encryption types with methods like ``private_bytes`` available on - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey` and - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. All other classes in this section represent the available choices for - encryption and have this interface. They are used with - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`. + encryption and have this interface. .. class:: BestAvailableEncryption(password) + :canonical: cryptography.hazmat.primitives._serialization.BestAvailableEncryption - Encrypt using the best available encryption for a given key's backend. + Encrypt using the best available encryption for a given key. This is a curated encryption choice and the algorithm may change over - time. + time. The encryption algorithm may vary based on which version of OpenSSL + the library is compiled against. :param bytes password: The password to use for encryption. .. class:: NoEncryption + :canonical: cryptography.hazmat.primitives._serialization.NoEncryption Do not encrypt. +.. class:: KeySerializationEncryptionBuilder + + .. versionadded:: 38.0.0 + + A builder that can be used to configure how data is encrypted. To + create one, call :meth:`PrivateFormat.encryption_builder`. Different + serialization types will support different options on this builder. + + .. method:: kdf_rounds(rounds) + + Set the number of rounds the Key Derivation Function should use. The + meaning of the number of rounds varies on the KDF being used. + + :param int rounds: Number of rounds. + + .. method:: key_cert_algorithm(algorithm) + + Set the encryption algorithm to use when encrypting the key and + certificate in a PKCS12 structure. + + :param algorithm: A value from the :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PBES` + enumeration. + + .. method:: hmac_hash(algorithm) + + Set the hash algorithm to use within the MAC for a PKCS12 structure. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + .. method:: build(password) + + Turns the builder into an instance of + :class:`KeySerializationEncryption` with a given password. + + :param bytes password: The password. + :returns: A :class:`KeySerializationEncryption` encryption object + that can be passed to methods like ``private_bytes`` or + :func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates`. + +.. _`a bug in Firefox`: https://bugzilla.mozilla.org/show_bug.cgi?id=773111 .. _`PKCS3`: https://www.teletrust.de/fileadmin/files/oid/oid_pkcs-3v1-4.pdf .. _`SEC 1 v2.0`: https://www.secg.org/sec1-v2.pdf .. _`PROTOCOL.key`: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key diff --git a/docs/hazmat/primitives/asymmetric/x25519.rst b/docs/hazmat/primitives/asymmetric/x25519.rst index 014f3d01d5d3..859e0a54aece 100644 --- a/docs/hazmat/primitives/asymmetric/x25519.rst +++ b/docs/hazmat/primitives/asymmetric/x25519.rst @@ -129,6 +129,20 @@ Key interfaces :return bytes: Serialized key. + .. method:: private_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`private_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding, + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` + format, and + :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. + + :return bytes: Raw key. + .. class:: X25519PublicKey .. versionadded:: 2.0 @@ -176,6 +190,19 @@ Key interfaces :returns bytes: The public key bytes. + .. method:: public_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`public_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding and + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` + format. + + :return bytes: Raw key. + .. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange .. _`Curve25519`: https://en.wikipedia.org/wiki/Curve25519 diff --git a/docs/hazmat/primitives/asymmetric/x448.rst b/docs/hazmat/primitives/asymmetric/x448.rst index f166355b83fa..439c3b4ec8ec 100644 --- a/docs/hazmat/primitives/asymmetric/x448.rst +++ b/docs/hazmat/primitives/asymmetric/x448.rst @@ -123,6 +123,20 @@ Key interfaces :return bytes: Serialized key. + .. method:: private_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`private_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding, + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` + format, and + :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. + + :return bytes: Raw key. + .. class:: X448PublicKey .. versionadded:: 2.5 @@ -171,6 +185,19 @@ Key interfaces :returns bytes: The public key bytes. + .. method:: public_bytes_raw() + + .. versionadded:: 40 + + Allows serialization of the key to raw bytes. This method is a + convenience shortcut for calling :meth:`public_bytes` with + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + encoding and + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` + format. + + :return bytes: Raw key. + .. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange .. _`Curve448`: https://en.wikipedia.org/wiki/Curve448 diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst index 4cdc034a6b84..b6c889df4a81 100644 --- a/docs/hazmat/primitives/cryptographic-hashes.rst +++ b/docs/hazmat/primitives/cryptographic-hashes.rst @@ -5,7 +5,7 @@ Message digests (Hashing) .. module:: cryptography.hazmat.primitives.hashes -.. class:: Hash(algorithm, backend=None) +.. class:: Hash(algorithm) A cryptographic hash function takes an arbitrary block of data and calculates a fixed-size bit string (a digest), such that different data @@ -27,10 +27,6 @@ Message digests (Hashing) >>> digest.finalize() b'l\xa1=R\xcap\xc8\x83\xe0\xf0\xbb\x10\x1eBZ\x89\xe8bM\xe5\x1d\xb2\xd29%\x93\xafj\x84\x11\x80\x90' - If the backend doesn't support the requested ``algorithm`` an - :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be - raised. - Keep in mind that attacks against cryptographic hashes only get stronger with time, and that often algorithms that were once thought to be strong, become broken. Because of this it's important to include a plan for @@ -41,13 +37,9 @@ Message digests (Hashing) :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance such as those described in :ref:`below `. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.HashBackend` - instance. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + provided ``algorithm`` is unsupported. .. method:: update(data) @@ -125,7 +117,7 @@ SHA-family of hashes. .. note:: While the RFC specifies keying, personalization, and salting features, - these are not supported at this time due to limitations in OpenSSL 1.1.0. + these are not supported at this time due to limitations in OpenSSL. .. class:: BLAKE2b(digest_size) @@ -245,6 +237,20 @@ MD5 message digest and has practical known collision attacks. +SM3 +~~~ + +.. class:: SM3() + + .. versionadded:: 35.0.0 + + SM3 is a cryptographic hash function standardized by the Chinese National + Cryptography Administration in `GM/T 0004-2012`_. It produces 256-bit + message digests. (An English description is available at + `draft-sca-cfrg-sm3`_.) This hash should be used for compatibility + purposes where required and is not otherwise recommended for use. + + Interfaces ~~~~~~~~~~ @@ -286,3 +292,5 @@ Interfaces .. _`Lifetimes of cryptographic hash functions`: https://valerieaurora.org/hash.html .. _`BLAKE2`: https://blake2.net .. _`length-extension attacks`: https://en.wikipedia.org/wiki/Length_extension_attack +.. _`GM/T 0004-2012`: https://www.oscca.gov.cn/sca/xxgk/2010-12/17/1002389/files/302a3ada057c4a73830536d03e683110.pdf +.. _`draft-sca-cfrg-sm3`: https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3 diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 62457b28490c..7c5c643e2218 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -36,7 +36,7 @@ PBKDF2 .. currentmodule:: cryptography.hazmat.primitives.kdf.pbkdf2 -.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend=None) +.. class:: PBKDF2HMAC(algorithm, length, salt, iterations) .. versionadded:: 0.2 @@ -62,7 +62,7 @@ PBKDF2 ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=100000, + ... iterations=480000, ... ) >>> key = kdf.derive(b"my great password") >>> # verify @@ -70,7 +70,7 @@ PBKDF2 ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=100000, + ... iterations=480000, ... ) >>> kdf.verify(b"my great password", key) @@ -85,12 +85,6 @@ PBKDF2 takes. Higher numbers help mitigate brute force attacks against derived keys. A `more detailed description`_ can be consulted for additional information. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. @@ -139,7 +133,7 @@ Scrypt .. currentmodule:: cryptography.hazmat.primitives.kdf.scrypt -.. class:: Scrypt(salt, length, n, r, p, backend=None) +.. class:: Scrypt(salt, length, n, r, p) .. versionadded:: 1.6 @@ -181,8 +175,6 @@ Scrypt power of 2. :param int r: Block size parameter. :param int p: Parallelization parameter. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend`. The computational and memory cost of Scrypt can be adjusted by manipulating the 3 parameters: ``n``, ``r``, and ``p``. In general, the memory cost of @@ -196,9 +188,8 @@ Scrypt minimum value of ``n=2**14`` for interactive logins (t < 100ms), or ``n=2**20`` for more sensitive files (t < 5s). - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend` + :raises cryptography.exceptions.UnsupportedAlgorithm: If Scrypt is not + supported by the OpenSSL version ``cryptography`` is using. :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. :raises ValueError: This exception is raised if ``n`` is less than 2, if @@ -251,7 +242,7 @@ ConcatKDF .. currentmodule:: cryptography.hazmat.primitives.kdf.concatkdf -.. class:: ConcatKDFHash(algorithm, length, otherinfo, backend=None) +.. class:: ConcatKDFHash(algorithm, length, otherinfo) .. versionadded:: 1.0 @@ -291,13 +282,6 @@ ConcatKDF :param bytes otherinfo: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.HashBackend`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - if the provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HashBackend` - :raises TypeError: This exception is raised if ``otherinfo`` is not ``bytes``. @@ -337,7 +321,7 @@ ConcatKDF raises an exception if they do not match. -.. class:: ConcatKDFHMAC(algorithm, length, salt, otherinfo, backend=None) +.. class:: ConcatKDFHMAC(algorithm, length, salt, otherinfo) .. versionadded:: 1.0 @@ -386,13 +370,6 @@ ConcatKDF :param bytes otherinfo: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` - :raises TypeError: This exception is raised if ``salt`` or ``otherinfo`` is not ``bytes``. @@ -436,7 +413,7 @@ HKDF .. currentmodule:: cryptography.hazmat.primitives.kdf.hkdf -.. class:: HKDF(algorithm, length, salt, info, backend=None) +.. class:: HKDF(algorithm, length, salt, info) .. versionadded:: 0.2 @@ -483,18 +460,12 @@ HKDF to be secret, but may cause stronger security guarantees if secret; see :rfc:`5869` and the `HKDF paper`_ for more details. If ``None`` is explicitly passed a default salt of ``algorithm.digest_size // 8`` null - bytes will be used. + bytes will be used. See `understanding HKDF`_ for additional detail about + the salt and info parameters. :param bytes info: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` - :raises TypeError: This exception is raised if ``salt`` or ``info`` is not ``bytes``. @@ -535,7 +506,7 @@ HKDF raises an exception if they do not match. -.. class:: HKDFExpand(algorithm, length, info, backend=None) +.. class:: HKDFExpand(algorithm, length, info) .. versionadded:: 0.5 @@ -579,12 +550,6 @@ HKDF :param bytes info: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` :raises TypeError: This exception is raised if ``info`` is not ``bytes``. .. method:: derive(key_material) @@ -632,7 +597,7 @@ KBKDF .. currentmodule:: cryptography.hazmat.primitives.kdf.kbkdf .. class:: KBKDFHMAC(algorithm, mode, length, rlen, llen, location,\ - label, context, fixed, backend=None) + label, context, fixed) .. versionadded:: 1.4 @@ -706,19 +671,20 @@ KBKDF may supply your own fixed data. If ``fixed`` is specified, ``label`` and ``context`` is ignored. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - if the provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + :param int break_location: A keyword-only argument. An integer that + indicates the bytes offset where counter bytes are to be located. + Required when ``location`` is + :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. :raises TypeError: This exception is raised if ``label`` or ``context`` - is not ``bytes``. Also raised if ``rlen`` or ``llen`` is not ``int``. + is not ``bytes``. Also raised if ``rlen``, ``llen``, or + ``break_location`` is not ``int``. :raises ValueError: This exception is raised if ``rlen`` or ``llen`` is greater than 4 or less than 1. This exception is also raised if - you specify a ``label`` or ``context`` and ``fixed``. + you specify a ``label`` or ``context`` and ``fixed``. This exception + is also raised if you specify ``break_location`` and ``location`` is not + :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. .. method:: derive(key_material) @@ -755,6 +721,140 @@ KBKDF ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. +.. class:: KBKDFCMAC(algorithm, mode, length, rlen, llen, location,\ + label, context, fixed) + + .. versionadded:: 35.0.0 + + KBKDF (Key Based Key Derivation Function) is defined by the + `NIST SP 800-108`_ document, to be used to derive additional + keys from a key that has been established through an automated + key-establishment scheme. + + .. warning:: + + KBKDFCMAC should not be used for password storage. + + .. doctest:: + + >>> from cryptography.hazmat.primitives.ciphers import algorithms + >>> from cryptography.hazmat.primitives.kdf.kbkdf import ( + ... CounterLocation, KBKDFCMAC, Mode + ... ) + >>> label = b"KBKDF CMAC Label" + >>> context = b"KBKDF CMAC Context" + >>> kdf = KBKDFCMAC( + ... algorithm=algorithms.AES, + ... mode=Mode.CounterMode, + ... length=32, + ... rlen=4, + ... llen=4, + ... location=CounterLocation.BeforeFixed, + ... label=label, + ... context=context, + ... fixed=None, + ... ) + >>> key = kdf.derive(b"32 bytes long input key material") + >>> kdf = KBKDFCMAC( + ... algorithm=algorithms.AES, + ... mode=Mode.CounterMode, + ... length=32, + ... rlen=4, + ... llen=4, + ... location=CounterLocation.BeforeFixed, + ... label=label, + ... context=context, + ... fixed=None, + ... ) + >>> kdf.verify(b"32 bytes long input key material", key) + + :param algorithm: A class implementing a block cipher algorithm being a + subclass of + :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm` and + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. + + :param mode: The desired mode of the PRF. A value from the + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.Mode` enum. + + :param int length: The desired length of the derived key in bytes. + + :param int rlen: An integer that indicates the length of the binary + representation of the counter in bytes. + + :param int llen: An integer that indicates the binary + representation of the ``length`` in bytes. + + :param location: The desired location of the counter. A value from the + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation` enum. + + :param bytes label: Application specific label information. If ``None`` + is explicitly passed an empty byte string will be used. + + :param bytes context: Application specific context information. If ``None`` + is explicitly passed an empty byte string will be used. + + :param bytes fixed: Instead of specifying ``label`` and ``context`` you + may supply your own fixed data. If ``fixed`` is specified, ``label`` + and ``context`` is ignored. + + :param int break_location: A keyword-only argument. An integer that + indicates the bytes offset where counter bytes are to be located. + Required when ``location`` is + :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + if ``algorithm`` is not a subclass of + :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm` and + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. + + :raises TypeError: This exception is raised if ``label`` or ``context`` + is not ``bytes``, ``rlen``, ``llen``, or ``break_location`` is not + ``int``, ``mode`` is not + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.Mode` or ``location`` + is not + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation`. + + :raises ValueError: This exception is raised if ``rlen`` or ``llen`` + is greater than 4 or less than 1. This exception is also raised if + you specify a ``label`` or ``context`` and ``fixed``. This exception + is also raised if you specify ``break_location`` and ``location`` is not + :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. + + .. method:: derive(key_material) + + :param key_material: The input key material. + :type key_material: :term:`bytes-like` + :return bytes: The derived key. + :raises TypeError: This exception is raised if ``key_material`` is + not ``bytes``. + :raises ValueError: This exception is raised if ``key_material`` is + not a valid key for ``algorithm`` passed to + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFCMAC` + constructor. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + Derives a new key from the input key material. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises: Exceptions raised by :meth:`derive`. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + .. class:: Mode An enumeration for the key based key derivative modes. @@ -778,13 +878,20 @@ KBKDF The counter iteration variable will be concatenated after the fixed input data. + .. attribute:: MiddleFixed + + .. versionadded:: 38.0.0 + + The counter iteration variable will be concatenated in the middle + of the fixed input data. + X963KDF ------- .. currentmodule:: cryptography.hazmat.primitives.kdf.x963kdf -.. class:: X963KDF(algorithm, length, otherinfo, backend=None) +.. class:: X963KDF(algorithm, length, otherinfo) .. versionadded:: 1.1 @@ -830,13 +937,6 @@ X963KDF :param bytes sharedinfo: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.HashBackend`. - - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised - if the provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HashBackend` - :raises TypeError: This exception is raised if ``sharedinfo`` is not ``bytes``. @@ -938,3 +1038,4 @@ Interface .. _`here`: https://stackoverflow.com/a/30308723/1170681 .. _`recommends`: https://tools.ietf.org/html/rfc7914#section-2 .. _`The scrypt paper`: https://www.tarsnap.com/scrypt/scrypt.pdf +.. _`understanding HKDF`: https://soatok.blog/2021/11/17/understanding-hkdf/ diff --git a/docs/hazmat/primitives/keywrap.rst b/docs/hazmat/primitives/keywrap.rst index 9d8abbd09171..323757372049 100644 --- a/docs/hazmat/primitives/keywrap.rst +++ b/docs/hazmat/primitives/keywrap.rst @@ -11,7 +11,7 @@ to protect keys at rest or transmit them over insecure networks. Many of the protections offered by key wrapping are also offered by using authenticated :doc:`symmetric encryption `. -.. function:: aes_key_wrap(wrapping_key, key_to_wrap, backend=None) +.. function:: aes_key_wrap(wrapping_key, key_to_wrap) .. versionadded:: 1.1 @@ -22,14 +22,9 @@ protections offered by key wrapping are also offered by using authenticated :param bytes key_to_wrap: The key to wrap. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` - instance that supports - :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`. - :return bytes: The wrapped key as bytes. -.. function:: aes_key_unwrap(wrapping_key, wrapped_key, backend=None) +.. function:: aes_key_unwrap(wrapping_key, wrapped_key) .. versionadded:: 1.1 @@ -40,17 +35,12 @@ protections offered by key wrapping are also offered by using authenticated :param bytes wrapped_key: The wrapped key. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` - instance that supports - :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`. - :return bytes: The unwrapped key as bytes. :raises cryptography.hazmat.primitives.keywrap.InvalidUnwrap: This is raised if the key is not successfully unwrapped. -.. function:: aes_key_wrap_with_padding(wrapping_key, key_to_wrap, backend=None) +.. function:: aes_key_wrap_with_padding(wrapping_key, key_to_wrap) .. versionadded:: 2.2 @@ -61,14 +51,9 @@ protections offered by key wrapping are also offered by using authenticated :param bytes key_to_wrap: The key to wrap. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` - instance that supports - :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`. - :return bytes: The wrapped key as bytes. -.. function:: aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend=None) +.. function:: aes_key_unwrap_with_padding(wrapping_key, wrapped_key) .. versionadded:: 2.2 @@ -79,11 +64,6 @@ protections offered by key wrapping are also offered by using authenticated :param bytes wrapped_key: The wrapped key. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` - instance that supports - :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`. - :return bytes: The unwrapped key as bytes. :raises cryptography.hazmat.primitives.keywrap.InvalidUnwrap: This is diff --git a/docs/hazmat/primitives/mac/cmac.rst b/docs/hazmat/primitives/mac/cmac.rst index b40a90a82aa9..c7eabd9d953f 100644 --- a/docs/hazmat/primitives/mac/cmac.rst +++ b/docs/hazmat/primitives/mac/cmac.rst @@ -17,7 +17,7 @@ of a message. A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. -.. class:: CMAC(algorithm, backend=None) +.. class:: CMAC(algorithm) .. versionadded:: 0.4 @@ -33,10 +33,6 @@ A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. >>> c.finalize() b'CT\x1d\xc8\x0e\x15\xbe4e\xdb\xb6\x84\xca\xd9Xk' - If the backend doesn't support the requested ``algorithm`` an - :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be - raised. - If ``algorithm`` isn't a :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` instance then ``TypeError`` will be raised. @@ -55,13 +51,10 @@ A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. - :param backend: An optional instance of - :class:`~cryptography.hazmat.backends.interfaces.CMACBackend`. :raises TypeError: This is raised if the provided ``algorithm`` is not an instance of :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.CMACBackend` + provided ``algorithm`` is unsupported. .. method:: update(data) diff --git a/docs/hazmat/primitives/mac/hmac.rst b/docs/hazmat/primitives/mac/hmac.rst index 3695270b7ab2..bce8538d1bfd 100644 --- a/docs/hazmat/primitives/mac/hmac.rst +++ b/docs/hazmat/primitives/mac/hmac.rst @@ -15,7 +15,7 @@ message authentication codes using a cryptographic hash function coupled with a secret key. You can use an HMAC to verify both the integrity and authenticity of a message. -.. class:: HMAC(key, algorithm, backend=None) +.. class:: HMAC(key, algorithm) HMAC objects take a ``key`` and a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance. @@ -28,14 +28,12 @@ of a message. .. doctest:: >>> from cryptography.hazmat.primitives import hashes, hmac + >>> key = b'test key. Beware! A real key should use os.urandom or TRNG to generate' >>> h = hmac.HMAC(key, hashes.SHA256()) >>> h.update(b"message to hash") - >>> h.finalize() - b'#F\xdaI\x8b"e\xc4\xf1\xbb\x9a\x8fc\xff\xf5\xdex.\xbc\xcd/+\x8a\x86\x1d\x84\'\xc3\xa6\x1d\xd8J' - - If the backend doesn't support the requested ``algorithm`` an - :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be - raised. + >>> signature = h.finalize() + >>> signature + b'k\xd9\xb29\xefS\xf8\xcf\xec\xed\xbf\x95\xe6\x97X\x18\x9e%\x11DU1\x9fq}\x9a\x9c\xe0)y`=' If ``algorithm`` isn't a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance @@ -48,24 +46,23 @@ of a message. >>> h = hmac.HMAC(key, hashes.SHA256()) >>> h.update(b"message to hash") - >>> h.verify(b"an incorrect signature") + >>> h_copy = h.copy() # get a copy of `h' to be reused + >>> h.verify(signature) + >>> + >>> h_copy.verify(b"an incorrect signature") Traceback (most recent call last): ... cryptography.exceptions.InvalidSignature: Signature did not match digest. - :param key: Secret key as ``bytes``. + :param key: The secret key. :type key: :term:`bytes-like` :param algorithm: An :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance such as those described in :ref:`Cryptographic Hashes `. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` - instance. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + provided ``algorithm`` isn't supported. .. method:: update(msg) diff --git a/docs/hazmat/primitives/mac/poly1305.rst b/docs/hazmat/primitives/mac/poly1305.rst index 7504a076e81b..e3240f5baccf 100644 --- a/docs/hazmat/primitives/mac/poly1305.rst +++ b/docs/hazmat/primitives/mac/poly1305.rst @@ -48,7 +48,7 @@ messages allows an attacker to forge tags. Poly1305 is described in ... cryptography.exceptions.InvalidSignature: Value did not match computed tag. - :param key: Secret key as ``bytes``. + :param key: The secret key. :type key: :term:`bytes-like` :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the version of OpenSSL ``cryptography`` is compiled against does not diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst index 99d500a05b68..ecd70e6d5084 100644 --- a/docs/hazmat/primitives/padding.rst +++ b/docs/hazmat/primitives/padding.rst @@ -58,7 +58,7 @@ multiple of the block size. .. versionadded:: 1.3 - `ANSI X.923`_ padding works by appending ``N-1`` bytes with the value of + `ANSI X9.23`_ padding works by appending ``N-1`` bytes with the value of ``0`` and a last byte with the value of ``chr(N)``, where ``N`` is the number of bytes required to make the final block of data the same size as the block size. A simple example of padding is: @@ -127,4 +127,4 @@ multiple of the block size. :raises ValueError: When trying to remove padding from incorrectly padded data. -.. _`ANSI X.923`: https://en.wikipedia.org/wiki/Padding_%28cryptography%29#ANSI_X9.23 +.. _`ANSI X9.23`: https://en.wikipedia.org/wiki/Padding_%28cryptography%29#ANSI_X9.23 diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 8551acb2693f..2bf7a88cb0a4 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -14,14 +14,18 @@ message but an attacker can create bogus messages and force the application to decrypt them. In many contexts, a lack of authentication on encrypted messages can result in a loss of secrecy as well. -For this reason it is **strongly** recommended to combine encryption with a -message authentication code, such as :doc:`HMAC `, -in an "encrypt-then-MAC" formulation as `described by Colin Percival`_. -``cryptography`` includes a recipe named :doc:`/fernet` that does this for you. -**To minimize the risk of security issues you should evaluate Fernet to see if -it fits your needs before implementing anything using this module.** - -.. class:: Cipher(algorithm, mode, backend=None) +For this reason in nearly all contexts it is necessary to combine encryption +with a message authentication code, such as +:doc:`HMAC `, in an "encrypt-then-MAC" +formulation as `described by Colin Percival`_. ``cryptography`` includes a +recipe named :doc:`/fernet` that does this for you. **To minimize the risk of +security issues you should evaluate Fernet to see if it fits your needs before +implementing anything using this module.** If :doc:`/fernet` is not +appropriate for your use-case then you may still benefit from +:doc:`/hazmat/primitives/aead` which combines encryption and authentication +securely. + +.. class:: Cipher(algorithm, mode) Cipher objects combine an algorithm such as :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` with a @@ -50,13 +54,9 @@ it fits your needs before implementing anything using this module.** :param mode: A :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode` instance such as those described :ref:`below `. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` - instance. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + provided ``algorithm`` is unsupported. .. method:: encryptor() @@ -64,8 +64,8 @@ it fits your needs before implementing anything using this module.** :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` instance. - If the backend doesn't support the requested combination of ``cipher`` - and ``mode`` an :class:`~cryptography.exceptions.UnsupportedAlgorithm` + If the requested combination of ``algorithm`` and ``mode`` is + unsupported an :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be raised. .. method:: decryptor() @@ -74,8 +74,8 @@ it fits your needs before implementing anything using this module.** :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` instance. - If the backend doesn't support the requested combination of ``cipher`` - and ``mode`` an :class:`~cryptography.exceptions.UnsupportedAlgorithm` + If the requested combination of ``algorithm`` and ``mode`` is + unsupported an :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be raised. .. _symmetric-encryption-algorithms: @@ -95,6 +95,28 @@ Algorithms ``192``, or ``256`` :term:`bits` long. :type key: :term:`bytes-like` +.. class:: AES128(key) + + .. versionadded:: 38.0.0 + + An AES class that only accepts 128 bit keys. This is identical to the + standard ``AES`` class except that it will only accept a single key length. + + :param key: The secret key. This must be kept secret. ``128`` + :term:`bits` long. + :type key: :term:`bytes-like` + +.. class:: AES256(key) + + .. versionadded:: 38.0.0 + + An AES class that only accepts 256 bit keys. This is identical to the + standard ``AES`` class except that it will only accept a single key length. + + :param key: The secret key. This must be kept secret. ``256`` + :term:`bits` long. + :type key: :term:`bytes-like` + .. class:: Camellia(key) Camellia is a block cipher approved for use by `CRYPTREC`_ and ISO/IEC. @@ -105,7 +127,7 @@ Algorithms ``192``, or ``256`` :term:`bits` long. :type key: :term:`bytes-like` -.. class:: ChaCha20(key) +.. class:: ChaCha20(key, nonce) .. versionadded:: 2.1 @@ -130,7 +152,9 @@ Algorithms nonce with the same key compromises the security of every message encrypted with that key. The nonce does not need to be kept secret and may be included with the ciphertext. This must be ``128`` - :term:`bits` in length. + :term:`bits` in length. The 128-bit value is a concatenation of 4-byte + little-endian counter and the 12-byte nonce (as described in + :rfc:`7539`). :type nonce: :term:`bytes-like` .. note:: @@ -196,6 +220,21 @@ Algorithms :term:`bits` in length. :type key: :term:`bytes-like` +.. class:: SM4(key) + + .. versionadded:: 35.0.0 + + SM4 is a block cipher developed by the Chinese Government and standardized + in the GB/T 32907-2016. It is used in the Chinese WAPI + (Wired Authentication and Privacy Infrastructure) standard. (An English + description is available at `draft-ribose-cfrg-sm4-10`_.) This block + cipher should be used for compatibility purposes where required and is + not otherwise recommended for use. + + :param key: The secret key. This must be kept secret. ``128`` + :term:`bits` in length. + :type key: :term:`bytes-like` + Weak ciphers ------------ @@ -581,8 +620,6 @@ Interfaces into. This buffer should be ``len(data) + n - 1`` bytes where ``n`` is the block size (in bytes) of the cipher and mode combination. :return int: Number of bytes written. - :raises NotImplementedError: This is raised if the version of ``cffi`` - used is too old (this can happen on older PyPy releases). :raises ValueError: This is raised if the supplied buffer is too small. .. doctest:: @@ -726,9 +763,6 @@ Interfaces used by the symmetric cipher modes described in This should be the standard shorthand name for the mode, for example Cipher-Block Chaining mode is "CBC". - The name may be used by a backend to influence the operation of a - cipher in conjunction with the algorithm's name. - .. method:: validate_for_algorithm(algorithm) :param cryptography.hazmat.primitives.ciphers.CipherAlgorithm algorithm: @@ -815,3 +849,4 @@ Exceptions .. _`International Data Encryption Algorithm`: https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm .. _`OpenPGP`: https://www.openpgp.org/ .. _`disk encryption`: https://en.wikipedia.org/wiki/Disk_encryption_theory#XTS +.. _`draft-ribose-cfrg-sm4-10`: https://tools.ietf.org/html/draft-ribose-cfrg-sm4-10 diff --git a/docs/hazmat/primitives/twofactor.rst b/docs/hazmat/primitives/twofactor.rst index 1d2ab452ce0a..4cd437bedcf1 100644 --- a/docs/hazmat/primitives/twofactor.rst +++ b/docs/hazmat/primitives/twofactor.rst @@ -18,7 +18,16 @@ codes (HMAC). .. currentmodule:: cryptography.hazmat.primitives.twofactor.hotp -.. class:: HOTP(key, length, algorithm, backend=None, enforce_key_length=True) +.. data:: HOTPHashTypes + + .. versionadded:: 40.0.0 + + Type alias: A union of supported hash algorithm types: + :class:`~cryptography.hazmat.primitives.hashes.SHA1`, + :class:`~cryptography.hazmat.primitives.hashes.SHA256` or + :class:`~cryptography.hazmat.primitives.hashes.SHA512`. + +.. class:: HOTP(key, length, algorithm, *, enforce_key_length=True) .. versionadded:: 0.3 @@ -47,10 +56,7 @@ codes (HMAC). :param int length: Length of generated one time password as ``int``. :param cryptography.hazmat.primitives.hashes.HashAlgorithm algorithm: A :class:`~cryptography.hazmat.primitives.hashes` - instance. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` - instance. + instance (must match :data:`HOTPHashTypes`). :param enforce_key_length: A boolean flag defaulting to True that toggles whether a minimum key length of 128 :term:`bits` is enforced. This exists to work around the fact that as documented in `Issue #2915`_, @@ -68,9 +74,6 @@ codes (HMAC). :class:`~cryptography.hazmat.primitives.hashes.SHA256()` or :class:`~cryptography.hazmat.primitives.hashes.SHA512()` or if the ``length`` parameter is not an integer. - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` .. method:: generate(counter) @@ -91,11 +94,11 @@ codes (HMAC). :param account_name: The display name of account, such as ``'Alice Smith'`` or ``'alice@example.com'``. - :type account_name: :term:`text` + :type account_name: str :param issuer: The optional display name of issuer. This is typically the provider or service the user wants to access using the OTP token. - :type issuer: :term:`text` or `None` + :type issuer: ``str`` or ``None`` :param int counter: The current value of counter. :return: A URI string. @@ -140,7 +143,7 @@ similar to the following code. .. currentmodule:: cryptography.hazmat.primitives.twofactor.totp -.. class:: TOTP(key, length, algorithm, time_step, backend=None, enforce_key_length=True) +.. class:: TOTP(key, length, algorithm, time_step, *, enforce_key_length=True) TOTP objects take a ``key``, ``length``, ``algorithm`` and ``time_step`` parameter. The ``key`` should be :doc:`randomly generated bytes @@ -171,9 +174,6 @@ similar to the following code. :class:`~cryptography.hazmat.primitives.hashes` instance. :param int time_step: The time step size. The recommended size is 30. - :param backend: An optional - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` - instance. :param enforce_key_length: A boolean flag defaulting to True that toggles whether a minimum key length of 128 :term:`bits` is enforced. This exists to work around the fact that as documented in `Issue #2915`_, the @@ -190,9 +190,6 @@ similar to the following code. :class:`~cryptography.hazmat.primitives.hashes.SHA256()` or :class:`~cryptography.hazmat.primitives.hashes.SHA512()` or if the ``length`` parameter is not an integer. - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the - provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` .. method:: generate(time) @@ -212,11 +209,11 @@ similar to the following code. :param account_name: The display name of account, such as ``'Alice Smith'`` or ``'alice@example.com'``. - :type account_name: :term:`text` + :type account_name: str :param issuer: The optional display name of issuer. This is typically the provider or service the user wants to access using the OTP token. - :type issuer: :term:`text` or `None` + :type issuer: ``str`` or ``None`` :return: A URI string. Provisioning URI diff --git a/docs/index.rst b/docs/index.rst index ec3913f41d8c..08fcba34d96f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,9 +14,9 @@ key derivation functions. For example, to encrypt something with >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token - '...' + b'...' >>> f.decrypt(token) - 'A really secret message. Not for prying eyes.' + b'A really secret message. Not for prying eyes.' If you are interested in learning more about the field of cryptography, we recommend `Crypto 101, by Laurens Van Houtven`_ and `The Cryptopals Crypto @@ -67,7 +67,6 @@ hazmat layer only when necessary. hazmat/primitives/index exceptions random-numbers - hazmat/backends/index .. toctree:: :maxdepth: 2 @@ -77,6 +76,7 @@ hazmat layer only when necessary. changelog faq development/index + openssl security limitations api-stability diff --git a/docs/installation.rst b/docs/installation.rst index c773fdce5d69..f35f270effea 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -7,34 +7,40 @@ You can install ``cryptography`` with ``pip``: $ pip install cryptography +If this does not work please **upgrade your pip** first, as that is the +single most common cause of installation problems. + Supported platforms ------------------- -Currently we test ``cryptography`` on Python 2.7, 3.5+, -PyPy 7.3.1, and PyPy3 7.3.1 on these operating systems. +Currently we test ``cryptography`` on Python 3.7+ and PyPy3 7.3.10+ on these +operating systems. -* x86-64 CentOS 7.x -* x86-64 & AArch64 CentOS 8.x +* x86-64 RHEL 8.x +* x86-64 CentOS 9 Stream * x86-64 Fedora (latest) -* x86-64 macOS 10.15 Catalina -* x86-64 & AArch64 Ubuntu 18.04, 20.04 -* x86-64 Ubuntu rolling -* x86-64 Debian Stretch (9.x), Buster (10.x), Bullseye (11.x), and Sid - (unstable) +* x86-64 macOS 12 Monterey +* ARM64 macOS 13 Ventura +* x86-64 Ubuntu 20.04, 22.04, rolling +* ARM64 Ubuntu 22.04 +* x86-64 Debian Buster (10.x), Bullseye (11.x), Bookworm (12.x) + and Sid (unstable) * x86-64 Alpine (latest) -* 32-bit and 64-bit Python on 64-bit Windows Server 2019 +* ARM64 Alpine (latest) +* 32-bit and 64-bit Python on 64-bit Windows Server 2022 We test compiling with ``clang`` as well as ``gcc`` and use the following -OpenSSL releases: +OpenSSL releases in addition to distribution provided releases from the +above supported platforms: -* ``OpenSSL 1.0.2-latest`` -* ``OpenSSL 1.1.0-latest`` * ``OpenSSL 1.1.1-latest`` +* ``OpenSSL 3.0-latest`` +* ``OpenSSL 3.1-latest`` -.. warning:: +We also test against the latest commit of BoringSSL as well as versions of +LibreSSL that are receiving security support at the time of a given +``cryptography`` release. - Cryptography 3.2 has dropped support for OpenSSL 1.0.2, see the - :doc:`FAQ ` for more details Building cryptography on Windows -------------------------------- @@ -50,22 +56,18 @@ just run If you prefer to compile it yourself you'll need to have OpenSSL installed. You can compile OpenSSL yourself as well or use `a binary distribution`_. Be sure to download the proper version for your architecture and Python -(VC2010 works for Python 2.7 while VC2015 is required for 3.5 and above). -Wherever you place your copy of OpenSSL you'll need to set the ``LIB`` and ``INCLUDE`` -environment variables to include the proper locations. For example: +(VC2015 is required for 3.7 and above). Wherever you place your copy of OpenSSL +you'll need to set the ``OPENSSL_DIR`` environment variable to include the +proper location. For example: .. code-block:: console C:\> \path\to\vcvarsall.bat x86_amd64 - C:\> set LIB=C:\OpenSSL-win64\lib;%LIB% - C:\> set INCLUDE=C:\OpenSSL-win64\include;%INCLUDE% + C:\> set OPENSSL_DIR=C:\OpenSSL-win64 C:\> pip install cryptography -As of OpenSSL 1.1.0 the library names have changed from ``libeay32`` and -``ssleay32`` to ``libcrypto`` and ``libssl`` (matching their names on all other -platforms). ``cryptography`` links against the new 1.1.0 names by default. If -you need to compile ``cryptography`` against an older version then you **must** -set ``CRYPTOGRAPHY_WINDOWS_LINK_LEGACY_OPENSSL`` or else installation will fail. +You will also need to have :ref:`Rust installed and +available`. If you need to rebuild ``cryptography`` for any reason be sure to clear the local `wheel cache`_. @@ -75,47 +77,73 @@ local `wheel cache`_. Building cryptography on Linux ------------------------------ +.. note:: + + You should **upgrade pip** and attempt to install ``cryptography`` again + before following the instructions to compile it below. Most Linux + platforms will receive a binary wheel and require no compiler if you have + an updated ``pip``! + ``cryptography`` ships ``manylinux`` wheels (as of 2.0) so all dependencies -are included. For users on pip 8.1 or above running on a ``manylinux1`` or -``manylinux2010`` compatible distribution (almost everything except Alpine) -all you should need to do is: +are included. For users on **pip 19.3** or above running on a ``manylinux2014`` +(or greater) compatible distribution (or **pip 21.2.4** for ``musllinux``) all +you should need to do is: .. code-block:: console $ pip install cryptography -If you are on Alpine or just want to compile it yourself then -``cryptography`` requires a compiler, headers for Python (if you're not -using ``pypy``), and headers for the OpenSSL and ``libffi`` libraries -available on your system. +If you want to compile ``cryptography`` yourself you'll need a C compiler, a +Rust compiler, headers for Python (if you're not using ``pypy``), and headers +for the OpenSSL and ``libffi`` libraries available on your system. + +On all Linux distributions you will need to have :ref:`Rust installed and +available`. Alpine ~~~~~~ -Replace ``python3-dev`` with ``python-dev`` if you're using Python 2. +.. warning:: + + The Rust available by default in Alpine < 3.15 is older than the minimum + supported version. See the :ref:`Rust installation instructions + ` for information about installing a newer Rust. .. code-block:: console - $ sudo apk add gcc musl-dev python3-dev libffi-dev openssl-dev + $ sudo apk add gcc musl-dev python3-dev libffi-dev openssl-dev cargo pkgconfig If you get an error with ``openssl-dev`` you may have to use ``libressl-dev``. Debian/Ubuntu ~~~~~~~~~~~~~ -Replace ``python3-dev`` with ``python-dev`` if you're using Python 2. +.. warning:: + + The Rust available in most Debian versions is older than the minimum + supported version. Debian Bookworm is sufficiently new, but otherwise + please see the :ref:`Rust installation instructions ` + for information about installing a newer Rust. .. code-block:: console - $ sudo apt-get install build-essential libssl-dev libffi-dev python3-dev + $ sudo apt-get install build-essential libssl-dev libffi-dev \ + python3-dev cargo pkg-config -RHEL/CentOS -~~~~~~~~~~~ +Fedora/RHEL/CentOS +~~~~~~~~~~~~~~~~~~ + +.. warning:: + + For RHEL and CentOS you must be on version 8.6 or newer for the command + below to install a sufficiently new Rust. If your Rust is less than 1.56.0 + please see the :ref:`Rust installation instructions ` + for information about installing a newer Rust. .. code-block:: console - $ sudo yum install redhat-rpm-config gcc libffi-devel python-devel \ - openssl-devel + $ sudo dnf install redhat-rpm-config gcc libffi-devel python3-devel \ + openssl-devel cargo pkg-config Building @@ -147,31 +175,21 @@ this when configuring OpenSSL: .. code-block:: console - $ ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared - -You'll also need to generate your own ``openssl.ld`` file. For example:: - - OPENSSL_1.1.0E_CUSTOM { - global: - *; - }; - -You should replace the version string on the first line as appropriate for your -build. + $ ./config -Wl,-Bsymbolic-functions -fPIC shared Static Wheels ~~~~~~~~~~~~~ Cryptography ships statically-linked wheels for macOS, Windows, and Linux (via -``manylinux``). This allows compatible environments to use the most recent -OpenSSL, regardless of what is shipped by default on those platforms. Some -Linux distributions (most notably Alpine) are not ``manylinux`` compatible so -we cannot distribute wheels for them. +``manylinux`` and ``musllinux``). This allows compatible environments to use +the most recent OpenSSL, regardless of what is shipped by default on those +platforms. -However, you can build your own statically-linked wheels that will work on your -own systems. This will allow you to continue to use relatively old Linux -distributions (such as LTS releases), while making sure you have the most -recent OpenSSL available to your Python programs. +If you are using a platform not covered by our wheels, you can build your own +statically-linked wheels that will work on your own systems. This will allow +you to continue to use relatively old Linux distributions (such as LTS +releases), while making sure you have the most recent OpenSSL available to +your Python programs. To do so, you should find yourself a machine that is as similar as possible to your target environment (e.g. your production environment): for example, spin @@ -183,7 +201,7 @@ available from your system package manager. Then, paste the following into a shell script. You'll need to populate the ``OPENSSL_VERSION`` variable. To do that, visit `openssl.org`_ and find the latest non-FIPS release version number, then set the string appropriately. For -example, for OpenSSL 1.0.2k, use ``OPENSSL_VERSION="1.0.2k"``. +example, for OpenSSL 1.1.1k, use ``OPENSSL_VERSION="1.1.1k"``. When this shell script is complete, you'll find a collection of wheel files in a directory called ``wheelhouse``. These wheels can be installed by a @@ -209,7 +227,7 @@ dependencies. ./config no-shared no-ssl2 no-ssl3 -fPIC --prefix=${CWD}/openssl make && make install cd .. - CFLAGS="-I${CWD}/openssl/include" LDFLAGS="-L${CWD}/openssl/lib" pip wheel --no-binary :all: cryptography + OPENSSL_DIR="${CWD}/openssl" pip wheel --no-cache-dir --no-binary cryptography cryptography Building cryptography on macOS ------------------------------ @@ -239,8 +257,15 @@ open a terminal window and run: This will install a compiler (clang) along with (most of) the required development headers. -You'll also need OpenSSL, which you can obtain from `Homebrew`_ or `MacPorts`_. -Cryptography does **not** support Apple's deprecated OpenSSL distribution. +You will also need to have :ref:`Rust installed and +available`, which can be obtained from `Homebrew`_, +`MacPorts`_, or directly from the Rust website. If you are linking against a +``universal2`` archive of OpenSSL, the minimum supported Rust version is +1.66.0. + +Finally you need OpenSSL, which you can obtain from `Homebrew`_ or `MacPorts`_. +Cryptography does **not** support the OpenSSL/LibreSSL libraries Apple ships +in its base operating system. To build cryptography and dynamically link it: @@ -248,15 +273,15 @@ To build cryptography and dynamically link it: .. code-block:: console - $ brew install openssl@1.1 - $ env LDFLAGS="-L$(brew --prefix openssl@1.1)/lib" CFLAGS="-I$(brew --prefix openssl@1.1)/include" pip install cryptography + $ brew install openssl@3 rust + $ env OPENSSL_DIR="$(brew --prefix openssl@3)" pip install cryptography `MacPorts`_: .. code-block:: console - $ sudo port install openssl - $ env LDFLAGS="-L/opt/local/lib" CFLAGS="-I/opt/local/include" pip install cryptography + $ sudo port install openssl rust + $ env OPENSSL_DIR="-L/opt/local" pip install cryptography You can also build cryptography statically: @@ -264,23 +289,51 @@ You can also build cryptography statically: .. code-block:: console - $ brew install openssl@1.1 - $ env CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 LDFLAGS="$(brew --prefix openssl@1.1)/lib/libssl.a $(brew --prefix openssl@1.1)/lib/libcrypto.a" CFLAGS="-I$(brew --prefix openssl@1.1)/include" pip install cryptography + $ brew install openssl@3 rust + $ env OPENSSL_STATIC=1 OPENSSL_DIR="$(brew --prefix openssl@3)" pip install cryptography `MacPorts`_: .. code-block:: console - $ sudo port install openssl - $ env CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 LDFLAGS="/opt/local/lib/libssl.a /opt/local/lib/libcrypto.a" CFLAGS="-I/opt/local/include" pip install cryptography + $ sudo port install openssl rust + $ env OPENSSL_STATIC=1 OPENSSL_DIR="/opt/local" pip install cryptography If you need to rebuild ``cryptography`` for any reason be sure to clear the local `wheel cache`_. +Rust +---- + +.. note:: + + If you are using Linux, then you should **upgrade pip** (in + a virtual environment!) and attempt to install ``cryptography`` again before + trying to install the Rust toolchain. On most Linux distributions, the latest + version of ``pip`` will be able to install a binary wheel, so you won't need + a Rust toolchain. + +Building ``cryptography`` requires having a working Rust toolchain. The current +minimum supported Rust version is 1.56.0. **This is newer than the Rust some +package managers ship**, so users may need to install with the +instructions below. + +Instructions for installing Rust can be found on `the Rust Project's website`_. +We recommend installing Rust with ``rustup`` (as documented by the Rust +Project) in order to ensure you have a recent version. + +Rust is only required when building ``cryptography``, meaning that you may +install it for the duration of your ``pip install`` command and then remove it +from a system. A Rust toolchain is not required to **use** ``cryptography``. In +deployments such as ``docker``, you may use a multi-stage ``Dockerfile`` where +you install Rust during the build phase but do not install it in the runtime +image. This is the same as the C compiler toolchain which is also required to +build ``cryptography``, but not afterwards. .. _`Homebrew`: https://brew.sh .. _`MacPorts`: https://www.macports.org .. _`a binary distribution`: https://wiki.openssl.org/index.php/Binaries .. _virtualenv: https://virtualenv.pypa.io/en/latest/ .. _openssl.org: https://www.openssl.org/source/ -.. _`wheel cache`: https://pip.pypa.io/en/stable/reference/pip_install/#caching +.. _`wheel cache`: https://pip.pypa.io/en/stable/cli/pip_install/#caching +.. _`the Rust Project's website`: https://www.rust-lang.org/tools/install diff --git a/docs/limitations.rst b/docs/limitations.rst index 092d8a7cff91..3f43c743c729 100644 --- a/docs/limitations.rst +++ b/docs/limitations.rst @@ -20,5 +20,27 @@ like almost all software in Python is potentially vulnerable to this attack. The Likelihood: unlikely, Remediation Cost: expensive to repair" and we do not consider this a high risk for most users. -.. _`Memory wiping`: https://devblogs.microsoft.com/oldnewthing/?p=4223 +RSA PKCS1 v1.5 constant time decryption +--------------------------------------- + +RSA decryption has several different modes, one of which is PKCS1 v1.5. When +used in **online contexts**, a secure protocol implementation requires that +peers not be able to tell whether RSA PKCS1 v1.5 decryption failed or +succeeded, even by timing variability. + +``cryptography`` does not provide an API that makes this possible, due to the +fact that RSA decryption raises an exception on failure, which takes a +different amount of time than returning a value in the success case. + +Fixing this would require a new API in ``cryptography``, but OpenSSL does +not expose an API for straightforwardly implementing this while reusing +its own constant-time logic. See `issue 6167`_ for more information. + +For this reason we recommend not implementing online protocols +that use RSA PKCS1 v1.5 decryption with ``cryptography`` -- independent of this +limitation, such protocols generally have poor security properties due to their +lack of forward security. + +.. _`Memory wiping`: https://devblogs.microsoft.com/oldnewthing/?p=4223 .. _`CERT secure coding guidelines`: https://wiki.sei.cmu.edu/confluence/display/c/MEM03-C.+Clear+sensitive+information+stored+in+reusable+resources +.. _`issue 6167`: https://github.com/pyca/cryptography/issues/6167#issuecomment-1276151799 \ No newline at end of file diff --git a/docs/openssl.rst b/docs/openssl.rst new file mode 100644 index 000000000000..d4e69f4c86f6 --- /dev/null +++ b/docs/openssl.rst @@ -0,0 +1,45 @@ +Use of OpenSSL +============== + +``cryptography`` depends on the `OpenSSL`_ C library for all cryptographic +operation. OpenSSL is the de facto standard for cryptographic libraries and +provides high performance along with various certifications that may be +relevant to developers. + +A list of supported versions can be found in our :doc:`/installation` +documentation. + +In general the backend should be considered an internal implementation detail +of the project, but there are some public methods available for debugging +purposes. + +.. data:: cryptography.hazmat.backends.openssl.backend + + .. method:: openssl_version_text() + + :return text: The friendly string name of the loaded OpenSSL library. + This is not necessarily the same version as it was compiled against. + + .. method:: openssl_version_number() + + .. versionadded:: 1.8 + + :return int: The integer version of the loaded OpenSSL library. This is + defined in ``opensslv.h`` as ``OPENSSL_VERSION_NUMBER`` and is + typically shown in hexadecimal (e.g. ``0x1010003f``). This is + not necessarily the same version as it was compiled against. + +.. _legacy-provider: + +Legacy provider in OpenSSL 3.x +------------------------------ + +.. versionadded:: 39.0.0 + +Users can set ``CRYPTOGRAPHY_OPENSSL_NO_LEGACY`` environment variable to +disable the legacy provider in OpenSSL 3.x. This will disable legacy +cryptographic algorithms, including ``Blowfish``, ``CAST5``, ``SEED``, +``ARC4``, and ``RC2`` (which is used by some encrypted serialization formats). + + +.. _`OpenSSL`: https://www.openssl.org/ diff --git a/docs/random-numbers.rst b/docs/random-numbers.rst index c6acd5b18b1d..5d6fd3d89736 100644 --- a/docs/random-numbers.rst +++ b/docs/random-numbers.rst @@ -18,20 +18,17 @@ you can obtain them with: >>> import os >>> iv = os.urandom(16) -This will use ``/dev/urandom`` on UNIX platforms, and ``CryptGenRandom`` on -Windows. -If you need your random number as an integer (for example, for -:meth:`~cryptography.x509.CertificateBuilder.serial_number`), you can use +If you need your random number as an big integer, you can use ``int.from_bytes`` to convert the result of ``os.urandom``: .. code-block:: pycon - >>> serial = int.from_bytes(os.urandom(20), byteorder="big") + >>> serial = int.from_bytes(os.urandom(16), byteorder="big") -Starting with Python 3.6 the `standard library includes`_ the ``secrets`` -module, which can be used for generating cryptographically secure random -numbers, with specific helpers for text-based formats. +In addition, the `Python standard library`_ includes the ``secrets`` module, +which can be used for generating cryptographically secure random numbers, with +specific helpers for text-based formats. .. _`always use your operating system's provided random number generator`: https://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ -.. _`standard library includes`: https://docs.python.org/3/library/secrets.html +.. _`Python standard library`: https://docs.python.org/3/library/secrets.html diff --git a/docs/security.rst b/docs/security.rst index 8cdd2d114d9a..e1fba3a1ecec 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -52,20 +52,17 @@ Reporting a security issue We ask that you do not report security issues to our normal GitHub issue tracker. -If you believe you've identified a security issue with ``cryptography``, please -report it to ``alex.gaynor@gmail.com``. Messages may be optionally encrypted -with PGP using key fingerprint -``F7FC 698F AAE2 D2EF BECD E98E D1B3 ADC0 E023 8CA6`` (this public key is -available from most commonly-used key servers). +If you believe you've identified a security issue with ``cryptography``, +please report it via our `security advisory page`_. -Once you've submitted an issue via email, you should receive an acknowledgment -within 48 hours, and depending on the action to be taken, you may receive -further follow-up emails. +Once you've submitted an issue, you should receive an acknowledgment within 48 +hours, and depending on the action to be taken, you may receive further +follow-up. Supported Versions ------------------ -At any given time, we will provide security support for the `master`_ branch +At any given time, we will provide security support for the `main`_ branch as well as the most recent release. New releases for OpenSSL updates @@ -90,4 +87,5 @@ The steps for issuing a security release are described in our :doc:`/doing-a-release` documentation. -.. _`master`: https://github.com/pyca/cryptography +.. _`security advisory page`: https://github.com/pyca/cryptography/security/advisories/new +.. _`main`: https://github.com/pyca/cryptography diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index c8c275142ff7..62a62fb96e34 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -10,10 +10,12 @@ bcrypt Bleichenbacher Blowfish boolean +BoringSSL Botan Brainpool Bullseye Capitan +CentOS changelog Changelog ciphertext @@ -26,6 +28,7 @@ Cryptanalysis crypto cryptographic cryptographically +de Debian deallocated decrypt @@ -48,6 +51,7 @@ El Encodings endian extendable +facto fallback Fernet fernet @@ -56,6 +60,9 @@ Google hazmat Homebrew hostname +hostnames +implementor +incrementing indistinguishability initialisms interoperability @@ -64,10 +71,14 @@ introspectability invariants iOS iterable +Kerberos +Keychain Koblitz Lange logins metadata +MGF +Monterey Mozilla multi namespace @@ -80,11 +91,16 @@ online paddings Parallelization personalization +RHEL +parsers +Parsers +PEM pickleable plaintext Poly pre precompute +precomputed preprocessor preprocessors presentational @@ -100,17 +116,21 @@ serializer Serializers SHA Solaris +Sur syscall Tanja testability +Thawte timestamp timestamps +toolchain tunable Ubuntu unencrypted unicode unpadded unpadding +Ventura verifier Verifier Verisign diff --git a/docs/x509/certificate-transparency.rst b/docs/x509/certificate-transparency.rst index f9e651edcb55..33933384e19f 100644 --- a/docs/x509/certificate-transparency.rst +++ b/docs/x509/certificate-transparency.rst @@ -50,6 +50,40 @@ issued. indicate a binding-intent to issue a certificate for the same data, with SCTs embedded in it. + .. attribute:: signature_hash_algorithm + + .. versionadded:: 38.0.0 + + :type: + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + The hashing algorithm used by this SCT's signature. + + .. attribute:: signature_algorithm + + .. versionadded:: 38.0.0 + + :type: + :class:`~cryptography.x509.certificate_transparency.SignatureAlgorithm` + + The signing algorithm used by this SCT's signature. + + .. attribute:: signature + + .. versionadded:: 38.0.0 + + :type: bytes + + The raw bytes of the signatures embedded in the SCT. + + .. attribute:: extension_bytes + + .. versionadded:: 38.0.0 + + :type: bytes + + Any raw extension bytes. + .. class:: Version @@ -75,5 +109,20 @@ issued. For SCTs corresponding to pre-certificates. +.. class:: SignatureAlgorithm + + .. versionadded:: 38.0.0 + + An enumeration for SignedCertificateTimestamp signature algorithms. + + These are exactly the same as SignatureAlgorithm in :rfc:`5246` (TLS 1.2). + + .. attribute:: ANONYMOUS + + .. attribute:: RSA + + .. attribute:: DSA + + .. attribute:: ECDSA .. _`Certificate Transparency`: https://www.certificate-transparency.org/ diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst index 0c2d07aef852..76bfc023f15f 100644 --- a/docs/x509/ocsp.rst +++ b/docs/x509/ocsp.rst @@ -134,7 +134,8 @@ Creating Requests .. method:: add_certificate(cert, issuer, algorithm) Adds a request using a certificate, issuer certificate, and hash - algorithm. This can only be called once. + algorithm. You can call this method or ``add_certificate_by_hash`` + only once. :param cert: The :class:`~cryptography.x509.Certificate` whose validity is being checked. @@ -151,11 +152,40 @@ Creating Requests :class:`~cryptography.hazmat.primitives.hashes.SHA384`, and :class:`~cryptography.hazmat.primitives.hashes.SHA512` are allowed. - .. method:: add_extension(extension, critical) + .. method:: add_certificate_by_hash(issuer_name_hash, issuer_key_hash, serial_number, algorithm) + + .. versionadded:: 39.0.0 + + Adds a request using the issuer's name hash, key hash, the certificate + serial number and hash algorithm. You can call this method or + ``add_certificate`` only once. + + :param issuer_name_hash: The hash of the issuer's DER encoded name using the + same hash algorithm as the one specified in the ``algorithm`` parameter. + :type issuer_name_hash: bytes + + :param issuer_key_hash: The hash of the issuer's public key bit string + DER encoding using the same hash algorithm as the one specified in + the ``algorithm`` parameter. + :type issuer_key_hash: bytes + + :param serial_number: The serial number of the certificate being checked. + :type serial_number: int + + :param algorithm: A + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + instance. For OCSP only + :class:`~cryptography.hazmat.primitives.hashes.SHA1`, + :class:`~cryptography.hazmat.primitives.hashes.SHA224`, + :class:`~cryptography.hazmat.primitives.hashes.SHA256`, + :class:`~cryptography.hazmat.primitives.hashes.SHA384`, and + :class:`~cryptography.hazmat.primitives.hashes.SHA512` are allowed. + + .. method:: add_extension(extval, critical) Adds an extension to the request. - :param extension: An extension conforming to the + :param extval: An extension conforming to the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and @@ -168,16 +198,19 @@ Creating Requests .. doctest:: >>> from cryptography.hazmat.primitives import serialization - >>> from cryptography.hazmat.primitives.hashes import SHA1 + >>> from cryptography.hazmat.primitives.hashes import SHA256 >>> from cryptography.x509 import load_pem_x509_certificate, ocsp >>> cert = load_pem_x509_certificate(pem_cert) >>> issuer = load_pem_x509_certificate(pem_issuer) >>> builder = ocsp.OCSPRequestBuilder() - >>> # SHA1 is in this example because RFC 5019 mandates its use. - >>> builder = builder.add_certificate(cert, issuer, SHA1()) + >>> # SHA256 is in this example because while RFC 5019 originally + >>> # required SHA1 RFC 6960 updates that to SHA256. + >>> # However, depending on your requirements you may need to use SHA1 + >>> # for compatibility reasons. + >>> builder = builder.add_certificate(cert, issuer, SHA256()) >>> req = builder.build() >>> base64.b64encode(req.public_bytes(serialization.Encoding.DER)) - b'MEMwQTA/MD0wOzAJBgUrDgMCGgUABBRAC0Z68eay0wmDug1gfn5ZN0gkxAQUw5zz/NNGCDS7zkZ/oHxb8+IIy1kCAj8g' + b'MF8wXTBbMFkwVzANBglghkgBZQMEAgEFAAQgn3BowBaoh77h17ULfkX6781dUDPD82Taj8wO1jZWhZoEINxPgjoQth3w7q4AouKKerMxIMIuUG4EuWU2pZfwih52AgI/IA==' Loading Responses ~~~~~~~~~~~~~~~~~ @@ -274,11 +307,11 @@ Creating Responses :attr:`~cryptography.x509.ocsp.OCSPResponderEncoding.HASH` or :attr:`~cryptography.x509.ocsp.OCSPResponderEncoding.NAME`. - .. method:: add_extension(extension, critical) + .. method:: add_extension(extval, critical) Adds an extension to the response. - :param extension: An extension conforming to the + :param extval: An extension conforming to the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and @@ -296,7 +329,7 @@ Creating Responses :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` - that will be used to sign the certificate. + that will be used to sign the response. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that @@ -321,9 +354,12 @@ Creating Responses >>> responder_cert = load_pem_x509_certificate(pem_responder_cert) >>> responder_key = serialization.load_pem_private_key(pem_responder_key, None) >>> builder = ocsp.OCSPResponseBuilder() - >>> # SHA1 is in this example because RFC 5019 mandates its use. + >>> # SHA256 is in this example because while RFC 5019 originally + >>> # required SHA1 RFC 6960 updates that to SHA256. + >>> # However, depending on your requirements you may need to use SHA1 + >>> # for compatibility reasons. >>> builder = builder.add_response( - ... cert=cert, issuer=issuer, algorithm=hashes.SHA1(), + ... cert=cert, issuer=issuer, algorithm=hashes.SHA256(), ... cert_status=ocsp.OCSPCertStatus.GOOD, ... this_update=datetime.datetime.now(), ... next_update=datetime.datetime.now(), @@ -511,7 +547,8 @@ Interfaces The status of the certificate being checked. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: revocation_time @@ -521,7 +558,8 @@ Interfaces or ``None`` if the certificate has not been revoked. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: revocation_reason @@ -531,7 +569,8 @@ Interfaces not revoked. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: this_update @@ -541,7 +580,8 @@ Interfaces being indicated is known by the responder to have been correct. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: next_update @@ -551,7 +591,8 @@ Interfaces be available. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: issuer_key_hash @@ -561,7 +602,8 @@ Interfaces is defined by the ``hash_algorithm`` property. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: issuer_name_hash @@ -571,7 +613,8 @@ Interfaces is defined by the ``hash_algorithm`` property. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: hash_algorithm @@ -581,7 +624,8 @@ Interfaces ``issuer_name_hash``. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: serial_number @@ -590,7 +634,8 @@ Interfaces The serial number of the certificate that was checked. :raises ValueError: If ``response_status`` is not - :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. .. attribute:: extensions @@ -606,6 +651,14 @@ Interfaces The single extensions encoded in the response. + .. attribute:: responses + + .. versionadded:: 37.0.0 + + :type: an iterator over :class:`~cryptography.x509.ocsp.OCSPSingleResponse` + + An iterator to access individual SINGLERESP structures. + .. method:: public_bytes(encoding) :param encoding: The encoding to use. Only @@ -684,3 +737,71 @@ Interfaces Encode the X.509 ``Name`` of the certificate whose private key signed the response. + +.. class:: OCSPSingleResponse + + .. versionadded:: 37.0.0 + + A class representing a single certificate response bundled into a + larger OCSPResponse. Accessed via OCSPResponse.responses. + + .. attribute:: certificate_status + + :type: :class:`~cryptography.x509.ocsp.OCSPCertStatus` + + The status of the certificate being checked. + + .. attribute:: revocation_time + + :type: :class:`datetime.datetime` or None + + A naïve datetime representing the time when the certificate was revoked + or ``None`` if the certificate has not been revoked. + + .. attribute:: revocation_reason + + :type: :class:`~cryptography.x509.ReasonFlags` or None + + The reason the certificate was revoked or ``None`` if not specified or + not revoked. + + .. attribute:: this_update + + :type: :class:`datetime.datetime` + + A naïve datetime representing the most recent time at which the status + being indicated is known by the responder to have been correct. + + .. attribute:: next_update + + :type: :class:`datetime.datetime` + + A naïve datetime representing the time when newer information will + be available. + + .. attribute:: issuer_key_hash + + :type: bytes + + The hash of the certificate issuer's key. The hash algorithm used + is defined by the ``hash_algorithm`` property. + + .. attribute:: issuer_name_hash + + :type: bytes + + The hash of the certificate issuer's name. The hash algorithm used + is defined by the ``hash_algorithm`` property. + + .. attribute:: hash_algorithm + + :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + The algorithm used to generate the ``issuer_key_hash`` and + ``issuer_name_hash``. + + .. attribute:: serial_number + + :type: int + + The serial number of the certificate that was checked. diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index a46c5d623238..e14c8ffc1093 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -7,7 +7,7 @@ X.509 Reference pem_crl_data = b""" -----BEGIN X509 CRL----- - MIIBtDCBnQIBAjANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE + MIIBtDCBnQIBATANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw MDAwWjA+MDwCAQAYDzIwMTUwMTAxMDAwMDAwWjAmMBgGA1UdGAQRGA8yMDE1MDEw MTAwMDAwMFowCgYDVR0VBAMKAQEwDQYJKoZIhvcNAQELBQADggEBABRA4ww50Lz5 @@ -146,10 +146,35 @@ X.509 Reference -----END CERTIFICATE----- """.strip() + rsa_pss_pem_cert = b""" + -----BEGIN CERTIFICATE----- + MIIDfTCCAjCgAwIBAgIUP4D/5rcT93vdYGPhsKf+hbes/JgwQgYJKoZIhvcNAQEK + MDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF + AKIEAgIA3jAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8wHhcNMjIwNDMwMjAz + MTE4WhcNMzMwNDEyMjAzMTE4WjAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8w + ggEgMAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKCAQEAt1jpboUoNppBVamc+nA+zEjl + jn/gPbRFCvyveRd8Yr0p8y1mlmjKXcQlXcHPVM4TopgFXqDykIHXxJxLV56ysb4K + UGe0nxpmhEso5ZGUgkDIIoH0NAQAsS8rS2ZzNJcLrLGrMY6DRgFsa+G6h2DvMwgl + nsX++a8FIm7Vu+OZnfWpDEuhJU4TRtHVviJSYkFMckyYBB48k1MU+0b4pezHconZ + mMEisBFFbwarNvowf2i/tRESe3myKXfiJsZZ2UzdE3FqycSgw1tx8qV/Z8myozUW + uihIdw8TGbbsJhEeVFxQEP/DVzC6HHDI3EVpr2jPYeIE60hhZwM7jUmQscLerQID + AQABo1MwUTAdBgNVHQ4EFgQUb1QD8QEIQn5DALIAujTDATssNcQwHwYDVR0jBBgw + FoAUb1QD8QEIQn5DALIAujTDATssNcQwDwYDVR0TAQH/BAUwAwEB/zBCBgkqhkiG + 9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFl + AwQCAQUAogQCAgDeA4IBAQAvKBXlx07tdmtfhNTPn16dupBIS5344ZE4tfGSE5Ir + iA1X0bukKQ6V+6xJXGreaIw0wvwtIeI/R0JwcR114HBDqjt40vklyNSpGCJzgkfD + Q/d8JXN/MLyQrk+5F9JMy+HuZAgefAQAjugC6389Klpqx2Z1CgwmALhjIs48GnMp + Iz9vU2O6RDkMBlBRdmfkJVjhhPvJYpDDW1ic5O3pxtMoiC1tAHHMm4gzM1WCFeOh + cDNxABlvVNPTnqkOhKBmmwRaBwdvvksgeu2RyBNR0KEy44gWzYB9/Ter2t4Z8ASq + qCv8TuYr2QGaCnI2FVS5S9n6l4JNkFHqPMtuhrkr3gEz + -----END CERTIFICATE----- + """.strip() + Loading Certificates ~~~~~~~~~~~~~~~~~~~~ -.. function:: load_pem_x509_certificate(data, backend=None) +.. function:: load_pem_x509_certificate(data) + :canonical: cryptography.x509.base.load_pem_x509_certificate .. versionadded:: 0.7 @@ -159,10 +184,6 @@ Loading Certificates :param bytes data: The PEM encoded certificate data. - :param backend: An optional backend supporting the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :returns: An instance of :class:`~cryptography.x509.Certificate`. .. doctest:: @@ -172,7 +193,25 @@ Loading Certificates >>> cert.serial_number 2 -.. function:: load_der_x509_certificate(data, backend=None) +.. function:: load_pem_x509_certificates(data) + :canonical: cryptography.x509.base.load_pem_x509_certificates + + .. versionadded:: 39.0.0 + + Deserialize one or more certificates from PEM encoded data. + + This is like :func:`~cryptography.x509.load_pem_x509_certificate`, but + allows for loading multiple certificates (as adjacent PEMs) at once. + + :param bytes data: One or more PEM-encoded certificates. + + :returns: list of :class:`~cryptography.x509.Certificate` + + :raises ValueError: If there isn't at least one certificate, or if any + certificate is malformed. + +.. function:: load_der_x509_certificate(data) + :canonical: cryptography.x509.base.load_der_x509_certificate .. versionadded:: 0.7 @@ -182,16 +221,13 @@ Loading Certificates :param bytes data: The DER encoded certificate data. - :param backend: An optional backend supporting the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :returns: An instance of :class:`~cryptography.x509.Certificate`. Loading Certificate Revocation Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. function:: load_pem_x509_crl(data, backend=None) +.. function:: load_pem_x509_crl(data) + :canonical: cryptography.x509.base.load_pem_x509_crl .. versionadded:: 1.1 @@ -201,10 +237,6 @@ Loading Certificate Revocation Lists :param bytes data: The PEM encoded request data. - :param backend: An optional backend supporting the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :returns: An instance of :class:`~cryptography.x509.CertificateRevocationList`. @@ -216,7 +248,8 @@ Loading Certificate Revocation Lists >>> isinstance(crl.signature_hash_algorithm, hashes.SHA256) True -.. function:: load_der_x509_crl(data, backend=None) +.. function:: load_der_x509_crl(data) + :canonical: cryptography.x509.base.load_der_x509_crl .. versionadded:: 1.1 @@ -225,17 +258,14 @@ Loading Certificate Revocation Lists :param bytes data: The DER encoded request data. - :param backend: An optional backend supporting the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :returns: An instance of :class:`~cryptography.x509.CertificateRevocationList`. Loading Certificate Signing Requests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. function:: load_pem_x509_csr(data, backend=None) +.. function:: load_pem_x509_csr(data) + :canonical: cryptography.x509.base.load_pem_x509_csr .. versionadded:: 0.9 @@ -246,10 +276,6 @@ Loading Certificate Signing Requests :param bytes data: The PEM encoded request data. - :param backend: An optional backend supporting the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :returns: An instance of :class:`~cryptography.x509.CertificateSigningRequest`. @@ -261,7 +287,8 @@ Loading Certificate Signing Requests >>> isinstance(csr.signature_hash_algorithm, hashes.SHA256) True -.. function:: load_der_x509_csr(data, backend=None) +.. function:: load_der_x509_csr(data) + :canonical: cryptography.x509.base.load_der_x509_csr .. versionadded:: 0.9 @@ -270,10 +297,6 @@ Loading Certificate Signing Requests :param bytes data: The DER encoded request data. - :param backend: An optional backend supporting the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :returns: An instance of :class:`~cryptography.x509.CertificateSigningRequest`. @@ -281,6 +304,7 @@ X.509 Certificate Object ~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: Certificate + :canonical: cryptography.x509.base.Certificate .. versionadded:: 0.7 @@ -331,11 +355,7 @@ X.509 Certificate Object The public key associated with the certificate. :returns: One of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey` + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: @@ -417,6 +437,34 @@ X.509 Certificate Object >>> cert.signature_algorithm_oid + .. attribute:: signature_algorithm_parameters + + .. versionadded:: 41.0.0 + + Returns the parameters of the signature algorithm used to sign the + certificate. 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 certificate. + + :returns: None, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`, or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` + + .. doctest:: + + >>> from cryptography.hazmat.primitives.asymmetric import padding + >>> pss_cert = x509.load_pem_x509_certificate(rsa_pss_pem_cert) + >>> isinstance(pss_cert.signature_algorithm_parameters, padding.PSS) + True + .. attribute:: extensions :type: :class:`Extensions` @@ -481,9 +529,61 @@ X.509 Certificate Object ... cert_to_check.signature_hash_algorithm, ... ) - An - :class:`~cryptography.exceptions.InvalidSignature` - exception will be raised if the signature fails to verify. + An :class:`~cryptography.exceptions.InvalidSignature` exception will be + raised if the signature fails to verify. + + .. method:: verify_directly_issued_by(issuer) + + .. versionadded:: 40.0.0 + + :param issuer: The issuer certificate to check against. + :type issuer: :class:`~cryptography.x509.Certificate` + + .. warning:: + This method verifies that the certificate issuer name matches the + issuer subject name and that the certificate is signed by the + issuer's private key. **No other validation is performed.** + Callers are responsible for performing any additional + validations required for their use case (e.g. checking the validity + period, whether the signer is allowed to issue certificates, + that the issuing certificate has a strong public key, etc). + + Validates that the certificate is signed by the provided issuer and + that the issuer's subject name matches the issuer name of the + certificate. + + :return: None + :raise ValueError: If the issuer name on the certificate does + not match the subject name of the issuer or the signature + algorithm is unsupported. + :raise TypeError: If the issuer does not have a supported public + key type. + :raise cryptography.exceptions.InvalidSignature: If the + signature fails to verify. + + + .. attribute:: tbs_precertificate_bytes + + .. versionadded:: 38.0.0 + + :type: bytes + + :raises ValueError: If the certificate doesn't have the expected + Certificate Transparency extensions. + + The DER encoded bytes payload (as defined by :rfc:`6962`) that is hashed + and then signed by the private key of the pre-certificate's issuer. + This data may be used to validate a Signed Certificate Timestamp's + signature, but use extreme caution as SCT validation is a complex + problem that involves much more than just signature checks. + + This method is primarily useful in the context of programs that + interact with and verify the products of Certificate Transparency logs, + as specified in :rfc:`6962`. If you are not directly interacting with a + Certificate Transparency log, this method unlikely to be what you + want. To make unintentional misuse less likely, it raises a + ``ValueError`` if the underlying certificate does not contain the + expected Certificate Transparency extensions. .. method:: public_bytes(encoding) @@ -500,6 +600,7 @@ X.509 CRL (Certificate Revocation List) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateRevocationList + :canonical: cryptography.x509.base.CertificateRevocationList .. versionadded:: 1.0 @@ -513,7 +614,7 @@ X.509 CRL (Certificate Revocation List) Object 1 >>> revoked_certificate = crl[0] >>> type(revoked_certificate) - + >>> for r in crl: ... print(r.serial_number) 0 @@ -531,7 +632,7 @@ X.509 CRL (Certificate Revocation List) Object >>> from cryptography.hazmat.primitives import hashes >>> crl.fingerprint(hashes.SHA256()) - b'e\xcf.\xc4:\x83?1\xdc\xf3\xfc\x95\xd7\xb3\x87\xb3\x8e\xf8\xb93!\x87\x07\x9d\x1b\xb4!\xb9\xe4W\xf4\x1f' + b'\xe3\x1d\xb5P\x18\x9ed\x9f\x16O\x9dm\xc1>\x8c\xca\xb1\xc6x?T\x9f\xe9t_\x1d\x8dF8V\xf78' .. method:: get_revoked_certificate_by_serial_number(serial_number) @@ -662,6 +763,7 @@ X.509 Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateBuilder + :canonical: cryptography.x509.base.CertificateBuilder .. versionadded:: 1.0 @@ -723,11 +825,7 @@ X.509 Certificate Builder Sets the subject's public key. :param public_key: The subject's public key. This can be one of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. method:: serial_number(serial_number) @@ -764,27 +862,23 @@ X.509 Certificate Builder expiration time for the certificate. The certificate may not be trusted clients if it is used after this time. - .. method:: add_extension(extension, critical) + .. method:: add_extension(extval, critical) Adds an X.509 extension to the certificate. - :param extension: An extension conforming to the + :param extval: An extension conforming to the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and handled by whoever reads the certificate. - .. method:: sign(private_key, algorithm, backend=None) + .. method:: sign(private_key, algorithm, *, rsa_padding=None) Sign the certificate using the CA's private key. - :param private_key: The - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` - that will be used to sign the certificate. + :param private_key: The key that will be used to sign the certificate, + one of + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that @@ -797,10 +891,21 @@ X.509 Certificate Builder :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. - :param backend: An optional backend used to build the certificate. - Must support the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. + :param rsa_padding: + + .. versionadded:: 41.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: :class:`~cryptography.x509.Certificate` @@ -809,6 +914,7 @@ X.509 CSR (Certificate Signing Request) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateSigningRequest + :canonical: cryptography.x509.base.CertificateSigningRequest .. versionadded:: 0.9 @@ -817,11 +923,7 @@ X.509 CSR (Certificate Signing Request) Object The public key associated with the request. :returns: One of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: @@ -880,17 +982,13 @@ X.509 CSR (Certificate Signing Request) Object :raises cryptography.x509.UnsupportedGeneralNameType: If an extension contains a general name that is not supported. - .. method:: get_attribute_for_oid(oid) - - .. versionadded:: 3.0 + .. attribute:: attributes - :param oid: An :class:`ObjectIdentifier` instance. + .. versionadded:: 36.0.0 - :returns: The bytes value of the attribute or an exception if not - found. + :type: :class:`Attributes` - :raises cryptography.x509.AttributeNotFound: If the request does - not have the attribute requested. + The attributes encoded in the certificate signing request. .. method:: public_bytes(encoding) @@ -933,6 +1031,7 @@ X.509 Certificate Revocation List Builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateRevocationListBuilder + :canonical: cryptography.x509.base.CertificateRevocationListBuilder .. versionadded:: 1.2 @@ -993,11 +1092,11 @@ X.509 Certificate Revocation List Builder :param time: The :class:`datetime.datetime` object (in UTC) that marks the next update time for this CRL. - .. method:: add_extension(extension, critical) + .. method:: add_extension(extval, critical) Adds an X.509 extension to this CRL. - :param extension: An extension with the + :param extval: An extension with the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and @@ -1012,17 +1111,13 @@ X.509 Certificate Revocation List Builder obtained from an existing CRL or created with :class:`~cryptography.x509.RevokedCertificateBuilder`. - .. method:: sign(private_key, algorithm, backend=None) + .. method:: sign(private_key, algorithm) Sign this CRL using the CA's private key. - :param private_key: The - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` - that will be used to sign the certificate. + :param private_key: The private key that will be used to sign the + certificate, one of + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that @@ -1035,17 +1130,13 @@ X.509 Certificate Revocation List Builder :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. - :param backend: An optional backend used to build the CRL. - Must support the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :returns: :class:`~cryptography.x509.CertificateRevocationList` X.509 Revoked Certificate Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: RevokedCertificate + :canonical: cryptography.x509.base.RevokedCertificate .. versionadded:: 1.0 @@ -1088,6 +1179,7 @@ X.509 Revoked Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: RevokedCertificateBuilder + :canonical: cryptography.x509.base.RevokedCertificateBuilder This class is used to create :class:`~cryptography.x509.RevokedCertificate` objects that can be used with the @@ -1120,24 +1212,19 @@ X.509 Revoked Certificate Builder :param time: The :class:`datetime.datetime` object (in UTC) that marks the revocation time for the certificate. - .. method:: add_extension(extension, critical) + .. method:: add_extension(extval, critical) Adds an X.509 extension to this revoked certificate. - :param extension: An instance of one of the + :param extval: An instance of one of the :ref:`CRL entry extensions `. :param critical: Set to ``True`` if the extension must be understood and handled. - .. method:: build(backend=None) - - Create a revoked certificate object using the provided backend. + .. method:: build() - :param backend: An optional backend used to build the revoked - certificate. Must support the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. + Create a revoked certificate object. :returns: :class:`~cryptography.x509.RevokedCertificate` @@ -1145,6 +1232,7 @@ X.509 CSR (Certificate Signing Request) Builder Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateSigningRequestBuilder + :canonical: cryptography.x509.base.CertificateSigningRequestBuilder .. versionadded:: 1.0 @@ -1200,22 +1288,13 @@ X.509 CSR (Certificate Signing Request) Builder Object :returns: A new :class:`~cryptography.x509.CertificateSigningRequestBuilder`. - .. method:: sign(private_key, algorithm, backend=None) + .. method:: sign(private_key, algorithm) - :param backend: An optional backend used to sign the request. - Must support the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - - :param private_key: The - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` + :param private_key: The private key that will be used to sign the request. When the request is signed by a certificate authority, the private key's associated - public key will be stored in the resulting certificate. + public key will be stored in the resulting certificate. One of + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` @@ -1233,6 +1312,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. class:: Name + :canonical: cryptography.x509.name.Name .. versionadded:: 0.8 @@ -1270,6 +1350,26 @@ X.509 CSR (Certificate Signing Request) Builder Object :type: list of :class:`RelativeDistinguishedName` + .. classmethod:: from_rfc4514_string(data, attr_name_overrides=None) + + .. versionadded: 37.0.0 + + :param str data: An :rfc:`4514` string. + :param attr_name_overrides: Specify custom OID to name mappings, which + can be used to match vendor-specific extensions. See + :class:`~cryptography.x509.oid.NameOID` for common attribute + OIDs. + + :returns: A :class:`Name` parsed from ``data``. + + + .. doctest:: + + >>> x509.Name.from_rfc4514_string("CN=cryptography.io") + + >>> x509.Name.from_rfc4514_string("E=pyca@cryptography.io", {"E": NameOID.EMAIL_ADDRESS}) + + .. method:: get_attributes_for_oid(oid) :param oid: An :class:`ObjectIdentifier` instance. @@ -1282,25 +1382,52 @@ X.509 CSR (Certificate Signing Request) Builder Object >>> cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) [, value='Good CA')>] - .. method:: public_bytes(backend=None) + .. method:: public_bytes() .. versionadded:: 1.6 - :param backend: An optional backend supporting the - :class:`~cryptography.hazmat.backends.interfaces.X509Backend` - interface. - :return bytes: The DER encoded name. - .. method:: rfc4514_string() + .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 + .. versionchanged:: 36.0.0 - :return str: Format the given name as a :rfc:`4514` Distinguished Name - string, for example ``CN=mydomain.com,O=My Org,C=US``. + Added ``attr_name_overrides`` parameter. + + Format the given name as a :rfc:`4514` Distinguished Name + string, for example ``CN=mydomain.com,O=My Org,C=US``. + + By default, attributes ``CN``, ``L``, ``ST``, ``O``, ``OU``, ``C``, + ``STREET``, ``DC``, ``UID`` are represented by their short name. + Unrecognized attributes are formatted as dotted OID strings. + + Example: + + .. doctest:: + + >>> name = x509.Name([ + ... x509.NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"), + ... x509.NameAttribute(NameOID.COMMON_NAME, "Santa Claus"), + ... ]) + >>> name.rfc4514_string() + 'CN=Santa Claus,1.2.840.113549.1.9.1=santa@north.pole' + >>> name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"}) + 'CN=Santa Claus,E=santa@north.pole' + + :type attr_name_overrides: + Dict-like mapping from :class:`~cryptography.x509.ObjectIdentifier` + to ``str`` + :param attr_name_overrides: Specify custom OID to name mappings, which + can be used to match vendor-specific extensions. See + :class:`~cryptography.x509.oid.NameOID` for common attribute + OIDs. + + :rtype: str .. class:: Version + :canonical: cryptography.x509.base.Version .. versionadded:: 0.7 @@ -1315,6 +1442,7 @@ X.509 CSR (Certificate Signing Request) Builder Object For version 3 X.509 certificates. .. class:: NameAttribute + :canonical: cryptography.x509.name.NameAttribute .. versionadded:: 0.8 @@ -1329,19 +1457,40 @@ X.509 CSR (Certificate Signing Request) Builder Object .. attribute:: value - :type: :term:`text` + :type: ``str`` or ``bytes`` + + The value of the attribute. This will generally be a ``str``, the only + times it can be a ``bytes`` is when :attr:`oid` is + ``X500_UNIQUE_IDENTIFIER``. - The value of the attribute. + .. attribute:: rfc4514_attribute_name - .. method:: rfc4514_string() + .. versionadded:: 35.0.0 + + :type: str + + The :rfc:`4514` short attribute name (for example "CN"), + or the OID dotted string if a short name is unavailable. + + .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 + .. versionchanged:: 36.0.0 + + Added ``attr_name_overrides`` parameter. :return str: Format the given attribute as a :rfc:`4514` Distinguished Name string. + :type attr_name_overrides: + Dict-like mapping from :class:`~cryptography.x509.ObjectIdentifier` + to ``str`` + :param attr_name_overrides: Specify custom OID to name mappings, which + can be used to match vendor-specific extensions. + .. class:: RelativeDistinguishedName(attributes) + :canonical: cryptography.x509.name.RelativeDistinguishedName .. versionadded:: 1.6 @@ -1356,15 +1505,25 @@ X.509 CSR (Certificate Signing Request) Builder Object :returns: A list of :class:`NameAttribute` instances that match the OID provided. The list should contain zero or one values. - .. method:: rfc4514_string() + .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 + .. versionchanged:: 36.0.0 + + Added ``attr_name_overrides`` parameter. :return str: Format the given RDN set as a :rfc:`4514` Distinguished Name string. + :type attr_name_overrides: + Dict-like mapping from :class:`~cryptography.x509.ObjectIdentifier` + to ``str`` + :param attr_name_overrides: Specify custom OID to name mappings, which + can be used to match vendor-specific extensions. + .. class:: ObjectIdentifier + :canonical: ObjectIdentifier .. versionadded:: 0.8 @@ -1384,6 +1543,7 @@ General Name Classes ~~~~~~~~~~~~~~~~~~~~ .. class:: GeneralName + :canonical: cryptography.x509.general_name.GeneralName .. versionadded:: 0.9 @@ -1391,6 +1551,7 @@ General Name Classes against. .. class:: RFC822Name(value) + :canonical: cryptography.x509.general_name.RFC822Name .. versionadded:: 0.9 @@ -1409,9 +1570,10 @@ General Name Classes .. attribute:: value - :type: :term:`text` + :type: str .. class:: DNSName(value) + :canonical: cryptography.x509.general_name.DNSName .. versionadded:: 0.9 @@ -1428,13 +1590,14 @@ General Name Classes :raises ValueError: If the provided string is not an :term:`A-label`. - :type: :term:`text` + :type: str .. attribute:: value - :type: :term:`text` + :type: str .. class:: DirectoryName(value) + :canonical: cryptography.x509.general_name.DirectoryName .. versionadded:: 0.9 @@ -1445,6 +1608,7 @@ General Name Classes :type: :class:`Name` .. class:: UniformResourceIdentifier(value) + :canonical: cryptography.x509.general_name.UniformResourceIdentifier .. versionadded:: 0.9 @@ -1464,9 +1628,10 @@ General Name Classes .. attribute:: value - :type: :term:`text` + :type: str .. class:: IPAddress(value) + :canonical: cryptography.x509.general_name.IPAddress .. versionadded:: 0.9 @@ -1479,6 +1644,7 @@ General Name Classes or :class:`~ipaddress.IPv6Network`. .. class:: RegisteredID(value) + :canonical: cryptography.x509.general_name.RegisteredID .. versionadded:: 0.9 @@ -1489,6 +1655,7 @@ General Name Classes :type: :class:`ObjectIdentifier` .. class:: OtherName(type_id, value) + :canonical: cryptography.x509.general_name.OtherName .. versionadded:: 1.0 @@ -1506,6 +1673,7 @@ X.509 Extensions ~~~~~~~~~~~~~~~~ .. class:: Extensions + :canonical: cryptography.x509.extensions.Extensions .. versionadded:: 0.9 @@ -1516,7 +1684,7 @@ X.509 Extensions :param oid: An :class:`ObjectIdentifier` instance. - :returns: An instance of the extension class. + :returns: An instance of :class:`Extension`. :raises cryptography.x509.ExtensionNotFound: If the certificate does not have the extension requested. @@ -1533,7 +1701,7 @@ X.509 Extensions :param extclass: An extension class. - :returns: An instance of the extension class. + :returns: An instance of :class:`Extension`. :raises cryptography.x509.ExtensionNotFound: If the certificate does not have the extension requested. @@ -1545,6 +1713,7 @@ X.509 Extensions , critical=True, value=)> .. class:: Extension + :canonical: cryptography.x509.extensions.Extension .. versionadded:: 0.9 @@ -1568,13 +1737,29 @@ X.509 Extensions Returns an instance of the extension type corresponding to the OID. .. class:: ExtensionType + :canonical: cryptography.x509.extensions.ExtensionType .. versionadded:: 1.0 This is the interface against which all the following extension types are registered. + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns the OID associated with the given extension type. + + .. method:: public_bytes() + + .. versionadded:: 36.0.0 + + :return bytes: + + A bytes string representing the extension's DER encoded value. + .. class:: KeyUsage(digital_signature, content_commitment, key_encipherment, data_encipherment, key_agreement, key_cert_sign, crl_sign, encipher_only, decipher_only) + :canonical: cryptography.x509.extensions.KeyUsage .. versionadded:: 0.9 @@ -1674,6 +1859,7 @@ X.509 Extensions .. class:: BasicConstraints(ca, path_length) + :canonical: cryptography.x509.extensions.BasicConstraints .. versionadded:: 0.9 @@ -1709,6 +1895,7 @@ X.509 Extensions is not allowed to create subordinates with ``ca`` set to true. .. class:: ExtendedKeyUsage(usages) + :canonical: cryptography.x509.extensions.ExtendedKeyusage .. versionadded:: 0.9 @@ -1731,6 +1918,7 @@ X.509 Extensions .. class:: OCSPNoCheck() + :canonical: cryptography.x509.extensions.OCSPNoCheck .. versionadded:: 1.0 @@ -1753,6 +1941,7 @@ X.509 Extensions .. class:: TLSFeature(features) + :canonical: cryptography.x509.extensions.TLSFeature .. versionadded:: 2.1 @@ -1771,6 +1960,7 @@ X.509 Extensions Returns :attr:`~cryptography.x509.oid.ExtensionOID.TLS_FEATURE`. .. class:: TLSFeatureType + :canonical: cryptography.x509.extensions.TLSFeatureType .. versionadded:: 2.1 @@ -1791,6 +1981,7 @@ X.509 Extensions .. class:: NameConstraints(permitted_subtrees, excluded_subtrees) + :canonical: cryptography.x509.extensions.NameConstraints .. versionadded:: 1.0 @@ -1825,6 +2016,7 @@ X.509 Extensions ``excluded_subtrees`` will be non-None. .. class:: AuthorityKeyIdentifier(key_identifier, authority_cert_issuer, authority_cert_serial_number) + :canonical: cryptography.x509.extensions.AuthorityKeyIdentifier .. versionadded:: 0.9 @@ -1886,11 +2078,7 @@ X.509 Extensions section 4.2.1.2. :param public_key: One of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPublicKeyTypes`. .. doctest:: @@ -1930,6 +2118,7 @@ X.509 Extensions .. class:: SubjectKeyIdentifier(digest) + :canonical: cryptography.x509.extensions.SubjectKeyIdentifier .. versionadded:: 0.9 @@ -1945,12 +2134,20 @@ X.509 Extensions Returns :attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_KEY_IDENTIFIER`. - .. attribute:: digest + .. attribute:: key_identifier + + .. versionadded:: 35.0.0 :type: bytes The binary value of the identifier. + .. attribute:: digest + + :type: bytes + + The binary value of the identifier. An alias of ``key_identifier``. + .. classmethod:: from_public_key(public_key) .. versionadded:: 1.0 @@ -1962,11 +2159,7 @@ X.509 Extensions recommendation in :rfc:`5280` section 4.2.1.2. :param public_key: One of - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: @@ -1976,6 +2169,7 @@ X.509 Extensions .. class:: SubjectAlternativeName(general_names) + :canonical: cryptography.x509.extensions.SubjectAlternativeName .. versionadded:: 0.9 @@ -2016,6 +2210,7 @@ X.509 Extensions .. class:: IssuerAlternativeName(general_names) + :canonical: cryptography.x509.extensions.IssuerAlternativeName .. versionadded:: 1.0 @@ -2044,6 +2239,7 @@ X.509 Extensions .. class:: PrecertificateSignedCertificateTimestamps(scts) + :canonical: cryptography.x509.extensions.PrecertificateSignedCertificateTimestamps .. versionadded:: 2.0 @@ -2070,6 +2266,7 @@ X.509 Extensions .. class:: PrecertPoison() + :canonical: cryptography.x509.extensions.PrecertPoison .. versionadded:: 2.4 @@ -2087,6 +2284,7 @@ X.509 Extensions .. class:: SignedCertificateTimestamps(scts) + :canonical: cryptography.x509.extensions.SignedCertificateTimestamps .. versionadded:: 3.0 @@ -2114,6 +2312,7 @@ X.509 Extensions .. class:: DeltaCRLIndicator(crl_number) + :canonical: cryptography.x509.extensions.DeltaCRLIndicator .. versionadded:: 2.1 @@ -2138,6 +2337,7 @@ X.509 Extensions .. class:: AuthorityInformationAccess(descriptions) + :canonical: cryptography.x509.extensions.AuthorityInformationAccess .. versionadded:: 0.9 @@ -2161,6 +2361,7 @@ X.509 Extensions .. class:: SubjectInformationAccess(descriptions) + :canonical: cryptography.x509.extensions.SubjectInformationAccess .. versionadded:: 3.0 @@ -2184,6 +2385,7 @@ X.509 Extensions .. class:: AccessDescription(access_method, access_location) + :canonical: cryptography.x509.extensions.AccessDescription .. versionadded:: 0.9 @@ -2217,6 +2419,7 @@ X.509 Extensions Where to access the information defined by the access method. .. class:: FreshestCRL(distribution_points) + :canonical: cryptography.x509.extensions.FreshestCRL .. versionadded:: 2.1 @@ -2235,6 +2438,7 @@ X.509 Extensions :attr:`~cryptography.x509.oid.ExtensionOID.FRESHEST_CRL`. .. class:: CRLDistributionPoints(distribution_points) + :canonical: cryptography.x509.extensions.CRLDistributionPoints .. versionadded:: 0.9 @@ -2255,6 +2459,7 @@ X.509 Extensions :attr:`~cryptography.x509.oid.ExtensionOID.CRL_DISTRIBUTION_POINTS`. .. class:: DistributionPoint(full_name, relative_name, reasons, crl_issuer) + :canonical: cryptography.x509.extensions.DistributionPoint .. versionadded:: 0.9 @@ -2290,6 +2495,7 @@ X.509 Extensions revocation checks. .. class:: ReasonFlags + :canonical: cryptography.x509.extensions.ReasonFlags .. versionadded:: 0.9 @@ -2342,6 +2548,7 @@ X.509 Extensions in a :class:`DistributionPoint`. .. class:: InhibitAnyPolicy(skip_certs) + :canonical: cryptography.x509.extensions.InhibitAnyPolicy .. versionadded:: 1.0 @@ -2371,6 +2578,7 @@ X.509 Extensions :type: int .. class:: PolicyConstraints + :canonical: cryptography.x509.extensions.PolicyConstraints .. versionadded:: 1.3 @@ -2409,6 +2617,7 @@ X.509 Extensions certificate, but not in additional certificates in the chain. .. class:: CRLNumber(crl_number) + :canonical: cryptography.x509.extensions.CRLNumber .. versionadded:: 1.2 @@ -2431,6 +2640,7 @@ X.509 Extensions .. class:: IssuingDistributionPoint(full_name, relative_name,\ only_contains_user_certs, only_contains_ca_certs, only_some_reasons,\ indirect_crl, only_contains_attribute_certs) + :canonical: cryptography.x509.extensions.IssuingDistributionPoint .. versionadded:: 2.5 @@ -2500,6 +2710,7 @@ X.509 Extensions non-None. .. class:: UnrecognizedExtension + :canonical: cryptography.x509.extensions.UnrecognizedExtension .. versionadded:: 1.2 @@ -2520,7 +2731,36 @@ X.509 Extensions Returns the DER encoded bytes payload of the extension. +.. class:: MSCertificateTemplate(template_id, major_version, minor_version) + :canonical: cryptography.x509.extensions.MSCertificateTemplate + + .. versionadded:: 41.0.0 + + The Microsoft certificate template extension is a proprietary Microsoft + PKI extension that is used to provide information about the template + associated with the certificate. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.MS_CERTIFICATE_TEMPLATE`. + + .. attribute:: template_id + + :type: :class:`ObjectIdentifier` + + .. attribute:: major_version + + :type: int or None + + .. attribute:: minor_version + + :type: int or None + .. class:: CertificatePolicies(policies) + :canonical: cryptography.x509.extensions.CertificatePolicies .. versionadded:: 0.9 @@ -2537,7 +2777,7 @@ X.509 Extensions def contains_domain_validated(policies): return any( - policy.oid.dotted_string == "2.23.140.1.2.1" + policy.policy_identifier.dotted_string == "2.23.140.1.2.1" for policy in policies ) @@ -2556,6 +2796,7 @@ Certificate Policies Classes These classes may be present within a :class:`CertificatePolicies` instance. .. class:: PolicyInformation(policy_identifier, policy_qualifiers) + :canonical: cryptography.x509.extensions.PolicyInformation .. versionadded:: 0.9 @@ -2569,13 +2810,13 @@ These classes may be present within a :class:`CertificatePolicies` instance. :type: list - A list consisting of :term:`text` and/or :class:`UserNotice` objects. - If the value is text it is a pointer to the practice statement - published by the certificate authority. If it is a user notice it is - meant for display to the relying party when the certificate is - used. + A list consisting of ``str`` and/or :class:`UserNotice` objects. If the + value is ``str`` it is a pointer to the practice statement published by + the certificate authority. If it is a user notice it is meant for + display to the relying party when the certificate is used. .. class:: UserNotice(notice_reference, explicit_text) + :canonical: cryptography.x509.extensions.UserNotice .. versionadded:: 0.9 @@ -2595,9 +2836,10 @@ These classes may be present within a :class:`CertificatePolicies` instance. This field includes an arbitrary textual statement directly in the certificate. - :type: :term:`text` + :type: str .. class:: NoticeReference(organization, notice_numbers) + :canonical: cryptography.x509.extensions.NoticeReference Notice reference can name an organization and provide information about notices related to the certificate. For example, it might identify the @@ -2610,7 +2852,7 @@ These classes may be present within a :class:`CertificatePolicies` instance. .. attribute:: organization - :type: :term:`text` + :type: str .. attribute:: notice_numbers @@ -2626,6 +2868,7 @@ CRL Entry Extensions These extensions are only valid within a :class:`RevokedCertificate` object. .. class:: CertificateIssuer(general_names) + :canonical: cryptography.x509.extensions.CertificateIssuer .. versionadded:: 1.2 @@ -2654,6 +2897,7 @@ These extensions are only valid within a :class:`RevokedCertificate` object. The type of the returned values depends on the :class:`GeneralName`. .. class:: CRLReason(reason) + :canonical: cryptography.x509.extensions.CRLReason .. versionadded:: 1.2 @@ -2675,6 +2919,7 @@ These extensions are only valid within a :class:`RevokedCertificate` object. :type: An element from :class:`~cryptography.x509.ReasonFlags` .. class:: InvalidityDate(invalidity_date) + :canonical: cryptography.x509.extensions.InvalidityDate .. versionadded:: 1.2 @@ -2703,6 +2948,7 @@ OCSP Extensions ~~~~~~~~~~~~~~~ .. class:: OCSPNonce(nonce) + :canonical: cryptography.x509.extensions.OCSPNonce .. versionadded:: 2.4 @@ -2724,6 +2970,72 @@ OCSP Extensions :type: bytes +.. class:: OCSPAcceptableResponses(response) + :canonical: cryptography.x509.extensions.OCSPAcceptableResponses + + .. versionadded:: 41.0.0 + + OCSP acceptable responses is an extension that is only valid inside + :class:`~cryptography.x509.ocsp.OCSPRequest` objects. This allows an OCSP + client to tell the server what types of responses it supports. In practice + this is rarely used, because there is only one kind of OCSP response in + wide use. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.OCSPExtensionOID.ACCEPTABLE_RESPONSES`. + + .. attribute:: nonce + + :type: bytes + + +X.509 Request Attributes +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: Attributes + :canonical: cryptography.x509.base.Attributes + + .. versionadded:: 36.0.0 + + An Attributes instance is an ordered list of attributes. The object + is iterable to get every attribute. Each returned element is an + :class:`Attribute`. + + .. method:: get_attribute_for_oid(oid) + + .. versionadded:: 36.0.0 + + :param oid: An :class:`ObjectIdentifier` instance. + + :returns: The :class:`Attribute` or an exception if not found. + + :raises cryptography.x509.AttributeNotFound: If the request does + not have the attribute requested. + + +.. class:: Attribute + :canonical: cryptography.x509.base.Attribute + + .. versionadded:: 36.0.0 + + An attribute associated with an X.509 request. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns the object identifier for the attribute. + + .. attribute:: value + + :type: bytes + + Returns the value of the attribute. + Object Identifiers ~~~~~~~~~~~~~~~~~~ @@ -2733,6 +3045,7 @@ instances. The following common OIDs are available as constants. .. currentmodule:: cryptography.x509.oid .. class:: NameOID + :canonical: cryptography.hazmat._oid.NameOID These OIDs are typically seen in X.509 names. @@ -2775,7 +3088,7 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"2.5.4.5"``. This is distinct from the serial number of the certificate itself (which can be obtained with - :func:`~cryptography.x509.Certificate.serial_number`). + :attr:`~cryptography.x509.Certificate.serial_number`). .. attribute:: SURNAME @@ -2789,6 +3102,12 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"2.5.4.12"``. + .. attribute:: INITIALS + + .. versionadded:: 41.0.0 + + Corresponds to the dotted string ``"2.5.4.43"``. + .. attribute:: GENERATION_QUALIFIER Corresponds to the dotted string ``"2.5.4.44"``. @@ -2860,6 +3179,7 @@ instances. The following common OIDs are available as constants. .. class:: SignatureAlgorithmOID + :canonical: cryptography.hazmat._oid.SignatureAlgorithmOID .. versionadded:: 1.0 @@ -2893,6 +3213,26 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"1.2.840.113549.1.1.13"``. This is a SHA512 digest signed by an RSA key. + .. attribute:: RSA_WITH_SHA3_224 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.13"``. This is + a SHA3-224 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA3_256 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.14"``. This is + a SHA3-256 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA3_384 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.15"``. This is + a SHA3-384 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA3_512 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.16"``. This is + a SHA3-512 digest signed by an RSA key. + .. attribute:: RSASSA_PSS .. versionadded:: 2.3 @@ -2927,6 +3267,26 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"1.2.840.10045.4.3.4"``. This is a SHA512 digest signed by an ECDSA key. + .. attribute:: ECDSA_WITH_SHA3_224 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.9"``. This is + a SHA3-224 digest signed by an ECDSA key. + + .. attribute:: ECDSA_WITH_SHA3_256 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.10"``. This is + a SHA3-256 digest signed by an ECDSA key. + + .. attribute:: ECDSA_WITH_SHA3_384 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.11"``. This is + a SHA3-384 digest signed by an ECDSA key. + + .. attribute:: ECDSA_WITH_SHA3_512 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.12"``. This is + a SHA3-512 digest signed by an ECDSA key. + .. attribute:: DSA_WITH_SHA1 Corresponds to the dotted string ``"1.2.840.10040.4.3"``. This is @@ -2942,6 +3302,20 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.2"``. This is a SHA256 digest signed by a DSA key. + .. attribute:: DSA_WITH_SHA384 + + .. versionadded:: 36.0.0 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.3"``. This is + a SHA384 digest signed by a DSA key. + + .. attribute:: DSA_WITH_SHA512 + + .. versionadded:: 36.0.0 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.4"``. This is + a SHA512 digest signed by a DSA key. + .. attribute:: ED25519 .. versionadded:: 2.8 @@ -2958,6 +3332,7 @@ instances. The following common OIDs are available as constants. .. class:: ExtendedKeyUsageOID + :canonical: cryptography.hazmat._oid.ExtendedKeyUsageOID .. versionadded:: 1.0 @@ -3005,8 +3380,44 @@ instances. The following common OIDs are available as constants. the application. Therefore, the presence of this OID does not mean a given application will accept the certificate for all purposes. + .. attribute:: SMARTCARD_LOGON + + .. versionadded:: 35.0.0 + + Corresponds to the dotted string ``"1.3.6.1.4.1.311.20.2.2"``. This + is used to denote that a certificate may be used for ``PKINIT`` access + on Windows. + + .. attribute:: KERBEROS_PKINIT_KDC + + .. versionadded:: 35.0.0 + + Corresponds to the dotted string ``"1.3.6.1.5.2.3.5"``. This + is used to denote that a certificate may be used as a Kerberos + domain controller certificate authorizing ``PKINIT`` access. For + more information see :rfc:`4556`. + + .. attribute:: IPSEC_IKE + + .. versionadded:: 37.0.0 + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.17"``. This + is used to denote that a certificate may be assigned to an IPSEC SA, + and can be used by the assignee to initiate an IPSec Internet Key + Exchange. For more information see :rfc:`4945`. + + .. attribute:: CERTIFICATE_TRANSPARENCY + + .. versionadded:: 38.0.0 + + Corresponds to the dotted string ``"1.3.6.1.4.1.11129.2.4.4"``. This + is used to denote that a certificate may be used as a pre-certificate + signing certificate for Certificate Transparency log operation + purposes. For more information see :rfc:`6962`. + .. class:: AuthorityInformationAccessOID + :canonical: cryptography.hazmat._oid.AuthorityInformationAccessOID .. versionadded:: 1.0 @@ -3024,6 +3435,7 @@ instances. The following common OIDs are available as constants. .. class:: SubjectInformationAccessOID + :canonical: cryptography.hazmat._oid.SubjectInformationAccessOID .. versionadded:: 3.0 @@ -3035,6 +3447,7 @@ instances. The following common OIDs are available as constants. .. class:: CertificatePoliciesOID + :canonical: cryptography.hazmat._oid.CertificatePoliciesOID .. versionadded:: 1.0 @@ -3052,6 +3465,7 @@ instances. The following common OIDs are available as constants. .. class:: ExtensionOID + :canonical: cryptography.hazmat._oid.ExtensionOID .. versionadded:: 1.0 @@ -3184,8 +3598,23 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"2.5.29.28"``. + .. attribute:: POLICY_MAPPINGS + + Corresponds to the dotted string ``"2.5.29.33"``. + + .. attribute:: SUBJECT_DIRECTORY_ATTRIBUTES + + Corresponds to the dotted string ``"2.5.29.9"``. + + .. attribute:: MS_CERTIFICATE_TEMPLATE + + .. versionadded:: 41.0.0 + + Corresponds to the dotted string ``"1.3.6.1.4.1.311.21.7"``. + .. class:: CRLEntryExtensionOID + :canonical: cryptography.hazmat._oid.CRLEntryExtensionOID .. versionadded:: 1.2 @@ -3203,6 +3632,7 @@ instances. The following common OIDs are available as constants. .. class:: OCSPExtensionOID + :canonical: cryptography.hazmat._oid.OCSPExtensionOID .. versionadded:: 2.4 @@ -3210,8 +3640,15 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.2"``. + .. attribute:: ACCEPTABLE_RESPONSES + + .. versionadded:: 41.0.0 + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.4"``. + .. class:: AttributeOID + :canonical: cryptography.hazmat._oid.AttributeOID .. versionadded:: 3.0 @@ -3228,6 +3665,7 @@ Helper Functions .. currentmodule:: cryptography.x509 .. function:: random_serial_number() + :canonical: cryptography.x509.base.random_serial_number .. versionadded:: 1.6 @@ -3239,6 +3677,7 @@ Exceptions .. currentmodule:: cryptography.x509 .. class:: InvalidVersion + :canonical: cryptography.x509.base.InvalidVersion This is raised when an X.509 certificate has an invalid version number. @@ -3249,6 +3688,7 @@ Exceptions Returns the raw version that was parsed from the certificate. .. class:: DuplicateExtension + :canonical: cryptography.x509.extensions.DuplicateExtension This is raised when more than one X.509 extension of the same type is found within a certificate. @@ -3260,6 +3700,7 @@ Exceptions Returns the OID. .. class:: ExtensionNotFound + :canonical: cryptography.x509.extensions.ExtensionNotFound This is raised when calling :meth:`Extensions.get_extension_for_oid` with an extension OID that is not present in the certificate. @@ -3271,9 +3712,10 @@ Exceptions Returns the OID. .. class:: AttributeNotFound + :canonical: cryptography.x509.base.AttributeNotFound This is raised when calling - :meth:`CertificateSigningRequest.get_attribute_for_oid` with + :meth:`Attributes.get_attribute_for_oid` with an attribute OID that is not present in the request. .. attribute:: oid @@ -3283,6 +3725,7 @@ Exceptions Returns the OID. .. class:: UnsupportedGeneralNameType + :canonical: cryptography.x509.general_name.UnsupportedGeneralNameType This is raised when a certificate contains an unsupported general name type in an extension. diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 000000000000..86a6a68b61a8 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,271 @@ +# 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 glob +import itertools +import json +import pathlib +import re +import sys +import typing +import uuid + +import nox + +nox.options.reuse_existing_virtualenvs = True + + +def install(session: nox.Session, *args: str) -> None: + session.install( + "-v", + "-c", + "ci-constraints-requirements.txt", + *args, + silent=False, + ) + + +@nox.session +@nox.session(name="tests-ssh") +@nox.session(name="tests-randomorder") +@nox.session(name="tests-nocoverage") +def tests(session: nox.Session) -> None: + extras = "test" + if session.name == "tests-ssh": + extras += ",ssh" + if session.name == "tests-randomorder": + extras += ",test-randomorder" + + prof_location = ( + pathlib.Path(".") / ".rust-cov" / str(uuid.uuid4()) + ).absolute() + if session.name != "tests-nocoverage": + session.env.update( + { + "RUSTFLAGS": "-Cinstrument-coverage " + + session.env.get("RUSTFLAGS", ""), + "LLVM_PROFILE_FILE": str(prof_location / "cov-%p.profraw"), + } + ) + + install(session, f".[{extras}]") + install(session, "-e", "./vectors") + + session.run("pip", "list") + + if session.name != "tests-nocoverage": + cov_args = [ + "--cov=cryptography", + "--cov=tests", + ] + else: + cov_args = [] + + session.run( + "pytest", + "-n", + "auto", + "--dist=worksteal", + *cov_args, + "--durations=10", + *session.posargs, + "tests/", + ) + + if session.name != "tests-nocoverage": + [rust_so] = glob.glob( + f"{session.virtualenv.location}/**/cryptography/hazmat/bindings/_rust.*", + recursive=True, + ) + process_rust_coverage(session, [rust_so], prof_location) + + +@nox.session +def docs(session: nox.Session) -> None: + install(session, ".[docs,docstest,sdist,ssh]") + + temp_dir = session.create_tmp() + session.run( + "sphinx-build", + "-T", + "-W", + "-b", + "html", + "-d", + f"{temp_dir}/doctrees", + "docs", + "docs/_build/html", + ) + session.run( + "sphinx-build", + "-T", + "-W", + "-b", + "latex", + "-d", + f"{temp_dir}/doctrees", + "docs", + "docs/_build/latex", + ) + + session.run( + "sphinx-build", + "-T", + "-W", + "-b", + "doctest", + "-d", + f"{temp_dir}/doctrees", + "docs", + "docs/_build/html", + ) + session.run( + "sphinx-build", + "-T", + "-W", + "-b", + "spelling", + "docs", + "docs/_build/html", + ) + + # This is in the docs job because `twine check` verifies that the README + # is valid reStructuredText. + session.run("python", "-m", "build", "--sdist") + session.run("twine", "check", "dist/*") + + +@nox.session(name="docs-linkcheck") +def docs_linkcheck(session: nox.Session) -> None: + install(session, ".[docs]") + + session.run( + "sphinx-build", "-W", "-b", "linkcheck", "docs", "docs/_build/html" + ) + + +@nox.session +def flake(session: nox.Session) -> None: + install(session, ".[pep8test,test,ssh,nox]") + + session.run("ruff", ".") + session.run("black", "--check", ".") + session.run("check-sdist") + session.run( + "mypy", + "src/cryptography/", + "vectors/cryptography_vectors/", + "tests/", + "release.py", + "noxfile.py", + ) + + +@nox.session +def rust(session: nox.Session) -> None: + prof_location = ( + pathlib.Path(".") / ".rust-cov" / str(uuid.uuid4()) + ).absolute() + session.env.update( + { + "RUSTFLAGS": "-Cinstrument-coverage " + + session.env.get("RUSTFLAGS", ""), + "LLVM_PROFILE_FILE": str(prof_location / "cov-%p.profraw"), + } + ) + + install(session, ".") + + with session.chdir("src/rust/"): + session.run("cargo", "fmt", "--all", "--", "--check", external=True) + session.run("cargo", "clippy", "--", "-D", "warnings", external=True) + + build_output = session.run( + "cargo", + "test", + "--no-default-features", + "--all", + "--no-run", + "-q", + "--message-format=json", + external=True, + silent=True, + ) + session.run( + "cargo", "test", "--no-default-features", "--all", external=True + ) + + # It's None on install-only invocations + if build_output is not None: + assert isinstance(build_output, str) + rust_tests = [] + for line in build_output.splitlines(): + data = json.loads(line) + if data.get("profile", {}).get("test", False): + rust_tests.extend(data["filenames"]) + + process_rust_coverage(session, rust_tests, prof_location) + + +LCOV_SOURCEFILE_RE = re.compile( + r"^SF:.*[\\/]src[\\/]rust[\\/](.*)$", flags=re.MULTILINE +) +BIN_EXT = ".exe" if sys.platform == "win32" else "" + + +def process_rust_coverage( + session: nox.Session, + rust_binaries: typing.List[str], + prof_raw_location: pathlib.Path, +) -> None: + # Hitting weird issues merging Windows and Linux Rust coverage, so just + # say the hell with it. + if sys.platform == "win32": + return + + target_libdir = session.run( + "rustc", "--print", "target-libdir", external=True, silent=True + ) + if target_libdir is not None: + target_bindir = pathlib.Path(target_libdir).parent / "bin" + + profraws = [ + str(prof_raw_location / p) + for p in prof_raw_location.glob("*.profraw") + ] + session.run( + str(target_bindir / ("llvm-profdata" + BIN_EXT)), + "merge", + "-sparse", + *profraws, + "-o", + "rust-cov.profdata", + external=True, + ) + + lcov_data = session.run( + str(target_bindir / ("llvm-cov" + BIN_EXT)), + "export", + rust_binaries[0], + *itertools.chain.from_iterable( + ["-object", b] for b in rust_binaries[1:] + ), + "-instr-profile=rust-cov.profdata", + "--ignore-filename-regex=[/\\].cargo[/\\]", + "--ignore-filename-regex=[/\\]rustc[/\\]", + "--ignore-filename-regex=[/\\].rustup[/\\]toolchains[/\\]", + "--ignore-filename-regex=[/\\]target[/\\]", + "--format=lcov", + silent=True, + external=True, + ) + assert isinstance(lcov_data, str) + lcov_data = LCOV_SOURCEFILE_RE.sub( + lambda m: "SF:src/rust/" + m.group(1).replace("\\", "/"), + lcov_data.replace("\r\n", "\n"), + ) + with open(f"{uuid.uuid4()}.lcov", "w") as f: + f.write(lcov_data) diff --git a/pyproject.toml b/pyproject.toml index 957b1fd04bb0..3669e4cefa1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,154 @@ [build-system] requires = [ - # The minimum setuptools version is specific to the PEP 517 backend, - # and may be stricter than the version required in `setup.py` - "setuptools>=40.6.0", + # First version of setuptools to support pyproject.toml configuration + "setuptools>=61.0.0", "wheel", - # Must be kept in sync with the `setup_requirements` in `setup.py` - "cffi>=1.8,!=1.11.3; platform_python_implementation != 'PyPy'", + # Must be kept in sync with `project.dependencies` + "cffi>=1.12; platform_python_implementation != 'PyPy'", + "setuptools-rust>=0.11.4", ] build-backend = "setuptools.build_meta" +[project] +name = "cryptography" +version = "41.0.4" +authors = [ + {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} +] +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +readme = "README.rst" +license = {text = "Apache-2.0 OR BSD-3-Clause"} +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX", + "Operating System :: POSIX :: BSD", + "Operating System :: POSIX :: Linux", + 'Operating System :: Microsoft :: Windows', + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Security :: Cryptography", +] +requires-python = ">=3.7" +dependencies = [ + # Must be kept in sync with `build-system.requires` + "cffi >=1.12", +] + +[project.urls] +homepage = "https://github.com/pyca/cryptography" +documentation = "https://cryptography.io/" +source = "https://github.com/pyca/cryptography/" +issues = "https://github.com/pyca/cryptography/issues" +changelog = "https://cryptography.io/en/latest/changelog/" + +[tool.setuptools] +zip-safe = false +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] +include = ["cryptography*"] + +[project.optional-dependencies] +ssh = ["bcrypt >=3.1.5"] + +# All the following are used for our own testing. +nox = ["nox"] +test = [ + "pytest >=6.2.0", + "pytest-benchmark", + "pytest-cov", + "pytest-xdist", + "pretend", +] +test-randomorder = ["pytest-randomly"] +docs = ["sphinx >=5.3.0", "sphinx-rtd-theme >=1.1.1"] +docstest = ["pyenchant >=1.6.11", "twine >=1.12.0", "sphinxcontrib-spelling >=4.0.1"] +sdist = ["build"] +pep8test = ["black", "ruff", "mypy", "check-sdist"] + [tool.black] line-length = 79 -target-version = ["py27"] +target-version = ["py37"] + +[tool.pytest.ini_options] +addopts = "-r s --capture=no --strict-markers --benchmark-disable" +console_output_style = "progress-even-when-capture-no" +markers = [ + "skip_fips: this test is not executed in FIPS mode", + "supported: parametrized test requiring only_if and skip_message", +] + +[tool.mypy] +show_error_codes = true +check_untyped_defs = true +no_implicit_reexport = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_unused_configs = true +strict_equality = true + +[[tool.mypy.overrides]] +module = [ + "pretend" +] +ignore_missing_imports = true + +[tool.coverage.run] +branch = true +relative_files = true +source = [ + "cryptography", + "tests/", +] + +[tool.coverage.paths] +source = [ + "src/cryptography", + "*.nox/*/lib*/python*/site-packages/cryptography", + "*.nox\\*\\Lib\\site-packages\\cryptography", + "*.nox/pypy/site-packages/cryptography", +] +tests =[ + "tests/", + "*tests\\", +] + +[tool.coverage.report] +exclude_lines = [ + "@abc.abstractmethod", + "@typing.overload", + "if typing.TYPE_CHECKING", +] + +[tool.ruff] +# UP006: Minimum Python 3.9 +# UP007, UP038: Minimum Python 3.10 +ignore = ['N818', 'UP006', 'UP007', 'UP038'] +select = ['E', 'F', 'I', 'N', 'W', 'UP'] +line-length = 79 + +[tool.ruff.isort] +known-first-party = ["cryptography", "cryptography_vectors", "tests"] + +[tool.check-sdist] +git-only = [ + "vectors/*", + "release.py", + "ci-constraints-requirements.txt", + ".gitattributes", + ".gitignore", +] \ No newline at end of file diff --git a/release.py b/release.py index 5f3f251d17bd..b4844a12a5e5 100644 --- a/release.py +++ b/release.py @@ -2,142 +2,74 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - -import getpass -import glob -import io -import json -import os +import pathlib +import re import subprocess -import time -import zipfile import click -import requests - - -def run(*args, **kwargs): - print("[running] {0}".format(list(args))) - subprocess.check_call(list(args), **kwargs) - -def wait_for_build_complete_github_actions(session, token, run_url): - while True: - response = session.get( - run_url, - headers={ - "Content-Type": "application/json", - "Authorization": "token {}".format(token), - }, - ) - response.raise_for_status() - if response.json()["conclusion"] is not None: - break - time.sleep(3) +def run(*args: str) -> None: + print(f"[running] {list(args)}") + subprocess.check_call(list(args)) -def download_artifacts_github_actions(session, token, run_url): - response = session.get( - run_url, - headers={ - "Content-Type": "application/json", - "Authorization": "token {}".format(token), - }, - ) - response.raise_for_status() - - response = session.get( - response.json()["artifacts_url"], - headers={ - "Content-Type": "application/json", - "Authorization": "token {}".format(token), - }, - ) - response.raise_for_status() - paths = [] - for artifact in response.json()["artifacts"]: - response = session.get( - artifact["archive_download_url"], - headers={ - "Content-Type": "application/json", - "Authorization": "token {}".format(token), - }, - ) - with zipfile.ZipFile(io.BytesIO(response.content)) as z: - for name in z.namelist(): - if not name.endswith(".whl"): - continue - p = z.open(name) - out_path = os.path.join( - os.path.dirname(__file__), - "dist", - os.path.basename(name), - ) - with open(out_path, "wb") as f: - f.write(p.read()) - paths.append(out_path) - return paths - - -def build_github_actions_wheels(token, version): - session = requests.Session() - - response = session.post( - "https://api.github.com/repos/pyca/cryptography/actions/workflows/" - "wheel-builder.yml/dispatches", - headers={ - "Content-Type": "application/json", - "Accept": "application/vnd.github.v3+json", - "Authorization": "token {}".format(token), - }, - data=json.dumps({"ref": "master", "inputs": {"version": version}}), - ) - response.raise_for_status() - - # Give it a few seconds for the run to kick off. - time.sleep(5) - response = session.get( - ( - "https://api.github.com/repos/pyca/cryptography/actions/workflows/" - "wheel-builder.yml/runs?event=workflow_dispatch" - ), - headers={ - "Content-Type": "application/json", - "Authorization": "token {}".format(token), - }, - ) - response.raise_for_status() - run_url = response.json()["workflow_runs"][0]["url"] - wait_for_build_complete_github_actions(session, token, run_url) - return download_artifacts_github_actions(session, token, run_url) +@click.group() +def cli(): + pass -@click.command() +@cli.command() @click.argument("version") -def release(version): +def release(version: str) -> None: """ ``version`` should be a string like '0.4' or '1.0'. """ - github_token = getpass.getpass("Github person access token: ") - - run("git", "tag", "-s", version, "-m", "{0} release".format(version)) + # Tag and push the tag (this will trigger the wheel builder in Actions) + run("git", "tag", "-s", version, "-m", f"{version} release") run("git", "push", "--tags") - run("python", "setup.py", "sdist") - run("python", "setup.py", "sdist", "bdist_wheel", cwd="vectors/") - packages = glob.glob("dist/cryptography-{0}*".format(version)) + glob.glob( - "vectors/dist/cryptography_vectors-{0}*".format(version) +def replace_version( + p: pathlib.Path, variable_name: str, new_version: str +) -> None: + with p.open() as f: + content = f.read() + + pattern = rf"^{variable_name}\s*=\s*.*$" + match = re.search(pattern, content, re.MULTILINE) + assert match is not None + + start, end = match.span() + new_content = ( + content[:start] + f'{variable_name} = "{new_version}"' + content[end:] ) - run("twine", "upload", "-s", *packages) - github_actions_wheel_paths = build_github_actions_wheels( - github_token, version + # Write back to file + with p.open("w") as f: + f.write(new_content) + + +@cli.command() +@click.argument("new_version") +def bump_version(new_version: str) -> None: + base_dir = pathlib.Path(__file__).parent + + replace_version(base_dir / "pyproject.toml", "version", new_version) + replace_version( + base_dir / "src/cryptography/__about__.py", "__version__", new_version + ) + replace_version( + base_dir / "vectors/pyproject.toml", + "version", + new_version, + ) + replace_version( + base_dir / "vectors/cryptography_vectors/__about__.py", + "__version__", + new_version, ) - run("twine", "upload", *github_actions_wheel_paths) if __name__ == "__main__": - release() + cli() diff --git a/rtd-requirements.txt b/rtd-requirements.txt deleted file mode 100644 index 142b6ca357fe..000000000000 --- a/rtd-requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e .[docs] diff --git a/setup.py b/setup.py index 82800a96e59b..4fe0c027c17c 100644 --- a/setup.py +++ b/setup.py @@ -4,25 +4,37 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - import os import platform +import re +import shutil +import subprocess import sys - -import pkg_resources - -import setuptools -from setuptools import find_packages, setup +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 -if pkg_resources.parse_version( - setuptools.__version__ -) < pkg_resources.parse_version("18.5"): - raise RuntimeError( - "cryptography requires setuptools 18.5 or newer, please upgrade to a " - "newer version of setuptools" - ) +# 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") @@ -31,94 +43,74 @@ # means that we need to add the src/ directory to the sys.path. sys.path.insert(0, src_dir) -about = {} -with open(os.path.join(src_dir, "cryptography", "__about__.py")) as f: - exec (f.read(), about) - - -# `setup_requirements` must be kept in sync with `pyproject.toml` -setup_requirements = ["cffi>=1.8,!=1.11.3"] - -if platform.python_implementation() == "PyPy": - if sys.pypy_version_info < (5, 4): - raise RuntimeError( - "cryptography is not compatible with PyPy < 5.4. Please upgrade " - "PyPy to use this library." - ) - - -with open(os.path.join(base_dir, "README.rst")) as f: - long_description = f.read() - - -setup( - name=about["__title__"], - version=about["__version__"], - description=about["__summary__"], - long_description=long_description, - long_description_content_type="text/x-rst", - license=about["__license__"], - url=about["__uri__"], - author=about["__author__"], - author_email=about["__email__"], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Operating System :: MacOS :: MacOS X", - "Operating System :: POSIX", - "Operating System :: POSIX :: BSD", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Security :: Cryptography", - ], - package_dir={"": "src"}, - packages=find_packages(where="src", exclude=["_cffi_src", "_cffi_src.*"]), - include_package_data=True, - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", - install_requires=["six >= 1.4.1"] + setup_requirements, - setup_requires=setup_requirements, - extras_require={ - ":python_version < '3'": ["enum34", "ipaddress"], - "test": [ - "pytest>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2", - "pretend", - "iso8601", - "pytz", - "hypothesis>=1.11.4,!=3.79.2", - ], - "docs": [ - "sphinx >= 1.6.5,!=1.8.0,!=3.1.0,!=3.1.1", - "sphinx_rtd_theme", +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.56.0", + ) ], - "docstest": [ - "doc8", - "pyenchant >= 1.6.11", - "twine >= 1.12.0", - "sphinxcontrib-spelling >= 4.0.1", - ], - "pep8test": ["black", "flake8", "flake8-import-order", "pep8-naming"], - # This extra is for OpenSSH private keys that use bcrypt KDF - # Versions: v3.1.3 - ignore_few_rounds, v3.1.5 - abi3 - "ssh": ["bcrypt >= 3.1.5"], - }, - # for cffi - zip_safe=False, - ext_package="cryptography.hazmat.bindings", - cffi_modules=[ - "src/_cffi_src/build_openssl.py:ffi", - "src/_cffi_src/build_padding.py:ffi", - ], -) + ) +except: # noqa: E722 + # 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/build_openssl.py b/src/_cffi_src/build_openssl.py index 35ccd6b9be0a..6c4fd90e143b 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -2,80 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import os +import pathlib +import platform import sys -from distutils import dist -from distutils.ccompiler import get_default_compiler -from distutils.command.config import config - -from _cffi_src.utils import ( - build_ffi_for_binding, - compiler_type, - extra_link_args, -) +# Add the src directory to the path so we can import _cffi_src.utils +src_dir = str(pathlib.Path(__file__).parent.parent) +sys.path.insert(0, src_dir) -def _get_openssl_libraries(platform): - if os.environ.get("CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS", None): - return [] - # OpenSSL goes by a different library name on different operating systems. - if platform == "win32" and compiler_type() == "msvc": - windows_link_legacy_openssl = os.environ.get( - "CRYPTOGRAPHY_WINDOWS_LINK_LEGACY_OPENSSL", None - ) - if windows_link_legacy_openssl is None: - # Link against the 1.1.0 names - # CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - libs = ["libssl", "libcrypto"] - else: - # Link against the 1.0.2 and lower names - libs = ["libeay32", "ssleay32"] - return libs + ["advapi32", "crypt32", "gdi32", "user32", "ws2_32"] - else: - # darwin, linux, mingw all use this path - # In some circumstances, the order in which these libs are - # specified on the linker command-line is significant; - # libssl must come before libcrypto - # (https://marc.info/?l=openssl-users&m=135361825921871) - # -lpthread required due to usage of pthread an potential - # existance of a static part containing e.g. pthread_atfork - # (https://github.com/pyca/cryptography/issues/5084) - if sys.platform == "zos": - return ["ssl", "crypto"] - else: - return ["ssl", "crypto", "pthread"] - - -def _extra_compile_args(platform): - """ - We set -Wconversion args here so that we only do Wconversion checks on the - code we're compiling and not on cffi itself (as passing -Wconversion in - CFLAGS would do). We set no error on sign conversion because some - function signatures in OpenSSL have changed from long -> unsigned long - in the past. Since that isn't a precision issue we don't care. - When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 we can - revisit this. - """ - # make sure the compiler used supports the flags to be added - is_gcc = False - if get_default_compiler() == "unix": - d = dist.Distribution() - cmd = config(d) - cmd._check_compiler() - is_gcc = ( - "gcc" in cmd.compiler.compiler[0] - or "clang" in cmd.compiler.compiler[0] - ) - if is_gcc or not ( - platform in ["win32", "hp-ux11", "sunos5"] - or platform.startswith("aix") - ): - return ["-Wconversion", "-Wno-error=sign-conversion"] - else: - return [] - +from _cffi_src.utils import build_ffi_for_binding # noqa: E402 ffi = build_ffi_for_binding( module_name="_openssl", @@ -83,29 +21,25 @@ def _extra_compile_args(platform): modules=[ # This goes first so we can define some cryptography-wide symbols. "cryptography", - "aes", + # Provider comes early as well so we define OSSL_LIB_CTX + "provider", "asn1", "bignum", "bio", "cmac", - "conf", "crypto", - "ct", "dh", "dsa", "ec", - "ecdh", "ecdsa", "engine", "err", "evp", + "evp_aead", "fips", - "hmac", "nid", "objects", - "ocsp", "opensslv", - "osrandom_engine", "pem", "pkcs12", "rand", @@ -118,14 +52,18 @@ def _extra_compile_args(platform): "pkcs7", "callbacks", ], - libraries=_get_openssl_libraries(sys.platform), - # These args are passed here so that we only do Wconversion checks on the - # code we're compiling and not on cffi itself (as passing -Wconversion in - # CFLAGS would do). We set no error on sign convesrion because some - # function signatures in OpenSSL have changed from long -> unsigned long - # in the past. Since that isn't a precision issue we don't care. - # When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 we can - # revisit this. - extra_compile_args=_extra_compile_args(sys.platform), - extra_link_args=extra_link_args(compiler_type()), ) + +if __name__ == "__main__": + out_dir = os.environ["OUT_DIR"] + module_name, source, source_extension, kwds = ffi._assigned_source + c_file = os.path.join(out_dir, module_name + source_extension) + if platform.python_implementation() == "PyPy": + # Necessary because CFFI will ignore this if there's no declarations. + ffi.embedding_api( + """ + extern "Python" void Cryptography_unused(void); + """ + ) + ffi.embedding_init_code("") + ffi.emit_c_code(c_file) diff --git a/src/_cffi_src/build_padding.py b/src/_cffi_src/build_padding.py deleted file mode 100644 index 207f4a658ea2..000000000000 --- a/src/_cffi_src/build_padding.py +++ /dev/null @@ -1,27 +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 absolute_import, division, print_function - -import os - -from _cffi_src.utils import build_ffi, compiler_type, extra_link_args - - -with open( - os.path.join(os.path.dirname(__file__), "hazmat_src/padding.h") -) as f: - types = f.read() - -with open( - os.path.join(os.path.dirname(__file__), "hazmat_src/padding.c") -) as f: - functions = f.read() - -ffi = build_ffi( - module_name="_padding", - cdef_source=types, - verify_source=functions, - extra_link_args=extra_link_args(compiler_type()), -) diff --git a/src/_cffi_src/hazmat_src/padding.c b/src/_cffi_src/hazmat_src/padding.c deleted file mode 100644 index a6e05dee1e39..000000000000 --- a/src/_cffi_src/hazmat_src/padding.c +++ /dev/null @@ -1,65 +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. - -/* Returns the value of the input with the most-significant-bit copied to all - of the bits. */ -static uint16_t Cryptography_DUPLICATE_MSB_TO_ALL(uint16_t a) { - return (1 - (a >> (sizeof(uint16_t) * 8 - 1))) - 1; -} - -/* This returns 0xFFFF if a < b else 0x0000, but does so in a constant time - fashion */ -static uint16_t Cryptography_constant_time_lt(uint16_t a, uint16_t b) { - a -= b; - return Cryptography_DUPLICATE_MSB_TO_ALL(a); -} - -uint8_t Cryptography_check_pkcs7_padding(const uint8_t *data, - uint16_t block_len) { - uint16_t i; - uint16_t pad_size = data[block_len - 1]; - uint16_t mismatch = 0; - for (i = 0; i < block_len; i++) { - unsigned int mask = Cryptography_constant_time_lt(i, pad_size); - uint16_t b = data[block_len - 1 - i]; - mismatch |= (mask & (pad_size ^ b)); - } - - /* Check to make sure the pad_size was within the valid range. */ - mismatch |= ~Cryptography_constant_time_lt(0, pad_size); - mismatch |= Cryptography_constant_time_lt(block_len, pad_size); - - /* Make sure any bits set are copied to the lowest bit */ - mismatch |= mismatch >> 8; - mismatch |= mismatch >> 4; - mismatch |= mismatch >> 2; - mismatch |= mismatch >> 1; - /* Now check the low bit to see if it's set */ - return (mismatch & 1) == 0; -} - -uint8_t Cryptography_check_ansix923_padding(const uint8_t *data, - uint16_t block_len) { - uint16_t i; - uint16_t pad_size = data[block_len - 1]; - uint16_t mismatch = 0; - /* Skip the first one with the pad size */ - for (i = 1; i < block_len; i++) { - unsigned int mask = Cryptography_constant_time_lt(i, pad_size); - uint16_t b = data[block_len - 1 - i]; - mismatch |= (mask & b); - } - - /* Check to make sure the pad_size was within the valid range. */ - mismatch |= ~Cryptography_constant_time_lt(0, pad_size); - mismatch |= Cryptography_constant_time_lt(block_len, pad_size); - - /* Make sure any bits set are copied to the lowest bit */ - mismatch |= mismatch >> 8; - mismatch |= mismatch >> 4; - mismatch |= mismatch >> 2; - mismatch |= mismatch >> 1; - /* Now check the low bit to see if it's set */ - return (mismatch & 1) == 0; -} diff --git a/src/_cffi_src/hazmat_src/padding.h b/src/_cffi_src/hazmat_src/padding.h deleted file mode 100644 index fb023c171108..000000000000 --- a/src/_cffi_src/hazmat_src/padding.h +++ /dev/null @@ -1,6 +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. - -uint8_t Cryptography_check_pkcs7_padding(const uint8_t *, uint8_t); -uint8_t Cryptography_check_ansix923_padding(const uint8_t *, uint8_t); diff --git a/src/_cffi_src/openssl/__init__.py b/src/_cffi_src/openssl/__init__.py index 4b540884df72..b509336233c2 100644 --- a/src/_cffi_src/openssl/__init__.py +++ b/src/_cffi_src/openssl/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/src/_cffi_src/openssl/aes.py b/src/_cffi_src/openssl/aes.py deleted file mode 100644 index 25ef3ec0e3cb..000000000000 --- a/src/_cffi_src/openssl/aes.py +++ /dev/null @@ -1,23 +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 absolute_import, division, print_function - -INCLUDES = """ -#include -""" - -TYPES = """ -typedef ... AES_KEY; -""" - -FUNCTIONS = """ -int AES_wrap_key(AES_KEY *, const unsigned char *, unsigned char *, - const unsigned char *, unsigned int); -int AES_unwrap_key(AES_KEY *, const unsigned char *, unsigned char *, - const unsigned char *, unsigned int); -""" - -CUSTOMIZATIONS = """ -""" diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py index da55b670e041..d2be452a687b 100644 --- a/src/_cffi_src/openssl/asn1.py +++ b/src/_cffi_src/openssl/asn1.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -33,7 +33,6 @@ } ASN1_TYPE; typedef ... ASN1_GENERALIZEDTIME; typedef ... ASN1_ENUMERATED; -typedef ... ASN1_NULL; static const int V_ASN1_GENERALIZEDTIME; @@ -41,19 +40,8 @@ """ FUNCTIONS = """ -void ASN1_OBJECT_free(ASN1_OBJECT *); - /* ASN1 STRING */ -unsigned char *ASN1_STRING_data(ASN1_STRING *); -int ASN1_STRING_set(ASN1_STRING *, const void *, int); - -/* ASN1 OCTET STRING */ -ASN1_OCTET_STRING *ASN1_OCTET_STRING_new(void); -void ASN1_OCTET_STRING_free(ASN1_OCTET_STRING *); -int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *, const unsigned char *, int); - -/* ASN1 IA5STRING */ -ASN1_IA5STRING *ASN1_IA5STRING_new(void); +const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *); /* ASN1 INTEGER */ void ASN1_INTEGER_free(ASN1_INTEGER *); @@ -65,7 +53,6 @@ int ASN1_TIME_set_string(ASN1_TIME *, const char *); /* ASN1 GENERALIZEDTIME */ -ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(ASN1_GENERALIZEDTIME *, time_t); void ASN1_GENERALIZEDTIME_free(ASN1_GENERALIZEDTIME *); /* ASN1 ENUMERATED */ @@ -73,35 +60,17 @@ void ASN1_ENUMERATED_free(ASN1_ENUMERATED *); int ASN1_ENUMERATED_set(ASN1_ENUMERATED *, long); -int ASN1_BIT_STRING_set_bit(ASN1_BIT_STRING *, int, int); -/* These became const ASN1_* in 1.1.0 */ -int ASN1_STRING_type(ASN1_STRING *); -int ASN1_STRING_to_UTF8(unsigned char **, ASN1_STRING *); -long ASN1_ENUMERATED_get(ASN1_ENUMERATED *); -int i2a_ASN1_INTEGER(BIO *, ASN1_INTEGER *); +int ASN1_STRING_type(const ASN1_STRING *); +int ASN1_STRING_to_UTF8(unsigned char **, const ASN1_STRING *); +int i2a_ASN1_INTEGER(BIO *, const ASN1_INTEGER *); -/* This became const ASN1_TIME in 1.1.0f */ -ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(ASN1_TIME *, +ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(const ASN1_TIME *, ASN1_GENERALIZEDTIME **); -ASN1_UTF8STRING *ASN1_UTF8STRING_new(void); -void ASN1_UTF8STRING_free(ASN1_UTF8STRING *); - -ASN1_BIT_STRING *ASN1_BIT_STRING_new(void); -void ASN1_BIT_STRING_free(ASN1_BIT_STRING *); -/* This is not a macro, but is const on some versions of OpenSSL */ -int ASN1_BIT_STRING_get_bit(ASN1_BIT_STRING *, int); - int ASN1_STRING_length(ASN1_STRING *); -int ASN1_STRING_set_default_mask_asc(char *); BIGNUM *ASN1_INTEGER_to_BN(ASN1_INTEGER *, BIGNUM *); ASN1_INTEGER *BN_to_ASN1_INTEGER(BIGNUM *, ASN1_INTEGER *); - -int i2d_ASN1_TYPE(ASN1_TYPE *, unsigned char **); -ASN1_TYPE *d2i_ASN1_TYPE(ASN1_TYPE **, const unsigned char **, long); - -ASN1_NULL *ASN1_NULL_new(void); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/bignum.py b/src/_cffi_src/openssl/bignum.py index 751018391d94..044403325582 100644 --- a/src/_cffi_src/openssl/bignum.py +++ b/src/_cffi_src/openssl/bignum.py @@ -2,13 +2,15 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ +static const long Cryptography_HAS_BN_FLAGS; + typedef ... BN_CTX; typedef ... BN_MONT_CTX; typedef ... BIGNUM; @@ -37,12 +39,8 @@ int BN_MONT_CTX_set(BN_MONT_CTX *, const BIGNUM *, BN_CTX *); void BN_MONT_CTX_free(BN_MONT_CTX *); -BIGNUM *BN_dup(const BIGNUM *); - int BN_set_word(BIGNUM *, BN_ULONG); -const BIGNUM *BN_value_one(void); - char *BN_bn2hex(const BIGNUM *); int BN_hex2bn(BIGNUM **, const char *); @@ -51,19 +49,8 @@ int BN_num_bits(const BIGNUM *); -int BN_cmp(const BIGNUM *, const BIGNUM *); int BN_is_negative(const BIGNUM *); -int BN_add(BIGNUM *, const BIGNUM *, const BIGNUM *); -int BN_sub(BIGNUM *, const BIGNUM *, const BIGNUM *); -int BN_nnmod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); -int BN_mod_add(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); -int BN_mod_sub(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); -int BN_mod_mul(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); -int BN_mod_exp(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); +int BN_is_odd(const BIGNUM *); int BN_mod_exp_mont(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *, BN_MONT_CTX *); int BN_mod_exp_mont_consttime(BIGNUM *, const BIGNUM *, const BIGNUM *, @@ -72,8 +59,6 @@ int BN_num_bytes(const BIGNUM *); -int BN_mod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); - /* The following 3 prime methods are exposed for Tribler. */ int BN_generate_prime_ex(BIGNUM *, int, int, const BIGNUM *, const BIGNUM *, BN_GENCB *); @@ -82,4 +67,13 @@ """ CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_BN_FLAGS = 0; + +static const int BN_FLG_CONSTTIME = 0; +void (*BN_set_flags)(BIGNUM *, int) = NULL; +int (*BN_prime_checks_for_size)(int) = NULL; +#else +static const long Cryptography_HAS_BN_FLAGS = 1; +#endif """ diff --git a/src/_cffi_src/openssl/bio.py b/src/_cffi_src/openssl/bio.py index 8f5a3e6a2b6f..1742e348122a 100644 --- a/src/_cffi_src/openssl/bio.py +++ b/src/_cffi_src/openssl/bio.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -11,23 +11,17 @@ TYPES = """ typedef ... BIO; typedef ... BIO_METHOD; +typedef ... BIO_ADDR; """ FUNCTIONS = """ int BIO_free(BIO *); -void BIO_free_all(BIO *); BIO *BIO_new_file(const char *, const char *); -BIO *BIO_new_dgram(int, int); -size_t BIO_ctrl_pending(BIO *); int BIO_read(BIO *, void *, int); -int BIO_gets(BIO *, char *, int); int BIO_write(BIO *, const void *, int); -/* Added in 1.1.0 */ -int BIO_up_ref(BIO *); BIO *BIO_new(BIO_METHOD *); -BIO_METHOD *BIO_s_mem(void); -BIO_METHOD *BIO_s_datagram(void); +const BIO_METHOD *BIO_s_mem(void); BIO *BIO_new_mem_buf(const void *, int); long BIO_set_mem_eof_return(BIO *, int); long BIO_get_mem_data(BIO *, char **); @@ -36,15 +30,27 @@ int BIO_should_io_special(BIO *); int BIO_should_retry(BIO *); int BIO_reset(BIO *); -void BIO_set_retry_read(BIO *); -void BIO_clear_retry_flags(BIO *); + +BIO_ADDR *BIO_ADDR_new(void); +void BIO_ADDR_free(BIO_ADDR *); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 && !CRYPTOGRAPHY_IS_LIBRESSL -int BIO_up_ref(BIO *b) { - CRYPTO_add(&b->references, 1, CRYPTO_LOCK_BIO); - return 1; +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL + +#if !defined(_WIN32) +#include +#endif + +#include +typedef struct sockaddr BIO_ADDR; + +BIO_ADDR *BIO_ADDR_new(void) { + return malloc(sizeof(struct sockaddr_storage)); +} + +void BIO_ADDR_free(BIO_ADDR *ptr) { + free(ptr); } #endif """ diff --git a/src/_cffi_src/openssl/callbacks.py b/src/_cffi_src/openssl/callbacks.py index 33ebf4df400f..ddb764283920 100644 --- a/src/_cffi_src/openssl/callbacks.py +++ b/src/_cffi_src/openssl/callbacks.py @@ -2,28 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ -#include -#include -#include -#include - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#else -#include -#include -#include -#endif - -#ifdef __MVS__ -#include -#endif +#include """ TYPES = """ @@ -37,124 +19,10 @@ """ FUNCTIONS = """ -int Cryptography_setup_ssl_threads(void); int Cryptography_pem_password_cb(char *, int, int, void *); """ CUSTOMIZATIONS = """ -/* This code is derived from the locking code found in the Python _ssl module's - locking callback for OpenSSL. - - Copyright 2001-2016 Python Software Foundation; All Rights Reserved. - - It has been subsequently modified to use cross platform locking without - using CPython APIs by Armin Rigo of the PyPy project. -*/ - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 -#ifdef _WIN32 -typedef CRITICAL_SECTION Cryptography_mutex; -static __inline void cryptography_mutex_init(Cryptography_mutex *mutex) { - InitializeCriticalSection(mutex); -} -static __inline void cryptography_mutex_lock(Cryptography_mutex *mutex) { - EnterCriticalSection(mutex); -} -static __inline void cryptography_mutex_unlock(Cryptography_mutex *mutex) { - LeaveCriticalSection(mutex); -} -#else -typedef pthread_mutex_t Cryptography_mutex; -#define ASSERT_STATUS(call) \ - if ((call) != 0) { \ - perror("Fatal error in callback initialization: " #call); \ - abort(); \ - } -#ifdef __MVS__ -/* When pthread_mutex_init is called more than once on the same mutex, - on z/OS this throws an EBUSY error. -*/ -#define ASSERT_STATUS_INIT(call) \ - if ((call) != 0 && errno != EBUSY) { \ - perror("Fatal error in callback initialization: " #call); \ - abort(); \ - } -#else -#define ASSERT_STATUS_INIT ASSERT_STATUS -#endif -static inline void cryptography_mutex_init(Cryptography_mutex *mutex) { -#if !defined(pthread_mutexattr_default) -# define pthread_mutexattr_default ((pthread_mutexattr_t *)NULL) -#endif - ASSERT_STATUS_INIT(pthread_mutex_init(mutex, pthread_mutexattr_default)); -} -static inline void cryptography_mutex_lock(Cryptography_mutex *mutex) { - ASSERT_STATUS(pthread_mutex_lock(mutex)); -} -static inline void cryptography_mutex_unlock(Cryptography_mutex *mutex) { - ASSERT_STATUS(pthread_mutex_unlock(mutex)); -} -#endif - - -static int _ssl_locks_count = 0; -static Cryptography_mutex *_ssl_locks = NULL; - -static void _ssl_thread_locking_function(int mode, int n, const char *file, - int line) { - /* this function is needed to perform locking on shared data - structures. (Note that OpenSSL uses a number of global data - structures that will be implicitly shared whenever multiple - threads use OpenSSL.) Multi-threaded applications will - crash at random if it is not set. - - locking_function() must be able to handle up to - CRYPTO_num_locks() different mutex locks. It sets the n-th - lock if mode & CRYPTO_LOCK, and releases it otherwise. - - file and line are the file number of the function setting the - lock. They can be useful for debugging. - */ - - if ((_ssl_locks == NULL) || - (n < 0) || (n >= _ssl_locks_count)) { - return; - } - - if (mode & CRYPTO_LOCK) { - cryptography_mutex_lock(_ssl_locks + n); - } else { - cryptography_mutex_unlock(_ssl_locks + n); - } -} - -static void init_mutexes(void) { - int i; - for (i = 0; i < _ssl_locks_count; i++) { - cryptography_mutex_init(_ssl_locks + i); - } -} - - -int Cryptography_setup_ssl_threads(void) { - if (_ssl_locks == NULL) { - _ssl_locks_count = CRYPTO_num_locks(); - _ssl_locks = calloc(_ssl_locks_count, sizeof(Cryptography_mutex)); - if (_ssl_locks == NULL) { - return 0; - } - init_mutexes(); - CRYPTO_set_locking_callback(_ssl_thread_locking_function); -#ifndef _WIN32 - pthread_atfork(NULL, NULL, &init_mutexes); -#endif - } - return 1; -} -#else -int (*Cryptography_setup_ssl_threads)(void) = NULL; -#endif - typedef struct { char *password; int length; @@ -164,7 +32,7 @@ } CRYPTOGRAPHY_PASSWORD_DATA; int Cryptography_pem_password_cb(char *buf, int size, - int rwflag, void *userdata) { + int rwflag, void *userdata) { /* The password cb is only invoked if OpenSSL decides the private key is encrypted. So this path only occurs if it needs a password */ CRYPTOGRAPHY_PASSWORD_DATA *st = (CRYPTOGRAPHY_PASSWORD_DATA *)userdata; @@ -174,7 +42,7 @@ st->error = -1; return 0; } else if (st->length < size) { - memcpy(buf, st->password, st->length); + memcpy(buf, st->password, (size_t)st->length); return st->length; } else { st->error = -2; diff --git a/src/_cffi_src/openssl/cmac.py b/src/_cffi_src/openssl/cmac.py index 557abd1ca8f9..7095066dac54 100644 --- a/src/_cffi_src/openssl/cmac.py +++ b/src/_cffi_src/openssl/cmac.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #if !defined(OPENSSL_NO_CMAC) diff --git a/src/_cffi_src/openssl/conf.py b/src/_cffi_src/openssl/conf.py deleted file mode 100644 index 9db0162a633f..000000000000 --- a/src/_cffi_src/openssl/conf.py +++ /dev/null @@ -1,21 +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 absolute_import, division, print_function - -INCLUDES = """ -#include -""" - -TYPES = """ -""" - -FUNCTIONS = """ -void OPENSSL_config(const char *); -/* This is a macro in 1.1.0 */ -void OPENSSL_no_config(void); -""" - -CUSTOMIZATIONS = """ -""" diff --git a/src/_cffi_src/openssl/crypto.py b/src/_cffi_src/openssl/crypto.py index f3623b21f146..b81b5de1da27 100644 --- a/src/_cffi_src/openssl/crypto.py +++ b/src/_cffi_src/openssl/crypto.py @@ -2,22 +2,15 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ -static const long Cryptography_HAS_LOCKING_CALLBACKS; static const long Cryptography_HAS_MEM_FUNCTIONS; -static const long Cryptography_HAS_OPENSSL_CLEANUP; -static const int SSLEAY_VERSION; -static const int SSLEAY_CFLAGS; -static const int SSLEAY_PLATFORM; -static const int SSLEAY_DIR; -static const int SSLEAY_BUILT_ON; static const int OPENSSL_VERSION; static const int OPENSSL_CFLAGS; static const int OPENSSL_BUILT_ON; @@ -28,23 +21,15 @@ FUNCTIONS = """ void OPENSSL_cleanup(void); -/* as of 1.1.0 OpenSSL does its own locking *angelic chorus*. This function - is now a noop macro. We can delete this once we drop 1.0.2 support. */ -void (*CRYPTO_get_locking_callback(void))(int, int, const char *, int); - -/* SSLeay was removed in 1.1.0 */ -unsigned long SSLeay(void); -const char *SSLeay_version(int); -/* these functions were added to replace the SSLeay functions in 1.1.0 */ unsigned long OpenSSL_version_num(void); const char *OpenSSL_version(int); -/* this is a macro in 1.1.0 */ void *OPENSSL_malloc(size_t); void OPENSSL_free(void *); -/* Signature changed significantly in 1.1.0, only expose there for sanity */ +/* Signature is significantly different in LibreSSL, so expose via different + symbol name */ int Cryptography_CRYPTO_set_mem_functions( void *(*)(size_t, const char *, int), void *(*)(void *, size_t, const char *, int), @@ -56,43 +41,7 @@ """ CUSTOMIZATIONS = """ -/* In 1.1.0 SSLeay has finally been retired. We bidirectionally define the - values so you can use either one. This is so we can use the new function - names no matter what OpenSSL we're running on, but users on older pyOpenSSL - releases won't see issues if they're running OpenSSL 1.1.0 */ -#if !defined(SSLEAY_VERSION) -# define SSLeay OpenSSL_version_num -# define SSLeay_version OpenSSL_version -# define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER -# define SSLEAY_VERSION OPENSSL_VERSION -# define SSLEAY_CFLAGS OPENSSL_CFLAGS -# define SSLEAY_BUILT_ON OPENSSL_BUILT_ON -# define SSLEAY_PLATFORM OPENSSL_PLATFORM -# define SSLEAY_DIR OPENSSL_DIR -#endif -#if !defined(OPENSSL_VERSION) -# define OpenSSL_version_num SSLeay -# define OpenSSL_version SSLeay_version -# define OPENSSL_VERSION SSLEAY_VERSION -# define OPENSSL_CFLAGS SSLEAY_CFLAGS -# define OPENSSL_BUILT_ON SSLEAY_BUILT_ON -# define OPENSSL_PLATFORM SSLEAY_PLATFORM -# define OPENSSL_DIR SSLEAY_DIR -#endif -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 -static const long Cryptography_HAS_LOCKING_CALLBACKS = 1; -#else -static const long Cryptography_HAS_LOCKING_CALLBACKS = 0; -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 -static const long Cryptography_HAS_OPENSSL_CLEANUP = 0; - -void (*OPENSSL_cleanup)(void) = NULL; - -/* This function has a significantly different signature pre-1.1.0. since it is - * for testing only, we don't bother to expose it on older OpenSSLs. - */ +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_MEM_FUNCTIONS = 0; int (*Cryptography_CRYPTO_set_mem_functions)( void *(*)(size_t, const char *, int), @@ -100,7 +49,6 @@ void (*)(void *, const char *, int)) = NULL; #else -static const long Cryptography_HAS_OPENSSL_CLEANUP = 1; static const long Cryptography_HAS_MEM_FUNCTIONS = 1; int Cryptography_CRYPTO_set_mem_functions( diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index d9d4a9ea0f41..f5fcb04405b5 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -2,13 +2,30 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ -/* define our OpenSSL API compatibility level to 1.0.1. Any symbols older than - that will raise an error during compilation. We can raise this number again - after we drop 1.0.2 support in the distant future. */ -#define OPENSSL_API_COMPAT 0x10001000L +/* define our OpenSSL API compatibility level to 1.1.0. Any symbols older than + that will raise an error during compilation. */ +#define OPENSSL_API_COMPAT 0x10100000L + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +/* + undef some macros that are defined by wincrypt.h but are also types in + boringssl. openssl has worked around this but boring has not yet. see: + https://chromium.googlesource.com/chromium/src/+/refs/heads/main/base + /win/wincrypt_shim.h +*/ +#undef X509_NAME +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO +#endif #include @@ -19,62 +36,45 @@ #define CRYPTOGRAPHY_IS_LIBRESSL 0 #endif -/* - LibreSSL removed e_os2.h from the public headers so we'll only include it - if we're using vanilla OpenSSL. -*/ -#if !CRYPTOGRAPHY_IS_LIBRESSL -#include -#endif -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#include -#include -#include +#if defined(OPENSSL_IS_BORINGSSL) +#define CRYPTOGRAPHY_IS_BORINGSSL 1 +#else +#define CRYPTOGRAPHY_IS_BORINGSSL 0 #endif -#define CRYPTOGRAPHY_OPENSSL_102L_OR_GREATER \ - (OPENSSL_VERSION_NUMBER >= 0x100020cf && !CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_102U_OR_GREATER \ - (OPENSSL_VERSION_NUMBER >= 0x1000215fL && !CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_110_OR_GREATER \ - (OPENSSL_VERSION_NUMBER >= 0x10100000 && !CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER \ - (OPENSSL_VERSION_NUMBER >= 0x1010006f && !CRYPTOGRAPHY_IS_LIBRESSL) - -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I \ - (OPENSSL_VERSION_NUMBER < 0x1000209f || CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 \ - (OPENSSL_VERSION_NUMBER < 0x10100000 || CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110J \ - (OPENSSL_VERSION_NUMBER < 0x101000af || CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 \ - (OPENSSL_VERSION_NUMBER < 0x10101000 || CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B \ - (OPENSSL_VERSION_NUMBER < 0x10101020 || CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111D \ - (OPENSSL_VERSION_NUMBER < 0x10101040 || CRYPTOGRAPHY_IS_LIBRESSL) -#if (CRYPTOGRAPHY_OPENSSL_LESS_THAN_111D && !defined(OPENSSL_NO_ENGINE)) || \ - defined(USE_OSRANDOM_RNG_FOR_TESTING) -#define CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE 1 +#if CRYPTOGRAPHY_IS_LIBRESSL +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 \ + (LIBRESSL_VERSION_NUMBER < 0x3070000f) + #else -#define CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE 0 +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 (0) +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10101040 + #error "pyca/cryptography MUST be linked with Openssl 1.1.1d or later" #endif + +#define CRYPTOGRAPHY_OPENSSL_300_OR_GREATER \ + (OPENSSL_VERSION_NUMBER >= 0x30000000 && !CRYPTOGRAPHY_IS_LIBRESSL) + +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E \ + (OPENSSL_VERSION_NUMBER < 0x10101050 || CRYPTOGRAPHY_IS_LIBRESSL) +/* Ed25519 support is in all supported OpenSSLs as well as LibreSSL 3.7.0. */ +#define CRYPTOGRAPHY_HAS_WORKING_ED25519 \ + (!CRYPTOGRAPHY_IS_LIBRESSL || \ + (CRYPTOGRAPHY_IS_LIBRESSL && !CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370)) """ TYPES = """ -static const int CRYPTOGRAPHY_OPENSSL_102L_OR_GREATER; -static const int CRYPTOGRAPHY_OPENSSL_102U_OR_GREATER; -static const int CRYPTOGRAPHY_OPENSSL_110_OR_GREATER; -static const int CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER; +static const int CRYPTOGRAPHY_OPENSSL_300_OR_GREATER; + +static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E; +static const int CRYPTOGRAPHY_HAS_WORKING_ED25519; -static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I; -static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_110; -static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111; -static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B; -static const int CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE; +static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370; static const int CRYPTOGRAPHY_IS_LIBRESSL; +static const int CRYPTOGRAPHY_IS_BORINGSSL; """ FUNCTIONS = """ diff --git a/src/_cffi_src/openssl/ct.py b/src/_cffi_src/openssl/ct.py deleted file mode 100644 index 162004a8da73..000000000000 --- a/src/_cffi_src/openssl/ct.py +++ /dev/null @@ -1,116 +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 absolute_import, division, print_function - -INCLUDES = """ -#if CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER && !defined(OPENSSL_NO_CT) -#include - -typedef STACK_OF(SCT) Cryptography_STACK_OF_SCT; -#endif -""" - -TYPES = """ -static const long Cryptography_HAS_SCT; - -typedef enum { - SCT_VERSION_NOT_SET, - SCT_VERSION_V1 -} sct_version_t; - -typedef enum { - CT_LOG_ENTRY_TYPE_NOT_SET, - CT_LOG_ENTRY_TYPE_X509, - CT_LOG_ENTRY_TYPE_PRECERT -} ct_log_entry_type_t; - -typedef enum { - SCT_SOURCE_UNKNOWN, - SCT_SOURCE_TLS_EXTENSION, - SCT_SOURCE_X509V3_EXTENSION, - SCT_SOURCE_OCSP_STAPLED_RESPONSE -} sct_source_t; - -typedef ... SCT; -typedef ... Cryptography_STACK_OF_SCT; -""" - -FUNCTIONS = """ -sct_version_t SCT_get_version(const SCT *); - -ct_log_entry_type_t SCT_get_log_entry_type(const SCT *); - -size_t SCT_get0_log_id(const SCT *, unsigned char **); - -size_t SCT_get0_signature(const SCT *, unsigned char **); - -uint64_t SCT_get_timestamp(const SCT *); - -int SCT_set_source(SCT *, sct_source_t); - -int sk_SCT_num(const Cryptography_STACK_OF_SCT *); -SCT *sk_SCT_value(const Cryptography_STACK_OF_SCT *, int); - -void SCT_LIST_free(Cryptography_STACK_OF_SCT *); - -int sk_SCT_push(Cryptography_STACK_OF_SCT *, SCT *); -Cryptography_STACK_OF_SCT *sk_SCT_new_null(void); -SCT *SCT_new(void); -int SCT_set1_log_id(SCT *, unsigned char *, size_t); -void SCT_set_timestamp(SCT *, uint64_t); -int SCT_set_version(SCT *, sct_version_t); -int SCT_set_log_entry_type(SCT *, ct_log_entry_type_t); -""" - -CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER && !defined(OPENSSL_NO_CT) -static const long Cryptography_HAS_SCT = 1; -#else -static const long Cryptography_HAS_SCT = 0; - -typedef enum { - SCT_VERSION_NOT_SET, - SCT_VERSION_V1 -} sct_version_t; -typedef enum { - CT_LOG_ENTRY_TYPE_NOT_SET, - CT_LOG_ENTRY_TYPE_X509, - CT_LOG_ENTRY_TYPE_PRECERT -} ct_log_entry_type_t; -typedef enum { - SCT_SOURCE_UNKNOWN, - SCT_SOURCE_TLS_EXTENSION, - SCT_SOURCE_X509V3_EXTENSION, - SCT_SOURCE_OCSP_STAPLED_RESPONSE -} sct_source_t; - -/* OpenSSL compiled with `no-ct` still defines the `SCT` struct. */ -#if !defined(OPENSSL_NO_CT) -typedef void SCT; -#endif - -typedef void Cryptography_STACK_OF_SCT; - -sct_version_t (*SCT_get_version)(const SCT *) = NULL; -ct_log_entry_type_t (*SCT_get_log_entry_type)(const SCT *) = NULL; -size_t (*SCT_get0_log_id)(const SCT *, unsigned char **) = NULL; -size_t (*SCT_get0_signature)(const SCT *, unsigned char **) = NULL; -uint64_t (*SCT_get_timestamp)(const SCT *) = NULL; - -int (*SCT_set_source)(SCT *, sct_source_t) = NULL; - -int (*sk_SCT_num)(const Cryptography_STACK_OF_SCT *) = NULL; -SCT *(*sk_SCT_value)(const Cryptography_STACK_OF_SCT *, int) = NULL; - -void (*SCT_LIST_free)(Cryptography_STACK_OF_SCT *) = NULL; -int (*sk_SCT_push)(Cryptography_STACK_OF_SCT *, SCT *) = NULL; -Cryptography_STACK_OF_SCT *(*sk_SCT_new_null)(void) = NULL; -SCT *(*SCT_new)(void) = NULL; -int (*SCT_set1_log_id)(SCT *, unsigned char *, size_t) = NULL; -void (*SCT_set_timestamp)(SCT *, uint64_t) = NULL; -int (*SCT_set_version)(SCT *, sct_version_t) = NULL; -int (*SCT_set_log_entry_type)(SCT *, ct_log_entry_type_t) = NULL; -#endif -""" diff --git a/src/_cffi_src/openssl/dh.py b/src/_cffi_src/openssl/dh.py index 0e1df23a6ac9..b4a42e7f6058 100644 --- a/src/_cffi_src/openssl/dh.py +++ b/src/_cffi_src/openssl/dh.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -15,224 +15,8 @@ """ FUNCTIONS = """ -DH *DH_new(void); void DH_free(DH *); -int DH_size(const DH *); -int DH_generate_key(DH *); -int DH_compute_key(unsigned char *, const BIGNUM *, DH *); -DH *DHparams_dup(DH *); - -/* added in 1.1.0 when the DH struct was opaqued */ -void DH_get0_pqg(const DH *, const BIGNUM **, const BIGNUM **, - const BIGNUM **); -int DH_set0_pqg(DH *, BIGNUM *, BIGNUM *, BIGNUM *); -void DH_get0_key(const DH *, const BIGNUM **, const BIGNUM **); -int DH_set0_key(DH *, BIGNUM *, BIGNUM *); - -int Cryptography_DH_check(const DH *, int *); -int DH_generate_parameters_ex(DH *, int, int, BN_GENCB *); -DH *d2i_DHparams_bio(BIO *, DH **); -int i2d_DHparams_bio(BIO *, DH *); -DH *Cryptography_d2i_DHxparams_bio(BIO *bp, DH **x); -int Cryptography_i2d_DHxparams_bio(BIO *bp, DH *x); """ CUSTOMIZATIONS = """ -/* These functions were added in OpenSSL 1.1.0 */ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 && !CRYPTOGRAPHY_IS_LIBRESSL -void DH_get0_pqg(const DH *dh, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) -{ - if (p != NULL) - *p = dh->p; - if (q != NULL) - *q = dh->q; - if (g != NULL) - *g = dh->g; -} - -int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) -{ - /* If the fields p and g in d are NULL, the corresponding input - * parameters MUST be non-NULL. q may remain NULL. - */ - if ((dh->p == NULL && p == NULL) - || (dh->g == NULL && g == NULL)) - return 0; - - if (p != NULL) { - BN_free(dh->p); - dh->p = p; - } - if (q != NULL) { - BN_free(dh->q); - dh->q = q; - } - if (g != NULL) { - BN_free(dh->g); - dh->g = g; - } - - if (q != NULL) { - dh->length = BN_num_bits(q); - } - - return 1; -} - -void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) -{ - if (pub_key != NULL) - *pub_key = dh->pub_key; - if (priv_key != NULL) - *priv_key = dh->priv_key; -} - -int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) -{ - /* If the field pub_key in dh is NULL, the corresponding input - * parameters MUST be non-NULL. The priv_key field may - * be left NULL. - */ - if (dh->pub_key == NULL && pub_key == NULL) - return 0; - - if (pub_key != NULL) { - BN_free(dh->pub_key); - dh->pub_key = pub_key; - } - if (priv_key != NULL) { - BN_free(dh->priv_key); - dh->priv_key = priv_key; - } - - return 1; -} -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 -#ifndef DH_CHECK_Q_NOT_PRIME -#define DH_CHECK_Q_NOT_PRIME 0x10 -#endif - -#ifndef DH_CHECK_INVALID_Q_VALUE -#define DH_CHECK_INVALID_Q_VALUE 0x20 -#endif - -#ifndef DH_CHECK_INVALID_J_VALUE -#define DH_CHECK_INVALID_J_VALUE 0x40 -#endif - -/* DH_check implementation taken from OpenSSL 1.1.0pre6 */ - -/*- - * Check that p is a safe prime and - * if g is 2, 3 or 5, check that it is a suitable generator - * where - * for 2, p mod 24 == 11 - * for 3, p mod 12 == 5 - * for 5, p mod 10 == 3 or 7 - * should hold. - */ - -int Cryptography_DH_check(const DH *dh, int *ret) -{ - int ok = 0, r; - BN_CTX *ctx = NULL; - BN_ULONG l; - BIGNUM *t1 = NULL, *t2 = NULL; - - *ret = 0; - ctx = BN_CTX_new(); - if (ctx == NULL) - goto err; - BN_CTX_start(ctx); - t1 = BN_CTX_get(ctx); - if (t1 == NULL) - goto err; - t2 = BN_CTX_get(ctx); - if (t2 == NULL) - goto err; - - if (dh->q) { - if (BN_cmp(dh->g, BN_value_one()) <= 0) - *ret |= DH_NOT_SUITABLE_GENERATOR; - else if (BN_cmp(dh->g, dh->p) >= 0) - *ret |= DH_NOT_SUITABLE_GENERATOR; - else { - /* Check g^q == 1 mod p */ - if (!BN_mod_exp(t1, dh->g, dh->q, dh->p, ctx)) - goto err; - if (!BN_is_one(t1)) - *ret |= DH_NOT_SUITABLE_GENERATOR; - } - r = BN_is_prime_ex(dh->q, BN_prime_checks, ctx, NULL); - if (r < 0) - goto err; - if (!r) - *ret |= DH_CHECK_Q_NOT_PRIME; - /* Check p == 1 mod q i.e. q divides p - 1 */ - if (!BN_div(t1, t2, dh->p, dh->q, ctx)) - goto err; - if (!BN_is_one(t2)) - *ret |= DH_CHECK_INVALID_Q_VALUE; - if (dh->j && BN_cmp(dh->j, t1)) - *ret |= DH_CHECK_INVALID_J_VALUE; - - } else if (BN_is_word(dh->g, DH_GENERATOR_2)) { - l = BN_mod_word(dh->p, 24); - if (l == (BN_ULONG)-1) - goto err; - if (l != 11) - *ret |= DH_NOT_SUITABLE_GENERATOR; - } else if (BN_is_word(dh->g, DH_GENERATOR_5)) { - l = BN_mod_word(dh->p, 10); - if (l == (BN_ULONG)-1) - goto err; - if ((l != 3) && (l != 7)) - *ret |= DH_NOT_SUITABLE_GENERATOR; - } else - *ret |= DH_UNABLE_TO_CHECK_GENERATOR; - - r = BN_is_prime_ex(dh->p, BN_prime_checks, ctx, NULL); - if (r < 0) - goto err; - if (!r) - *ret |= DH_CHECK_P_NOT_PRIME; - else if (!dh->q) { - if (!BN_rshift1(t1, dh->p)) - goto err; - r = BN_is_prime_ex(t1, BN_prime_checks, ctx, NULL); - if (r < 0) - goto err; - if (!r) - *ret |= DH_CHECK_P_NOT_SAFE_PRIME; - } - ok = 1; - err: - if (ctx != NULL) { - BN_CTX_end(ctx); - BN_CTX_free(ctx); - } - return (ok); -} -#else -int Cryptography_DH_check(const DH *dh, int *ret) { - return DH_check(dh, ret); -} -#endif - -/* These functions were added in OpenSSL 1.1.0f commit d0c50e80a8 */ -/* Define our own to simplify support across all versions. */ -#if defined(EVP_PKEY_DHX) && EVP_PKEY_DHX != -1 -DH *Cryptography_d2i_DHxparams_bio(BIO *bp, DH **x) { - return ASN1_d2i_bio_of(DH, DH_new, d2i_DHxparams, bp, x); -} -int Cryptography_i2d_DHxparams_bio(BIO *bp, DH *x) { - return ASN1_i2d_bio_of_const(DH, i2d_DHxparams, bp, x); -} -#else -DH *(*Cryptography_d2i_DHxparams_bio)(BIO *bp, DH **x) = NULL; -int (*Cryptography_i2d_DHxparams_bio)(BIO *bp, DH *x) = NULL; -#endif """ diff --git a/src/_cffi_src/openssl/dsa.py b/src/_cffi_src/openssl/dsa.py index 938c18fcf1b1..d91076393582 100644 --- a/src/_cffi_src/openssl/dsa.py +++ b/src/_cffi_src/openssl/dsa.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -23,81 +23,11 @@ int DSA_verify(int, const unsigned char *, int, const unsigned char *, int, DSA *); -/* added in 1.1.0 to access the opaque struct */ -void DSA_get0_pqg(const DSA *, const BIGNUM **, const BIGNUM **, - const BIGNUM **); int DSA_set0_pqg(DSA *, BIGNUM *, BIGNUM *, BIGNUM *); -void DSA_get0_key(const DSA *, const BIGNUM **, const BIGNUM **); int DSA_set0_key(DSA *, BIGNUM *, BIGNUM *); int DSA_generate_parameters_ex(DSA *, int, unsigned char *, int, int *, unsigned long *, BN_GENCB *); """ CUSTOMIZATIONS = """ -/* These functions were added in OpenSSL 1.1.0 */ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 && !CRYPTOGRAPHY_IS_LIBRESSL -void DSA_get0_pqg(const DSA *d, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) -{ - if (p != NULL) - *p = d->p; - if (q != NULL) - *q = d->q; - if (g != NULL) - *g = d->g; -} -int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) -{ - /* If the fields p, q and g in d are NULL, the corresponding input - * parameters MUST be non-NULL. - */ - if ((d->p == NULL && p == NULL) - || (d->q == NULL && q == NULL) - || (d->g == NULL && g == NULL)) - return 0; - - if (p != NULL) { - BN_free(d->p); - d->p = p; - } - if (q != NULL) { - BN_free(d->q); - d->q = q; - } - if (g != NULL) { - BN_free(d->g); - d->g = g; - } - - return 1; -} -void DSA_get0_key(const DSA *d, - const BIGNUM **pub_key, const BIGNUM **priv_key) -{ - if (pub_key != NULL) - *pub_key = d->pub_key; - if (priv_key != NULL) - *priv_key = d->priv_key; -} -int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) -{ - /* If the field pub_key in d is NULL, the corresponding input - * parameters MUST be non-NULL. The priv_key field may - * be left NULL. - */ - if (d->pub_key == NULL && pub_key == NULL) - return 0; - - if (pub_key != NULL) { - BN_free(d->pub_key); - d->pub_key = pub_key; - } - if (priv_key != NULL) { - BN_free(d->priv_key); - d->priv_key = priv_key; - } - - return 1; -} -#endif """ diff --git a/src/_cffi_src/openssl/ec.py b/src/_cffi_src/openssl/ec.py index 6432fc22e9e0..0e3604d1d29a 100644 --- a/src/_cffi_src/openssl/ec.py +++ b/src/_cffi_src/openssl/ec.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -10,14 +10,11 @@ """ TYPES = """ -static const int Cryptography_HAS_EC2M; - static const int OPENSSL_EC_NAMED_CURVE; typedef ... EC_KEY; typedef ... EC_GROUP; typedef ... EC_POINT; -typedef ... EC_METHOD; typedef struct { int nid; const char *comment; @@ -34,51 +31,30 @@ EC_GROUP *EC_GROUP_new_by_curve_name(int); -int EC_GROUP_get_degree(const EC_GROUP *); - -const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *); -const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *); int EC_GROUP_get_curve_name(const EC_GROUP *); size_t EC_get_builtin_curves(EC_builtin_curve *, size_t); -EC_KEY *EC_KEY_new(void); void EC_KEY_free(EC_KEY *); EC_KEY *EC_KEY_new_by_curve_name(int); const EC_GROUP *EC_KEY_get0_group(const EC_KEY *); -int EC_GROUP_get_order(const EC_GROUP *, BIGNUM *, BN_CTX *); -int EC_KEY_set_group(EC_KEY *, const EC_GROUP *); const BIGNUM *EC_KEY_get0_private_key(const EC_KEY *); int EC_KEY_set_private_key(EC_KEY *, const BIGNUM *); const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *); int EC_KEY_set_public_key(EC_KEY *, const EC_POINT *); void EC_KEY_set_asn1_flag(EC_KEY *, int); int EC_KEY_generate_key(EC_KEY *); -int EC_KEY_set_public_key_affine_coordinates(EC_KEY *, BIGNUM *, BIGNUM *); EC_POINT *EC_POINT_new(const EC_GROUP *); void EC_POINT_free(EC_POINT *); -void EC_POINT_clear_free(EC_POINT *); -EC_POINT *EC_POINT_dup(const EC_POINT *, const EC_GROUP *); - -int EC_POINT_set_affine_coordinates_GFp(const EC_GROUP *, EC_POINT *, - const BIGNUM *, const BIGNUM *, BN_CTX *); - -int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *, - const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *); - -int EC_POINT_set_compressed_coordinates_GFp(const EC_GROUP *, EC_POINT *, - const BIGNUM *, int, BN_CTX *); - -int EC_POINT_set_affine_coordinates_GF2m(const EC_GROUP *, EC_POINT *, - const BIGNUM *, const BIGNUM *, BN_CTX *); - -int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP *, - const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *); +int EC_POINT_cmp(const EC_GROUP *, const EC_POINT *, const EC_POINT *, + BN_CTX *); -int EC_POINT_set_compressed_coordinates_GF2m(const EC_GROUP *, EC_POINT *, - const BIGNUM *, int, BN_CTX *); +int EC_POINT_set_affine_coordinates(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, BN_CTX *); +int EC_POINT_get_affine_coordinates(const EC_GROUP *, const EC_POINT *, + BIGNUM *, BIGNUM *, BN_CTX *); size_t EC_POINT_point2oct(const EC_GROUP *, const EC_POINT *, point_conversion_form_t, @@ -87,40 +63,15 @@ int EC_POINT_oct2point(const EC_GROUP *, EC_POINT *, const unsigned char *, size_t, BN_CTX *); -int EC_POINT_add(const EC_GROUP *, EC_POINT *, const EC_POINT *, - const EC_POINT *, BN_CTX *); - -int EC_POINT_dbl(const EC_GROUP *, EC_POINT *, const EC_POINT *, BN_CTX *); -int EC_POINT_invert(const EC_GROUP *, EC_POINT *, BN_CTX *); int EC_POINT_is_at_infinity(const EC_GROUP *, const EC_POINT *); -int EC_POINT_is_on_curve(const EC_GROUP *, const EC_POINT *, BN_CTX *); - -int EC_POINT_cmp( - const EC_GROUP *, const EC_POINT *, const EC_POINT *, BN_CTX *); int EC_POINT_mul(const EC_GROUP *, EC_POINT *, const BIGNUM *, const EC_POINT *, const BIGNUM *, BN_CTX *); -int EC_METHOD_get_field_type(const EC_METHOD *); - const char *EC_curve_nid2nist(int); int EC_GROUP_get_asn1_flag(const EC_GROUP *); """ CUSTOMIZATIONS = """ -#if defined(OPENSSL_NO_EC2M) -static const long Cryptography_HAS_EC2M = 0; - -int (*EC_POINT_set_affine_coordinates_GF2m)(const EC_GROUP *, EC_POINT *, - const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; - -int (*EC_POINT_get_affine_coordinates_GF2m)(const EC_GROUP *, - const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *) = NULL; - -int (*EC_POINT_set_compressed_coordinates_GF2m)(const EC_GROUP *, EC_POINT *, - const BIGNUM *, int, BN_CTX *) = NULL; -#else -static const long Cryptography_HAS_EC2M = 1; -#endif """ diff --git a/src/_cffi_src/openssl/ecdh.py b/src/_cffi_src/openssl/ecdh.py deleted file mode 100644 index c73cc9f36fdd..000000000000 --- a/src/_cffi_src/openssl/ecdh.py +++ /dev/null @@ -1,21 +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 absolute_import, division, print_function - -INCLUDES = """ -#include -""" - -TYPES = """ -""" - -FUNCTIONS = """ -int ECDH_compute_key(void *, size_t, const EC_POINT *, EC_KEY *, - void *(*)(const void *, size_t, void *, size_t *)); -long SSL_CTX_set_ecdh_auto(SSL_CTX *, int); -""" - -CUSTOMIZATIONS = """ -""" diff --git a/src/_cffi_src/openssl/ecdsa.py b/src/_cffi_src/openssl/ecdsa.py index 3134e24b61d4..716b5d03016f 100644 --- a/src/_cffi_src/openssl/ecdsa.py +++ b/src/_cffi_src/openssl/ecdsa.py @@ -2,18 +2,13 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ -typedef ... ECDSA_SIG; - -typedef ... CRYPTO_EX_new; -typedef ... CRYPTO_EX_dup; -typedef ... CRYPTO_EX_free; """ FUNCTIONS = """ diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py index 24cdd42a8393..609313ec57ae 100644 --- a/src/_cffi_src/openssl/engine.py +++ b/src/_cffi_src/openssl/engine.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -38,6 +38,16 @@ #ifdef OPENSSL_NO_ENGINE static const long Cryptography_HAS_ENGINE = 0; +#if CRYPTOGRAPHY_IS_BORINGSSL +typedef void UI_METHOD; +#endif + +/* Despite being OPENSSL_NO_ENGINE, BoringSSL defines these symbols. */ +#if !CRYPTOGRAPHY_IS_BORINGSSL +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; @@ -47,13 +57,11 @@ int (*ENGINE_ctrl_cmd)(ENGINE *, const char *, long, void *, void (*)(void), int) = NULL; -int (*ENGINE_free)(ENGINE *) = NULL; const char *(*ENGINE_get_id)(const ENGINE *) = NULL; const char *(*ENGINE_get_name)(const ENGINE *) = NULL; int (*ENGINE_ctrl_cmd_string)(ENGINE *, const char *, const char *, int) = NULL; -void (*ENGINE_load_builtin_engines)(void) = NULL; EVP_PKEY *(*ENGINE_load_private_key)(ENGINE *, const char *, UI_METHOD *, void *) = NULL; EVP_PKEY *(*ENGINE_load_public_key)(ENGINE *, const char *, diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index 0dd7414674fe..2bb2545fc932 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -2,35 +2,36 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ +static const int CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; + static const int EVP_F_EVP_ENCRYPTFINAL_EX; static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; static const int EVP_R_BAD_DECRYPT; static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM; static const int PKCS12_R_PKCS12_CIPHERFINAL_ERROR; -static const int PEM_R_UNSUPPORTED_ENCRYPTION; -static const int EVP_R_UNKNOWN_PBE_ALGORITHM; +static const int EVP_R_XTS_DUPLICATED_KEYS; static const int ERR_LIB_EVP; -static const int ERR_LIB_PEM; -static const int ERR_LIB_ASN1; +static const int ERR_LIB_PROV; static const int ERR_LIB_PKCS12; static const int SSL_TLSEXT_ERR_OK; static const int SSL_TLSEXT_ERR_ALERT_FATAL; static const int SSL_TLSEXT_ERR_NOACK; -static const int X509_R_CERT_ALREADY_IN_HASH_TABLE; +static const int SSL_R_UNEXPECTED_EOF_WHILE_READING; + +static const int Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING; """ FUNCTIONS = """ -void ERR_error_string_n(unsigned long, char *, size_t); const char *ERR_lib_error_string(unsigned long); const char *ERR_func_error_string(unsigned long); const char *ERR_reason_error_string(unsigned long); @@ -39,11 +40,37 @@ void ERR_clear_error(void); void ERR_put_error(int, int, int, const char *, int); -int ERR_GET_LIB(unsigned long); -int ERR_GET_FUNC(unsigned long); int ERR_GET_REASON(unsigned long); - """ CUSTOMIZATIONS = """ +/* This define is tied to provider support and is conditionally + removed if Cryptography_HAS_PROVIDERS is false */ +#ifndef ERR_LIB_PROV +#define ERR_LIB_PROV 0 +#endif + +#ifndef EVP_R_XTS_DUPLICATED_KEYS +static const int EVP_R_XTS_DUPLICATED_KEYS = 0; +#endif + +#if CRYPTOGRAPHY_IS_BORINGSSL +static const int ERR_LIB_PKCS12 = 0; +static const int EVP_F_EVP_ENCRYPTFINAL_EX = 0; +static const int EVP_R_BAD_DECRYPT = 0; +static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH = 0; +static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM = 0; +static const int PKCS12_R_PKCS12_CIPHERFINAL_ERROR = 0; +#else +static const int CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH = 0; +#endif + +/* SSL_R_UNEXPECTED_EOF_WHILE_READING is needed for pyOpenSSL + with OpenSSL 3+ */ +#if defined(SSL_R_UNEXPECTED_EOF_WHILE_READING) +#define Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING 1 +#else +#define Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING 0 +#define SSL_R_UNEXPECTED_EOF_WHILE_READING 0 +#endif """ diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py index d7ac93e603fc..ce54fd9fe931 100644 --- a/src/_cffi_src/openssl/evp.py +++ b/src/_cffi_src/openssl/evp.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -17,6 +17,7 @@ typedef ... EVP_PKEY; typedef ... EVP_PKEY_CTX; static const int EVP_PKEY_RSA; +static const int EVP_PKEY_RSA_PSS; static const int EVP_PKEY_DSA; static const int EVP_PKEY_DH; static const int EVP_PKEY_DHX; @@ -33,30 +34,30 @@ static const int Cryptography_HAS_SCRYPT; static const int Cryptography_HAS_EVP_PKEY_DHX; -static const int Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint; -static const int Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY; static const long Cryptography_HAS_RAW_KEY; static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF; +static const long Cryptography_HAS_300_FIPS; +static const long Cryptography_HAS_300_EVP_CIPHER; +static const long Cryptography_HAS_EVP_PKEY_DH; +static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX; """ FUNCTIONS = """ const EVP_CIPHER *EVP_get_cipherbyname(const char *); +EVP_CIPHER *EVP_CIPHER_fetch(OSSL_LIB_CTX *, const char *, const char *); +void EVP_CIPHER_free(EVP_CIPHER *); + int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int); int EVP_CipherInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, const unsigned char *, const unsigned char *, int); int EVP_CipherUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, const unsigned char *, int); int EVP_CipherFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); -int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *); +int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *); EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void); void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *); int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *, int); -const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *); -int EVP_MD_CTX_copy_ex(EVP_MD_CTX *, const EVP_MD_CTX *); -int EVP_DigestInit_ex(EVP_MD_CTX *, const EVP_MD *, ENGINE *); -int EVP_DigestUpdate(EVP_MD_CTX *, const void *, size_t); -int EVP_DigestFinal_ex(EVP_MD_CTX *, unsigned char *, unsigned int *); int EVP_DigestFinalXOF(EVP_MD_CTX *, unsigned char *, size_t); const EVP_MD *EVP_get_digestbyname(const char *); @@ -65,8 +66,6 @@ int EVP_PKEY_type(int); int EVP_PKEY_size(EVP_PKEY *); RSA *EVP_PKEY_get1_RSA(EVP_PKEY *); -DSA *EVP_PKEY_get1_DSA(EVP_PKEY *); -DH *EVP_PKEY_get1_DH(EVP_PKEY *); int EVP_PKEY_encrypt(EVP_PKEY_CTX *, unsigned char *, size_t *, const unsigned char *, size_t); @@ -82,18 +81,8 @@ int EVP_VerifyFinal(EVP_MD_CTX *, const unsigned char *, unsigned int, EVP_PKEY *); -int EVP_DigestSignInit(EVP_MD_CTX *, EVP_PKEY_CTX **, const EVP_MD *, - ENGINE *, EVP_PKEY *); -int EVP_DigestSignUpdate(EVP_MD_CTX *, const void *, size_t); -int EVP_DigestSignFinal(EVP_MD_CTX *, unsigned char *, size_t *); -int EVP_DigestVerifyInit(EVP_MD_CTX *, EVP_PKEY_CTX **, const EVP_MD *, - ENGINE *, EVP_PKEY *); - - EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *, ENGINE *); -EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int, ENGINE *); -EVP_PKEY_CTX *EVP_PKEY_CTX_dup(EVP_PKEY_CTX *); void EVP_PKEY_CTX_free(EVP_PKEY_CTX *); int EVP_PKEY_sign_init(EVP_PKEY_CTX *); int EVP_PKEY_sign(EVP_PKEY_CTX *, unsigned char *, size_t *, @@ -101,6 +90,9 @@ int EVP_PKEY_verify_init(EVP_PKEY_CTX *); int EVP_PKEY_verify(EVP_PKEY_CTX *, const unsigned char *, size_t, const unsigned char *, size_t); +int EVP_PKEY_verify_recover_init(EVP_PKEY_CTX *); +int EVP_PKEY_verify_recover(EVP_PKEY_CTX *, unsigned char *, + size_t *, const unsigned char *, size_t); int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *); int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *); @@ -110,35 +102,18 @@ int EVP_PKEY_cmp(const EVP_PKEY *, const EVP_PKEY *); -int EVP_PKEY_keygen_init(EVP_PKEY_CTX *); -int EVP_PKEY_keygen(EVP_PKEY_CTX *, EVP_PKEY **); int EVP_PKEY_derive_init(EVP_PKEY_CTX *); int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *, EVP_PKEY *); +int EVP_PKEY_derive_set_peer_ex(EVP_PKEY_CTX *, EVP_PKEY *, int); int EVP_PKEY_derive(EVP_PKEY_CTX *, unsigned char *, size_t *); -int EVP_PKEY_set_type(EVP_PKEY *, int); int EVP_PKEY_id(const EVP_PKEY *); -int Cryptography_EVP_PKEY_id(const EVP_PKEY *); - -/* in 1.1.0 _create and _destroy were renamed to _new and _free. The following - two functions wrap both the old and new functions so we can call them - without worrying about what OpenSSL we're running against. */ -EVP_MD_CTX *Cryptography_EVP_MD_CTX_new(void); -void Cryptography_EVP_MD_CTX_free(EVP_MD_CTX *); -/* Added in 1.1.1 */ -int EVP_DigestSign(EVP_MD_CTX *, unsigned char *, size_t *, - const unsigned char *, size_t); -int EVP_DigestVerify(EVP_MD_CTX *, const unsigned char *, size_t, - const unsigned char *, size_t); -/* Added in 1.1.0 */ -size_t EVP_PKEY_get1_tls_encodedpoint(EVP_PKEY *, unsigned char **); -int EVP_PKEY_set1_tls_encodedpoint(EVP_PKEY *, const unsigned char *, - size_t); -/* EVP_PKEY * became const in 1.1.0 */ -int EVP_PKEY_bits(EVP_PKEY *); +EVP_MD_CTX *EVP_MD_CTX_new(void); +void EVP_MD_CTX_free(EVP_MD_CTX *); + +int EVP_PKEY_bits(const EVP_PKEY *); -void OpenSSL_add_all_algorithms(void); int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); EC_KEY *EVP_PKEY_get1_EC_KEY(EVP_PKEY *); @@ -146,21 +121,17 @@ int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *, int, int, void *); -int PKCS5_PBKDF2_HMAC(const char *, int, const unsigned char *, int, int, - const EVP_MD *, int, unsigned char *); - int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX *, const EVP_MD *); -int EVP_PBE_scrypt(const char *, size_t, const unsigned char *, size_t, - uint64_t, uint64_t, uint64_t, uint64_t, unsigned char *, - size_t); - EVP_PKEY *EVP_PKEY_new_raw_private_key(int, ENGINE *, const unsigned char *, size_t); EVP_PKEY *EVP_PKEY_new_raw_public_key(int, ENGINE *, const unsigned char *, size_t); int EVP_PKEY_get_raw_private_key(const EVP_PKEY *, unsigned char *, size_t *); int EVP_PKEY_get_raw_public_key(const EVP_PKEY *, unsigned char *, size_t *); + +int EVP_default_properties_is_fips_enabled(OSSL_LIB_CTX *); +int EVP_default_properties_enable_fips(OSSL_LIB_CTX *, int); """ CUSTOMIZATIONS = """ @@ -171,51 +142,17 @@ const long EVP_PKEY_DHX = -1; #endif -int Cryptography_EVP_PKEY_id(const EVP_PKEY *key) { - return EVP_PKEY_id(key); -} - -EVP_MD_CTX *Cryptography_EVP_MD_CTX_new(void) { -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 - return EVP_MD_CTX_create(); -#else - return EVP_MD_CTX_new(); -#endif -} -void Cryptography_EVP_MD_CTX_free(EVP_MD_CTX *ctx) { -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 - EVP_MD_CTX_destroy(ctx); -#else - EVP_MD_CTX_free(ctx); -#endif -} -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(OPENSSL_NO_SCRYPT) +#if CRYPTOGRAPHY_IS_LIBRESSL || defined(OPENSSL_NO_SCRYPT) static const long Cryptography_HAS_SCRYPT = 0; -int (*EVP_PBE_scrypt)(const char *, size_t, const unsigned char *, size_t, - uint64_t, uint64_t, uint64_t, uint64_t, unsigned char *, - size_t) = NULL; #else static const long Cryptography_HAS_SCRYPT = 1; #endif -#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER -static const long Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint = 1; -#else -static const long Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint = 0; -size_t (*EVP_PKEY_get1_tls_encodedpoint)(EVP_PKEY *, unsigned char **) = NULL; -int (*EVP_PKEY_set1_tls_encodedpoint)(EVP_PKEY *, const unsigned char *, - size_t) = NULL; -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 -static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 0; -static const long Cryptography_HAS_RAW_KEY = 0; +#if CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 0; int (*EVP_DigestFinalXOF)(EVP_MD_CTX *, unsigned char *, size_t) = NULL; -int (*EVP_DigestSign)(EVP_MD_CTX *, unsigned char *, size_t *, - const unsigned char *tbs, size_t) = NULL; -int (*EVP_DigestVerify)(EVP_MD_CTX *, const unsigned char *, size_t, - const unsigned char *, size_t) = NULL; +#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 +static const long Cryptography_HAS_RAW_KEY = 0; EVP_PKEY *(*EVP_PKEY_new_raw_private_key)(int, ENGINE *, const unsigned char *, size_t) = NULL; EVP_PKEY *(*EVP_PKEY_new_raw_public_key)(int, ENGINE *, const unsigned char *, @@ -225,20 +162,18 @@ int (*EVP_PKEY_get_raw_public_key)(const EVP_PKEY *, unsigned char *, size_t *) = NULL; #else -static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 1; +static const long Cryptography_HAS_RAW_KEY = 1; +#endif +#else static const long Cryptography_HAS_RAW_KEY = 1; static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 1; #endif -/* OpenSSL 1.1.0+ does this define for us, but if not present we'll do it */ -#if !defined(EVP_CTRL_AEAD_SET_IVLEN) -# define EVP_CTRL_AEAD_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN -#endif -#if !defined(EVP_CTRL_AEAD_GET_TAG) -# define EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG -#endif -#if !defined(EVP_CTRL_AEAD_SET_TAG) -# define EVP_CTRL_AEAD_SET_TAG EVP_CTRL_GCM_SET_TAG +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX = 1; +#else +static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX = 0; +int (*EVP_PKEY_derive_set_peer_ex)(EVP_PKEY_CTX *, EVP_PKEY *, int) = NULL; #endif /* This is tied to X25519 support so we reuse the Cryptography_HAS_X25519 @@ -273,4 +208,24 @@ #ifndef EVP_PKEY_POLY1305 #define EVP_PKEY_POLY1305 NID_poly1305 #endif + +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +static const long Cryptography_HAS_300_FIPS = 1; +static const long Cryptography_HAS_300_EVP_CIPHER = 1; +#else +static const long Cryptography_HAS_300_FIPS = 0; +static const long Cryptography_HAS_300_EVP_CIPHER = 0; +int (*EVP_default_properties_is_fips_enabled)(OSSL_LIB_CTX *) = NULL; +int (*EVP_default_properties_enable_fips)(OSSL_LIB_CTX *, int) = NULL; +EVP_CIPHER * (*EVP_CIPHER_fetch)(OSSL_LIB_CTX *, const char *, + const char *) = NULL; +void (*EVP_CIPHER_free)(EVP_CIPHER *) = NULL; +#endif + +#if CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_EVP_PKEY_DH = 0; +int (*EVP_PKEY_set1_DH)(EVP_PKEY *, DH *) = NULL; +#else +static const long Cryptography_HAS_EVP_PKEY_DH = 1; +#endif """ diff --git a/src/_cffi_src/openssl/evp_aead.py b/src/_cffi_src/openssl/evp_aead.py new file mode 100644 index 000000000000..a748bcd7a6a8 --- /dev/null +++ b/src/_cffi_src/openssl/evp_aead.py @@ -0,0 +1,88 @@ +# 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 + +INCLUDES = """ +#if CRYPTOGRAPHY_IS_BORINGSSL +#include +#endif +""" + +TYPES = """ +typedef ... EVP_AEAD; +typedef ... EVP_AEAD_CTX; +static const size_t EVP_AEAD_DEFAULT_TAG_LENGTH; + +static const long Cryptography_HAS_EVP_AEAD; +""" + +FUNCTIONS = """ +const EVP_AEAD *EVP_aead_chacha20_poly1305(void); +void EVP_AEAD_CTX_free(EVP_AEAD_CTX *); +int EVP_AEAD_CTX_seal(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t); +int EVP_AEAD_CTX_open(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t); +size_t EVP_AEAD_max_overhead(const EVP_AEAD *); +/* The function EVP_AEAD_CTX_NEW() has different signatures in BoringSSL and + LibreSSL, so we cannot declare it here. We define a wrapper for it instead. +*/ +EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *, + const uint8_t *, size_t, + size_t); +""" + +CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_LIBRESSL +static const long Cryptography_HAS_EVP_AEAD = 1; +#else +static const long Cryptography_HAS_EVP_AEAD = 0; +#endif + +#if CRYPTOGRAPHY_IS_BORINGSSL +EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *aead, + const uint8_t *key, + size_t key_len, size_t tag_len) { + return EVP_AEAD_CTX_new(aead, key, key_len, tag_len); +} +#elif CRYPTOGRAPHY_IS_LIBRESSL +EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *aead, + const uint8_t *key, + size_t key_len, size_t tag_len) { + EVP_AEAD_CTX *ctx = EVP_AEAD_CTX_new(); + if (ctx == NULL) { + return NULL; + } + + /* This mimics BoringSSL's behavior: any error here is pushed onto + the stack. + */ + int result = EVP_AEAD_CTX_init(ctx, aead, key, key_len, tag_len, NULL); + if (result != 1) { + return NULL; + } + + return ctx; +} +#else +typedef void EVP_AEAD; +typedef void EVP_AEAD_CTX; +static const size_t EVP_AEAD_DEFAULT_TAG_LENGTH = 0; +const EVP_AEAD *(*EVP_aead_chacha20_poly1305)(void) = NULL; +void (*EVP_AEAD_CTX_free)(EVP_AEAD_CTX *) = NULL; +int (*EVP_AEAD_CTX_seal)(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t) = NULL; +int (*EVP_AEAD_CTX_open)(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t) = NULL; +size_t (*EVP_AEAD_max_overhead)(const EVP_AEAD *) = NULL; +EVP_AEAD_CTX *(*Cryptography_EVP_AEAD_CTX_new)(const EVP_AEAD *, + const uint8_t *, size_t, + size_t) = NULL; +#endif +""" diff --git a/src/_cffi_src/openssl/fips.py b/src/_cffi_src/openssl/fips.py index c92bca494be0..9e3ce9524b44 100644 --- a/src/_cffi_src/openssl/fips.py +++ b/src/_cffi_src/openssl/fips.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -18,7 +18,7 @@ """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_LIBRESSL +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER static const long Cryptography_HAS_FIPS = 0; int (*FIPS_mode_set)(int) = NULL; int (*FIPS_mode)(void) = NULL; diff --git a/src/_cffi_src/openssl/hmac.py b/src/_cffi_src/openssl/hmac.py deleted file mode 100644 index 2bc70068ed6b..000000000000 --- a/src/_cffi_src/openssl/hmac.py +++ /dev/null @@ -1,48 +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 absolute_import, division, print_function - -INCLUDES = """ -#include -""" - -TYPES = """ -typedef ... HMAC_CTX; -""" - -FUNCTIONS = """ -int HMAC_Init_ex(HMAC_CTX *, const void *, int, const EVP_MD *, ENGINE *); -int HMAC_Update(HMAC_CTX *, const unsigned char *, size_t); -int HMAC_Final(HMAC_CTX *, unsigned char *, unsigned int *); -int HMAC_CTX_copy(HMAC_CTX *, HMAC_CTX *); - -HMAC_CTX *Cryptography_HMAC_CTX_new(void); -void Cryptography_HMAC_CTX_free(HMAC_CTX *ctx); -""" - -CUSTOMIZATIONS = """ -HMAC_CTX *Cryptography_HMAC_CTX_new(void) { -#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - return HMAC_CTX_new(); -#else - /* This uses OPENSSL_zalloc in 1.1.0, which is malloc + memset */ - HMAC_CTX *ctx = (HMAC_CTX *)OPENSSL_malloc(sizeof(HMAC_CTX)); - memset(ctx, 0, sizeof(HMAC_CTX)); - return ctx; -#endif -} - - -void Cryptography_HMAC_CTX_free(HMAC_CTX *ctx) { -#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - HMAC_CTX_free(ctx); -#else - if (ctx != NULL) { - HMAC_CTX_cleanup(ctx); - OPENSSL_free(ctx); - } -#endif -} -""" diff --git a/src/_cffi_src/openssl/nid.py b/src/_cffi_src/openssl/nid.py index aecdc9c0f4ed..7f6cb62303af 100644 --- a/src/_cffi_src/openssl/nid.py +++ b/src/_cffi_src/openssl/nid.py @@ -2,23 +2,20 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ -static const int Cryptography_HAS_X25519; -static const int Cryptography_HAS_X448; static const int Cryptography_HAS_ED448; static const int Cryptography_HAS_ED25519; static const int Cryptography_HAS_POLY1305; static const int NID_undef; +static const int NID_aes_256_cbc; static const int NID_pbe_WithSHA1And3_Key_TripleDES_CBC; -static const int NID_X25519; -static const int NID_X448; static const int NID_ED25519; static const int NID_ED448; static const int NID_poly1305; @@ -33,24 +30,12 @@ """ CUSTOMIZATIONS = """ -#ifndef NID_X25519 -static const long Cryptography_HAS_X25519 = 0; -static const int NID_X25519 = 0; -#else -static const long Cryptography_HAS_X25519 = 1; -#endif #ifndef NID_ED25519 static const long Cryptography_HAS_ED25519 = 0; static const int NID_ED25519 = 0; #else static const long Cryptography_HAS_ED25519 = 1; #endif -#ifndef NID_X448 -static const long Cryptography_HAS_X448 = 0; -static const int NID_X448 = 0; -#else -static const long Cryptography_HAS_X448 = 1; -#endif #ifndef NID_ED448 static const long Cryptography_HAS_ED448 = 0; static const int NID_ED448 = 0; diff --git a/src/_cffi_src/openssl/objects.py b/src/_cffi_src/openssl/objects.py index 236903d986a9..5f9bdb3361d0 100644 --- a/src/_cffi_src/openssl/objects.py +++ b/src/_cffi_src/openssl/objects.py @@ -2,21 +2,13 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ -typedef struct { - int type; - int alias; - const char *name; - const char *data; -} OBJ_NAME; - -static const long OBJ_NAME_TYPE_MD_METH; """ FUNCTIONS = """ @@ -25,8 +17,6 @@ int OBJ_obj2nid(const ASN1_OBJECT *); int OBJ_sn2nid(const char *); int OBJ_txt2nid(const char *); -ASN1_OBJECT *OBJ_txt2obj(const char *, int); -int OBJ_obj2txt(char *, int, const ASN1_OBJECT *, int); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/ocsp.py b/src/_cffi_src/openssl/ocsp.py deleted file mode 100644 index f1a8bf617941..000000000000 --- a/src/_cffi_src/openssl/ocsp.py +++ /dev/null @@ -1,166 +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 absolute_import, division, print_function - -INCLUDES = """ -#include -""" - -TYPES = """ -typedef ... OCSP_REQUEST; -typedef ... OCSP_ONEREQ; -typedef ... OCSP_RESPONSE; -typedef ... OCSP_BASICRESP; -typedef ... OCSP_SINGLERESP; -typedef ... OCSP_CERTID; -typedef ... OCSP_RESPDATA; -static const long OCSP_NOCERTS; -static const long OCSP_RESPID_KEY; -""" - -FUNCTIONS = """ -int OCSP_response_status(OCSP_RESPONSE *); -OCSP_BASICRESP *OCSP_response_get1_basic(OCSP_RESPONSE *); -int OCSP_BASICRESP_get_ext_count(OCSP_BASICRESP *); -const ASN1_OCTET_STRING *OCSP_resp_get0_signature(const OCSP_BASICRESP *); -Cryptography_STACK_OF_X509 *OCSP_resp_get0_certs(const OCSP_BASICRESP *); -const ASN1_GENERALIZEDTIME *OCSP_resp_get0_produced_at( - const OCSP_BASICRESP *); -const OCSP_CERTID *OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP *); -int OCSP_resp_get0_id(const OCSP_BASICRESP *, const ASN1_OCTET_STRING **, - const X509_NAME **); -const X509_ALGOR *OCSP_resp_get0_tbs_sigalg(const OCSP_BASICRESP *); -const OCSP_RESPDATA *OCSP_resp_get0_respdata(const OCSP_BASICRESP *); -X509_EXTENSION *OCSP_BASICRESP_get_ext(OCSP_BASICRESP *, int); -int OCSP_resp_count(OCSP_BASICRESP *); -OCSP_SINGLERESP *OCSP_resp_get0(OCSP_BASICRESP *, int); -int OCSP_SINGLERESP_get_ext_count(OCSP_SINGLERESP *); -X509_EXTENSION *OCSP_SINGLERESP_get_ext(OCSP_SINGLERESP *, int); - -int OCSP_single_get0_status(OCSP_SINGLERESP *, int *, ASN1_GENERALIZEDTIME **, - ASN1_GENERALIZEDTIME **, ASN1_GENERALIZEDTIME **); - -int OCSP_REQUEST_get_ext_count(OCSP_REQUEST *); -X509_EXTENSION *OCSP_REQUEST_get_ext(OCSP_REQUEST *, int); -int OCSP_request_onereq_count(OCSP_REQUEST *); -OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *, int); -OCSP_CERTID *OCSP_onereq_get0_id(OCSP_ONEREQ *); -OCSP_ONEREQ *OCSP_request_add0_id(OCSP_REQUEST *, OCSP_CERTID *); -OCSP_CERTID *OCSP_cert_to_id(const EVP_MD *, const X509 *, const X509 *); -void OCSP_CERTID_free(OCSP_CERTID *); - - -OCSP_BASICRESP *OCSP_BASICRESP_new(void); -void OCSP_BASICRESP_free(OCSP_BASICRESP *); -OCSP_SINGLERESP *OCSP_basic_add1_status(OCSP_BASICRESP *, OCSP_CERTID *, int, - int, ASN1_TIME *, ASN1_TIME *, - ASN1_TIME *); -int OCSP_basic_add1_cert(OCSP_BASICRESP *, X509 *); -int OCSP_BASICRESP_add_ext(OCSP_BASICRESP *, X509_EXTENSION *, int); -int OCSP_basic_sign(OCSP_BASICRESP *, X509 *, EVP_PKEY *, const EVP_MD *, - Cryptography_STACK_OF_X509 *, unsigned long); -OCSP_RESPONSE *OCSP_response_create(int, OCSP_BASICRESP *); -void OCSP_RESPONSE_free(OCSP_RESPONSE *); - -OCSP_REQUEST *OCSP_REQUEST_new(void); -void OCSP_REQUEST_free(OCSP_REQUEST *); -int OCSP_REQUEST_add_ext(OCSP_REQUEST *, X509_EXTENSION *, int); -int OCSP_id_get0_info(ASN1_OCTET_STRING **, ASN1_OBJECT **, - ASN1_OCTET_STRING **, ASN1_INTEGER **, OCSP_CERTID *); -OCSP_REQUEST *d2i_OCSP_REQUEST_bio(BIO *, OCSP_REQUEST **); -OCSP_RESPONSE *d2i_OCSP_RESPONSE_bio(BIO *, OCSP_RESPONSE **); -int i2d_OCSP_REQUEST_bio(BIO *, OCSP_REQUEST *); -int i2d_OCSP_RESPONSE_bio(BIO *, OCSP_RESPONSE *); -int i2d_OCSP_RESPDATA(OCSP_RESPDATA *, unsigned char **); -""" - -CUSTOMIZATIONS = """ -#if ( \ - CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && \ - CRYPTOGRAPHY_OPENSSL_LESS_THAN_110J \ - ) -/* These structs come from ocsp_lcl.h and are needed to de-opaque the struct - for the getters in OpenSSL 1.1.0 through 1.1.0i */ -struct ocsp_responder_id_st { - int type; - union { - X509_NAME *byName; - ASN1_OCTET_STRING *byKey; - } value; -}; -struct ocsp_response_data_st { - ASN1_INTEGER *version; - OCSP_RESPID responderId; - ASN1_GENERALIZEDTIME *producedAt; - STACK_OF(OCSP_SINGLERESP) *responses; - STACK_OF(X509_EXTENSION) *responseExtensions; -}; -struct ocsp_basic_response_st { - OCSP_RESPDATA tbsResponseData; - X509_ALGOR signatureAlgorithm; - ASN1_BIT_STRING *signature; - STACK_OF(X509) *certs; -}; -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 -/* These functions are all taken from ocsp_cl.c in OpenSSL 1.1.0 */ -const OCSP_CERTID *OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP *single) -{ - return single->certId; -} -const Cryptography_STACK_OF_X509 *OCSP_resp_get0_certs( - const OCSP_BASICRESP *bs) -{ - return bs->certs; -} -int OCSP_resp_get0_id(const OCSP_BASICRESP *bs, - const ASN1_OCTET_STRING **pid, - const X509_NAME **pname) -{ - const OCSP_RESPID *rid = bs->tbsResponseData->responderId; - - if (rid->type == V_OCSP_RESPID_NAME) { - *pname = rid->value.byName; - *pid = NULL; - } else if (rid->type == V_OCSP_RESPID_KEY) { - *pid = rid->value.byKey; - *pname = NULL; - } else { - return 0; - } - return 1; -} -const ASN1_GENERALIZEDTIME *OCSP_resp_get0_produced_at( - const OCSP_BASICRESP* bs) -{ - return bs->tbsResponseData->producedAt; -} -const ASN1_OCTET_STRING *OCSP_resp_get0_signature(const OCSP_BASICRESP *bs) -{ - return bs->signature; -} -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110J -const X509_ALGOR *OCSP_resp_get0_tbs_sigalg(const OCSP_BASICRESP *bs) -{ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 - return bs->signatureAlgorithm; -#else - return &bs->signatureAlgorithm; -#endif -} - -const OCSP_RESPDATA *OCSP_resp_get0_respdata(const OCSP_BASICRESP *bs) -{ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 - return bs->tbsResponseData; -#else - return &bs->tbsResponseData; -#endif -} -#endif -""" diff --git a/src/_cffi_src/openssl/opensslv.py b/src/_cffi_src/openssl/opensslv.py index 9b0c68933860..7957bd7dd58c 100644 --- a/src/_cffi_src/openssl/opensslv.py +++ b/src/_cffi_src/openssl/opensslv.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include diff --git a/src/_cffi_src/openssl/osrandom_engine.py b/src/_cffi_src/openssl/osrandom_engine.py deleted file mode 100644 index ed1068ef8aa4..000000000000 --- a/src/_cffi_src/openssl/osrandom_engine.py +++ /dev/null @@ -1,24 +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 absolute_import, division, print_function - -import os - -HERE = os.path.dirname(os.path.abspath(__file__)) - -with open(os.path.join(HERE, "src/osrandom_engine.h")) as f: - INCLUDES = f.read() - -TYPES = """ -static const char *const Cryptography_osrandom_engine_name; -static const char *const Cryptography_osrandom_engine_id; -""" - -FUNCTIONS = """ -int Cryptography_add_osrandom_engine(void); -""" - -with open(os.path.join(HERE, "src/osrandom_engine.c")) as f: - CUSTOMIZATIONS = f.read() diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py index 3f279c4fffa9..950bd3780c9c 100644 --- a/src/_cffi_src/openssl/pem.py +++ b/src/_cffi_src/openssl/pem.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -28,7 +28,6 @@ int i2d_PKCS8PrivateKey_bio(BIO *, EVP_PKEY *, const EVP_CIPHER *, char *, int, pem_password_cb *, void *); -int i2d_PKCS7_bio(BIO *, PKCS7 *); PKCS7 *d2i_PKCS7_bio(BIO *, PKCS7 **); EVP_PKEY *d2i_PKCS8PrivateKey_bio(BIO *, EVP_PKEY **, pem_password_cb *, @@ -43,7 +42,6 @@ int PEM_write_bio_X509_CRL(BIO *, X509_CRL *); PKCS7 *PEM_read_bio_PKCS7(BIO *, PKCS7 **, pem_password_cb *, void *); -int PEM_write_bio_PKCS7(BIO *, PKCS7 *); DH *PEM_read_bio_DHparams(BIO *, DH **, pem_password_cb *, void *); @@ -64,12 +62,7 @@ int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *, unsigned char *, int, pem_password_cb *, void *); -int PEM_write_bio_DHparams(BIO *, DH *); -int PEM_write_bio_DHxparams(BIO *, DH *); """ CUSTOMIZATIONS = """ -#if !defined(EVP_PKEY_DHX) || EVP_PKEY_DHX == -1 -int (*PEM_write_bio_DHxparams)(BIO *, DH *) = NULL; -#endif """ diff --git a/src/_cffi_src/openssl/pkcs12.py b/src/_cffi_src/openssl/pkcs12.py index 21a8481faa30..234f97b3ea65 100644 --- a/src/_cffi_src/openssl/pkcs12.py +++ b/src/_cffi_src/openssl/pkcs12.py @@ -2,13 +2,15 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ +static const long Cryptography_HAS_PKCS12_SET_MAC; + typedef ... PKCS12; """ @@ -21,7 +23,16 @@ Cryptography_STACK_OF_X509 **); PKCS12 *PKCS12_create(char *, char *, EVP_PKEY *, X509 *, Cryptography_STACK_OF_X509 *, int, int, int, int, int); +int PKCS12_set_mac(PKCS12 *, const char *, int, unsigned char *, int, int, + const EVP_MD *); """ CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_PKCS12_SET_MAC = 0; +int (*PKCS12_set_mac)(PKCS12 *, const char *, int, unsigned char *, int, int, + const EVP_MD *) = NULL; +#else +static const long Cryptography_HAS_PKCS12_SET_MAC = 1; +#endif """ diff --git a/src/_cffi_src/openssl/pkcs7.py b/src/_cffi_src/openssl/pkcs7.py index c22263dfe6c1..ef75157a80da 100644 --- a/src/_cffi_src/openssl/pkcs7.py +++ b/src/_cffi_src/openssl/pkcs7.py @@ -2,13 +2,15 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include """ TYPES = """ +static const long Cryptography_HAS_PKCS7_FUNCS; + typedef struct { Cryptography_STACK_OF_X509 *cert; Cryptography_STACK_OF_X509_CRL *crl; @@ -41,24 +43,11 @@ ...; } PKCS7; -static const int PKCS7_BINARY; -static const int PKCS7_DETACHED; -static const int PKCS7_NOATTR; -static const int PKCS7_NOCERTS; -static const int PKCS7_NOCHAIN; -static const int PKCS7_NOINTERN; -static const int PKCS7_NOSIGS; -static const int PKCS7_NOSMIMECAP; -static const int PKCS7_NOVERIFY; -static const int PKCS7_STREAM; static const int PKCS7_TEXT; -static const int PKCS7_PARTIAL; """ FUNCTIONS = """ void PKCS7_free(PKCS7 *); -PKCS7 *PKCS7_sign(X509 *, EVP_PKEY *, Cryptography_STACK_OF_X509 *, - BIO *, int); int SMIME_write_PKCS7(BIO *, PKCS7 *, BIO *, int); int PEM_write_bio_PKCS7_stream(BIO *, PKCS7 *, BIO *, int); PKCS7_SIGNER_INFO *PKCS7_sign_add_signer(PKCS7 *, X509 *, EVP_PKEY *, @@ -69,6 +58,11 @@ int PKCS7_verify(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, BIO *, int); PKCS7 *SMIME_read_PKCS7(BIO *, BIO **); +/* Included due to external consumer, see + https://github.com/pyca/pyopenssl/issues/1031 */ +Cryptography_STACK_OF_X509 *PKCS7_get0_signers(PKCS7 *, + Cryptography_STACK_OF_X509 *, + int); int PKCS7_type_is_signed(PKCS7 *); int PKCS7_type_is_enveloped(PKCS7 *); @@ -76,4 +70,22 @@ int PKCS7_type_is_data(PKCS7 *); """ -CUSTOMIZATIONS = "" +CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_PKCS7_FUNCS = 0; + +int (*SMIME_write_PKCS7)(BIO *, PKCS7 *, BIO *, int) = NULL; +int (*PEM_write_bio_PKCS7_stream)(BIO *, PKCS7 *, BIO *, int) = NULL; +PKCS7_SIGNER_INFO *(*PKCS7_sign_add_signer)(PKCS7 *, X509 *, EVP_PKEY *, + const EVP_MD *, int) = NULL; +int (*PKCS7_final)(PKCS7 *, BIO *, int); +int (*PKCS7_verify)(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, + BIO *, int) = NULL; +PKCS7 *(*SMIME_read_PKCS7)(BIO *, BIO **) = NULL; +Cryptography_STACK_OF_X509 *(*PKCS7_get0_signers)(PKCS7 *, + Cryptography_STACK_OF_X509 *, + int) = NULL; +#else +static const long Cryptography_HAS_PKCS7_FUNCS = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/provider.py b/src/_cffi_src/openssl/provider.py new file mode 100644 index 000000000000..769fded96d23 --- /dev/null +++ b/src/_cffi_src/openssl/provider.py @@ -0,0 +1,43 @@ +# 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 + +INCLUDES = """ +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +#include +#include +#endif +""" + +TYPES = """ +static const long Cryptography_HAS_PROVIDERS; + +typedef ... OSSL_PROVIDER; +typedef ... OSSL_LIB_CTX; + +static const long PROV_R_BAD_DECRYPT; +static const long PROV_R_XTS_DUPLICATED_KEYS; +static const long PROV_R_WRONG_FINAL_BLOCK_LENGTH; +""" + +FUNCTIONS = """ +OSSL_PROVIDER *OSSL_PROVIDER_load(OSSL_LIB_CTX *, const char *); +int OSSL_PROVIDER_unload(OSSL_PROVIDER *prov); +""" + +CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +static const long Cryptography_HAS_PROVIDERS = 1; +#else +static const long Cryptography_HAS_PROVIDERS = 0; +typedef void OSSL_PROVIDER; +typedef void OSSL_LIB_CTX; +static const long PROV_R_BAD_DECRYPT = 0; +static const long PROV_R_XTS_DUPLICATED_KEYS = 0; +static const long PROV_R_WRONG_FINAL_BLOCK_LENGTH = 0; +OSSL_PROVIDER *(*OSSL_PROVIDER_load)(OSSL_LIB_CTX *, const char *) = NULL; +int (*OSSL_PROVIDER_unload)(OSSL_PROVIDER *) = NULL; +#endif +""" diff --git a/src/_cffi_src/openssl/rand.py b/src/_cffi_src/openssl/rand.py index c0cd68365960..ee00fe68d821 100644 --- a/src/_cffi_src/openssl/rand.py +++ b/src/_cffi_src/openssl/rand.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -10,22 +10,13 @@ TYPES = """ typedef ... RAND_METHOD; - -static const long Cryptography_HAS_EGD; """ FUNCTIONS = """ -int RAND_set_rand_method(const RAND_METHOD *); void RAND_add(const void *, int, double); int RAND_status(void); int RAND_bytes(unsigned char *, int); -/* ERR_load_RAND_strings started returning an int in 1.1.0. Unfortunately we - can't declare a conditional signature like that. Since it always returns - 1 we'll just lie about the signature to preserve compatibility for - pyOpenSSL (which calls this in its rand.py as of mid-2016) */ -void ERR_load_RAND_strings(void); """ CUSTOMIZATIONS = """ -static const long Cryptography_HAS_EGD = 0; """ diff --git a/src/_cffi_src/openssl/rsa.py b/src/_cffi_src/openssl/rsa.py index 765498fbc9ef..eea6e396e3fb 100644 --- a/src/_cffi_src/openssl/rsa.py +++ b/src/_cffi_src/openssl/rsa.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -12,14 +12,12 @@ typedef ... RSA; typedef ... BN_GENCB; static const int RSA_PKCS1_PADDING; -static const int RSA_NO_PADDING; static const int RSA_PKCS1_OAEP_PADDING; static const int RSA_PKCS1_PSS_PADDING; static const int RSA_F4; +static const int RSA_PSS_SALTLEN_AUTO; -static const int Cryptography_HAS_PSS_PADDING; -static const int Cryptography_HAS_RSA_OAEP_MD; -static const int Cryptography_HAS_RSA_OAEP_LABEL; +static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION; """ FUNCTIONS = """ @@ -31,7 +29,6 @@ int RSA_blinding_on(RSA *, BN_CTX *); int RSA_print(BIO *, const RSA *, int); -/* added in 1.1.0 when the RSA struct was opaqued */ int RSA_set0_key(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); int RSA_set0_factors(RSA *, BIGNUM *, BIGNUM *); int RSA_set0_crt_params(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); @@ -49,127 +46,15 @@ """ CUSTOMIZATIONS = """ -static const long Cryptography_HAS_PSS_PADDING = 1; - -#if defined(EVP_PKEY_CTX_set_rsa_oaep_md) -static const long Cryptography_HAS_RSA_OAEP_MD = 1; -#else -static const long Cryptography_HAS_RSA_OAEP_MD = 0; -int (*EVP_PKEY_CTX_set_rsa_oaep_md)(EVP_PKEY_CTX *, EVP_MD *) = NULL; +// BoringSSL doesn't define this constant, but the value is used for +// automatic salt length computation as in OpenSSL and LibreSSL +#if !defined(RSA_PSS_SALTLEN_AUTO) +#define RSA_PSS_SALTLEN_AUTO -2 #endif -#if defined(EVP_PKEY_CTX_set0_rsa_oaep_label) -static const long Cryptography_HAS_RSA_OAEP_LABEL = 1; +#if defined(EVP_PKEY_CTRL_RSA_IMPLICIT_REJECTION) +static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION = 1; #else -static const long Cryptography_HAS_RSA_OAEP_LABEL = 0; -int (*EVP_PKEY_CTX_set0_rsa_oaep_label)(EVP_PKEY_CTX *, unsigned char *, - int) = NULL; -#endif - -/* These functions were added in OpenSSL 1.1.0 */ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 && !CRYPTOGRAPHY_IS_LIBRESSL -int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) -{ - /* If the fields n and e in r are NULL, the corresponding input - * parameters MUST be non-NULL for n and e. d may be - * left NULL (in case only the public key is used). - */ - if ((r->n == NULL && n == NULL) - || (r->e == NULL && e == NULL)) - return 0; - - if (n != NULL) { - BN_free(r->n); - r->n = n; - } - if (e != NULL) { - BN_free(r->e); - r->e = e; - } - if (d != NULL) { - BN_free(r->d); - r->d = d; - } - - return 1; -} - -int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) -{ - /* If the fields p and q in r are NULL, the corresponding input - * parameters MUST be non-NULL. - */ - if ((r->p == NULL && p == NULL) - || (r->q == NULL && q == NULL)) - return 0; - - if (p != NULL) { - BN_free(r->p); - r->p = p; - } - if (q != NULL) { - BN_free(r->q); - r->q = q; - } - - return 1; -} - -int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) -{ - /* If the fields dmp1, dmq1 and iqmp in r are NULL, the corresponding input - * parameters MUST be non-NULL. - */ - if ((r->dmp1 == NULL && dmp1 == NULL) - || (r->dmq1 == NULL && dmq1 == NULL) - || (r->iqmp == NULL && iqmp == NULL)) - return 0; - - if (dmp1 != NULL) { - BN_free(r->dmp1); - r->dmp1 = dmp1; - } - if (dmq1 != NULL) { - BN_free(r->dmq1); - r->dmq1 = dmq1; - } - if (iqmp != NULL) { - BN_free(r->iqmp); - r->iqmp = iqmp; - } - - return 1; -} - -void RSA_get0_key(const RSA *r, - const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) -{ - if (n != NULL) - *n = r->n; - if (e != NULL) - *e = r->e; - if (d != NULL) - *d = r->d; -} - -void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) -{ - if (p != NULL) - *p = r->p; - if (q != NULL) - *q = r->q; -} - -void RSA_get0_crt_params(const RSA *r, - const BIGNUM **dmp1, const BIGNUM **dmq1, - const BIGNUM **iqmp) -{ - if (dmp1 != NULL) - *dmp1 = r->dmp1; - if (dmq1 != NULL) - *dmq1 = r->dmq1; - if (iqmp != NULL) - *iqmp = r->iqmp; -} +static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION = 0; #endif """ diff --git a/src/_cffi_src/openssl/src/osrandom_engine.c b/src/_cffi_src/openssl/src/osrandom_engine.c deleted file mode 100644 index a84857b86df4..000000000000 --- a/src/_cffi_src/openssl/src/osrandom_engine.c +++ /dev/null @@ -1,660 +0,0 @@ -/* osurandom engine - * - * Windows CryptGenRandom() - * macOS >= 10.12 getentropy() - * OpenBSD 5.6+ getentropy() - * other BSD getentropy() if SYS_getentropy is defined - * Linux 3.17+ getrandom() with fallback to /dev/urandom - * other /dev/urandom with cached fd - * - * The /dev/urandom, getrandom and getentropy code is derived from Python's - * Python/random.c, written by Antoine Pitrou and Victor Stinner. - * - * Copyright 2001-2016 Python Software Foundation; All Rights Reserved. - */ - -#ifdef __linux__ -#include -#endif - -#if CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE -/* OpenSSL has ENGINE support and is older than 1.1.1d (the first version that - * properly implements fork safety in its RNG) so build the engine. */ -static const char *Cryptography_osrandom_engine_id = "osrandom"; - -/**************************************************************************** - * Windows - */ -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM -static const char *Cryptography_osrandom_engine_name = "osrandom_engine CryptGenRandom()"; -static HCRYPTPROV hCryptProv = 0; - -static int osrandom_init(ENGINE *e) { - if (hCryptProv != 0) { - return 1; - } - if (CryptAcquireContext(&hCryptProv, NULL, NULL, - PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - return 1; - } else { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_INIT, - CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT, - __FILE__, __LINE__ - ); - return 0; - } -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - if (hCryptProv == 0) { - return 0; - } - - if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM, - __FILE__, __LINE__ - ); - return 0; - } - return 1; -} - -static int osrandom_finish(ENGINE *e) { - if (CryptReleaseContext(hCryptProv, 0)) { - hCryptProv = 0; - return 1; - } else { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_FINISH, - CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT, - __FILE__, __LINE__ - ); - return 0; - } -} - -static int osrandom_rand_status(void) { - return hCryptProv != 0; -} - -static const char *osurandom_get_implementation(void) { - return "CryptGenRandom"; -} - -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM */ - -/**************************************************************************** - * /dev/urandom helpers for all non-BSD Unix platforms - */ -#ifdef CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM - -static struct { - int fd; - dev_t st_dev; - ino_t st_ino; -} urandom_cache = { -1 }; - -static int open_cloexec(const char *path) { - int open_flags = O_RDONLY; -#ifdef O_CLOEXEC - open_flags |= O_CLOEXEC; -#endif - - int fd = open(path, open_flags); - if (fd == -1) { - return -1; - } - -#ifndef O_CLOEXEC - int flags = fcntl(fd, F_GETFD); - if (flags == -1) { - return -1; - } - if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { - return -1; - } -#endif - return fd; -} - -#ifdef __linux__ -/* On Linux, we open("/dev/random") and use poll() to wait until it's readable - * before we read from /dev/urandom, this ensures that we don't read from - * /dev/urandom before the kernel CSPRNG is initialized. This isn't necessary on - * other platforms because they don't have the same _bug_ as Linux does with - * /dev/urandom and early boot. */ -static int wait_on_devrandom(void) { - struct pollfd pfd = {}; - int ret = 0; - int random_fd = open_cloexec("/dev/random"); - if (random_fd < 0) { - return -1; - } - pfd.fd = random_fd; - pfd.events = POLLIN; - pfd.revents = 0; - do { - ret = poll(&pfd, 1, -1); - } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); - close(random_fd); - return ret; -} -#endif - -/* return -1 on error */ -static int dev_urandom_fd(void) { - int fd = -1; - struct stat st; - - /* Check that fd still points to the correct device */ - if (urandom_cache.fd >= 0) { - if (fstat(urandom_cache.fd, &st) - || st.st_dev != urandom_cache.st_dev - || st.st_ino != urandom_cache.st_ino) { - /* Somebody replaced our FD. Invalidate our cache but don't - * close the fd. */ - urandom_cache.fd = -1; - } - } - if (urandom_cache.fd < 0) { -#ifdef __linux__ - if (wait_on_devrandom() < 0) { - goto error; - } -#endif - - fd = open_cloexec("/dev/urandom"); - if (fd < 0) { - goto error; - } - if (fstat(fd, &st)) { - goto error; - } - /* Another thread initialized the fd */ - if (urandom_cache.fd >= 0) { - close(fd); - return urandom_cache.fd; - } - urandom_cache.st_dev = st.st_dev; - urandom_cache.st_ino = st.st_ino; - urandom_cache.fd = fd; - } - return urandom_cache.fd; - - error: - if (fd != -1) { - close(fd); - } - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD, - CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED, - __FILE__, __LINE__ - ); - return -1; -} - -static int dev_urandom_read(unsigned char *buffer, int size) { - int fd; - int n; - - fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - - while (size > 0) { - do { - n = (int)read(fd, buffer, (size_t)size); - } while (n < 0 && errno == EINTR); - - if (n <= 0) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ, - CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED, - __FILE__, __LINE__ - ); - return 0; - } - buffer += n; - size -= n; - } - return 1; -} - -static void dev_urandom_close(void) { - if (urandom_cache.fd >= 0) { - int fd; - struct stat st; - - if (fstat(urandom_cache.fd, &st) - && st.st_dev == urandom_cache.st_dev - && st.st_ino == urandom_cache.st_ino) { - fd = urandom_cache.fd; - urandom_cache.fd = -1; - close(fd); - } - } -} -#endif /* CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM */ - -/**************************************************************************** - * BSD getentropy - */ -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY -static const char *Cryptography_osrandom_engine_name = "osrandom_engine getentropy()"; - -static int getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_NOT_INIT; - -static int osrandom_init(ENGINE *e) { -#if !defined(__APPLE__) - getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS; -#else - if (__builtin_available(macOS 10.12, *)) { - getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS; - } else { - getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK; - int fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - } -#endif - return 1; -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - int len; - int res; - - switch(getentropy_works) { -#if defined(__APPLE__) - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK: - return dev_urandom_read(buffer, size); -#endif - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS: - while (size > 0) { - /* OpenBSD and macOS restrict maximum buffer size to 256. */ - len = size > 256 ? 256 : size; -/* on mac, availability is already checked using `__builtin_available` above */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - res = getentropy(buffer, (size_t)len); -#pragma clang diagnostic pop - if (res < 0) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED, - __FILE__, __LINE__ - ); - return 0; - } - buffer += len; - size -= len; - } - return 1; - } - __builtin_unreachable(); -} - -static int osrandom_finish(ENGINE *e) { - return 1; -} - -static int osrandom_rand_status(void) { - return 1; -} - -static const char *osurandom_get_implementation(void) { - switch(getentropy_works) { - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK: - return "/dev/urandom"; - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS: - return "getentropy"; - } - __builtin_unreachable(); -} -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY */ - -/**************************************************************************** - * Linux getrandom engine with fallback to dev_urandom - */ - -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM -static const char *Cryptography_osrandom_engine_name = "osrandom_engine getrandom()"; - -static int getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT; - -static int osrandom_init(ENGINE *e) { - /* We try to detect working getrandom until we succeed. */ - if (getrandom_works != CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS) { - long n; - char dest[1]; - /* if the kernel CSPRNG is not initialized this will block */ - n = syscall(SYS_getrandom, dest, sizeof(dest), 0); - if (n == sizeof(dest)) { - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS; - } else { - int e = errno; - switch(e) { - case ENOSYS: - /* Fallback: Kernel does not support the syscall. */ - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK; - break; - case EPERM: - /* Fallback: seccomp prevents syscall */ - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK; - break; - default: - /* EINTR cannot occur for buflen < 256. */ - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_INIT, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED, - "errno", e - ); - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED; - break; - } - } - } - - /* fallback to dev urandom */ - if (getrandom_works == CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK) { - int fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - } - return 1; -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - long n; - - switch(getrandom_works) { - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED, - __FILE__, __LINE__ - ); - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT, - __FILE__, __LINE__ - ); - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: - return dev_urandom_read(buffer, size); - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: - while (size > 0) { - do { - n = syscall(SYS_getrandom, buffer, size, 0); - } while (n < 0 && errno == EINTR); - - if (n <= 0) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED, - __FILE__, __LINE__ - ); - return 0; - } - buffer += n; - size -= (int)n; - } - return 1; - } - __builtin_unreachable(); -} - -static int osrandom_finish(ENGINE *e) { - dev_urandom_close(); - return 1; -} - -static int osrandom_rand_status(void) { - switch(getrandom_works) { - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: - return urandom_cache.fd >= 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: - return 1; - } - __builtin_unreachable(); -} - -static const char *osurandom_get_implementation(void) { - switch(getrandom_works) { - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: - return ""; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: - return ""; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: - return "/dev/urandom"; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: - return "getrandom"; - } - __builtin_unreachable(); -} -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM */ - -/**************************************************************************** - * dev_urandom engine for all remaining platforms - */ - -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM -static const char *Cryptography_osrandom_engine_name = "osrandom_engine /dev/urandom"; - -static int osrandom_init(ENGINE *e) { - int fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - return 1; -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - return dev_urandom_read(buffer, size); -} - -static int osrandom_finish(ENGINE *e) { - dev_urandom_close(); - return 1; -} - -static int osrandom_rand_status(void) { - return urandom_cache.fd >= 0; -} - -static const char *osurandom_get_implementation(void) { - return "/dev/urandom"; -} -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM */ - -/**************************************************************************** - * ENGINE boiler plate - */ - -/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a - -1 in the event that there is an error when calling RAND_pseudo_bytes. */ -static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) { - int res = osrandom_rand_bytes(buffer, size); - if (res == 0) { - return -1; - } else { - return res; - } -} - -static RAND_METHOD osrandom_rand = { - NULL, - osrandom_rand_bytes, - NULL, - NULL, - osrandom_pseudo_rand_bytes, - osrandom_rand_status, -}; - -static const ENGINE_CMD_DEFN osrandom_cmd_defns[] = { - {CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION, - "get_implementation", - "Get CPRNG implementation.", - ENGINE_CMD_FLAG_NO_INPUT}, - {0, NULL, NULL, 0} -}; - -static int osrandom_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void)) { - const char *name; - size_t len; - - switch (cmd) { - case CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION: - /* i: buffer size, p: char* buffer */ - name = osurandom_get_implementation(); - len = strlen(name); - if ((p == NULL) && (i == 0)) { - /* return required buffer len */ - return (int)len; - } - if ((p == NULL) || i < 0 || ((size_t)i <= len)) { - /* no buffer or buffer too small */ - ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_INVALID_ARGUMENT); - return 0; - } - strcpy((char *)p, name); - return (int)len; - default: - ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_CTRL_COMMAND_NOT_IMPLEMENTED); - return 0; - } -} - -/* error reporting */ -#define ERR_FUNC(func) ERR_PACK(0, func, 0) -#define ERR_REASON(reason) ERR_PACK(0, 0, reason) - -static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_lib_name[] = { - {0, "osrandom_engine"}, - {0, NULL} -}; - -static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_funcs[] = { - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_INIT), - "osrandom_init"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES), - "osrandom_rand_bytes"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_FINISH), - "osrandom_finish"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD), - "dev_urandom_fd"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ), - "dev_urandom_read"}, - {0, NULL} -}; - -static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_reasons[] = { - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT), - "CryptAcquireContext() failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM), - "CryptGenRandom() failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT), - "CryptReleaseContext() failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED), - "getentropy() failed"}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED), - "open('/dev/urandom') failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED), - "Reading from /dev/urandom fd failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED), - "getrandom() initialization failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED), - "getrandom() initialization failed with unexpected errno."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED), - "getrandom() syscall failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT), - "getrandom() engine was not properly initialized."}, - {0, NULL} -}; - -static int Cryptography_OSRandom_lib_error_code = 0; - -static void ERR_load_Cryptography_OSRandom_strings(void) -{ - if (Cryptography_OSRandom_lib_error_code == 0) { - Cryptography_OSRandom_lib_error_code = ERR_get_next_error_library(); - ERR_load_strings(Cryptography_OSRandom_lib_error_code, - CRYPTOGRAPHY_OSRANDOM_lib_name); - ERR_load_strings(Cryptography_OSRandom_lib_error_code, - CRYPTOGRAPHY_OSRANDOM_str_funcs); - ERR_load_strings(Cryptography_OSRandom_lib_error_code, - CRYPTOGRAPHY_OSRANDOM_str_reasons); - } -} - -static void ERR_Cryptography_OSRandom_error(int function, int reason, - char *file, int line) -{ - ERR_PUT_error(Cryptography_OSRandom_lib_error_code, function, reason, - file, line); -} - -/* Returns 1 if successfully added, 2 if engine has previously been added, - and 0 for error. */ -int Cryptography_add_osrandom_engine(void) { - ENGINE *e; - - ERR_load_Cryptography_OSRandom_strings(); - - e = ENGINE_by_id(Cryptography_osrandom_engine_id); - if (e != NULL) { - ENGINE_free(e); - return 2; - } else { - ERR_clear_error(); - } - - e = ENGINE_new(); - if (e == NULL) { - return 0; - } - if (!ENGINE_set_id(e, Cryptography_osrandom_engine_id) || - !ENGINE_set_name(e, Cryptography_osrandom_engine_name) || - !ENGINE_set_RAND(e, &osrandom_rand) || - !ENGINE_set_init_function(e, osrandom_init) || - !ENGINE_set_finish_function(e, osrandom_finish) || - !ENGINE_set_cmd_defns(e, osrandom_cmd_defns) || - !ENGINE_set_ctrl_function(e, osrandom_ctrl)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_add(e)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_free(e)) { - return 0; - } - - return 1; -} - -#else -/* If OpenSSL has no ENGINE support then we don't want - * to compile the osrandom engine, but we do need some - * placeholders */ -static const char *Cryptography_osrandom_engine_id = "no-engine-support"; -static const char *Cryptography_osrandom_engine_name = "osrandom_engine disabled"; - -int Cryptography_add_osrandom_engine(void) { - return 0; -} - -#endif diff --git a/src/_cffi_src/openssl/src/osrandom_engine.h b/src/_cffi_src/openssl/src/osrandom_engine.h deleted file mode 100644 index 93d918b88bf5..000000000000 --- a/src/_cffi_src/openssl/src/osrandom_engine.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef OPENSSL_NO_ENGINE -/* OpenSSL has ENGINE support so include all of this. */ -#ifdef _WIN32 - #include -#else - #include - #include - /* for defined(BSD) */ - #ifndef __MVS__ - #include - #endif - - #ifdef BSD - /* for SYS_getentropy */ - #include - #endif - - #ifdef __APPLE__ - #include - /* To support weak linking we need to declare this as a weak import even if - * it's not present in sys/random (e.g. macOS < 10.12). */ - extern int getentropy(void *buffer, size_t size) __attribute((weak_import)); - #endif - - #ifdef __linux__ - /* for SYS_getrandom */ - #include - #ifndef GRND_NONBLOCK - #define GRND_NONBLOCK 0x0001 - #endif /* GRND_NONBLOCK */ - - #ifndef SYS_getrandom - /* We only bother to define the constants for platforms where we ship - * wheels, since that's the predominant way you get a situation where - * you don't have SYS_getrandom at compile time but do have the syscall - * at runtime */ - #if defined(__x86_64__) - #define SYS_getrandom 318 - #elif defined(__i386__) - #define SYS_getrandom 355 - #elif defined(__aarch64__) - #define SYS_getrandom 278 - #endif - #endif - #endif /* __linux__ */ -#endif /* _WIN32 */ - -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM 1 -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY 2 -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM 3 -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM 4 - -#ifndef CRYPTOGRAPHY_OSRANDOM_ENGINE - #if defined(_WIN32) - /* Windows */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM - #elif defined(BSD) && defined(SYS_getentropy) - /* OpenBSD 5.6+ & macOS with SYS_getentropy defined, although < 10.12 will fallback - * to urandom */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY - #elif defined(__linux__) && defined(SYS_getrandom) - /* Linux 3.17+ */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM - #else - /* Keep this as last entry, fall back to /dev/urandom */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM - #endif -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE */ - -/* Fallbacks need /dev/urandom helper functions. */ -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM || \ - CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM || \ - (CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY && \ - defined(__APPLE__)) - #define CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM 1 -#endif - -enum { - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED = -2, - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT, - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK, - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS -}; - -enum { - CRYPTOGRAPHY_OSRANDOM_GETENTROPY_NOT_INIT, - CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK, - CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS -}; - -/* engine ctrl */ -#define CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION ENGINE_CMD_BASE - -/* error reporting */ -static void ERR_load_Cryptography_OSRandom_strings(void); -static void ERR_Cryptography_OSRandom_error(int function, int reason, - char *file, int line); - -#define CRYPTOGRAPHY_OSRANDOM_F_INIT 100 -#define CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES 101 -#define CRYPTOGRAPHY_OSRANDOM_F_FINISH 102 -#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD 300 -#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ 301 - -#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT 100 -#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM 101 -#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT 102 - -#define CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED 200 - -#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED 300 -#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED 301 - -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED 400 -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED 402 -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED 403 -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT 404 -#endif diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py index c38e309a1835..dfab7f651341 100644 --- a/src/_cffi_src/openssl/ssl.py +++ b/src/_cffi_src/openssl/ssl.py @@ -2,54 +2,32 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include - -typedef STACK_OF(SSL_CIPHER) Cryptography_STACK_OF_SSL_CIPHER; """ TYPES = """ static const long Cryptography_HAS_SSL_ST; static const long Cryptography_HAS_TLS_ST; -static const long Cryptography_HAS_SSL2; -static const long Cryptography_HAS_SSL3_METHOD; -static const long Cryptography_HAS_TLSv1_1; -static const long Cryptography_HAS_TLSv1_2; -static const long Cryptography_HAS_TLSv1_3; -static const long Cryptography_HAS_SECURE_RENEGOTIATION; -static const long Cryptography_HAS_TLSEXT_STATUS_REQ_CB; -static const long Cryptography_HAS_STATUS_REQ_OCSP_RESP; -static const long Cryptography_HAS_TLSEXT_STATUS_REQ_TYPE; -static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS; -static const long Cryptography_HAS_DTLS; +static const long Cryptography_HAS_TLSv1_3_FUNCTIONS; static const long Cryptography_HAS_SIGALGS; static const long Cryptography_HAS_PSK; -static const long Cryptography_HAS_CIPHER_DETAILS; +static const long Cryptography_HAS_PSK_TLSv1_3; static const long Cryptography_HAS_VERIFIED_CHAIN; static const long Cryptography_HAS_KEYLOG; +static const long Cryptography_HAS_SSL_COOKIE; -/* Internally invented symbol to tell us if SNI is supported */ -static const long Cryptography_HAS_TLSEXT_HOSTNAME; - -/* Internally invented symbol to tell us if SSL_MODE_RELEASE_BUFFERS is - * supported - */ -static const long Cryptography_HAS_RELEASE_BUFFERS; - -/* Internally invented symbol to tell us if SSL_OP_NO_COMPRESSION is - * supported - */ -static const long Cryptography_HAS_OP_NO_COMPRESSION; -static const long Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING; -static const long Cryptography_HAS_SSL_SET_SSL_CTX; -static const long Cryptography_HAS_SSL_OP_NO_TICKET; +static const long Cryptography_HAS_OP_NO_RENEGOTIATION; +static const long Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF; static const long Cryptography_HAS_ALPN; static const long Cryptography_HAS_NEXTPROTONEG; static const long Cryptography_HAS_SET_CERT_CB; +static const long Cryptography_HAS_GET_EXTMS_SUPPORT; static const long Cryptography_HAS_CUSTOM_EXT; static const long Cryptography_HAS_SRTP; +static const long Cryptography_HAS_DTLS_GET_DATA_MTU; static const long SSL_FILETYPE_PEM; static const long SSL_FILETYPE_ASN1; @@ -58,7 +36,6 @@ static const long SSL_ERROR_WANT_READ; static const long SSL_ERROR_WANT_WRITE; static const long SSL_ERROR_WANT_X509_LOOKUP; -static const long SSL_ERROR_WANT_CONNECT; static const long SSL_ERROR_SYSCALL; static const long SSL_ERROR_SSL; static const long SSL_SENT_SHUTDOWN; @@ -69,8 +46,7 @@ static const long SSL_OP_NO_TLSv1_1; static const long SSL_OP_NO_TLSv1_2; static const long SSL_OP_NO_TLSv1_3; -static const long SSL_OP_NO_DTLSv1; -static const long SSL_OP_NO_DTLSv1_2; +static const long SSL_OP_NO_RENEGOTIATION; static const long SSL_OP_NO_COMPRESSION; static const long SSL_OP_SINGLE_DH_USE; static const long SSL_OP_EPHEMERAL_RSA; @@ -95,8 +71,7 @@ static const long SSL_OP_NO_TICKET; static const long SSL_OP_ALL; static const long SSL_OP_SINGLE_ECDH_USE; -static const long SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; -static const long SSL_OP_LEGACY_SERVER_CONNECT; +static const long SSL_OP_IGNORE_UNEXPECTED_EOF; static const long SSL_VERIFY_PEER; static const long SSL_VERIFY_FAIL_IF_NO_PEER_CERT; static const long SSL_VERIFY_CLIENT_ONCE; @@ -132,12 +107,16 @@ 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 SSL3_RANDOM_SIZE; static const long TLS_ST_BEFORE; static const long TLS_ST_OK; +static const long SSL3_VERSION; +static const long TLS1_VERSION; +static const long TLS1_1_VERSION; +static const long TLS1_2_VERSION; +static const long TLS1_3_VERSION; + typedef ... SSL_METHOD; typedef ... SSL_CTX; @@ -149,7 +128,6 @@ static const long TLSEXT_STATUSTYPE_ocsp; typedef ... SSL_CIPHER; -typedef ... Cryptography_STACK_OF_SSL_CIPHER; typedef struct { const char *name; @@ -178,14 +156,14 @@ X509 *SSL_get_certificate(const SSL *); X509 *SSL_get_peer_certificate(const SSL *); int SSL_get_ex_data_X509_STORE_CTX_idx(void); +void SSL_set_verify(SSL *, int, int (*)(int, X509_STORE_CTX *)); +int SSL_get_verify_mode(const SSL *); + +long SSL_get_extms_support(SSL *); -/* Added in 1.0.2 */ X509_VERIFY_PARAM *SSL_get0_param(SSL *); X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *); -int SSL_get_sigalgs(SSL *, int, int *, int *, int *, unsigned char *, - unsigned char *); - Cryptography_STACK_OF_X509 *SSL_get_peer_cert_chain(const SSL *); Cryptography_STACK_OF_X509 *SSL_get0_verified_chain(const SSL *); Cryptography_STACK_OF_X509_NAME *SSL_get_client_CA_list(const SSL *); @@ -197,6 +175,8 @@ int SSL_renegotiate(SSL *); int SSL_renegotiate_pending(SSL *); const char *SSL_get_cipher_list(const SSL *, int); +int SSL_use_certificate(SSL *, X509 *); +int SSL_use_PrivateKey(SSL *, EVP_PKEY *); /* context */ void SSL_CTX_free(SSL_CTX *); @@ -209,16 +189,12 @@ int SSL_CTX_set_cipher_list(SSL_CTX *, const char *); int SSL_CTX_load_verify_locations(SSL_CTX *, const char *, const char *); void SSL_CTX_set_default_passwd_cb(SSL_CTX *, pem_password_cb *); -void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *, void *); int SSL_CTX_use_certificate(SSL_CTX *, X509 *); int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int); int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *); int SSL_CTX_use_PrivateKey(SSL_CTX *, EVP_PKEY *); int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int); int SSL_CTX_check_private_key(const SSL_CTX *); -void SSL_CTX_set_cert_verify_callback(SSL_CTX *, - int (*)(X509_STORE_CTX *, void *), - void *); void SSL_CTX_set_cookie_generate_cb(SSL_CTX *, int (*)( @@ -226,8 +202,12 @@ unsigned char *, unsigned int * )); -long SSL_CTX_get_read_ahead(SSL_CTX *); -long SSL_CTX_set_read_ahead(SSL_CTX *, long); +void SSL_CTX_set_cookie_verify_cb(SSL_CTX *, + int (*)( + SSL *, + const unsigned char *, + unsigned int + )); int SSL_CTX_use_psk_identity_hint(SSL_CTX *, const char *); void SSL_CTX_set_psk_server_callback(SSL_CTX *, @@ -246,18 +226,51 @@ unsigned char *, unsigned int )); +void SSL_CTX_set_psk_find_session_callback(SSL_CTX *, + int (*)( + SSL *, + const unsigned char *, + size_t, + SSL_SESSION ** + )); +void SSL_CTX_set_psk_use_session_callback(SSL_CTX *, + int (*)( + SSL *, + const EVP_MD *, + const unsigned char **, + size_t *, + SSL_SESSION ** + )); +const SSL_CIPHER *SSL_CIPHER_find(SSL *, const unsigned char *); +/* Wrap SSL_SESSION_new to avoid namespace collision. */ +SSL_SESSION *Cryptography_SSL_SESSION_new(void); +int SSL_SESSION_set1_master_key(SSL_SESSION *, const unsigned char *, + size_t); +int SSL_SESSION_set_cipher(SSL_SESSION *, const SSL_CIPHER *); +int SSL_SESSION_set_protocol_version(SSL_SESSION *, int); int SSL_CTX_set_session_id_context(SSL_CTX *, const unsigned char *, unsigned int); -void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *); X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *); +void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *); int SSL_CTX_add_client_CA(SSL_CTX *, X509 *); void SSL_CTX_set_client_CA_list(SSL_CTX *, Cryptography_STACK_OF_X509_NAME *); void SSL_CTX_set_info_callback(SSL_CTX *, void (*)(const SSL *, int, int)); -void (*SSL_CTX_get_info_callback(SSL_CTX *))(const SSL *, int, int); + +void SSL_CTX_set_msg_callback(SSL_CTX *, + void (*)( + int, + int, + int, + const void *, + size_t, + SSL *, + void * + )); +void SSL_CTX_set_msg_callback_arg(SSL_CTX *, void *); void SSL_CTX_set_keylog_callback(SSL_CTX *, void (*)(const SSL *, const char *)); @@ -271,105 +284,48 @@ /* Information about actually used cipher */ const char *SSL_CIPHER_get_name(const SSL_CIPHER *); int SSL_CIPHER_get_bits(const SSL_CIPHER *, int *); -/* the modern signature of this is uint32_t, but older openssl declared it - as unsigned long. To make our compiler flags happy we'll declare it as a - 64-bit wide value, which should always be safe */ -uint64_t SSL_CIPHER_get_id(const SSL_CIPHER *); -int SSL_CIPHER_is_aead(const SSL_CIPHER *); -int SSL_CIPHER_get_cipher_nid(const SSL_CIPHER *); -int SSL_CIPHER_get_digest_nid(const SSL_CIPHER *); -int SSL_CIPHER_get_kx_nid(const SSL_CIPHER *); -int SSL_CIPHER_get_auth_nid(const SSL_CIPHER *); size_t SSL_get_finished(const SSL *, void *, size_t); size_t SSL_get_peer_finished(const SSL *, void *, size_t); Cryptography_STACK_OF_X509_NAME *SSL_load_client_CA_file(const char *); const char *SSL_get_servername(const SSL *, const int); -/* Function signature changed to const char * in 1.1.0 */ const char *SSL_CIPHER_get_version(const SSL_CIPHER *); -/* These became macros in 1.1.0 */ -int SSL_library_init(void); -void SSL_load_error_strings(void); - -/* these CRYPTO_EX_DATA functions became macros in 1.1.0 */ -int SSL_get_ex_new_index(long, void *, CRYPTO_EX_new *, CRYPTO_EX_dup *, - CRYPTO_EX_free *); -int SSL_set_ex_data(SSL *, int, void *); -int SSL_CTX_get_ex_new_index(long, void *, CRYPTO_EX_new *, CRYPTO_EX_dup *, - CRYPTO_EX_free *); -int SSL_CTX_set_ex_data(SSL_CTX *, int, void *); SSL_SESSION *SSL_get_session(const SSL *); -const unsigned char *SSL_SESSION_get_id(const SSL_SESSION *, unsigned int *); -long SSL_SESSION_get_time(const SSL_SESSION *); -long SSL_SESSION_get_timeout(const SSL_SESSION *); -int SSL_SESSION_has_ticket(const SSL_SESSION *); -long SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *); -unsigned long SSL_set_mode(SSL *, unsigned long); -unsigned long SSL_clear_mode(SSL *, unsigned long); -unsigned long SSL_get_mode(SSL *); - -unsigned long SSL_set_options(SSL *, unsigned long); -unsigned long SSL_get_options(SSL *); +uint64_t SSL_set_options(SSL *, uint64_t); +uint64_t SSL_get_options(SSL *); int SSL_want_read(const SSL *); int SSL_want_write(const SSL *); long SSL_total_renegotiations(SSL *); -long SSL_get_secure_renegotiation_support(SSL *); - -/* Defined as unsigned long because SSL_OP_ALL is greater than signed 32-bit - and Windows defines long as 32-bit. */ -unsigned long SSL_CTX_set_options(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_clear_options(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_get_options(SSL_CTX *); -unsigned long SSL_CTX_set_mode(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_clear_mode(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_get_mode(SSL_CTX *); -unsigned long SSL_CTX_set_session_cache_mode(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_get_session_cache_mode(SSL_CTX *); -unsigned long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *); -unsigned long SSL_CTX_set_tmp_ecdh(SSL_CTX *, EC_KEY *); -unsigned long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *); - -/*- These aren't macros these functions are all const X on openssl > 1.0.x -*/ - -/* methods */ -/* - * TLSv1_1 and TLSv1_2 are recent additions. Only sufficiently new versions of - * OpenSSL support them. - */ -const SSL_METHOD *TLSv1_1_method(void); -const SSL_METHOD *TLSv1_1_server_method(void); -const SSL_METHOD *TLSv1_1_client_method(void); - -const SSL_METHOD *TLSv1_2_method(void); -const SSL_METHOD *TLSv1_2_server_method(void); -const SSL_METHOD *TLSv1_2_client_method(void); - -const SSL_METHOD *SSLv3_method(void); -const SSL_METHOD *SSLv3_server_method(void); -const SSL_METHOD *SSLv3_client_method(void); - -const SSL_METHOD *TLSv1_method(void); -const SSL_METHOD *TLSv1_server_method(void); -const SSL_METHOD *TLSv1_client_method(void); - -const SSL_METHOD *DTLSv1_method(void); -const SSL_METHOD *DTLSv1_server_method(void); -const SSL_METHOD *DTLSv1_client_method(void); - -/* Added in 1.0.2 */ +long SSL_CTX_set_min_proto_version(SSL_CTX *, int); +long SSL_CTX_set_max_proto_version(SSL_CTX *, int); + +long SSL_CTX_set_tmp_ecdh(SSL_CTX *, EC_KEY *); +long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *); +long SSL_CTX_set_session_cache_mode(SSL_CTX *, long); +long SSL_CTX_get_session_cache_mode(SSL_CTX *); +long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *); + +uint64_t SSL_CTX_set_options(SSL_CTX *, uint64_t); +uint64_t SSL_CTX_get_options(SSL_CTX *); + +long SSL_CTX_set_mode(SSL_CTX *, long); +long SSL_CTX_clear_mode(SSL_CTX *, long); +long SSL_set_mode(SSL *, long); +long SSL_clear_mode(SSL *, long); + const SSL_METHOD *DTLS_method(void); const SSL_METHOD *DTLS_server_method(void); const SSL_METHOD *DTLS_client_method(void); -const SSL_METHOD *SSLv23_method(void); -const SSL_METHOD *SSLv23_server_method(void); -const SSL_METHOD *SSLv23_client_method(void); +const SSL_METHOD *TLS_method(void); +const SSL_METHOD *TLS_server_method(void); +const SSL_METHOD *TLS_client_method(void); /*- These aren't macros these arguments are all const X on openssl > 1.0.x -*/ SSL_CTX *SSL_CTX_new(SSL_METHOD *); @@ -379,15 +335,10 @@ const char *SSL_get_version(const SSL *); int SSL_version(const SSL *); -void *SSL_CTX_get_ex_data(const SSL_CTX *, int); -void *SSL_get_ex_data(const SSL *, int); - void SSL_set_tlsext_host_name(SSL *, char *); void SSL_CTX_set_tlsext_servername_callback( SSL_CTX *, int (*)(SSL *, int *, void *)); -void SSL_CTX_set_tlsext_servername_arg( - SSL_CTX *, void *); long SSL_set_tlsext_status_ocsp_resp(SSL *, unsigned char *, int); long SSL_get_tlsext_status_ocsp_resp(SSL *, const unsigned char **); @@ -399,18 +350,6 @@ int SSL_set_tlsext_use_srtp(SSL *, const char *); SRTP_PROTECTION_PROFILE *SSL_get_selected_srtp_profile(SSL *); -long SSL_session_reused(SSL *); - -int SSL_select_next_proto(unsigned char **, unsigned char *, - const unsigned char *, unsigned int, - const unsigned char *, unsigned int); - -int sk_SSL_CIPHER_num(Cryptography_STACK_OF_SSL_CIPHER *); -const SSL_CIPHER *sk_SSL_CIPHER_value(Cryptography_STACK_OF_SSL_CIPHER *, int); - -/* ALPN APIs were introduced in OpenSSL 1.0.2. To continue to support earlier - * versions some special handling of these is necessary. - */ int SSL_CTX_set_alpn_protos(SSL_CTX *, const unsigned char *, unsigned); int SSL_set_alpn_protos(SSL *, const unsigned char *, unsigned); void SSL_CTX_set_alpn_select_cb(SSL_CTX *, @@ -423,17 +362,9 @@ void *); void SSL_get0_alpn_selected(const SSL *, const unsigned char **, unsigned *); -long SSL_get_server_tmp_key(SSL *, EVP_PKEY **); - -/* SSL_CTX_set_cert_cb is introduced in OpenSSL 1.0.2. To continue to support - * earlier versions some special handling of these is necessary. - */ void SSL_CTX_set_cert_cb(SSL_CTX *, int (*)(SSL *, void *), void *); void SSL_set_cert_cb(SSL *, int (*)(SSL *, void *), void *); -int SSL_SESSION_set1_id_context(SSL_SESSION *, const unsigned char *, - unsigned int); -/* Added in 1.1.0 for the great opaquing of structs */ size_t SSL_SESSION_get_master_key(const SSL_SESSION *, unsigned char *, size_t); size_t SSL_get_client_random(const SSL *, unsigned char *, size_t); @@ -441,24 +372,13 @@ int SSL_export_keying_material(SSL *, unsigned char *, size_t, const char *, size_t, const unsigned char *, size_t, int); -long SSL_CTX_sess_number(SSL_CTX *); -long SSL_CTX_sess_connect(SSL_CTX *); -long SSL_CTX_sess_connect_good(SSL_CTX *); -long SSL_CTX_sess_connect_renegotiate(SSL_CTX *); -long SSL_CTX_sess_accept(SSL_CTX *); -long SSL_CTX_sess_accept_good(SSL_CTX *); -long SSL_CTX_sess_accept_renegotiate(SSL_CTX *); -long SSL_CTX_sess_hits(SSL_CTX *); -long SSL_CTX_sess_cb_hits(SSL_CTX *); -long SSL_CTX_sess_misses(SSL_CTX *); -long SSL_CTX_sess_timeouts(SSL_CTX *); -long SSL_CTX_sess_cache_full(SSL_CTX *); - /* DTLS support */ long Cryptography_DTLSv1_get_timeout(SSL *, time_t *, long *); long DTLSv1_handle_timeout(SSL *); -long DTLS_set_link_mtu(SSL *, long); -long DTLS_get_link_min_mtu(SSL *); +long SSL_set_mtu(SSL *, long); +int DTLSv1_listen(SSL *, BIO_ADDR *); +size_t DTLS_get_data_mtu(SSL *); + /* Custom extensions. */ typedef int (*custom_ext_add_cb)(SSL *, unsigned int, @@ -498,125 +418,60 @@ int SSL_write_early_data(SSL *, const void *, size_t, size_t *); int SSL_read_early_data(SSL *, void *, size_t, size_t *); int SSL_CTX_set_max_early_data(SSL_CTX *, uint32_t); + +/* + Added as an advanced user escape hatch. This symbol is tied to + engine support but is declared in ssl.h +*/ +int SSL_CTX_set_client_cert_engine(SSL_CTX *, ENGINE *); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 +#ifdef OPENSSL_NO_ENGINE +int (*SSL_CTX_set_client_cert_engine)(SSL_CTX *, ENGINE *) = NULL; +#endif + +#if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_VERIFIED_CHAIN = 0; Cryptography_STACK_OF_X509 *(*SSL_get0_verified_chain)(const SSL *) = NULL; #else static const long Cryptography_HAS_VERIFIED_CHAIN = 1; #endif -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 -static const long Cryptography_HAS_KEYLOG = 0; -void (*SSL_CTX_set_keylog_callback)(SSL_CTX *, - void (*) (const SSL *, const char *) - ) = NULL; -void (*(*SSL_CTX_get_keylog_callback)(SSL_CTX *))( - const SSL *, - const char * - ) = NULL; -#else static const long Cryptography_HAS_KEYLOG = 1; -#endif - -/* Added in 1.1.0 in the great opaquing, but we need to define it for older - OpenSSLs. Such is our burden. */ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 && !CRYPTOGRAPHY_IS_LIBRESSL -/* from ssl/ssl_lib.c */ -size_t SSL_get_client_random(const SSL *ssl, unsigned char *out, size_t outlen) -{ - if (outlen == 0) - return sizeof(ssl->s3->client_random); - if (outlen > sizeof(ssl->s3->client_random)) - outlen = sizeof(ssl->s3->client_random); - memcpy(out, ssl->s3->client_random, outlen); - return outlen; -} -/* Added in 1.1.0 as well */ -/* from ssl/ssl_lib.c */ -size_t SSL_get_server_random(const SSL *ssl, unsigned char *out, size_t outlen) -{ - if (outlen == 0) - return sizeof(ssl->s3->server_random); - if (outlen > sizeof(ssl->s3->server_random)) - outlen = sizeof(ssl->s3->server_random); - memcpy(out, ssl->s3->server_random, outlen); - return outlen; -} -/* Added in 1.1.0 as well */ -/* from ssl/ssl_lib.c */ -size_t SSL_SESSION_get_master_key(const SSL_SESSION *session, - unsigned char *out, size_t outlen) -{ - if (session->master_key_length < 0) { - /* Should never happen */ - return 0; - } - if (outlen == 0) - return session->master_key_length; - if (outlen > (size_t)session->master_key_length) - outlen = session->master_key_length; - memcpy(out, session->master_key, outlen); - return outlen; -} -/* from ssl/ssl_sess.c */ -int SSL_SESSION_has_ticket(const SSL_SESSION *s) -{ - return (s->tlsext_ticklen > 0) ? 1 : 0; -} -/* from ssl/ssl_sess.c */ -unsigned long SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *s) -{ - return s->tlsext_tick_lifetime_hint; -} -#endif - -static const long Cryptography_HAS_SECURE_RENEGOTIATION = 1; -/* Cryptography now compiles out all SSLv2 bindings. This exists to allow - * clients that use it to check for SSLv2 support to keep functioning as - * expected. - */ -static const long Cryptography_HAS_SSL2 = 0; +static const long Cryptography_HAS_NEXTPROTONEG = 0; +static const long Cryptography_HAS_ALPN = 1; -#ifdef OPENSSL_NO_SSL3_METHOD -static const long Cryptography_HAS_SSL3_METHOD = 0; -SSL_METHOD* (*SSLv3_method)(void) = NULL; -SSL_METHOD* (*SSLv3_client_method)(void) = NULL; -SSL_METHOD* (*SSLv3_server_method)(void) = NULL; +#ifdef SSL_OP_NO_RENEGOTIATION +static const long Cryptography_HAS_OP_NO_RENEGOTIATION = 1; #else -static const long Cryptography_HAS_SSL3_METHOD = 1; +static const long Cryptography_HAS_OP_NO_RENEGOTIATION = 0; +static const long SSL_OP_NO_RENEGOTIATION = 0; #endif -static const long Cryptography_HAS_TLSEXT_HOSTNAME = 1; -static const long Cryptography_HAS_TLSEXT_STATUS_REQ_CB = 1; -static const long Cryptography_HAS_STATUS_REQ_OCSP_RESP = 1; -static const long Cryptography_HAS_TLSEXT_STATUS_REQ_TYPE = 1; -static const long Cryptography_HAS_RELEASE_BUFFERS = 1; -static const long Cryptography_HAS_OP_NO_COMPRESSION = 1; -static const long Cryptography_HAS_TLSv1_1 = 1; -static const long Cryptography_HAS_TLSv1_2 = 1; -static const long Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING = 1; -static const long Cryptography_HAS_SSL_OP_NO_TICKET = 1; -static const long Cryptography_HAS_SSL_SET_SSL_CTX = 1; -static const long Cryptography_HAS_NEXTPROTONEG = 0; -static const long Cryptography_HAS_ALPN = 1; +#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF +static const long Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF = 1; +#else +static const long Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF = 0; +static const long SSL_OP_IGNORE_UNEXPECTED_EOF = 1; +#endif #if CRYPTOGRAPHY_IS_LIBRESSL void (*SSL_CTX_set_cert_cb)(SSL_CTX *, int (*)(SSL *, void *), void *) = NULL; void (*SSL_set_cert_cb)(SSL *, int (*)(SSL *, void *), void *) = NULL; static const long Cryptography_HAS_SET_CERT_CB = 0; + +long (*SSL_get_extms_support)(SSL *) = NULL; +static const long Cryptography_HAS_GET_EXTMS_SUPPORT = 0; #else static const long Cryptography_HAS_SET_CERT_CB = 1; +static const long Cryptography_HAS_GET_EXTMS_SUPPORT = 1; #endif -static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS = 1; - /* in OpenSSL 1.1.0 the SSL_ST values were renamed to TLS_ST and several were removed */ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_SSL_ST = 1; #else static const long Cryptography_HAS_SSL_ST = 0; @@ -625,7 +480,7 @@ static const long SSL_ST_INIT = 0; static const long SSL_ST_RENEGOTIATE = 0; #endif -#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER +#if !CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_TLS_ST = 1; #else static const long Cryptography_HAS_TLS_ST = 0; @@ -633,14 +488,13 @@ static const long TLS_ST_OK = 0; #endif -#if CRYPTOGRAPHY_IS_LIBRESSL -static const long SSL_OP_NO_DTLSv1 = 0; -static const long SSL_OP_NO_DTLSv1_2 = 0; -long (*DTLS_set_link_mtu)(SSL *, long) = NULL; -long (*DTLS_get_link_min_mtu)(SSL *) = NULL; +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_DTLS_GET_DATA_MTU = 0; +size_t (*DTLS_get_data_mtu)(SSL *) = NULL; +#else +static const long Cryptography_HAS_DTLS_GET_DATA_MTU = 1; #endif -static const long Cryptography_HAS_DTLS = 1; /* Wrap DTLSv1_get_timeout to avoid cffi to handle a 'struct timeval'. */ long Cryptography_DTLSv1_get_timeout(SSL *ssl, time_t *ptv_sec, long *ptv_usec) { @@ -662,8 +516,6 @@ #if CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_SIGALGS = 0; -const int (*SSL_get_sigalgs)(SSL *, int, int *, int *, int *, unsigned char *, - unsigned char *) = NULL; const long (*SSL_CTX_set1_sigalgs_list)(SSL_CTX *, const char *) = NULL; #else static const long Cryptography_HAS_SIGALGS = 1; @@ -692,7 +544,7 @@ static const long Cryptography_HAS_PSK = 1; #endif -#if !CRYPTOGRAPHY_IS_LIBRESSL +#if !CRYPTOGRAPHY_IS_LIBRESSL && !CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_CUSTOM_EXT = 1; #else static const long Cryptography_HAS_CUSTOM_EXT = 0; @@ -729,20 +581,9 @@ SRTP_PROTECTION_PROFILE * (*SSL_get_selected_srtp_profile)(SSL *) = NULL; #endif -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 && !CRYPTOGRAPHY_IS_LIBRESSL -int (*SSL_CIPHER_is_aead)(const SSL_CIPHER *) = NULL; -int (*SSL_CIPHER_get_cipher_nid)(const SSL_CIPHER *) = NULL; -int (*SSL_CIPHER_get_digest_nid)(const SSL_CIPHER *) = NULL; -int (*SSL_CIPHER_get_kx_nid)(const SSL_CIPHER *) = NULL; -int (*SSL_CIPHER_get_auth_nid)(const SSL_CIPHER *) = NULL; -static const long Cryptography_HAS_CIPHER_DETAILS = 0; -#else -static const long Cryptography_HAS_CIPHER_DETAILS = 1; -#endif +#if CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 0; -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 -static const long Cryptography_HAS_TLSv1_3 = 0; -static const long SSL_OP_NO_TLSv1_3 = 0; static const long SSL_VERIFY_POST_HANDSHAKE = 0; int (*SSL_CTX_set_ciphersuites)(SSL_CTX *, const char *) = NULL; int (*SSL_verify_client_post_handshake)(SSL *) = NULL; @@ -753,6 +594,60 @@ int (*SSL_read_early_data)(SSL *, void *, size_t, size_t *) = NULL; int (*SSL_CTX_set_max_early_data)(SSL_CTX *, uint32_t) = NULL; #else -static const long Cryptography_HAS_TLSv1_3 = 1; +static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 1; +#endif + +#if CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_SSL_COOKIE = 0; + +static const long SSL_OP_COOKIE_EXCHANGE = 0; +int (*DTLSv1_listen)(SSL *, BIO_ADDR *) = NULL; +void (*SSL_CTX_set_cookie_generate_cb)(SSL_CTX *, + int (*)( + SSL *, + unsigned char *, + unsigned int * + )) = NULL; +void (*SSL_CTX_set_cookie_verify_cb)(SSL_CTX *, + int (*)( + SSL *, + const unsigned char *, + unsigned int + )) = NULL; +#else +static const long Cryptography_HAS_SSL_COOKIE = 1; +#endif +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL +static const long Cryptography_HAS_PSK_TLSv1_3 = 0; +void (*SSL_CTX_set_psk_find_session_callback)(SSL_CTX *, + int (*)( + SSL *, + const unsigned char *, + size_t, + SSL_SESSION ** + )) = NULL; +void (*SSL_CTX_set_psk_use_session_callback)(SSL_CTX *, + int (*)( + SSL *, + const EVP_MD *, + const unsigned char **, + size_t *, + SSL_SESSION ** + )) = NULL; +#if CRYPTOGRAPHY_IS_BORINGSSL +const SSL_CIPHER *(*SSL_CIPHER_find)(SSL *, const unsigned char *) = NULL; +#endif +int (*SSL_SESSION_set1_master_key)(SSL_SESSION *, const unsigned char *, + size_t) = NULL; +int (*SSL_SESSION_set_cipher)(SSL_SESSION *, const SSL_CIPHER *) = NULL; +#if !CRYPTOGRAPHY_IS_BORINGSSL +int (*SSL_SESSION_set_protocol_version)(SSL_SESSION *, int) = NULL; +#endif +SSL_SESSION *(*Cryptography_SSL_SESSION_new)(void) = NULL; +#else +static const long Cryptography_HAS_PSK_TLSv1_3 = 1; +SSL_SESSION *Cryptography_SSL_SESSION_new(void) { + return SSL_SESSION_new(); +} #endif """ diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index b88daa1f213d..66e8592042fd 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -48,7 +48,6 @@ X509 *X509_new(void); void X509_free(X509 *); X509 *X509_dup(X509 *); -int X509_cmp(const X509 *, const X509 *); int X509_up_ref(X509 *); int X509_print_ex(BIO *, X509 *, unsigned long, unsigned long); @@ -59,6 +58,7 @@ int X509_set_pubkey(X509 *, EVP_PKEY *); unsigned char *X509_alias_get0(X509 *, int *); +int X509_alias_set1(X509 *, const unsigned char *, int); int X509_sign(X509 *, EVP_PKEY *, const EVP_MD *); int X509_digest(const X509 *, const EVP_MD *, unsigned char *, unsigned int *); @@ -81,20 +81,12 @@ X509_REQ *X509_REQ_new(void); void X509_REQ_free(X509_REQ *); int X509_REQ_set_pubkey(X509_REQ *, EVP_PKEY *); -int X509_REQ_set_subject_name(X509_REQ *, X509_NAME *); int X509_REQ_sign(X509_REQ *, EVP_PKEY *, const EVP_MD *); int X509_REQ_verify(X509_REQ *, EVP_PKEY *); EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *); int X509_REQ_print_ex(BIO *, X509_REQ *, unsigned long, unsigned long); int X509_REQ_add_extensions(X509_REQ *, X509_EXTENSIONS *); X509_EXTENSIONS *X509_REQ_get_extensions(X509_REQ *); -X509_ATTRIBUTE *X509_REQ_get_attr(const X509_REQ *, int); -int X509_REQ_get_attr_by_OBJ(const X509_REQ *, const ASN1_OBJECT *, int); -void *X509_ATTRIBUTE_get0_data(X509_ATTRIBUTE *, int, int, void *); -ASN1_TYPE *X509_ATTRIBUTE_get0_type(X509_ATTRIBUTE *, int); -int X509_ATTRIBUTE_count(const X509_ATTRIBUTE *); -int X509_REQ_add1_attr_by_OBJ(X509_REQ *, const ASN1_OBJECT *, - int, const unsigned char *, int); int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int); ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *); @@ -104,31 +96,25 @@ int X509_REVOKED_set_serialNumber(X509_REVOKED *, ASN1_INTEGER *); -int X509_REVOKED_add_ext(X509_REVOKED *, X509_EXTENSION*, int); int X509_REVOKED_add1_ext_i2d(X509_REVOKED *, int, void *, int, unsigned long); X509_EXTENSION *X509_REVOKED_delete_ext(X509_REVOKED *, int); int X509_REVOKED_set_revocationDate(X509_REVOKED *, ASN1_TIME *); X509_CRL *X509_CRL_new(void); -X509_CRL *X509_CRL_dup(X509_CRL *); X509_CRL *d2i_X509_CRL_bio(BIO *, X509_CRL **); int X509_CRL_add0_revoked(X509_CRL *, X509_REVOKED *); -int X509_CRL_add_ext(X509_CRL *, X509_EXTENSION *, int); -int X509_CRL_cmp(const X509_CRL *, const X509_CRL *); int X509_CRL_print(BIO *, X509_CRL *); int X509_CRL_set_issuer_name(X509_CRL *, X509_NAME *); int X509_CRL_set_version(X509_CRL *, long); int X509_CRL_sign(X509_CRL *, EVP_PKEY *, const EVP_MD *); int X509_CRL_sort(X509_CRL *); -int X509_CRL_verify(X509_CRL *, EVP_PKEY *); int i2d_X509_CRL_bio(BIO *, X509_CRL *); void X509_CRL_free(X509_CRL *); int NETSCAPE_SPKI_verify(NETSCAPE_SPKI *, EVP_PKEY *); int NETSCAPE_SPKI_sign(NETSCAPE_SPKI *, EVP_PKEY *, const EVP_MD *); char *NETSCAPE_SPKI_b64_encode(NETSCAPE_SPKI *); -NETSCAPE_SPKI *NETSCAPE_SPKI_b64_decode(const char *, int); EVP_PKEY *NETSCAPE_SPKI_get_pubkey(NETSCAPE_SPKI *); int NETSCAPE_SPKI_set_pubkey(NETSCAPE_SPKI *, EVP_PKEY *); NETSCAPE_SPKI *NETSCAPE_SPKI_new(void); @@ -161,49 +147,24 @@ int i2d_RSAPublicKey_bio(BIO *, RSA *); int i2d_DSAPrivateKey_bio(BIO *, DSA *); -/* These became const X509 in 1.1.0 */ -int X509_get_ext_count(X509 *); -X509_EXTENSION *X509_get_ext(X509 *, int); -X509_NAME *X509_get_subject_name(X509 *); -X509_NAME *X509_get_issuer_name(X509 *); +int X509_get_ext_count(const X509 *); +X509_EXTENSION *X509_get_ext(const X509 *, int); +X509_NAME *X509_get_subject_name(const X509 *); +X509_NAME *X509_get_issuer_name(const X509 *); -/* This became const ASN1_OBJECT * in 1.1.0 */ -X509_EXTENSION *X509_EXTENSION_create_by_OBJ(X509_EXTENSION **, - ASN1_OBJECT *, int, - ASN1_OCTET_STRING *); +int X509_EXTENSION_get_critical(const X509_EXTENSION *); - -/* This became const X509_EXTENSION * in 1.1.0 */ -int X509_EXTENSION_get_critical(X509_EXTENSION *); - -/* This became const X509_REVOKED * in 1.1.0 */ -int X509_REVOKED_get_ext_count(X509_REVOKED *); -X509_EXTENSION *X509_REVOKED_get_ext(X509_REVOKED *, int); - -/* This became const X509_CRL * in 1.1.0 */ -X509_EXTENSION *X509_CRL_get_ext(X509_CRL *, int); -int X509_CRL_get_ext_count(X509_CRL *); - -int X509_CRL_get0_by_serial(X509_CRL *, X509_REVOKED **, ASN1_INTEGER *); +int X509_REVOKED_get_ext_count(const X509_REVOKED *); +X509_EXTENSION *X509_REVOKED_get_ext(const X509_REVOKED *, int); X509_REVOKED *X509_REVOKED_dup(X509_REVOKED *); -X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *); - -/* new in 1.0.2 */ -int i2d_re_X509_tbs(X509 *, unsigned char **); -int X509_get_signature_nid(const X509 *); const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *); -void X509_get0_signature(const ASN1_BIT_STRING **, - const X509_ALGOR **, const X509 *); - long X509_get_version(X509 *); -ASN1_TIME *X509_get_notBefore(X509 *); -ASN1_TIME *X509_get_notAfter(X509 *); -ASN1_TIME *X509_getm_notBefore(X509 *); -ASN1_TIME *X509_getm_notAfter(X509 *); +ASN1_TIME *X509_getm_notBefore(const X509 *); +ASN1_TIME *X509_getm_notAfter(const X509 *); long X509_REQ_get_version(X509_REQ *); X509_NAME *X509_REQ_get_subject_name(X509_REQ *); @@ -218,127 +179,23 @@ int sk_X509_EXTENSION_num(X509_EXTENSIONS *); X509_EXTENSION *sk_X509_EXTENSION_value(X509_EXTENSIONS *, int); int sk_X509_EXTENSION_push(X509_EXTENSIONS *, X509_EXTENSION *); -int sk_X509_EXTENSION_insert(X509_EXTENSIONS *, X509_EXTENSION *, int); -X509_EXTENSION *sk_X509_EXTENSION_delete(X509_EXTENSIONS *, int); void sk_X509_EXTENSION_free(X509_EXTENSIONS *); void sk_X509_EXTENSION_pop_free(X509_EXTENSIONS *, sk_X509_EXTENSION_freefunc); int sk_X509_REVOKED_num(Cryptography_STACK_OF_X509_REVOKED *); X509_REVOKED *sk_X509_REVOKED_value(Cryptography_STACK_OF_X509_REVOKED *, int); -Cryptography_STACK_OF_X509_CRL *sk_X509_CRL_new_null(void); -void sk_X509_CRL_free(Cryptography_STACK_OF_X509_CRL *); -int sk_X509_CRL_num(Cryptography_STACK_OF_X509_CRL *); -int sk_X509_CRL_push(Cryptography_STACK_OF_X509_CRL *, X509_CRL *); -X509_CRL *sk_X509_CRL_value(Cryptography_STACK_OF_X509_CRL *, int); - -long X509_CRL_get_version(X509_CRL *); -ASN1_TIME *X509_CRL_get_lastUpdate(X509_CRL *); -ASN1_TIME *X509_CRL_get_nextUpdate(X509_CRL *); X509_NAME *X509_CRL_get_issuer(X509_CRL *); Cryptography_STACK_OF_X509_REVOKED *X509_CRL_get_REVOKED(X509_CRL *); -/* These aren't macros these arguments are all const X on openssl > 1.0.x */ -int X509_CRL_set_lastUpdate(X509_CRL *, ASN1_TIME *); -int X509_CRL_set_nextUpdate(X509_CRL *, ASN1_TIME *); -int X509_set_notBefore(X509 *, ASN1_TIME *); -int X509_set_notAfter(X509 *, ASN1_TIME *); -int X509_set1_notBefore(X509 *, ASN1_TIME *); -int X509_set1_notAfter(X509 *, ASN1_TIME *); - -EC_KEY *d2i_EC_PUBKEY_bio(BIO *, EC_KEY **); -int i2d_EC_PUBKEY_bio(BIO *, EC_KEY *); -EC_KEY *d2i_ECPrivateKey_bio(BIO *, EC_KEY **); -int i2d_ECPrivateKey_bio(BIO *, EC_KEY *); +int X509_CRL_set1_lastUpdate(X509_CRL *, const ASN1_TIME *); +int X509_CRL_set1_nextUpdate(X509_CRL *, const ASN1_TIME *); -// declared in safestack -int sk_ASN1_OBJECT_num(Cryptography_STACK_OF_ASN1_OBJECT *); -ASN1_OBJECT *sk_ASN1_OBJECT_value(Cryptography_STACK_OF_ASN1_OBJECT *, int); -void sk_ASN1_OBJECT_free(Cryptography_STACK_OF_ASN1_OBJECT *); -Cryptography_STACK_OF_ASN1_OBJECT *sk_ASN1_OBJECT_new_null(void); -int sk_ASN1_OBJECT_push(Cryptography_STACK_OF_ASN1_OBJECT *, ASN1_OBJECT *); +int i2d_ECPrivateKey_bio(BIO *, EC_KEY *); -/* these functions were added in 1.1.0 */ const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *); const ASN1_TIME *X509_REVOKED_get0_revocationDate(const X509_REVOKED *); -void X509_CRL_get0_signature(const X509_CRL *, const ASN1_BIT_STRING **, - const X509_ALGOR **); -int i2d_re_X509_REQ_tbs(X509_REQ *, unsigned char **); -int i2d_re_X509_CRL_tbs(X509_CRL *, unsigned char **); -void X509_REQ_get0_signature(const X509_REQ *, const ASN1_BIT_STRING **, - const X509_ALGOR **); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_LIBRESSL -int i2d_re_X509_tbs(X509 *x, unsigned char **pp) -{ - /* in 1.0.2+ this function also sets x->cert_info->enc.modified = 1 - but older OpenSSLs don't have the enc ASN1_ENCODING member in the - X509 struct. Setting modified to 1 marks the encoding - (x->cert_info->enc.enc) as invalid, but since the entire struct isn't - present we don't care. */ - return i2d_X509_CINF(x->cert_info, pp); -} -#endif - -/* Being kept around for pyOpenSSL */ -X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *rev) { - return X509_REVOKED_dup(rev); -} -/* Added in 1.1.0 but we need it in all versions now due to the great - opaquing. */ -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 -int i2d_re_X509_REQ_tbs(X509_REQ *req, unsigned char **pp) -{ - req->req_info->enc.modified = 1; - return i2d_X509_REQ_INFO(req->req_info, pp); -} -int i2d_re_X509_CRL_tbs(X509_CRL *crl, unsigned char **pp) { - crl->crl->enc.modified = 1; - return i2d_X509_CRL_INFO(crl->crl, pp); -} - -#if !CRYPTOGRAPHY_IS_LIBRESSL -int X509_up_ref(X509 *x) { - return CRYPTO_add(&x->references, 1, CRYPTO_LOCK_X509); -} - -const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *x) -{ - return x->cert_info->signature; -} - -/* from x509/x509_req.c */ -void X509_REQ_get0_signature(const X509_REQ *req, const ASN1_BIT_STRING **psig, - const X509_ALGOR **palg) -{ - if (psig != NULL) - *psig = req->signature; - if (palg != NULL) - *palg = req->sig_alg; -} -void X509_CRL_get0_signature(const X509_CRL *crl, const ASN1_BIT_STRING **psig, - const X509_ALGOR **palg) -{ - if (psig != NULL) - *psig = crl->signature; - if (palg != NULL) - *palg = crl->sig_alg; -} -const ASN1_TIME *X509_REVOKED_get0_revocationDate(const X509_REVOKED *x) -{ - return x->revocationDate; -} -const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *x) -{ - return x->serialNumber; -} - -#define X509_set1_notBefore X509_set_notBefore -#define X509_set1_notAfter X509_set_notAfter -#define X509_getm_notAfter X509_get_notAfter -#define X509_getm_notBefore X509_get_notBefore -#endif -#endif """ diff --git a/src/_cffi_src/openssl/x509_vfy.py b/src/_cffi_src/openssl/x509_vfy.py index d2bc5f4ec6e1..f1ea8ee6af82 100644 --- a/src/_cffi_src/openssl/x509_vfy.py +++ b/src/_cffi_src/openssl/x509_vfy.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -19,8 +19,6 @@ """ TYPES = """ -static const long Cryptography_HAS_102_VERIFICATION; -static const long Cryptography_HAS_110_VERIFICATION_PARAMS; static const long Cryptography_HAS_X509_STORE_CTX_GET_ISSUER; typedef ... Cryptography_STACK_OF_ASN1_OBJECT; @@ -33,11 +31,6 @@ typedef int (*X509_STORE_CTX_get_issuer_fn)(X509 **, X509_STORE_CTX *, X509 *); -/* While these are defined in the source as ints, they're tagged here - as longs, just in case they ever grow to large, such as what we saw - with OP_ALL. */ - -/* Verification error codes */ static const int X509_V_OK; static const int X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; static const int X509_V_ERR_UNABLE_TO_GET_CRL; @@ -91,20 +84,17 @@ static const int X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX; static const int X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; static const int X509_V_ERR_CRL_PATH_VALIDATION_ERROR; -static const int X509_V_ERR_SUITE_B_INVALID_VERSION; -static const int X509_V_ERR_SUITE_B_INVALID_ALGORITHM; -static const int X509_V_ERR_SUITE_B_INVALID_CURVE; -static const int X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM; -static const int X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED; -static const int X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256; static const int X509_V_ERR_HOSTNAME_MISMATCH; static const int X509_V_ERR_EMAIL_MISMATCH; static const int X509_V_ERR_IP_ADDRESS_MISMATCH; static const int X509_V_ERR_APPLICATION_VERIFICATION; + +/* While these are defined in the source as ints, they're tagged here + as longs, just in case they ever grow to large, such as what we saw + with OP_ALL. */ + /* Verification parameters */ -static const long X509_V_FLAG_CB_ISSUER_CHECK; -static const long X509_V_FLAG_USE_CHECK_TIME; static const long X509_V_FLAG_CRL_CHECK; static const long X509_V_FLAG_CRL_CHECK_ALL; static const long X509_V_FLAG_IGNORE_CRITICAL; @@ -112,27 +102,31 @@ static const long X509_V_FLAG_ALLOW_PROXY_CERTS; static const long X509_V_FLAG_POLICY_CHECK; static const long X509_V_FLAG_EXPLICIT_POLICY; -static const long X509_V_FLAG_INHIBIT_ANY; static const long X509_V_FLAG_INHIBIT_MAP; static const long X509_V_FLAG_NOTIFY_POLICY; -static const long X509_V_FLAG_EXTENDED_CRL_SUPPORT; -static const long X509_V_FLAG_USE_DELTAS; static const long X509_V_FLAG_CHECK_SS_SIGNATURE; -static const long X509_V_FLAG_TRUSTED_FIRST; -static const long X509_V_FLAG_SUITEB_128_LOS_ONLY; -static const long X509_V_FLAG_SUITEB_192_LOS; -static const long X509_V_FLAG_SUITEB_128_LOS; static const long X509_V_FLAG_PARTIAL_CHAIN; -static const long X509_LU_X509; -static const long X509_LU_CRL; - static const long X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; static const long X509_CHECK_FLAG_NO_WILDCARDS; static const long X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; static const long X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS; static const long X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS; static const long X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; + +/* Included due to external consumer, see + https://github.com/pyca/pyopenssl/issues/1031 */ +static const long X509_PURPOSE_SSL_CLIENT; +static const long X509_PURPOSE_SSL_SERVER; +static const long X509_PURPOSE_NS_SSL_SERVER; +static const long X509_PURPOSE_SMIME_SIGN; +static const long X509_PURPOSE_SMIME_ENCRYPT; +static const long X509_PURPOSE_CRL_SIGN; +static const long X509_PURPOSE_ANY; +static const long X509_PURPOSE_OCSP_HELPER; +static const long X509_PURPOSE_TIMESTAMP_SIGN; +static const long X509_PURPOSE_MIN; +static const long X509_PURPOSE_MAX; """ FUNCTIONS = """ @@ -146,6 +140,10 @@ int X509_STORE_set1_param(X509_STORE *, X509_VERIFY_PARAM *); int X509_STORE_set_default_paths(X509_STORE *); int X509_STORE_set_flags(X509_STORE *, unsigned long); +/* Included due to external consumer, see + https://github.com/pyca/pyopenssl/issues/1031 */ +int X509_STORE_set_purpose(X509_STORE *, int); +int X509_STORE_up_ref(X509_STORE *); void X509_STORE_free(X509_STORE *); /* X509_STORE_CTX */ @@ -154,121 +152,36 @@ void X509_STORE_CTX_free(X509_STORE_CTX *); int X509_STORE_CTX_init(X509_STORE_CTX *, X509_STORE *, X509 *, Cryptography_STACK_OF_X509 *); -void X509_STORE_CTX_trusted_stack(X509_STORE_CTX *, - Cryptography_STACK_OF_X509 *); -void X509_STORE_CTX_set_cert(X509_STORE_CTX *, X509 *); -void X509_STORE_CTX_set_chain(X509_STORE_CTX *,Cryptography_STACK_OF_X509 *); -X509_VERIFY_PARAM *X509_STORE_CTX_get0_param(X509_STORE_CTX *); -void X509_STORE_CTX_set0_param(X509_STORE_CTX *, X509_VERIFY_PARAM *); -int X509_STORE_CTX_set_default(X509_STORE_CTX *, const char *); -void X509_STORE_CTX_set_verify_cb(X509_STORE_CTX *, - int (*)(int, X509_STORE_CTX *)); -Cryptography_STACK_OF_X509 *X509_STORE_CTX_get_chain(X509_STORE_CTX *); Cryptography_STACK_OF_X509 *X509_STORE_CTX_get1_chain(X509_STORE_CTX *); int X509_STORE_CTX_get_error(X509_STORE_CTX *); void X509_STORE_CTX_set_error(X509_STORE_CTX *, int); int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *); X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *); -int X509_STORE_CTX_set_ex_data(X509_STORE_CTX *, int, void *); void *X509_STORE_CTX_get_ex_data(X509_STORE_CTX *, int); -int X509_STORE_CTX_get1_issuer(X509 **, X509_STORE_CTX *, X509 *); /* X509_VERIFY_PARAM */ X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void); int X509_VERIFY_PARAM_set_flags(X509_VERIFY_PARAM *, unsigned long); -int X509_VERIFY_PARAM_clear_flags(X509_VERIFY_PARAM *, unsigned long); -unsigned long X509_VERIFY_PARAM_get_flags(X509_VERIFY_PARAM *); -int X509_VERIFY_PARAM_set_purpose(X509_VERIFY_PARAM *, int); -int X509_VERIFY_PARAM_set_trust(X509_VERIFY_PARAM *, int); void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *, time_t); -int X509_VERIFY_PARAM_add0_policy(X509_VERIFY_PARAM *, ASN1_OBJECT *); -int X509_VERIFY_PARAM_set1_policies(X509_VERIFY_PARAM *, - Cryptography_STACK_OF_ASN1_OBJECT *); -void X509_VERIFY_PARAM_set_depth(X509_VERIFY_PARAM *, int); -int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *); void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *); -/* this CRYPTO_EX_DATA function became a macro in 1.1.0 */ -int X509_STORE_CTX_get_ex_new_index(long, void *, CRYPTO_EX_new *, - CRYPTO_EX_dup *, CRYPTO_EX_free *); - -/* X509_STORE_CTX */ -void X509_STORE_CTX_set0_crls(X509_STORE_CTX *, - Cryptography_STACK_OF_X509_CRL *); -/* X509_VERIFY_PARAM */ int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *, const char *, size_t); void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *, unsigned int); -int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *, const char *, - size_t); int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *, const unsigned char *, size_t); -int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *, const char *); int sk_X509_OBJECT_num(Cryptography_STACK_OF_X509_OBJECT *); -X509_OBJECT *sk_X509_OBJECT_value(Cryptography_STACK_OF_X509_OBJECT *, int); -X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *); Cryptography_STACK_OF_X509_OBJECT *X509_STORE_get0_objects(X509_STORE *); -X509 *X509_OBJECT_get0_X509(X509_OBJECT *); -int X509_OBJECT_get_type(const X509_OBJECT *); -/* added in 1.1.0 */ X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *); -X509_STORE_CTX_get_issuer_fn X509_STORE_get_get_issuer(X509_STORE *); void X509_STORE_set_get_issuer(X509_STORE *, X509_STORE_CTX_get_issuer_fn); """ CUSTOMIZATIONS = """ -#if !CRYPTOGRAPHY_IS_LIBRESSL -static const long Cryptography_HAS_102_VERIFICATION = 1; -#else -static const long Cryptography_HAS_102_VERIFICATION = 0; -static const long X509_V_ERR_SUITE_B_INVALID_VERSION = 0; -static const long X509_V_ERR_SUITE_B_INVALID_ALGORITHM = 0; -static const long X509_V_ERR_SUITE_B_INVALID_CURVE = 0; -static const long X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM = 0; -static const long X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED = 0; -static const long X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256 = 0; -static const long X509_V_FLAG_SUITEB_128_LOS_ONLY = 0; -static const long X509_V_FLAG_SUITEB_192_LOS = 0; -static const long X509_V_FLAG_SUITEB_128_LOS = 0; -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || CRYPTOGRAPHY_IS_LIBRESSL -static const long Cryptography_HAS_110_VERIFICATION_PARAMS = 0; -#ifndef X509_CHECK_FLAG_NEVER_CHECK_SUBJECT -static const long X509_CHECK_FLAG_NEVER_CHECK_SUBJECT = 0; -#endif -#else -static const long Cryptography_HAS_110_VERIFICATION_PARAMS = 1; -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 && !CRYPTOGRAPHY_IS_LIBRESSL -Cryptography_STACK_OF_X509_OBJECT *X509_STORE_get0_objects(X509_STORE *ctx) { - return ctx->objs; -} -X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *store) { - return store->param; -} -int X509_OBJECT_get_type(const X509_OBJECT *x) { - return x->type; -} - -/* from x509/x509_vfy.c */ -X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *ctx) -{ - return ctx->cert; -} - -X509 *X509_OBJECT_get0_X509(X509_OBJECT *x) { - return x->data.x509; -} -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 +#if CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_X509_STORE_CTX_GET_ISSUER = 0; typedef void *X509_STORE_CTX_get_issuer_fn; -X509_STORE_CTX_get_issuer_fn (*X509_STORE_get_get_issuer)(X509_STORE *) = NULL; void (*X509_STORE_set_get_issuer)(X509_STORE *, X509_STORE_CTX_get_issuer_fn) = NULL; #else diff --git a/src/_cffi_src/openssl/x509name.py b/src/_cffi_src/openssl/x509name.py index f88c8b063b33..5e0349e4846a 100644 --- a/src/_cffi_src/openssl/x509name.py +++ b/src/_cffi_src/openssl/x509name.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include @@ -28,60 +28,27 @@ unsigned long X509_NAME_hash(X509_NAME *); int i2d_X509_NAME(X509_NAME *, unsigned char **); -int X509_NAME_add_entry_by_txt(X509_NAME *, const char *, int, - const unsigned char *, int, int, int); X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *, int); void X509_NAME_ENTRY_free(X509_NAME_ENTRY *); int X509_NAME_get_index_by_NID(X509_NAME *, int, int); int X509_NAME_cmp(const X509_NAME *, const X509_NAME *); X509_NAME *X509_NAME_dup(X509_NAME *); -int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *); -/* These became const X509_NAME * in 1.1.0 */ -int X509_NAME_entry_count(X509_NAME *); -X509_NAME_ENTRY *X509_NAME_get_entry(X509_NAME *, int); -char *X509_NAME_oneline(X509_NAME *, char *, int); -int X509_NAME_print_ex(BIO *, X509_NAME *, int, unsigned long); +int X509_NAME_entry_count(const X509_NAME *); +X509_NAME_ENTRY *X509_NAME_get_entry(const X509_NAME *, int); +char *X509_NAME_oneline(const X509_NAME *, char *, int); -/* These became const X509_NAME_ENTRY * in 1.1.0 */ -ASN1_OBJECT *X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *); -ASN1_STRING *X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *); -int X509_NAME_add_entry(X509_NAME *, X509_NAME_ENTRY *, int, int); +ASN1_OBJECT *X509_NAME_ENTRY_get_object(const X509_NAME_ENTRY *); +ASN1_STRING *X509_NAME_ENTRY_get_data(const X509_NAME_ENTRY *); -/* this became const unsigned char * in 1.1.0 */ -int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, unsigned char *, +int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, const unsigned char *, int, int, int); -/* These became const ASN1_OBJECT * in 1.1.0 */ -X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_OBJ(X509_NAME_ENTRY **, - ASN1_OBJECT *, int, - const unsigned char *, int); -int X509_NAME_add_entry_by_OBJ(X509_NAME *, ASN1_OBJECT *, int, - unsigned char *, int, int, int); - Cryptography_STACK_OF_X509_NAME *sk_X509_NAME_new_null(void); int sk_X509_NAME_num(Cryptography_STACK_OF_X509_NAME *); int sk_X509_NAME_push(Cryptography_STACK_OF_X509_NAME *, X509_NAME *); X509_NAME *sk_X509_NAME_value(Cryptography_STACK_OF_X509_NAME *, int); void sk_X509_NAME_free(Cryptography_STACK_OF_X509_NAME *); -int sk_X509_NAME_ENTRY_num(Cryptography_STACK_OF_X509_NAME_ENTRY *); -Cryptography_STACK_OF_X509_NAME_ENTRY *sk_X509_NAME_ENTRY_new_null(void); -int sk_X509_NAME_ENTRY_push(Cryptography_STACK_OF_X509_NAME_ENTRY *, - X509_NAME_ENTRY *); -X509_NAME_ENTRY *sk_X509_NAME_ENTRY_value( - Cryptography_STACK_OF_X509_NAME_ENTRY *, int); -Cryptography_STACK_OF_X509_NAME_ENTRY *sk_X509_NAME_ENTRY_dup( - Cryptography_STACK_OF_X509_NAME_ENTRY * -); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER -int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) { - return X509_NAME_ENTRY_set(ne); -} -#else -int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) { - return ne->set; -} -#endif """ diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 59681206524b..dae98da1bf4e 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -2,34 +2,21 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations INCLUDES = """ #include /* * This is part of a work-around for the difficulty cffi has in dealing with - * `LHASH_OF(foo)` as the name of a type. We invent a new, simpler name that + * `STACK_OF(foo)` as the name of a type. We invent a new, simpler name that * will be an alias for this type and use the alias throughout. This works * together with another opaque typedef for the same name in the TYPES section. * Note that the result is an opaque type. */ -typedef LHASH_OF(CONF_VALUE) Cryptography_LHASH_OF_CONF_VALUE; - -typedef STACK_OF(ACCESS_DESCRIPTION) Cryptography_STACK_OF_ACCESS_DESCRIPTION; -typedef STACK_OF(DIST_POINT) Cryptography_STACK_OF_DIST_POINT; -typedef STACK_OF(POLICYQUALINFO) Cryptography_STACK_OF_POLICYQUALINFO; -typedef STACK_OF(POLICYINFO) Cryptography_STACK_OF_POLICYINFO; -typedef STACK_OF(ASN1_INTEGER) Cryptography_STACK_OF_ASN1_INTEGER; -typedef STACK_OF(GENERAL_SUBTREE) Cryptography_STACK_OF_GENERAL_SUBTREE; """ TYPES = """ -typedef ... Cryptography_STACK_OF_ACCESS_DESCRIPTION; -typedef ... Cryptography_STACK_OF_POLICYQUALINFO; -typedef ... Cryptography_STACK_OF_POLICYINFO; -typedef ... Cryptography_STACK_OF_ASN1_INTEGER; -typedef ... Cryptography_STACK_OF_GENERAL_SUBTREE; typedef ... EXTENDED_KEY_USAGE; typedef ... CONF; @@ -39,275 +26,36 @@ ...; } X509V3_CTX; -typedef void * (*X509V3_EXT_D2I)(void *, const unsigned char **, long); - -static const int GEN_OTHERNAME; static const int GEN_EMAIL; -static const int GEN_X400; static const int GEN_DNS; static const int GEN_URI; -static const int GEN_DIRNAME; -static const int GEN_EDIPARTY; -static const int GEN_IPADD; -static const int GEN_RID; - -typedef struct { - ASN1_OBJECT *type_id; - ASN1_TYPE *value; -} OTHERNAME; - -typedef struct { - ...; -} EDIPARTYNAME; - -typedef struct { - int ca; - ASN1_INTEGER *pathlen; -} BASIC_CONSTRAINTS; - -typedef struct { - Cryptography_STACK_OF_GENERAL_SUBTREE *permittedSubtrees; - Cryptography_STACK_OF_GENERAL_SUBTREE *excludedSubtrees; -} NAME_CONSTRAINTS; - -typedef struct { - ASN1_INTEGER *requireExplicitPolicy; - ASN1_INTEGER *inhibitPolicyMapping; -} POLICY_CONSTRAINTS; +typedef struct stack_st_GENERAL_NAME GENERAL_NAMES; +/* Only include the one union element used by pyOpenSSL. */ typedef struct { int type; union { - char *ptr; - OTHERNAME *otherName; /* otherName */ - ASN1_IA5STRING *rfc822Name; - ASN1_IA5STRING *dNSName; - ASN1_TYPE *x400Address; - X509_NAME *directoryName; - EDIPARTYNAME *ediPartyName; - ASN1_IA5STRING *uniformResourceIdentifier; - ASN1_OCTET_STRING *iPAddress; - ASN1_OBJECT *registeredID; - - /* Old names */ - ASN1_OCTET_STRING *ip; /* iPAddress */ - X509_NAME *dirn; /* dirn */ ASN1_IA5STRING *ia5; /* rfc822Name, dNSName, */ /* uniformResourceIdentifier */ - ASN1_OBJECT *rid; /* registeredID */ - ASN1_TYPE *other; /* x400Address */ } d; ...; } GENERAL_NAME; - -typedef struct { - GENERAL_NAME *base; - ASN1_INTEGER *minimum; - ASN1_INTEGER *maximum; -} GENERAL_SUBTREE; - -typedef struct stack_st_GENERAL_NAME GENERAL_NAMES; - -typedef struct { - ASN1_OCTET_STRING *keyid; - GENERAL_NAMES *issuer; - ASN1_INTEGER *serial; -} AUTHORITY_KEYID; - -typedef struct { - ASN1_OBJECT *method; - GENERAL_NAME *location; -} ACCESS_DESCRIPTION; - -typedef ... Cryptography_LHASH_OF_CONF_VALUE; - - -typedef ... Cryptography_STACK_OF_DIST_POINT; - -typedef struct { - int type; - union { - GENERAL_NAMES *fullname; - Cryptography_STACK_OF_X509_NAME_ENTRY *relativename; - } name; - ...; -} DIST_POINT_NAME; - -typedef struct { - DIST_POINT_NAME *distpoint; - ASN1_BIT_STRING *reasons; - GENERAL_NAMES *CRLissuer; - ...; -} DIST_POINT; - -typedef struct { - DIST_POINT_NAME *distpoint; - int onlyuser; - int onlyCA; - ASN1_BIT_STRING *onlysomereasons; - int indirectCRL; - int onlyattr; -} ISSUING_DIST_POINT; - -typedef struct { - ASN1_STRING *organization; - Cryptography_STACK_OF_ASN1_INTEGER *noticenos; -} NOTICEREF; - -typedef struct { - NOTICEREF *noticeref; - ASN1_STRING *exptext; -} USERNOTICE; - -typedef struct { - ASN1_OBJECT *pqualid; - union { - ASN1_IA5STRING *cpsuri; - USERNOTICE *usernotice; - ASN1_TYPE *other; - } d; -} POLICYQUALINFO; - -typedef struct { - ASN1_OBJECT *policyid; - Cryptography_STACK_OF_POLICYQUALINFO *qualifiers; -} POLICYINFO; - -typedef void (*sk_GENERAL_NAME_freefunc)(GENERAL_NAME *); -typedef void (*sk_DIST_POINT_freefunc)(DIST_POINT *); -typedef void (*sk_POLICYINFO_freefunc)(POLICYINFO *); -typedef void (*sk_ACCESS_DESCRIPTION_freefunc)(ACCESS_DESCRIPTION *); """ FUNCTIONS = """ -int X509V3_EXT_add_alias(int, int); void X509V3_set_ctx(X509V3_CTX *, X509 *, X509 *, X509_REQ *, X509_CRL *, int); int GENERAL_NAME_print(BIO *, GENERAL_NAME *); -GENERAL_NAMES *GENERAL_NAMES_new(void); void GENERAL_NAMES_free(GENERAL_NAMES *); void *X509V3_EXT_d2i(X509_EXTENSION *); -int X509_check_ca(X509 *); -/* X509 became a const arg in 1.1.0 */ -void *X509_get_ext_d2i(X509 *, int, int *, int *); -/* The last two char * args became const char * in 1.1.0 */ -X509_EXTENSION *X509V3_EXT_nconf(CONF *, X509V3_CTX *, char *, char *); -/* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the - x509v3.h header. */ -BASIC_CONSTRAINTS *BASIC_CONSTRAINTS_new(void); -void BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *); -/* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the - x509v3.h header. */ -AUTHORITY_KEYID *AUTHORITY_KEYID_new(void); -void AUTHORITY_KEYID_free(AUTHORITY_KEYID *); - -NAME_CONSTRAINTS *NAME_CONSTRAINTS_new(void); -void NAME_CONSTRAINTS_free(NAME_CONSTRAINTS *); +X509_EXTENSION *X509V3_EXT_nconf(CONF *, X509V3_CTX *, const char *, + const char *); -OTHERNAME *OTHERNAME_new(void); -void OTHERNAME_free(OTHERNAME *); - -POLICY_CONSTRAINTS *POLICY_CONSTRAINTS_new(void); -void POLICY_CONSTRAINTS_free(POLICY_CONSTRAINTS *); - -void *X509V3_set_ctx_nodb(X509V3_CTX *); - -int i2d_GENERAL_NAMES(GENERAL_NAMES *, unsigned char **); -GENERAL_NAMES *d2i_GENERAL_NAMES(GENERAL_NAMES **, const unsigned char **, - long); +void X509V3_set_ctx_nodb(X509V3_CTX *); int sk_GENERAL_NAME_num(struct stack_st_GENERAL_NAME *); -int sk_GENERAL_NAME_push(struct stack_st_GENERAL_NAME *, GENERAL_NAME *); GENERAL_NAME *sk_GENERAL_NAME_value(struct stack_st_GENERAL_NAME *, int); -void sk_GENERAL_NAME_pop_free(struct stack_st_GENERAL_NAME *, - sk_GENERAL_NAME_freefunc); - -Cryptography_STACK_OF_ACCESS_DESCRIPTION *sk_ACCESS_DESCRIPTION_new_null(void); -int sk_ACCESS_DESCRIPTION_num(Cryptography_STACK_OF_ACCESS_DESCRIPTION *); -ACCESS_DESCRIPTION *sk_ACCESS_DESCRIPTION_value( - Cryptography_STACK_OF_ACCESS_DESCRIPTION *, int -); -void sk_ACCESS_DESCRIPTION_free(Cryptography_STACK_OF_ACCESS_DESCRIPTION *); -void sk_ACCESS_DESCRIPTION_pop_free(Cryptography_STACK_OF_ACCESS_DESCRIPTION *, - sk_ACCESS_DESCRIPTION_freefunc); -int sk_ACCESS_DESCRIPTION_push(Cryptography_STACK_OF_ACCESS_DESCRIPTION *, - ACCESS_DESCRIPTION *); - -ACCESS_DESCRIPTION *ACCESS_DESCRIPTION_new(void); -void ACCESS_DESCRIPTION_free(ACCESS_DESCRIPTION *); - -X509_EXTENSION *X509V3_EXT_conf_nid(Cryptography_LHASH_OF_CONF_VALUE *, - X509V3_CTX *, int, char *); - -Cryptography_STACK_OF_DIST_POINT *sk_DIST_POINT_new_null(void); -void sk_DIST_POINT_free(Cryptography_STACK_OF_DIST_POINT *); -int sk_DIST_POINT_num(Cryptography_STACK_OF_DIST_POINT *); -DIST_POINT *sk_DIST_POINT_value(Cryptography_STACK_OF_DIST_POINT *, int); -int sk_DIST_POINT_push(Cryptography_STACK_OF_DIST_POINT *, DIST_POINT *); -void sk_DIST_POINT_pop_free(Cryptography_STACK_OF_DIST_POINT *, - sk_DIST_POINT_freefunc); -void CRL_DIST_POINTS_free(Cryptography_STACK_OF_DIST_POINT *); - -void sk_POLICYINFO_free(Cryptography_STACK_OF_POLICYINFO *); -int sk_POLICYINFO_num(Cryptography_STACK_OF_POLICYINFO *); -POLICYINFO *sk_POLICYINFO_value(Cryptography_STACK_OF_POLICYINFO *, int); -int sk_POLICYINFO_push(Cryptography_STACK_OF_POLICYINFO *, POLICYINFO *); -Cryptography_STACK_OF_POLICYINFO *sk_POLICYINFO_new_null(void); -void sk_POLICYINFO_pop_free(Cryptography_STACK_OF_POLICYINFO *, - sk_POLICYINFO_freefunc); -void CERTIFICATEPOLICIES_free(Cryptography_STACK_OF_POLICYINFO *); - -POLICYINFO *POLICYINFO_new(void); -void POLICYINFO_free(POLICYINFO *); - -POLICYQUALINFO *POLICYQUALINFO_new(void); -void POLICYQUALINFO_free(POLICYQUALINFO *); - -NOTICEREF *NOTICEREF_new(void); -void NOTICEREF_free(NOTICEREF *); - -USERNOTICE *USERNOTICE_new(void); -void USERNOTICE_free(USERNOTICE *); - -void sk_POLICYQUALINFO_free(Cryptography_STACK_OF_POLICYQUALINFO *); -int sk_POLICYQUALINFO_num(Cryptography_STACK_OF_POLICYQUALINFO *); -POLICYQUALINFO *sk_POLICYQUALINFO_value(Cryptography_STACK_OF_POLICYQUALINFO *, - int); -int sk_POLICYQUALINFO_push(Cryptography_STACK_OF_POLICYQUALINFO *, - POLICYQUALINFO *); -Cryptography_STACK_OF_POLICYQUALINFO *sk_POLICYQUALINFO_new_null(void); - -Cryptography_STACK_OF_GENERAL_SUBTREE *sk_GENERAL_SUBTREE_new_null(void); -void sk_GENERAL_SUBTREE_free(Cryptography_STACK_OF_GENERAL_SUBTREE *); -int sk_GENERAL_SUBTREE_num(Cryptography_STACK_OF_GENERAL_SUBTREE *); -GENERAL_SUBTREE *sk_GENERAL_SUBTREE_value( - Cryptography_STACK_OF_GENERAL_SUBTREE *, int -); -int sk_GENERAL_SUBTREE_push(Cryptography_STACK_OF_GENERAL_SUBTREE *, - GENERAL_SUBTREE *); - -GENERAL_SUBTREE *GENERAL_SUBTREE_new(void); - -void sk_ASN1_INTEGER_free(Cryptography_STACK_OF_ASN1_INTEGER *); -int sk_ASN1_INTEGER_num(Cryptography_STACK_OF_ASN1_INTEGER *); -ASN1_INTEGER *sk_ASN1_INTEGER_value(Cryptography_STACK_OF_ASN1_INTEGER *, int); -int sk_ASN1_INTEGER_push(Cryptography_STACK_OF_ASN1_INTEGER *, ASN1_INTEGER *); -Cryptography_STACK_OF_ASN1_INTEGER *sk_ASN1_INTEGER_new_null(void); - -X509_EXTENSION *X509V3_EXT_i2d(int, int, void *); - -DIST_POINT *DIST_POINT_new(void); -void DIST_POINT_free(DIST_POINT *); - -DIST_POINT_NAME *DIST_POINT_NAME_new(void); -void DIST_POINT_NAME_free(DIST_POINT_NAME *); - -GENERAL_NAME *GENERAL_NAME_new(void); -void GENERAL_NAME_free(GENERAL_NAME *); - -ISSUING_DIST_POINT *ISSUING_DIST_POINT_new(void); -void ISSUING_DIST_POINT_free(ISSUING_DIST_POINT *); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/utils.py b/src/_cffi_src/utils.py index 56745a3e5b2e..b5fba37091d9 100644 --- a/src/_cffi_src/utils.py +++ b/src/_cffi_src/utils.py @@ -2,30 +2,26 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import os +import platform import sys -from distutils.ccompiler import new_compiler -from distutils.dist import Distribution +import typing from cffi import FFI - # Load the cryptography __about__ to get the current package version base_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) about = {} with open(os.path.join(base_src, "cryptography", "__about__.py")) as f: - exec (f.read(), about) + exec(f.read(), about) def build_ffi_for_binding( - module_name, - module_prefix, - modules, - libraries=[], - extra_compile_args=[], - extra_link_args=[], + module_name: str, + module_prefix: str, + modules: typing.List[str], ): """ Modules listed in ``modules`` should have the following attributes: @@ -51,25 +47,17 @@ def build_ffi_for_binding( customizations.append(module.CUSTOMIZATIONS) verify_source = "\n".join(includes + customizations) - ffi = build_ffi( + return build_ffi( module_name, cdef_source="\n".join(types + functions), verify_source=verify_source, - libraries=libraries, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, ) - return ffi - def build_ffi( - module_name, - cdef_source, - verify_source, - libraries=[], - extra_compile_args=[], - extra_link_args=[], + module_name: str, + cdef_source: str, + verify_source: str, ): ffi = FFI() # Always add the CRYPTOGRAPHY_PACKAGE_VERSION to the shared object @@ -77,34 +65,21 @@ def build_ffi( verify_source += '\n#define CRYPTOGRAPHY_PACKAGE_VERSION "{}"'.format( about["__version__"] ) + if platform.python_implementation() == "PyPy": + verify_source += r""" +int Cryptography_make_openssl_module(void) { + int result; + + Py_BEGIN_ALLOW_THREADS + result = cffi_start_python(); + Py_END_ALLOW_THREADS + + return result; +} +""" ffi.cdef(cdef_source) ffi.set_source( module_name, verify_source, - libraries=libraries, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, ) return ffi - - -def extra_link_args(compiler_type): - if compiler_type == "msvc": - # Enable NX and ASLR for Windows builds on MSVC. These are enabled by - # default on Python 3.3+ but not on 2.x. - return ["/NXCOMPAT", "/DYNAMICBASE"] - else: - return [] - - -def compiler_type(): - """ - Gets the compiler type from distutils. On Windows with MSVC it will be - "msvc". On macOS and linux it is "unix". - """ - dist = Distribution() - dist.parse_config_files() - cmd = dist.get_command_obj("build") - cmd.ensure_finalized() - compiler = new_compiler(compiler=cmd.compiler) - return compiler.compiler_type diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py index b960f7ed1af5..a83d2d1d14b1 100644 --- a/src/cryptography/__about__.py +++ b/src/cryptography/__about__.py @@ -2,30 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations __all__ = [ - "__title__", - "__summary__", - "__uri__", "__version__", "__author__", - "__email__", - "__license__", "__copyright__", ] -__title__ = "cryptography" -__summary__ = ( - "cryptography is a package which provides cryptographic recipes" - " and primitives to Python developers." -) -__uri__ = "https://github.com/pyca/cryptography" +__version__ = "41.0.4" -__version__ = "3.2" -__author__ = "The cryptography developers" -__email__ = "cryptography-dev@python.org" - -__license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2013-2019 {}".format(__author__) +__author__ = "The Python Cryptographic Authority and individual contributors" +__copyright__ = f"Copyright 2013-2023 {__author__}" diff --git a/src/cryptography/__init__.py b/src/cryptography/__init__.py index 7211614d7f4a..86b9a25726d1 100644 --- a/src/cryptography/__init__.py +++ b/src/cryptography/__init__.py @@ -2,47 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - -import sys -import warnings - -from cryptography.__about__ import ( - __author__, - __copyright__, - __email__, - __license__, - __summary__, - __title__, - __uri__, - __version__, -) -from cryptography.utils import CryptographyDeprecationWarning +from __future__ import annotations +from cryptography.__about__ import __author__, __copyright__, __version__ __all__ = [ - "__title__", - "__summary__", - "__uri__", "__version__", "__author__", - "__email__", - "__license__", "__copyright__", ] - -if sys.version_info[0] == 2: - warnings.warn( - "Python 2 is no longer supported by the Python core team. Support for " - "it is now deprecated in cryptography, and will be removed in a " - "future release.", - CryptographyDeprecationWarning, - stacklevel=2, - ) -if sys.version_info[:2] == (3, 5): - warnings.warn( - "Python 3.5 support will be dropped in the next release of " - "cryptography. Please upgrade your Python.", - CryptographyDeprecationWarning, - stacklevel=2, - ) diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index 1d52d7dcfc5e..47fdd18eeeb2 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -2,29 +2,23 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -from enum import Enum +import typing +from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions -class _Reasons(Enum): - BACKEND_MISSING_INTERFACE = 0 - UNSUPPORTED_HASH = 1 - UNSUPPORTED_CIPHER = 2 - UNSUPPORTED_PADDING = 3 - UNSUPPORTED_MGF = 4 - UNSUPPORTED_PUBLIC_KEY_ALGORITHM = 5 - UNSUPPORTED_ELLIPTIC_CURVE = 6 - UNSUPPORTED_SERIALIZATION = 7 - UNSUPPORTED_X509 = 8 - UNSUPPORTED_EXCHANGE_ALGORITHM = 9 - UNSUPPORTED_DIFFIE_HELLMAN = 10 - UNSUPPORTED_MAC = 11 +if typing.TYPE_CHECKING: + from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +_Reasons = rust_exceptions._Reasons class UnsupportedAlgorithm(Exception): - def __init__(self, message, reason=None): - super(UnsupportedAlgorithm, self).__init__(message) + def __init__( + self, message: str, reason: typing.Optional[_Reasons] = None + ) -> None: + super().__init__(message) self._reason = reason @@ -49,8 +43,10 @@ class InvalidSignature(Exception): class InternalError(Exception): - def __init__(self, msg, err_code): - super(InternalError, self).__init__(msg) + def __init__( + self, msg: str, err_code: typing.List[rust_openssl.OpenSSLError] + ) -> None: + super().__init__(msg) self.err_code = err_code diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py index 00c25286715a..ad8fb40b9d44 100644 --- a/src/cryptography/fernet.py +++ b/src/cryptography/fernet.py @@ -2,19 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import base64 import binascii import os -import struct import time - -import six +import typing from cryptography import utils from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.primitives import hashes, padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.hmac import HMAC @@ -27,11 +24,18 @@ class InvalidToken(Exception): _MAX_CLOCK_SKEW = 60 -class Fernet(object): - def __init__(self, key, backend=None): - backend = _get_backend(backend) - - key = base64.urlsafe_b64decode(key) +class Fernet: + def __init__( + self, + key: typing.Union[bytes, str], + backend: typing.Any = None, + ) -> None: + try: + key = base64.urlsafe_b64decode(key) + except binascii.Error as exc: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes." + ) from exc if len(key) != 32: raise ValueError( "Fernet key must be 32 url-safe base64-encoded bytes." @@ -39,83 +43,106 @@ def __init__(self, key, backend=None): self._signing_key = key[:16] self._encryption_key = key[16:] - self._backend = backend @classmethod - def generate_key(cls): + def generate_key(cls) -> bytes: return base64.urlsafe_b64encode(os.urandom(32)) - def encrypt(self, data): + def encrypt(self, data: bytes) -> bytes: return self.encrypt_at_time(data, int(time.time())) - def encrypt_at_time(self, data, current_time): + def encrypt_at_time(self, data: bytes, current_time: int) -> bytes: iv = os.urandom(16) return self._encrypt_from_parts(data, current_time, iv) - def _encrypt_from_parts(self, data, current_time, iv): + def _encrypt_from_parts( + self, data: bytes, current_time: int, iv: bytes + ) -> bytes: utils._check_bytes("data", data) padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() encryptor = Cipher( - algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend + algorithms.AES(self._encryption_key), + modes.CBC(iv), ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() basic_parts = ( - b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + b"\x80" + + current_time.to_bytes(length=8, byteorder="big") + + iv + + ciphertext ) - h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) + h = HMAC(self._signing_key, hashes.SHA256()) h.update(basic_parts) hmac = h.finalize() return base64.urlsafe_b64encode(basic_parts + hmac) - def decrypt(self, token, ttl=None): + def decrypt( + self, token: typing.Union[bytes, str], ttl: typing.Optional[int] = None + ) -> bytes: timestamp, data = Fernet._get_unverified_token_data(token) - return self._decrypt_data(data, timestamp, ttl, int(time.time())) + if ttl is None: + time_info = None + else: + time_info = (ttl, int(time.time())) + return self._decrypt_data(data, timestamp, time_info) - def decrypt_at_time(self, token, ttl, current_time): + def decrypt_at_time( + self, token: typing.Union[bytes, str], ttl: int, current_time: int + ) -> bytes: if ttl is None: raise ValueError( "decrypt_at_time() can only be used with a non-None ttl" ) timestamp, data = Fernet._get_unverified_token_data(token) - return self._decrypt_data(data, timestamp, ttl, current_time) + return self._decrypt_data(data, timestamp, (ttl, current_time)) - def extract_timestamp(self, token): + def extract_timestamp(self, token: typing.Union[bytes, str]) -> int: timestamp, data = Fernet._get_unverified_token_data(token) # Verify the token was not tampered with. self._verify_signature(data) return timestamp @staticmethod - def _get_unverified_token_data(token): - utils._check_bytes("token", token) + def _get_unverified_token_data( + token: typing.Union[bytes, str] + ) -> typing.Tuple[int, bytes]: + if not isinstance(token, (str, bytes)): + raise TypeError("token must be bytes or str") + try: data = base64.urlsafe_b64decode(token) except (TypeError, binascii.Error): raise InvalidToken - if not data or six.indexbytes(data, 0) != 0x80: + if not data or data[0] != 0x80: raise InvalidToken - try: - (timestamp,) = struct.unpack(">Q", data[1:9]) - except struct.error: + if len(data) < 9: raise InvalidToken + + timestamp = int.from_bytes(data[1:9], byteorder="big") return timestamp, data - def _verify_signature(self, data): - h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) + def _verify_signature(self, data: bytes) -> None: + h = HMAC(self._signing_key, hashes.SHA256()) h.update(data[:-32]) try: h.verify(data[-32:]) except InvalidSignature: raise InvalidToken - def _decrypt_data(self, data, timestamp, ttl, current_time): - if ttl is not None: + def _decrypt_data( + self, + data: bytes, + timestamp: int, + time_info: typing.Optional[typing.Tuple[int, int]], + ) -> bytes: + if time_info is not None: + ttl, current_time = time_info if timestamp + ttl < current_time: raise InvalidToken @@ -127,7 +154,7 @@ def _decrypt_data(self, data, timestamp, ttl, current_time): iv = data[9:25] ciphertext = data[25:-32] decryptor = Cipher( - algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend + algorithms.AES(self._encryption_key), modes.CBC(iv) ).decryptor() plaintext_padded = decryptor.update(ciphertext) try: @@ -144,8 +171,8 @@ def _decrypt_data(self, data, timestamp, ttl, current_time): return unpadded -class MultiFernet(object): - def __init__(self, fernets): +class MultiFernet: + def __init__(self, fernets: typing.Iterable[Fernet]): fernets = list(fernets) if not fernets: raise ValueError( @@ -153,17 +180,17 @@ def __init__(self, fernets): ) self._fernets = fernets - def encrypt(self, msg): + def encrypt(self, msg: bytes) -> bytes: return self.encrypt_at_time(msg, int(time.time())) - def encrypt_at_time(self, msg, current_time): + def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes: return self._fernets[0].encrypt_at_time(msg, current_time) - def rotate(self, msg): + def rotate(self, msg: typing.Union[bytes, str]) -> bytes: timestamp, data = Fernet._get_unverified_token_data(msg) for f in self._fernets: try: - p = f._decrypt_data(data, timestamp, None, None) + p = f._decrypt_data(data, timestamp, None) break except InvalidToken: pass @@ -173,7 +200,9 @@ def rotate(self, msg): iv = os.urandom(16) return self._fernets[0]._encrypt_from_parts(p, timestamp, iv) - def decrypt(self, msg, ttl=None): + def decrypt( + self, msg: typing.Union[bytes, str], ttl: typing.Optional[int] = None + ) -> bytes: for f in self._fernets: try: return f.decrypt(msg, ttl) @@ -181,7 +210,9 @@ def decrypt(self, msg, ttl=None): pass raise InvalidToken - def decrypt_at_time(self, msg, ttl, current_time): + def decrypt_at_time( + self, msg: typing.Union[bytes, str], ttl: int, current_time: int + ) -> bytes: for f in self._fernets: try: return f.decrypt_at_time(msg, ttl, current_time) diff --git a/src/cryptography/hazmat/__init__.py b/src/cryptography/hazmat/__init__.py index 9f06a9949a31..b9f1187011bd 100644 --- a/src/cryptography/hazmat/__init__.py +++ b/src/cryptography/hazmat/__init__.py @@ -1,6 +1,9 @@ # 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 + """ Hazardous Materials @@ -8,4 +11,3 @@ 100% absolutely sure that you know what you're doing because this module is full of land mines, dragons, and dinosaurs with laser guns. """ -from __future__ import absolute_import, division, print_function diff --git a/src/cryptography/hazmat/_der.py b/src/cryptography/hazmat/_der.py deleted file mode 100644 index 462b911b4532..000000000000 --- a/src/cryptography/hazmat/_der.py +++ /dev/null @@ -1,156 +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 absolute_import, division, print_function - -import six - -from cryptography.utils import int_from_bytes, int_to_bytes - - -# This module contains a lightweight DER encoder and decoder. See X.690 for the -# specification. This module intentionally does not implement the more complex -# BER encoding, only DER. -# -# Note this implementation treats an element's constructed bit as part of the -# tag. This is fine for DER, where the bit is always computable from the type. - - -CONSTRUCTED = 0x20 -CONTEXT_SPECIFIC = 0x80 - -INTEGER = 0x02 -BIT_STRING = 0x03 -OCTET_STRING = 0x04 -NULL = 0x05 -OBJECT_IDENTIFIER = 0x06 -SEQUENCE = 0x10 | CONSTRUCTED -SET = 0x11 | CONSTRUCTED -PRINTABLE_STRING = 0x13 -UTC_TIME = 0x17 -GENERALIZED_TIME = 0x18 - - -class DERReader(object): - def __init__(self, data): - self.data = memoryview(data) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, tb): - if exc_value is None: - self.check_empty() - - def is_empty(self): - return len(self.data) == 0 - - def check_empty(self): - if not self.is_empty(): - raise ValueError("Invalid DER input: trailing data") - - def read_byte(self): - if len(self.data) < 1: - raise ValueError("Invalid DER input: insufficient data") - ret = six.indexbytes(self.data, 0) - self.data = self.data[1:] - return ret - - def read_bytes(self, n): - if len(self.data) < n: - raise ValueError("Invalid DER input: insufficient data") - ret = self.data[:n] - self.data = self.data[n:] - return ret - - def read_any_element(self): - tag = self.read_byte() - # Tag numbers 31 or higher are stored in multiple bytes. No supported - # ASN.1 types use such tags, so reject these. - if tag & 0x1F == 0x1F: - raise ValueError("Invalid DER input: unexpected high tag number") - length_byte = self.read_byte() - if length_byte & 0x80 == 0: - # If the high bit is clear, the first length byte is the length. - length = length_byte - else: - # If the high bit is set, the first length byte encodes the length - # of the length. - length_byte &= 0x7F - if length_byte == 0: - raise ValueError( - "Invalid DER input: indefinite length form is not allowed " - "in DER" - ) - length = 0 - for i in range(length_byte): - length <<= 8 - length |= self.read_byte() - if length == 0: - raise ValueError( - "Invalid DER input: length was not minimally-encoded" - ) - if length < 0x80: - # If the length could have been encoded in short form, it must - # not use long form. - raise ValueError( - "Invalid DER input: length was not minimally-encoded" - ) - body = self.read_bytes(length) - return tag, DERReader(body) - - def read_element(self, expected_tag): - tag, body = self.read_any_element() - if tag != expected_tag: - raise ValueError("Invalid DER input: unexpected tag") - return body - - def read_single_element(self, expected_tag): - with self: - return self.read_element(expected_tag) - - def read_optional_element(self, expected_tag): - if len(self.data) > 0 and six.indexbytes(self.data, 0) == expected_tag: - return self.read_element(expected_tag) - return None - - def as_integer(self): - if len(self.data) == 0: - raise ValueError("Invalid DER input: empty integer contents") - first = six.indexbytes(self.data, 0) - if first & 0x80 == 0x80: - raise ValueError("Negative DER integers are not supported") - # The first 9 bits must not all be zero or all be ones. Otherwise, the - # encoding should have been one byte shorter. - if len(self.data) > 1: - second = six.indexbytes(self.data, 1) - if first == 0 and second & 0x80 == 0: - raise ValueError( - "Invalid DER input: integer not minimally-encoded" - ) - return int_from_bytes(self.data, "big") - - -def encode_der_integer(x): - if not isinstance(x, six.integer_types): - raise ValueError("Value must be an integer") - if x < 0: - raise ValueError("Negative integers are not supported") - n = x.bit_length() // 8 + 1 - return int_to_bytes(x, n) - - -def encode_der(tag, *children): - length = 0 - for child in children: - length += len(child) - chunks = [six.int2byte(tag)] - if length < 0x80: - chunks.append(six.int2byte(length)) - else: - length_bytes = int_to_bytes(length) - chunks.append(six.int2byte(0x80 | len(length_bytes))) - chunks.append(length_bytes) - chunks.extend(children) - return b"".join(chunks) diff --git a/src/cryptography/hazmat/_oid.py b/src/cryptography/hazmat/_oid.py index de2771a7379a..01d4b3406062 100644 --- a/src/cryptography/hazmat/_oid.py +++ b/src/cryptography/hazmat/_oid.py @@ -2,76 +2,298 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - -from cryptography import utils - - -class ObjectIdentifier(object): - def __init__(self, dotted_string): - self._dotted_string = dotted_string - - nodes = self._dotted_string.split(".") - intnodes = [] - - # There must be at least 2 nodes, the first node must be 0..2, and - # if less than 2, the second node cannot have a value outside the - # range 0..39. All nodes must be integers. - for node in nodes: - try: - node_value = int(node, 10) - except ValueError: - raise ValueError( - "Malformed OID: %s (non-integer nodes)" - % (self._dotted_string) - ) - if node_value < 0: - raise ValueError( - "Malformed OID: %s (negative-integer nodes)" - % (self._dotted_string) - ) - intnodes.append(node_value) - - if len(nodes) < 2: - raise ValueError( - "Malformed OID: %s (insufficient number of nodes)" - % (self._dotted_string) - ) - - if intnodes[0] > 2: - raise ValueError( - "Malformed OID: %s (first node outside valid range)" - % (self._dotted_string) - ) - - if intnodes[0] < 2 and intnodes[1] >= 40: - raise ValueError( - "Malformed OID: %s (second node outside valid range)" - % (self._dotted_string) - ) - - def __eq__(self, other): - if not isinstance(other, ObjectIdentifier): - return NotImplemented - - return self.dotted_string == other.dotted_string - - def __ne__(self, other): - return not self == other - - def __repr__(self): - return "".format( - self.dotted_string, self._name - ) - - def __hash__(self): - return hash(self.dotted_string) - - @property - def _name(self): - # Lazy import to avoid an import cycle - from cryptography.x509.oid import _OID_NAMES - - return _OID_NAMES.get(self, "Unknown OID") - - dotted_string = utils.read_only_property("_dotted_string") +from __future__ import annotations + +import typing + +from cryptography.hazmat.bindings._rust import ( + ObjectIdentifier as ObjectIdentifier, +) +from cryptography.hazmat.primitives import hashes + + +class ExtensionOID: + SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9") + SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14") + KEY_USAGE = ObjectIdentifier("2.5.29.15") + SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") + ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") + BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") + NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30") + CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31") + CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32") + POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33") + AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35") + POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36") + EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37") + FRESHEST_CRL = ObjectIdentifier("2.5.29.46") + INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54") + ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28") + AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1") + SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11") + OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") + TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24") + CRL_NUMBER = ObjectIdentifier("2.5.29.20") + DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27") + PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier( + "1.3.6.1.4.1.11129.2.4.2" + ) + PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3") + SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") + MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7") + + +class OCSPExtensionOID: + NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2") + ACCEPTABLE_RESPONSES = ObjectIdentifier("1.3.6.1.5.5.7.48.1.4") + + +class CRLEntryExtensionOID: + CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") + CRL_REASON = ObjectIdentifier("2.5.29.21") + INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") + + +class NameOID: + COMMON_NAME = ObjectIdentifier("2.5.4.3") + COUNTRY_NAME = ObjectIdentifier("2.5.4.6") + LOCALITY_NAME = ObjectIdentifier("2.5.4.7") + STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") + STREET_ADDRESS = ObjectIdentifier("2.5.4.9") + ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") + ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") + SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") + SURNAME = ObjectIdentifier("2.5.4.4") + GIVEN_NAME = ObjectIdentifier("2.5.4.42") + TITLE = ObjectIdentifier("2.5.4.12") + INITIALS = ObjectIdentifier("2.5.4.43") + GENERATION_QUALIFIER = ObjectIdentifier("2.5.4.44") + X500_UNIQUE_IDENTIFIER = ObjectIdentifier("2.5.4.45") + DN_QUALIFIER = ObjectIdentifier("2.5.4.46") + PSEUDONYM = ObjectIdentifier("2.5.4.65") + USER_ID = ObjectIdentifier("0.9.2342.19200300.100.1.1") + DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25") + EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1") + JURISDICTION_COUNTRY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.3") + JURISDICTION_LOCALITY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.1") + JURISDICTION_STATE_OR_PROVINCE_NAME = ObjectIdentifier( + "1.3.6.1.4.1.311.60.2.1.2" + ) + BUSINESS_CATEGORY = ObjectIdentifier("2.5.4.15") + POSTAL_ADDRESS = ObjectIdentifier("2.5.4.16") + POSTAL_CODE = ObjectIdentifier("2.5.4.17") + INN = ObjectIdentifier("1.2.643.3.131.1.1") + OGRN = ObjectIdentifier("1.2.643.100.1") + SNILS = ObjectIdentifier("1.2.643.100.3") + UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") + + +class SignatureAlgorithmOID: + RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4") + RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5") + # This is an alternate OID for RSA with SHA1 that is occasionally seen + _RSA_WITH_SHA1 = ObjectIdentifier("1.3.14.3.2.29") + RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14") + RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11") + RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12") + RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13") + RSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.13") + RSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.14") + RSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.15") + RSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.16") + RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") + ECDSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10045.4.1") + ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1") + ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2") + ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3") + ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4") + ECDSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.9") + ECDSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.10") + ECDSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.11") + ECDSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.12") + DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3") + DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1") + DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2") + DSA_WITH_SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.3.3") + DSA_WITH_SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.3.4") + ED25519 = ObjectIdentifier("1.3.101.112") + ED448 = ObjectIdentifier("1.3.101.113") + GOSTR3411_94_WITH_3410_2001 = ObjectIdentifier("1.2.643.2.2.3") + GOSTR3410_2012_WITH_3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") + GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") + + +_SIG_OIDS_TO_HASH: typing.Dict[ + ObjectIdentifier, typing.Optional[hashes.HashAlgorithm] +] = { + SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), + SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.RSA_WITH_SHA3_224: hashes.SHA3_224(), + SignatureAlgorithmOID.RSA_WITH_SHA3_256: hashes.SHA3_256(), + SignatureAlgorithmOID.RSA_WITH_SHA3_384: hashes.SHA3_384(), + SignatureAlgorithmOID.RSA_WITH_SHA3_512: hashes.SHA3_512(), + SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_224: hashes.SHA3_224(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_256: hashes.SHA3_256(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_384: hashes.SHA3_384(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_512: hashes.SHA3_512(), + SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.ED25519: None, + SignatureAlgorithmOID.ED448: None, + SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: None, + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: None, + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: None, +} + + +class ExtendedKeyUsageOID: + SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") + CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") + CODE_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.3") + EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4") + TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8") + OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9") + ANY_EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37.0") + SMARTCARD_LOGON = ObjectIdentifier("1.3.6.1.4.1.311.20.2.2") + KERBEROS_PKINIT_KDC = ObjectIdentifier("1.3.6.1.5.2.3.5") + IPSEC_IKE = ObjectIdentifier("1.3.6.1.5.5.7.3.17") + CERTIFICATE_TRANSPARENCY = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4") + + +class AuthorityInformationAccessOID: + CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") + OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") + + +class SubjectInformationAccessOID: + CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5") + + +class CertificatePoliciesOID: + CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1") + CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2") + ANY_POLICY = ObjectIdentifier("2.5.29.32.0") + + +class AttributeOID: + CHALLENGE_PASSWORD = ObjectIdentifier("1.2.840.113549.1.9.7") + UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") + + +_OID_NAMES = { + NameOID.COMMON_NAME: "commonName", + NameOID.COUNTRY_NAME: "countryName", + NameOID.LOCALITY_NAME: "localityName", + NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName", + NameOID.STREET_ADDRESS: "streetAddress", + NameOID.ORGANIZATION_NAME: "organizationName", + NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName", + NameOID.SERIAL_NUMBER: "serialNumber", + NameOID.SURNAME: "surname", + NameOID.GIVEN_NAME: "givenName", + NameOID.TITLE: "title", + NameOID.GENERATION_QUALIFIER: "generationQualifier", + NameOID.X500_UNIQUE_IDENTIFIER: "x500UniqueIdentifier", + NameOID.DN_QUALIFIER: "dnQualifier", + NameOID.PSEUDONYM: "pseudonym", + NameOID.USER_ID: "userID", + NameOID.DOMAIN_COMPONENT: "domainComponent", + NameOID.EMAIL_ADDRESS: "emailAddress", + NameOID.JURISDICTION_COUNTRY_NAME: "jurisdictionCountryName", + NameOID.JURISDICTION_LOCALITY_NAME: "jurisdictionLocalityName", + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME: ( + "jurisdictionStateOrProvinceName" + ), + NameOID.BUSINESS_CATEGORY: "businessCategory", + NameOID.POSTAL_ADDRESS: "postalAddress", + NameOID.POSTAL_CODE: "postalCode", + NameOID.INN: "INN", + NameOID.OGRN: "OGRN", + NameOID.SNILS: "SNILS", + NameOID.UNSTRUCTURED_NAME: "unstructuredName", + SignatureAlgorithmOID.RSA_WITH_MD5: "md5WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA1: "sha1WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA224: "sha224WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", + SignatureAlgorithmOID.RSASSA_PSS: "RSASSA-PSS", + SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", + SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", + SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", + SignatureAlgorithmOID.ECDSA_WITH_SHA384: "ecdsa-with-SHA384", + SignatureAlgorithmOID.ECDSA_WITH_SHA512: "ecdsa-with-SHA512", + SignatureAlgorithmOID.DSA_WITH_SHA1: "dsa-with-sha1", + SignatureAlgorithmOID.DSA_WITH_SHA224: "dsa-with-sha224", + SignatureAlgorithmOID.DSA_WITH_SHA256: "dsa-with-sha256", + SignatureAlgorithmOID.ED25519: "ed25519", + SignatureAlgorithmOID.ED448: "ed448", + SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: ( + "GOST R 34.11-94 with GOST R 34.10-2001" + ), + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)" + ), + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" + ), + ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", + ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", + ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", + ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection", + ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping", + ExtendedKeyUsageOID.OCSP_SIGNING: "OCSPSigning", + ExtendedKeyUsageOID.SMARTCARD_LOGON: "msSmartcardLogin", + ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC: "pkInitKDC", + ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", + ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", + ExtensionOID.KEY_USAGE: "keyUsage", + ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", + ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", + ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", + ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( + "signedCertificateTimestampList" + ), + ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: ( + "signedCertificateTimestampList" + ), + ExtensionOID.PRECERT_POISON: "ctPoison", + ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate", + CRLEntryExtensionOID.CRL_REASON: "cRLReason", + CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", + CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", + ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", + ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", + ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", + ExtensionOID.POLICY_MAPPINGS: "policyMappings", + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "authorityKeyIdentifier", + ExtensionOID.POLICY_CONSTRAINTS: "policyConstraints", + ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", + ExtensionOID.FRESHEST_CRL: "freshestCRL", + ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", + ExtensionOID.ISSUING_DISTRIBUTION_POINT: ("issuingDistributionPoint"), + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", + ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", + ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", + ExtensionOID.CRL_NUMBER: "cRLNumber", + ExtensionOID.DELTA_CRL_INDICATOR: "deltaCRLIndicator", + ExtensionOID.TLS_FEATURE: "TLSFeature", + AuthorityInformationAccessOID.OCSP: "OCSP", + AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers", + SubjectInformationAccessOID.CA_REPOSITORY: "caRepository", + CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps", + CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice", + OCSPExtensionOID.NONCE: "OCSPNonce", + AttributeOID.CHALLENGE_PASSWORD: "challengePassword", +} diff --git a/src/cryptography/hazmat/backends/__init__.py b/src/cryptography/hazmat/backends/__init__.py index 1563936dde6e..b4400aa03745 100644 --- a/src/cryptography/hazmat/backends/__init__.py +++ b/src/cryptography/hazmat/backends/__init__.py @@ -2,25 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations +from typing import Any -_default_backend = None +def default_backend() -> Any: + from cryptography.hazmat.backends.openssl.backend import backend -def default_backend(): - global _default_backend - - if _default_backend is None: - from cryptography.hazmat.backends.openssl.backend import backend - - _default_backend = backend - - return _default_backend - - -def _get_backend(backend): - if backend is None: - return default_backend() - else: - return backend + return backend diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py deleted file mode 100644 index 418980a34e0d..000000000000 --- a/src/cryptography/hazmat/backends/interfaces.py +++ /dev/null @@ -1,396 +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 absolute_import, division, print_function - -import abc - -import six - - -@six.add_metaclass(abc.ABCMeta) -class CipherBackend(object): - @abc.abstractmethod - def cipher_supported(self, cipher, mode): - """ - Return True if the given cipher and mode are supported. - """ - - @abc.abstractmethod - def create_symmetric_encryption_ctx(self, cipher, mode): - """ - Get a CipherContext that can be used for encryption. - """ - - @abc.abstractmethod - def create_symmetric_decryption_ctx(self, cipher, mode): - """ - Get a CipherContext that can be used for decryption. - """ - - -@six.add_metaclass(abc.ABCMeta) -class HashBackend(object): - @abc.abstractmethod - def hash_supported(self, algorithm): - """ - Return True if the hash algorithm is supported by this backend. - """ - - @abc.abstractmethod - def create_hash_ctx(self, algorithm): - """ - Create a HashContext for calculating a message digest. - """ - - -@six.add_metaclass(abc.ABCMeta) -class HMACBackend(object): - @abc.abstractmethod - def hmac_supported(self, algorithm): - """ - Return True if the hash algorithm is supported for HMAC by this - backend. - """ - - @abc.abstractmethod - def create_hmac_ctx(self, key, algorithm): - """ - Create a context for calculating a message authentication code. - """ - - -@six.add_metaclass(abc.ABCMeta) -class CMACBackend(object): - @abc.abstractmethod - def cmac_algorithm_supported(self, algorithm): - """ - Returns True if the block cipher is supported for CMAC by this backend - """ - - @abc.abstractmethod - def create_cmac_ctx(self, algorithm): - """ - Create a context for calculating a message authentication code. - """ - - -@six.add_metaclass(abc.ABCMeta) -class PBKDF2HMACBackend(object): - @abc.abstractmethod - def pbkdf2_hmac_supported(self, algorithm): - """ - Return True if the hash algorithm is supported for PBKDF2 by this - backend. - """ - - @abc.abstractmethod - def derive_pbkdf2_hmac( - self, algorithm, length, salt, iterations, key_material - ): - """ - Return length bytes derived from provided PBKDF2 parameters. - """ - - -@six.add_metaclass(abc.ABCMeta) -class RSABackend(object): - @abc.abstractmethod - def generate_rsa_private_key(self, public_exponent, key_size): - """ - Generate an RSAPrivateKey instance with public_exponent and a modulus - of key_size bits. - """ - - @abc.abstractmethod - def rsa_padding_supported(self, padding): - """ - Returns True if the backend supports the given padding options. - """ - - @abc.abstractmethod - def generate_rsa_parameters_supported(self, public_exponent, key_size): - """ - Returns True if the backend supports the given parameters for key - generation. - """ - - @abc.abstractmethod - def load_rsa_private_numbers(self, numbers): - """ - Returns an RSAPrivateKey provider. - """ - - @abc.abstractmethod - def load_rsa_public_numbers(self, numbers): - """ - Returns an RSAPublicKey provider. - """ - - -@six.add_metaclass(abc.ABCMeta) -class DSABackend(object): - @abc.abstractmethod - def generate_dsa_parameters(self, key_size): - """ - Generate a DSAParameters instance with a modulus of key_size bits. - """ - - @abc.abstractmethod - def generate_dsa_private_key(self, parameters): - """ - Generate a DSAPrivateKey instance with parameters as a DSAParameters - object. - """ - - @abc.abstractmethod - def generate_dsa_private_key_and_parameters(self, key_size): - """ - Generate a DSAPrivateKey instance using key size only. - """ - - @abc.abstractmethod - def dsa_hash_supported(self, algorithm): - """ - Return True if the hash algorithm is supported by the backend for DSA. - """ - - @abc.abstractmethod - def dsa_parameters_supported(self, p, q, g): - """ - Return True if the parameters are supported by the backend for DSA. - """ - - @abc.abstractmethod - def load_dsa_private_numbers(self, numbers): - """ - Returns a DSAPrivateKey provider. - """ - - @abc.abstractmethod - def load_dsa_public_numbers(self, numbers): - """ - Returns a DSAPublicKey provider. - """ - - @abc.abstractmethod - def load_dsa_parameter_numbers(self, numbers): - """ - Returns a DSAParameters provider. - """ - - -@six.add_metaclass(abc.ABCMeta) -class EllipticCurveBackend(object): - @abc.abstractmethod - def elliptic_curve_signature_algorithm_supported( - self, signature_algorithm, curve - ): - """ - Returns True if the backend supports the named elliptic curve with the - specified signature algorithm. - """ - - @abc.abstractmethod - def elliptic_curve_supported(self, curve): - """ - Returns True if the backend supports the named elliptic curve. - """ - - @abc.abstractmethod - def generate_elliptic_curve_private_key(self, curve): - """ - Return an object conforming to the EllipticCurvePrivateKey interface. - """ - - @abc.abstractmethod - def load_elliptic_curve_public_numbers(self, numbers): - """ - Return an EllipticCurvePublicKey provider using the given numbers. - """ - - @abc.abstractmethod - def load_elliptic_curve_private_numbers(self, numbers): - """ - Return an EllipticCurvePrivateKey provider using the given numbers. - """ - - @abc.abstractmethod - def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): - """ - Returns whether the exchange algorithm is supported by this backend. - """ - - @abc.abstractmethod - def derive_elliptic_curve_private_key(self, private_value, curve): - """ - Compute the private key given the private value and curve. - """ - - -@six.add_metaclass(abc.ABCMeta) -class PEMSerializationBackend(object): - @abc.abstractmethod - def load_pem_private_key(self, data, password): - """ - Loads a private key from PEM encoded data, using the provided password - if the data is encrypted. - """ - - @abc.abstractmethod - def load_pem_public_key(self, data): - """ - Loads a public key from PEM encoded data. - """ - - @abc.abstractmethod - def load_pem_parameters(self, data): - """ - Load encryption parameters from PEM encoded data. - """ - - -@six.add_metaclass(abc.ABCMeta) -class DERSerializationBackend(object): - @abc.abstractmethod - def load_der_private_key(self, data, password): - """ - Loads a private key from DER encoded data. Uses the provided password - if the data is encrypted. - """ - - @abc.abstractmethod - def load_der_public_key(self, data): - """ - Loads a public key from DER encoded data. - """ - - @abc.abstractmethod - def load_der_parameters(self, data): - """ - Load encryption parameters from DER encoded data. - """ - - -@six.add_metaclass(abc.ABCMeta) -class X509Backend(object): - @abc.abstractmethod - def load_pem_x509_certificate(self, data): - """ - Load an X.509 certificate from PEM encoded data. - """ - - @abc.abstractmethod - def load_der_x509_certificate(self, data): - """ - Load an X.509 certificate from DER encoded data. - """ - - @abc.abstractmethod - def load_der_x509_csr(self, data): - """ - Load an X.509 CSR from DER encoded data. - """ - - @abc.abstractmethod - def load_pem_x509_csr(self, data): - """ - Load an X.509 CSR from PEM encoded data. - """ - - @abc.abstractmethod - def create_x509_csr(self, builder, private_key, algorithm): - """ - Create and sign an X.509 CSR from a CSR builder object. - """ - - @abc.abstractmethod - def create_x509_certificate(self, builder, private_key, algorithm): - """ - Create and sign an X.509 certificate from a CertificateBuilder object. - """ - - @abc.abstractmethod - def create_x509_crl(self, builder, private_key, algorithm): - """ - Create and sign an X.509 CertificateRevocationList from a - CertificateRevocationListBuilder object. - """ - - @abc.abstractmethod - def create_x509_revoked_certificate(self, builder): - """ - Create a RevokedCertificate object from a RevokedCertificateBuilder - object. - """ - - @abc.abstractmethod - def x509_name_bytes(self, name): - """ - Compute the DER encoded bytes of an X509 Name object. - """ - - -@six.add_metaclass(abc.ABCMeta) -class DHBackend(object): - @abc.abstractmethod - def generate_dh_parameters(self, generator, key_size): - """ - Generate a DHParameters instance with a modulus of key_size bits. - Using the given generator. Often 2 or 5. - """ - - @abc.abstractmethod - def generate_dh_private_key(self, parameters): - """ - Generate a DHPrivateKey instance with parameters as a DHParameters - object. - """ - - @abc.abstractmethod - def generate_dh_private_key_and_parameters(self, generator, key_size): - """ - Generate a DHPrivateKey instance using key size only. - Using the given generator. Often 2 or 5. - """ - - @abc.abstractmethod - def load_dh_private_numbers(self, numbers): - """ - Load a DHPrivateKey from DHPrivateNumbers - """ - - @abc.abstractmethod - def load_dh_public_numbers(self, numbers): - """ - Load a DHPublicKey from DHPublicNumbers. - """ - - @abc.abstractmethod - def load_dh_parameter_numbers(self, numbers): - """ - Load DHParameters from DHParameterNumbers. - """ - - @abc.abstractmethod - def dh_parameters_supported(self, p, g, q=None): - """ - Returns whether the backend supports DH with these parameter values. - """ - - @abc.abstractmethod - def dh_x942_serialization_supported(self): - """ - Returns True if the backend supports the serialization of DH objects - with subgroup order (q). - """ - - -@six.add_metaclass(abc.ABCMeta) -class ScryptBackend(object): - @abc.abstractmethod - def derive_scrypt(self, key_material, salt, length, n, r, p): - """ - Return bytes derived from provided Scrypt parameters. - """ diff --git a/src/cryptography/hazmat/backends/openssl/__init__.py b/src/cryptography/hazmat/backends/openssl/__init__.py index 8eadeb6e1867..51b04476cbb7 100644 --- a/src/cryptography/hazmat/backends/openssl/__init__.py +++ b/src/cryptography/hazmat/backends/openssl/__init__.py @@ -2,9 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations from cryptography.hazmat.backends.openssl.backend import backend - __all__ = ["backend"] diff --git a/src/cryptography/hazmat/backends/openssl/aead.py b/src/cryptography/hazmat/backends/openssl/aead.py index 4494916852ae..b36f535f3f8f 100644 --- a/src/cryptography/hazmat/backends/openssl/aead.py +++ b/src/cryptography/hazmat/backends/openssl/aead.py @@ -2,34 +2,301 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import typing from cryptography.exceptions import InvalidTag +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.backend import Backend + from cryptography.hazmat.primitives.ciphers.aead import ( + AESCCM, + AESGCM, + AESOCB3, + AESSIV, + ChaCha20Poly1305, + ) + + _AEADTypes = typing.Union[ + AESCCM, AESGCM, AESOCB3, AESSIV, ChaCha20Poly1305 + ] + + +def _is_evp_aead_supported_cipher( + backend: Backend, cipher: _AEADTypes +) -> bool: + """ + Checks whether the given cipher is supported through + EVP_AEAD rather than the normal OpenSSL EVP_CIPHER API. + """ + from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 + + return backend._lib.Cryptography_HAS_EVP_AEAD and isinstance( + cipher, ChaCha20Poly1305 + ) + + +def _aead_cipher_supported(backend: Backend, cipher: _AEADTypes) -> bool: + if _is_evp_aead_supported_cipher(backend, cipher): + return True + else: + cipher_name = _evp_cipher_cipher_name(cipher) + if backend._fips_enabled and cipher_name not in backend._fips_aead: + return False + # SIV isn't loaded through get_cipherbyname but instead a new fetch API + # only available in 3.0+. But if we know we're on 3.0+ then we know + # it's supported. + if cipher_name.endswith(b"-siv"): + return backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER == 1 + else: + return ( + backend._lib.EVP_get_cipherbyname(cipher_name) + != backend._ffi.NULL + ) + + +def _aead_create_ctx( + backend: Backend, + cipher: _AEADTypes, + key: bytes, +): + if _is_evp_aead_supported_cipher(backend, cipher): + return _evp_aead_create_ctx(backend, cipher, key) + else: + return _evp_cipher_create_ctx(backend, cipher, key) + + +def _encrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any = None, +) -> bytes: + if _is_evp_aead_supported_cipher(backend, cipher): + return _evp_aead_encrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + else: + return _evp_cipher_encrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + + +def _decrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any = None, +) -> bytes: + if _is_evp_aead_supported_cipher(backend, cipher): + return _evp_aead_decrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + else: + return _evp_cipher_decrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + + +def _evp_aead_create_ctx( + backend: Backend, + cipher: _AEADTypes, + key: bytes, + tag_len: typing.Optional[int] = None, +): + aead_cipher = _evp_aead_get_cipher(backend, cipher) + assert aead_cipher is not None + key_ptr = backend._ffi.from_buffer(key) + tag_len = ( + backend._lib.EVP_AEAD_DEFAULT_TAG_LENGTH + if tag_len is None + else tag_len + ) + ctx = backend._lib.Cryptography_EVP_AEAD_CTX_new( + aead_cipher, key_ptr, len(key), tag_len + ) + backend.openssl_assert(ctx != backend._ffi.NULL) + ctx = backend._ffi.gc(ctx, backend._lib.EVP_AEAD_CTX_free) + return ctx + + +def _evp_aead_get_cipher(backend: Backend, cipher: _AEADTypes): + from cryptography.hazmat.primitives.ciphers.aead import ( + ChaCha20Poly1305, + ) + + # Currently only ChaCha20-Poly1305 is supported using this API + assert isinstance(cipher, ChaCha20Poly1305) + return backend._lib.EVP_aead_chacha20_poly1305() + + +def _evp_aead_encrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any, +) -> bytes: + assert ctx is not None + + aead_cipher = _evp_aead_get_cipher(backend, cipher) + assert aead_cipher is not None + + out_len = backend._ffi.new("size_t *") + # max_out_len should be in_len plus the result of + # EVP_AEAD_max_overhead. + max_out_len = len(data) + backend._lib.EVP_AEAD_max_overhead(aead_cipher) + out_buf = backend._ffi.new("uint8_t[]", max_out_len) + data_ptr = backend._ffi.from_buffer(data) + nonce_ptr = backend._ffi.from_buffer(nonce) + aad = b"".join(associated_data) + aad_ptr = backend._ffi.from_buffer(aad) + + res = backend._lib.EVP_AEAD_CTX_seal( + ctx, + out_buf, + out_len, + max_out_len, + nonce_ptr, + len(nonce), + data_ptr, + len(data), + aad_ptr, + len(aad), + ) + backend.openssl_assert(res == 1) + encrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] + return encrypted_data + + +def _evp_aead_decrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any, +) -> bytes: + if len(data) < tag_length: + raise InvalidTag + + assert ctx is not None + + out_len = backend._ffi.new("size_t *") + # max_out_len should at least in_len + max_out_len = len(data) + out_buf = backend._ffi.new("uint8_t[]", max_out_len) + data_ptr = backend._ffi.from_buffer(data) + nonce_ptr = backend._ffi.from_buffer(nonce) + aad = b"".join(associated_data) + aad_ptr = backend._ffi.from_buffer(aad) + + res = backend._lib.EVP_AEAD_CTX_open( + ctx, + out_buf, + out_len, + max_out_len, + nonce_ptr, + len(nonce), + data_ptr, + len(data), + aad_ptr, + len(aad), + ) + + if res == 0: + backend._consume_errors() + raise InvalidTag + + decrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] + return decrypted_data + _ENCRYPT = 1 _DECRYPT = 0 -def _aead_cipher_name(cipher): +def _evp_cipher_cipher_name(cipher: _AEADTypes) -> bytes: from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, + AESOCB3, + AESSIV, ChaCha20Poly1305, ) if isinstance(cipher, ChaCha20Poly1305): return b"chacha20-poly1305" elif isinstance(cipher, AESCCM): - return "aes-{}-ccm".format(len(cipher._key) * 8).encode("ascii") + return f"aes-{len(cipher._key) * 8}-ccm".encode("ascii") + elif isinstance(cipher, AESOCB3): + return f"aes-{len(cipher._key) * 8}-ocb".encode("ascii") + elif isinstance(cipher, AESSIV): + return f"aes-{len(cipher._key) * 8 // 2}-siv".encode("ascii") else: assert isinstance(cipher, AESGCM) - return "aes-{}-gcm".format(len(cipher._key) * 8).encode("ascii") + return f"aes-{len(cipher._key) * 8}-gcm".encode("ascii") + + +def _evp_cipher(cipher_name: bytes, backend: Backend): + if cipher_name.endswith(b"-siv"): + evp_cipher = backend._lib.EVP_CIPHER_fetch( + backend._ffi.NULL, + cipher_name, + backend._ffi.NULL, + ) + backend.openssl_assert(evp_cipher != backend._ffi.NULL) + evp_cipher = backend._ffi.gc(evp_cipher, backend._lib.EVP_CIPHER_free) + else: + evp_cipher = backend._lib.EVP_get_cipherbyname(cipher_name) + backend.openssl_assert(evp_cipher != backend._ffi.NULL) + + return evp_cipher + + +def _evp_cipher_create_ctx( + backend: Backend, + cipher: _AEADTypes, + key: bytes, +): + ctx = backend._lib.EVP_CIPHER_CTX_new() + backend.openssl_assert(ctx != backend._ffi.NULL) + ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) + cipher_name = _evp_cipher_cipher_name(cipher) + evp_cipher = _evp_cipher(cipher_name, backend) + key_ptr = backend._ffi.from_buffer(key) + res = backend._lib.EVP_CipherInit_ex( + ctx, + evp_cipher, + backend._ffi.NULL, + key_ptr, + backend._ffi.NULL, + 0, + ) + backend.openssl_assert(res != 0) + return ctx -def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): - evp_cipher = backend._lib.EVP_get_cipherbyname(cipher_name) - backend.openssl_assert(evp_cipher != backend._ffi.NULL) +def _evp_cipher_aead_setup( + backend: Backend, + cipher_name: bytes, + key: bytes, + nonce: bytes, + tag: typing.Optional[bytes], + tag_len: int, + operation: int, +): + evp_cipher = _evp_cipher(cipher_name, backend) ctx = backend._lib.EVP_CIPHER_CTX_new() ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) res = backend._lib.EVP_CipherInit_ex( @@ -41,8 +308,7 @@ def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): int(operation == _ENCRYPT), ) backend.openssl_assert(res != 0) - res = backend._lib.EVP_CIPHER_CTX_set_key_length(ctx, len(key)) - backend.openssl_assert(res != 0) + # CCM requires the IVLEN to be set before calling SET_TAG on decrypt res = backend._lib.EVP_CIPHER_CTX_ctrl( ctx, backend._lib.EVP_CTRL_AEAD_SET_IVLEN, @@ -51,13 +317,14 @@ def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): ) backend.openssl_assert(res != 0) if operation == _DECRYPT: - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag - ) - backend.openssl_assert(res != 0) + assert tag is not None + _evp_cipher_set_tag(backend, ctx, tag) elif cipher_name.endswith(b"-ccm"): res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, tag_len, backend._ffi.NULL + ctx, + backend._lib.EVP_CTRL_AEAD_SET_TAG, + tag_len, + backend._ffi.NULL, ) backend.openssl_assert(res != 0) @@ -75,7 +342,30 @@ def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): return ctx -def _set_length(backend, ctx, data_len): +def _evp_cipher_set_tag(backend, ctx, tag: bytes) -> None: + tag_ptr = backend._ffi.from_buffer(tag) + res = backend._lib.EVP_CIPHER_CTX_ctrl( + ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag_ptr + ) + backend.openssl_assert(res != 0) + + +def _evp_cipher_set_nonce_operation( + backend, ctx, nonce: bytes, operation: int +) -> None: + nonce_ptr = backend._ffi.from_buffer(nonce) + res = backend._lib.EVP_CipherInit_ex( + ctx, + backend._ffi.NULL, + backend._ffi.NULL, + backend._ffi.NULL, + nonce_ptr, + int(operation == _ENCRYPT), + ) + backend.openssl_assert(res != 0) + + +def _evp_cipher_set_length(backend: Backend, ctx, data_len: int) -> None: intptr = backend._ffi.new("int *") res = backend._lib.EVP_CipherUpdate( ctx, backend._ffi.NULL, intptr, backend._ffi.NULL, data_len @@ -83,40 +373,71 @@ def _set_length(backend, ctx, data_len): backend.openssl_assert(res != 0) -def _process_aad(backend, ctx, associated_data): +def _evp_cipher_process_aad( + backend: Backend, ctx, associated_data: bytes +) -> None: outlen = backend._ffi.new("int *") + a_data_ptr = backend._ffi.from_buffer(associated_data) res = backend._lib.EVP_CipherUpdate( - ctx, backend._ffi.NULL, outlen, associated_data, len(associated_data) + ctx, backend._ffi.NULL, outlen, a_data_ptr, len(associated_data) ) backend.openssl_assert(res != 0) -def _process_data(backend, ctx, data): +def _evp_cipher_process_data(backend: Backend, ctx, data: bytes) -> bytes: outlen = backend._ffi.new("int *") buf = backend._ffi.new("unsigned char[]", len(data)) - res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data, len(data)) - backend.openssl_assert(res != 0) + data_ptr = backend._ffi.from_buffer(data) + res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data_ptr, len(data)) + if res == 0: + # AES SIV can error here if the data is invalid on decrypt + backend._consume_errors() + raise InvalidTag return backend._ffi.buffer(buf, outlen[0])[:] -def _encrypt(backend, cipher, nonce, data, associated_data, tag_length): - from cryptography.hazmat.primitives.ciphers.aead import AESCCM +def _evp_cipher_encrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any = None, +) -> bytes: + from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV - cipher_name = _aead_cipher_name(cipher) - ctx = _aead_setup( - backend, cipher_name, cipher._key, nonce, None, tag_length, _ENCRYPT - ) - # CCM requires us to pass the length of the data before processing anything + if ctx is None: + cipher_name = _evp_cipher_cipher_name(cipher) + ctx = _evp_cipher_aead_setup( + backend, + cipher_name, + cipher._key, + nonce, + None, + tag_length, + _ENCRYPT, + ) + else: + _evp_cipher_set_nonce_operation(backend, ctx, nonce, _ENCRYPT) + + # CCM requires us to pass the length of the data before processing + # anything. # However calling this with any other AEAD results in an error if isinstance(cipher, AESCCM): - _set_length(backend, ctx, len(data)) + _evp_cipher_set_length(backend, ctx, len(data)) - _process_aad(backend, ctx, associated_data) - processed_data = _process_data(backend, ctx, data) + for ad in associated_data: + _evp_cipher_process_aad(backend, ctx, ad) + processed_data = _evp_cipher_process_data(backend, ctx, data) outlen = backend._ffi.new("int *") - res = backend._lib.EVP_CipherFinal_ex(ctx, backend._ffi.NULL, outlen) + # All AEADs we support besides OCB are streaming so they return nothing + # in finalization. OCB can return up to (16 byte block - 1) bytes so + # we need a buffer here too. + buf = backend._ffi.new("unsigned char[]", 16) + res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen) backend.openssl_assert(res != 0) - backend.openssl_assert(outlen[0] == 0) + processed_data += backend._ffi.buffer(buf, outlen[0])[:] tag_buf = backend._ffi.new("unsigned char[]", tag_length) res = backend._lib.EVP_CIPHER_CTX_ctrl( ctx, backend._lib.EVP_CTRL_AEAD_GET_TAG, tag_length, tag_buf @@ -124,41 +445,81 @@ def _encrypt(backend, cipher, nonce, data, associated_data, tag_length): backend.openssl_assert(res != 0) tag = backend._ffi.buffer(tag_buf)[:] - return processed_data + tag + if isinstance(cipher, AESSIV): + # 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 + backend.openssl_assert(len(tag) == 16) + return tag + processed_data + else: + return processed_data + tag -def _decrypt(backend, cipher, nonce, data, associated_data, tag_length): - from cryptography.hazmat.primitives.ciphers.aead import AESCCM +def _evp_cipher_decrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any = None, +) -> bytes: + from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV if len(data) < tag_length: raise InvalidTag - tag = data[-tag_length:] - data = data[:-tag_length] - cipher_name = _aead_cipher_name(cipher) - ctx = _aead_setup( - backend, cipher_name, cipher._key, nonce, tag, tag_length, _DECRYPT - ) - # CCM requires us to pass the length of the data before processing anything + + if isinstance(cipher, AESSIV): + # 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 + tag = data[:tag_length] + data = data[tag_length:] + else: + tag = data[-tag_length:] + data = data[:-tag_length] + if ctx is None: + cipher_name = _evp_cipher_cipher_name(cipher) + ctx = _evp_cipher_aead_setup( + backend, + cipher_name, + cipher._key, + nonce, + tag, + tag_length, + _DECRYPT, + ) + else: + _evp_cipher_set_nonce_operation(backend, ctx, nonce, _DECRYPT) + _evp_cipher_set_tag(backend, ctx, tag) + + # CCM requires us to pass the length of the data before processing + # anything. # However calling this with any other AEAD results in an error if isinstance(cipher, AESCCM): - _set_length(backend, ctx, len(data)) + _evp_cipher_set_length(backend, ctx, len(data)) - _process_aad(backend, ctx, associated_data) + for ad in associated_data: + _evp_cipher_process_aad(backend, ctx, ad) # CCM has a different error path if the tag doesn't match. Errors are # raised in Update and Final is irrelevant. if isinstance(cipher, AESCCM): outlen = backend._ffi.new("int *") buf = backend._ffi.new("unsigned char[]", len(data)) - res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data, len(data)) + d_ptr = backend._ffi.from_buffer(data) + res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, d_ptr, len(data)) if res != 1: backend._consume_errors() raise InvalidTag processed_data = backend._ffi.buffer(buf, outlen[0])[:] else: - processed_data = _process_data(backend, ctx, data) + processed_data = _evp_cipher_process_data(backend, ctx, data) outlen = backend._ffi.new("int *") - res = backend._lib.EVP_CipherFinal_ex(ctx, backend._ffi.NULL, outlen) + # OCB can return up to 15 bytes (16 byte block - 1) in finalization + buf = backend._ffi.new("unsigned char[]", 16) + res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen) + processed_data += backend._ffi.buffer(buf, outlen[0])[:] if res == 0: backend._consume_errors() raise InvalidTag diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index b7757e333d9e..02d51094cfe5 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -2,143 +2,68 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import collections import contextlib import itertools -import warnings +import typing from contextlib import contextmanager -import six -from six.moves import range - from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat._der import ( - INTEGER, - NULL, - SEQUENCE, - encode_der, - encode_der_integer, -) -from cryptography.hazmat.backends.interfaces import ( - CMACBackend, - CipherBackend, - DERSerializationBackend, - DHBackend, - DSABackend, - EllipticCurveBackend, - HMACBackend, - HashBackend, - PBKDF2HMACBackend, - PEMSerializationBackend, - RSABackend, - ScryptBackend, - X509Backend, -) 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.decode_asn1 import ( - _CRL_ENTRY_REASON_ENUM_TO_CODE, - _CRL_EXTENSION_HANDLERS, - _EXTENSION_HANDLERS_BASE, - _EXTENSION_HANDLERS_SCT, - _OCSP_BASICRESP_EXTENSION_HANDLERS, - _OCSP_REQ_EXTENSION_HANDLERS, - _OCSP_SINGLERESP_EXTENSION_HANDLERS_SCT, - _REVOKED_EXTENSION_HANDLERS, - _X509ExtensionParser, -) -from cryptography.hazmat.backends.openssl.dh import ( - _DHParameters, - _DHPrivateKey, - _DHPublicKey, - _dh_params_dup, -) -from cryptography.hazmat.backends.openssl.dsa import ( - _DSAParameters, - _DSAPrivateKey, - _DSAPublicKey, -) from cryptography.hazmat.backends.openssl.ec import ( _EllipticCurvePrivateKey, _EllipticCurvePublicKey, ) -from cryptography.hazmat.backends.openssl.ed25519 import ( - _Ed25519PrivateKey, - _Ed25519PublicKey, -) -from cryptography.hazmat.backends.openssl.ed448 import ( - _ED448_KEY_SIZE, - _Ed448PrivateKey, - _Ed448PublicKey, -) -from cryptography.hazmat.backends.openssl.encode_asn1 import ( - _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, - _CRL_EXTENSION_ENCODE_HANDLERS, - _EXTENSION_ENCODE_HANDLERS, - _OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS, - _OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS, - _encode_asn1_int_gc, - _encode_asn1_str_gc, - _encode_name_gc, - _txt2obj_gc, -) -from cryptography.hazmat.backends.openssl.hashes import _HashContext -from cryptography.hazmat.backends.openssl.hmac import _HMACContext -from cryptography.hazmat.backends.openssl.ocsp import ( - _OCSPRequest, - _OCSPResponse, -) -from cryptography.hazmat.backends.openssl.poly1305 import ( - _POLY1305_KEY_SIZE, - _Poly1305Context, -) from cryptography.hazmat.backends.openssl.rsa import ( _RSAPrivateKey, _RSAPublicKey, ) -from cryptography.hazmat.backends.openssl.x25519 import ( - _X25519PrivateKey, - _X25519PublicKey, -) -from cryptography.hazmat.backends.openssl.x448 import ( - _X448PrivateKey, - _X448PublicKey, -) -from cryptography.hazmat.backends.openssl.x509 import ( - _Certificate, - _CertificateRevocationList, - _CertificateSigningRequest, - _RevokedCertificate, -) +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl import binding from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding from cryptography.hazmat.primitives.asymmetric import ( + dh, dsa, ec, - ed25519, ed448, + ed25519, rsa, + x448, + x25519, ) from cryptography.hazmat.primitives.asymmetric.padding import ( MGF1, OAEP, - PKCS1v15, PSS, + PKCS1v15, +) +from cryptography.hazmat.primitives.asymmetric.types import ( + PrivateKeyTypes, + PublicKeyTypes, +) +from cryptography.hazmat.primitives.ciphers import ( + BlockCipherAlgorithm, + CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, + AES128, + AES256, ARC4, - Blowfish, - CAST5, + SM4, Camellia, ChaCha20, - IDEA, - SEED, TripleDES, + _BlowfishInternal, + _CAST5Internal, + _IDEAInternal, + _SEEDInternal, ) from cryptography.hazmat.primitives.ciphers.modes import ( CBC, @@ -149,36 +74,26 @@ GCM, OFB, XTS, + Mode, +) +from cryptography.hazmat.primitives.serialization import ssh +from cryptography.hazmat.primitives.serialization.pkcs12 import ( + PBES, + PKCS12Certificate, + PKCS12KeyAndCertificates, + PKCS12PrivateKeyTypes, + _PKCS12CATypes, ) -from cryptography.hazmat.primitives.kdf import scrypt -from cryptography.hazmat.primitives.serialization import pkcs7, ssh -from cryptography.x509 import ocsp - _MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) # Not actually supported, just used as a marker for some serialization tests. -class _RC2(object): +class _RC2: pass -@utils.register_interface(CipherBackend) -@utils.register_interface(CMACBackend) -@utils.register_interface(DERSerializationBackend) -@utils.register_interface(DHBackend) -@utils.register_interface(DSABackend) -@utils.register_interface(EllipticCurveBackend) -@utils.register_interface(HashBackend) -@utils.register_interface(HMACBackend) -@utils.register_interface(PBKDF2HMACBackend) -@utils.register_interface(RSABackend) -@utils.register_interface(PEMSerializationBackend) -@utils.register_interface(X509Backend) -@utils.register_interface_if( - binding.Binding().lib.Cryptography_HAS_SCRYPT, ScryptBackend -) -class Backend(object): +class Backend: """ OpenSSL API binding interfaces. """ @@ -197,9 +112,12 @@ class Backend(object): b"aes-192-gcm", b"aes-256-gcm", } - _fips_ciphers = (AES, TripleDES) + # TripleDES encryption is disallowed/deprecated throughout 2023 in + # FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA). + _fips_ciphers = (AES,) + # Sometimes SHA1 is still permissible. That logic is contained + # within the various *_supported methods. _fips_hashes = ( - hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, @@ -213,99 +131,55 @@ class Backend(object): hashes.SHAKE128, hashes.SHAKE256, ) + _fips_ecdh_curves = ( + ec.SECP224R1, + ec.SECP256R1, + ec.SECP384R1, + ec.SECP521R1, + ) _fips_rsa_min_key_size = 2048 _fips_rsa_min_public_exponent = 65537 _fips_dsa_min_modulus = 1 << 2048 _fips_dh_min_key_size = 2048 _fips_dh_min_modulus = 1 << _fips_dh_min_key_size - def __init__(self): + def __init__(self) -> None: self._binding = binding.Binding() self._ffi = self._binding.ffi self._lib = self._binding.lib - self._fips_enabled = self._is_fips_enabled() + self._fips_enabled = rust_openssl.is_fips_enabled() - self._cipher_registry = {} + self._cipher_registry: typing.Dict[ + typing.Tuple[typing.Type[CipherAlgorithm], typing.Type[Mode]], + typing.Callable, + ] = {} self._register_default_ciphers() - self._register_x509_ext_parsers() - self._register_x509_encoders() - if self._fips_enabled and self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - warnings.warn( - "OpenSSL FIPS mode is enabled. Can't enable DRBG fork safety.", - UserWarning, - ) - else: - self.activate_osrandom_engine() self._dh_types = [self._lib.EVP_PKEY_DH] if self._lib.Cryptography_HAS_EVP_PKEY_DHX: self._dh_types.append(self._lib.EVP_PKEY_DHX) - def openssl_assert(self, ok, errors=None): - return binding._openssl_assert(self._lib, ok, errors=errors) - - def _is_fips_enabled(self): - fips_mode = getattr(self._lib, "FIPS_mode", lambda: 0) - mode = fips_mode() - if mode == 0: - # OpenSSL without FIPS pushes an error on the error stack - self._lib.ERR_clear_error() - return bool(mode) - - def activate_builtin_random(self): - if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - # Obtain a new structural reference. - e = self._lib.ENGINE_get_default_RAND() - if e != self._ffi.NULL: - self._lib.ENGINE_unregister_RAND(e) - # Reset the RNG to use the built-in. - res = self._lib.RAND_set_rand_method(self._ffi.NULL) - self.openssl_assert(res == 1) - # decrement the structural reference from get_default_RAND - res = self._lib.ENGINE_finish(e) - self.openssl_assert(res == 1) - - @contextlib.contextmanager - def _get_osurandom_engine(self): - # Fetches an engine by id and returns it. This creates a structural - # reference. - e = self._lib.ENGINE_by_id(self._lib.Cryptography_osrandom_engine_id) - self.openssl_assert(e != self._ffi.NULL) - # Initialize the engine for use. This adds a functional reference. - res = self._lib.ENGINE_init(e) - self.openssl_assert(res == 1) - - try: - yield e - finally: - # Decrement the structural ref incremented by ENGINE_by_id. - res = self._lib.ENGINE_free(e) - self.openssl_assert(res == 1) - # Decrement the functional ref incremented by ENGINE_init. - res = self._lib.ENGINE_finish(e) - self.openssl_assert(res == 1) + def __repr__(self) -> str: + return "".format( + self.openssl_version_text(), + self._fips_enabled, + self._binding._legacy_provider_loaded, + ) - def activate_osrandom_engine(self): - if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - # Unregister and free the current engine. - self.activate_builtin_random() - with self._get_osurandom_engine() as e: - # Set the engine as the default RAND provider. - res = self._lib.ENGINE_set_default_RAND(e) - self.openssl_assert(res == 1) - # Reset the RNG to use the engine - res = self._lib.RAND_set_rand_method(self._ffi.NULL) - self.openssl_assert(res == 1) + def openssl_assert( + self, + ok: bool, + errors: typing.Optional[typing.List[rust_openssl.OpenSSLError]] = None, + ) -> None: + return binding._openssl_assert(self._lib, ok, errors=errors) - def osrandom_engine_implementation(self): - buf = self._ffi.new("char[]", 64) - with self._get_osurandom_engine() as e: - res = self._lib.ENGINE_ctrl_cmd( - e, b"get_implementation", len(buf), buf, self._ffi.NULL, 0 - ) - self.openssl_assert(res > 0) - return self._ffi.string(buf).decode("ascii") + def _enable_fips(self) -> None: + # This function enables FIPS mode for OpenSSL 3.0.0 on installs that + # have the FIPS provider installed properly. + self._binding._enable_fips() + assert rust_openssl.is_fips_enabled() + self._fips_enabled = rust_openssl.is_fips_enabled() - def openssl_version_text(self): + def openssl_version_text(self) -> str: """ Friendly string name of the loaded OpenSSL library. This is not necessarily the same version as it was compiled against. @@ -316,13 +190,10 @@ def openssl_version_text(self): self._lib.OpenSSL_version(self._lib.OPENSSL_VERSION) ).decode("ascii") - def openssl_version_number(self): + def openssl_version_number(self) -> int: return self._lib.OpenSSL_version_num() - def create_hmac_ctx(self, key, algorithm): - return _HMACContext(self, key, algorithm) - - def _evp_md_from_algorithm(self, algorithm): + def _evp_md_from_algorithm(self, algorithm: hashes.HashAlgorithm): if algorithm.name == "blake2b" or algorithm.name == "blake2s": alg = "{}{}".format( algorithm.name, algorithm.digest_size * 8 @@ -333,27 +204,47 @@ def _evp_md_from_algorithm(self, algorithm): evp_md = self._lib.EVP_get_digestbyname(alg) return evp_md - def _evp_md_non_null_from_algorithm(self, algorithm): + def _evp_md_non_null_from_algorithm(self, algorithm: hashes.HashAlgorithm): evp_md = self._evp_md_from_algorithm(algorithm) self.openssl_assert(evp_md != self._ffi.NULL) return evp_md - def hash_supported(self, algorithm): + def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): return False evp_md = self._evp_md_from_algorithm(algorithm) return evp_md != self._ffi.NULL - def hmac_supported(self, algorithm): + def signature_hash_supported( + self, algorithm: hashes.HashAlgorithm + ) -> bool: + # Dedicated check for hashing algorithm use in message digest for + # signatures, e.g. RSA PKCS#1 v1.5 SHA1 (sha1WithRSAEncryption). + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False return self.hash_supported(algorithm) - def create_hash_ctx(self, algorithm): - return _HashContext(self, algorithm) - - def cipher_supported(self, cipher, mode): - if self._fips_enabled and not isinstance(cipher, self._fips_ciphers): + def scrypt_supported(self) -> bool: + if self._fips_enabled: return False + else: + return self._lib.Cryptography_HAS_SCRYPT == 1 + + def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + # FIPS mode still allows SHA1 for HMAC + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return True + + return self.hash_supported(algorithm) + + def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: + if self._fips_enabled: + # FIPS mode requires AES. TripleDES is disallowed/deprecated in + # FIPS 140-3. + if not isinstance(cipher, self._fips_ciphers): + return False + try: adapter = self._cipher_registry[type(cipher), type(mode)] except KeyError: @@ -361,7 +252,7 @@ def cipher_supported(self, cipher, mode): evp_cipher = adapter(self, cipher, mode) return self._ffi.NULL != evp_cipher - def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): + def register_cipher_adapter(self, cipher_cls, mode_cls, adapter) -> None: if (cipher_cls, mode_cls) in self._cipher_registry: raise ValueError( "Duplicate registration for: {} {}.".format( @@ -370,13 +261,16 @@ def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): ) self._cipher_registry[cipher_cls, mode_cls] = adapter - def _register_default_ciphers(self): - for mode_cls in [CBC, CTR, ECB, OFB, CFB, CFB8, GCM]: - self.register_cipher_adapter( - AES, - mode_cls, - GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}"), - ) + def _register_default_ciphers(self) -> None: + for cipher_cls in [AES, AES128, AES256]: + for mode_cls in [CBC, CTR, ECB, OFB, CFB, CFB8, GCM]: + self.register_cipher_adapter( + cipher_cls, + mode_cls, + GetCipherByName( + "{cipher.name}-{cipher.key_size}-{mode.name}" + ), + ) for mode_cls in [CBC, CTR, ECB, OFB, CFB]: self.register_cipher_adapter( Camellia, @@ -390,187 +284,94 @@ def _register_default_ciphers(self): self.register_cipher_adapter( TripleDES, ECB, GetCipherByName("des-ede3") ) - for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + ChaCha20, type(None), GetCipherByName("chacha20") + ) + self.register_cipher_adapter(AES, XTS, _get_xts_cipher) + for mode_cls in [ECB, CBC, OFB, CFB, CTR]: self.register_cipher_adapter( - Blowfish, mode_cls, GetCipherByName("bf-{mode.name}") + SM4, mode_cls, GetCipherByName("sm4-{mode.name}") ) - for mode_cls in [CBC, CFB, OFB, ECB]: + # Don't register legacy ciphers if they're unavailable. Hypothetically + # this wouldn't be necessary because we test availability by seeing if + # we get an EVP_CIPHER * in the _CipherContext __init__, but OpenSSL 3 + # will return a valid pointer even though the cipher is unavailable. + if ( + self._binding._legacy_provider_loaded + or not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER + ): + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + _BlowfishInternal, + mode_cls, + GetCipherByName("bf-{mode.name}"), + ) + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + _SEEDInternal, + mode_cls, + GetCipherByName("seed-{mode.name}"), + ) + for cipher_cls, mode_cls in itertools.product( + [_CAST5Internal, _IDEAInternal], + [CBC, OFB, CFB, ECB], + ): + self.register_cipher_adapter( + cipher_cls, + mode_cls, + GetCipherByName("{cipher.name}-{mode.name}"), + ) self.register_cipher_adapter( - SEED, mode_cls, GetCipherByName("seed-{mode.name}") + ARC4, type(None), GetCipherByName("rc4") ) - for cipher_cls, mode_cls in itertools.product( - [CAST5, IDEA], - [CBC, OFB, CFB, ECB], - ): + # We don't actually support RC2, this is just used by some tests. self.register_cipher_adapter( - cipher_cls, - mode_cls, - GetCipherByName("{cipher.name}-{mode.name}"), + _RC2, type(None), GetCipherByName("rc2") ) - self.register_cipher_adapter(ARC4, type(None), GetCipherByName("rc4")) - # We don't actually support RC2, this is just used by some tests. - self.register_cipher_adapter(_RC2, type(None), GetCipherByName("rc2")) - self.register_cipher_adapter( - ChaCha20, type(None), GetCipherByName("chacha20") - ) - self.register_cipher_adapter(AES, XTS, _get_xts_cipher) - - def _register_x509_ext_parsers(self): - ext_handlers = _EXTENSION_HANDLERS_BASE.copy() - # All revoked extensions are valid single response extensions, see: - # https://tools.ietf.org/html/rfc6960#section-4.4.5 - singleresp_handlers = _REVOKED_EXTENSION_HANDLERS.copy() - - if self._lib.Cryptography_HAS_SCT: - ext_handlers.update(_EXTENSION_HANDLERS_SCT) - singleresp_handlers.update(_OCSP_SINGLERESP_EXTENSION_HANDLERS_SCT) - - self._certificate_extension_parser = _X509ExtensionParser( - self, - ext_count=self._lib.X509_get_ext_count, - get_ext=self._lib.X509_get_ext, - handlers=ext_handlers, - ) - self._csr_extension_parser = _X509ExtensionParser( - self, - ext_count=self._lib.sk_X509_EXTENSION_num, - get_ext=self._lib.sk_X509_EXTENSION_value, - handlers=ext_handlers, - ) - self._revoked_cert_extension_parser = _X509ExtensionParser( - self, - ext_count=self._lib.X509_REVOKED_get_ext_count, - get_ext=self._lib.X509_REVOKED_get_ext, - handlers=_REVOKED_EXTENSION_HANDLERS, - ) - self._crl_extension_parser = _X509ExtensionParser( - self, - ext_count=self._lib.X509_CRL_get_ext_count, - get_ext=self._lib.X509_CRL_get_ext, - handlers=_CRL_EXTENSION_HANDLERS, - ) - self._ocsp_req_ext_parser = _X509ExtensionParser( - self, - ext_count=self._lib.OCSP_REQUEST_get_ext_count, - get_ext=self._lib.OCSP_REQUEST_get_ext, - handlers=_OCSP_REQ_EXTENSION_HANDLERS, - ) - self._ocsp_basicresp_ext_parser = _X509ExtensionParser( - self, - ext_count=self._lib.OCSP_BASICRESP_get_ext_count, - get_ext=self._lib.OCSP_BASICRESP_get_ext, - handlers=_OCSP_BASICRESP_EXTENSION_HANDLERS, - ) - self._ocsp_singleresp_ext_parser = _X509ExtensionParser( - self, - ext_count=self._lib.OCSP_SINGLERESP_get_ext_count, - get_ext=self._lib.OCSP_SINGLERESP_get_ext, - handlers=singleresp_handlers, - ) - - def _register_x509_encoders(self): - self._extension_encode_handlers = _EXTENSION_ENCODE_HANDLERS.copy() - self._crl_extension_encode_handlers = ( - _CRL_EXTENSION_ENCODE_HANDLERS.copy() - ) - self._crl_entry_extension_encode_handlers = ( - _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS.copy() - ) - self._ocsp_request_extension_encode_handlers = ( - _OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS.copy() - ) - self._ocsp_basicresp_extension_encode_handlers = ( - _OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS.copy() - ) - def create_symmetric_encryption_ctx(self, cipher, mode): + def create_symmetric_encryption_ctx( + self, cipher: CipherAlgorithm, mode: Mode + ) -> _CipherContext: return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) - def create_symmetric_decryption_ctx(self, cipher, mode): + def create_symmetric_decryption_ctx( + self, cipher: CipherAlgorithm, mode: Mode + ) -> _CipherContext: return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) - def pbkdf2_hmac_supported(self, algorithm): + def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: return self.hmac_supported(algorithm) - def derive_pbkdf2_hmac( - self, algorithm, length, salt, iterations, key_material - ): - buf = self._ffi.new("unsigned char[]", length) - evp_md = self._evp_md_non_null_from_algorithm(algorithm) - key_material_ptr = self._ffi.from_buffer(key_material) - res = self._lib.PKCS5_PBKDF2_HMAC( - key_material_ptr, - len(key_material), - salt, - len(salt), - iterations, - evp_md, - length, - buf, - ) - self.openssl_assert(res == 1) - return self._ffi.buffer(buf)[:] - - def _consume_errors(self): - return binding._consume_errors(self._lib) - - def _consume_errors_with_text(self): - return binding._consume_errors_with_text(self._lib) + def _consume_errors(self) -> typing.List[rust_openssl.OpenSSLError]: + return rust_openssl.capture_error_stack() - def _bn_to_int(self, bn): + def _bn_to_int(self, bn) -> int: assert bn != self._ffi.NULL + self.openssl_assert(not self._lib.BN_is_negative(bn)) - if not six.PY2: - # Python 3 has constant time from_bytes, so use that. - bn_num_bytes = self._lib.BN_num_bytes(bn) - bin_ptr = self._ffi.new("unsigned char[]", bn_num_bytes) - bin_len = self._lib.BN_bn2bin(bn, bin_ptr) - # A zero length means the BN has value 0 - self.openssl_assert(bin_len >= 0) - val = int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") - if self._lib.BN_is_negative(bn): - val = -val - return val - else: - # Under Python 2 the best we can do is hex() - hex_cdata = self._lib.BN_bn2hex(bn) - self.openssl_assert(hex_cdata != self._ffi.NULL) - hex_str = self._ffi.string(hex_cdata) - self._lib.OPENSSL_free(hex_cdata) - return int(hex_str, 16) - - def _int_to_bn(self, num, bn=None): + bn_num_bytes = self._lib.BN_num_bytes(bn) + bin_ptr = self._ffi.new("unsigned char[]", bn_num_bytes) + bin_len = self._lib.BN_bn2bin(bn, bin_ptr) + # A zero length means the BN has value 0 + self.openssl_assert(bin_len >= 0) + val = int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") + return val + + def _int_to_bn(self, num: int): """ Converts a python integer to a BIGNUM. The returned BIGNUM will not be garbage collected (to support adding them to structs that take ownership of the object). Be sure to register it for GC if it will be discarded after use. """ - assert bn is None or bn != self._ffi.NULL - - if bn is None: - bn = self._ffi.NULL - - if not six.PY2: - # Python 3 has constant time to_bytes, so use that. - - binary = num.to_bytes(int(num.bit_length() / 8.0 + 1), "big") - bn_ptr = self._lib.BN_bin2bn(binary, len(binary), bn) - self.openssl_assert(bn_ptr != self._ffi.NULL) - return bn_ptr - - else: - # Under Python 2 the best we can do is hex(), [2:] removes the 0x - # prefix. - hex_num = hex(num).rstrip("L")[2:].encode("ascii") - bn_ptr = self._ffi.new("BIGNUM **") - bn_ptr[0] = bn - res = self._lib.BN_hex2bn(bn_ptr, hex_num) - self.openssl_assert(res != 0) - self.openssl_assert(bn_ptr[0] != self._ffi.NULL) - return bn_ptr[0] - - def generate_rsa_private_key(self, public_exponent, key_size): + binary = num.to_bytes(int(num.bit_length() / 8.0 + 1), "big") + bn_ptr = self._lib.BN_bin2bn(binary, len(binary), self._ffi.NULL) + self.openssl_assert(bn_ptr != self._ffi.NULL) + return bn_ptr + + 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() @@ -586,16 +387,25 @@ def generate_rsa_private_key(self, public_exponent, key_size): self.openssl_assert(res == 1) evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - return _RSAPrivateKey(self, rsa_cdata, evp_pkey) + # 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 + ) - def generate_rsa_parameters_supported(self, public_exponent, key_size): + def generate_rsa_parameters_supported( + self, public_exponent: int, key_size: int + ) -> bool: return ( public_exponent >= 3 and public_exponent & 1 != 0 and key_size >= 512 ) - def load_rsa_private_numbers(self, numbers): + def load_rsa_private_numbers( + self, + numbers: rsa.RSAPrivateNumbers, + unsafe_skip_rsa_key_validation: bool, + ) -> rsa.RSAPrivateKey: rsa._check_private_key_components( numbers.p, numbers.q, @@ -625,9 +435,16 @@ def load_rsa_private_numbers(self, numbers): self.openssl_assert(res == 1) evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - return _RSAPrivateKey(self, rsa_cdata, evp_pkey) + return _RSAPrivateKey( + self, + rsa_cdata, + evp_pkey, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, + ) - def load_rsa_public_numbers(self, numbers): + 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) @@ -652,7 +469,7 @@ def _rsa_cdata_to_evp_pkey(self, rsa_cdata): self.openssl_assert(res == 1) return evp_pkey - def _bytes_to_bio(self, data): + def _bytes_to_bio(self, data: bytes) -> _MemoryBIO: """ Return a _MemoryBIO namedtuple of (BIO, char*). @@ -676,7 +493,7 @@ def _create_mem_bio_gc(self): bio = self._ffi.gc(bio, self._lib.BIO_free) return bio - def _read_mem_bio(self, bio): + def _read_mem_bio(self, bio) -> bytes: """ Reads a memory BIO. This only works on memory BIOs. """ @@ -687,7 +504,9 @@ def _read_mem_bio(self, bio): bio_data = self._ffi.buffer(buf[0], buf_len)[:] return bio_data - def _evp_pkey_to_private_key(self, evp_pkey): + def _evp_pkey_to_private_key( + self, evp_pkey, unsafe_skip_rsa_key_validation: bool + ) -> PrivateKeyTypes: """ Return the appropriate type of PrivateKey given an evp_pkey cdata pointer. @@ -699,38 +518,69 @@ def _evp_pkey_to_private_key(self, evp_pkey): 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 _RSAPrivateKey( + self, + rsa_cdata, + evp_pkey, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, + ) + elif ( + key_type == self._lib.EVP_PKEY_RSA_PSS + and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + and not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + ): + # At the moment the way we handle RSA PSS keys is to strip the + # PSS constraints from them and treat them as normal RSA keys + # Unfortunately the RSA * itself tracks this data so we need to + # extract, serialize, and reload it without the constraints. + 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) + bio = self._create_mem_bio_gc() + res = self._lib.i2d_RSAPrivateKey_bio(bio, rsa_cdata) + self.openssl_assert(res == 1) + return self.load_der_private_key( + self._read_mem_bio(bio), + password=None, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, + ) elif key_type == self._lib.EVP_PKEY_DSA: - dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - return _DSAPrivateKey(self, dsa_cdata, evp_pkey) + return rust_openssl.dsa.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == self._lib.EVP_PKEY_EC: ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) self.openssl_assert(ec_cdata != self._ffi.NULL) ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) elif key_type in self._dh_types: - dh_cdata = self._lib.EVP_PKEY_get1_DH(evp_pkey) - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHPrivateKey(self, dh_cdata, evp_pkey) + return rust_openssl.dh.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1 - return _Ed25519PrivateKey(self, evp_pkey) + # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed25519.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 - return _X448PrivateKey(self, evp_pkey) - elif key_type == getattr(self._lib, "EVP_PKEY_X25519", None): - # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.0 - return _X25519PrivateKey(self, evp_pkey) + # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.x448.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) + elif key_type == self._lib.EVP_PKEY_X25519: + return rust_openssl.x25519.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_ED448 is not present in OpenSSL < 1.1.1 - return _Ed448PrivateKey(self, evp_pkey) + # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed448.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) else: raise UnsupportedAlgorithm("Unsupported key type.") - def _evp_pkey_to_public_key(self, evp_pkey): + def _evp_pkey_to_public_key(self, evp_pkey) -> PublicKeyTypes: """ Return the appropriate type of PublicKey given an evp_pkey cdata pointer. @@ -743,514 +593,180 @@ def _evp_pkey_to_public_key(self, 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) + elif ( + key_type == self._lib.EVP_PKEY_RSA_PSS + and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + and not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + ): + 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) + bio = self._create_mem_bio_gc() + res = self._lib.i2d_RSAPublicKey_bio(bio, rsa_cdata) + self.openssl_assert(res == 1) + return self.load_der_public_key(self._read_mem_bio(bio)) elif key_type == self._lib.EVP_PKEY_DSA: - dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - return _DSAPublicKey(self, dsa_cdata, evp_pkey) + return rust_openssl.dsa.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == self._lib.EVP_PKEY_EC: ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) - self.openssl_assert(ec_cdata != self._ffi.NULL) + if ec_cdata == self._ffi.NULL: + errors = self._consume_errors() + raise ValueError("Unable to load EC key", errors) ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) elif key_type in self._dh_types: - dh_cdata = self._lib.EVP_PKEY_get1_DH(evp_pkey) - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHPublicKey(self, dh_cdata, evp_pkey) + return rust_openssl.dh.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1 - return _Ed25519PublicKey(self, evp_pkey) + # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed25519.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 - return _X448PublicKey(self, evp_pkey) - elif key_type == getattr(self._lib, "EVP_PKEY_X25519", None): - # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.0 - return _X25519PublicKey(self, evp_pkey) + # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.x448.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) + elif key_type == self._lib.EVP_PKEY_X25519: + return rust_openssl.x25519.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.1 - return _Ed448PublicKey(self, evp_pkey) + # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed448.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) else: raise UnsupportedAlgorithm("Unsupported key type.") - def _oaep_hash_supported(self, algorithm): - if self._lib.Cryptography_HAS_RSA_OAEP_MD: - return isinstance( - algorithm, - ( - hashes.SHA1, - hashes.SHA224, - hashes.SHA256, - hashes.SHA384, - hashes.SHA512, - ), - ) - else: - return isinstance(algorithm, hashes.SHA1) + def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False - def rsa_padding_supported(self, padding): + return isinstance( + algorithm, + ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + ), + ) + + def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: if isinstance(padding, PKCS1v15): return True elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): - return self.hash_supported(padding._mgf._algorithm) + # SHA1 is permissible in MGF1 in FIPS even when SHA1 is blocked + # as signature algorithm. + if self._fips_enabled and isinstance( + padding._mgf._algorithm, hashes.SHA1 + ): + return True + else: + return self.hash_supported(padding._mgf._algorithm) elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): - return ( - self._oaep_hash_supported(padding._mgf._algorithm) - and self._oaep_hash_supported(padding._algorithm) - and ( - (padding._label is None or len(padding._label) == 0) - or self._lib.Cryptography_HAS_RSA_OAEP_LABEL == 1 - ) - ) + return self._oaep_hash_supported( + padding._mgf._algorithm + ) and self._oaep_hash_supported(padding._algorithm) else: return False - def generate_dsa_parameters(self, key_size): + def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool: + if self._fips_enabled and isinstance(padding, PKCS1v15): + return False + else: + return self.rsa_padding_supported(padding) + + def generate_dsa_parameters(self, key_size: int) -> dsa.DSAParameters: if key_size not in (1024, 2048, 3072, 4096): raise ValueError( "Key size must be 1024, 2048, 3072, or 4096 bits." ) - ctx = self._lib.DSA_new() - self.openssl_assert(ctx != self._ffi.NULL) - ctx = self._ffi.gc(ctx, self._lib.DSA_free) + return rust_openssl.dsa.generate_parameters(key_size) - res = self._lib.DSA_generate_parameters_ex( - ctx, - key_size, - self._ffi.NULL, - 0, - self._ffi.NULL, - self._ffi.NULL, - self._ffi.NULL, - ) - - self.openssl_assert(res == 1) - - return _DSAParameters(self, ctx) - - def generate_dsa_private_key(self, parameters): - ctx = self._lib.DSAparams_dup(parameters._dsa_cdata) - self.openssl_assert(ctx != self._ffi.NULL) - ctx = self._ffi.gc(ctx, self._lib.DSA_free) - self._lib.DSA_generate_key(ctx) - evp_pkey = self._dsa_cdata_to_evp_pkey(ctx) + def generate_dsa_private_key( + self, parameters: dsa.DSAParameters + ) -> dsa.DSAPrivateKey: + return parameters.generate_private_key() - return _DSAPrivateKey(self, ctx, evp_pkey) - - def generate_dsa_private_key_and_parameters(self, key_size): + def generate_dsa_private_key_and_parameters( + self, key_size: int + ) -> dsa.DSAPrivateKey: parameters = self.generate_dsa_parameters(key_size) return self.generate_dsa_private_key(parameters) - def _dsa_cdata_set_values(self, dsa_cdata, p, q, g, pub_key, priv_key): - res = self._lib.DSA_set0_pqg(dsa_cdata, p, q, g) - self.openssl_assert(res == 1) - res = self._lib.DSA_set0_key(dsa_cdata, pub_key, priv_key) - self.openssl_assert(res == 1) - - def load_dsa_private_numbers(self, numbers): + def load_dsa_private_numbers( + self, numbers: dsa.DSAPrivateNumbers + ) -> dsa.DSAPrivateKey: dsa._check_dsa_private_numbers(numbers) - parameter_numbers = numbers.public_numbers.parameter_numbers - - dsa_cdata = self._lib.DSA_new() - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + return rust_openssl.dsa.from_private_numbers(numbers) - p = self._int_to_bn(parameter_numbers.p) - q = self._int_to_bn(parameter_numbers.q) - g = self._int_to_bn(parameter_numbers.g) - pub_key = self._int_to_bn(numbers.public_numbers.y) - priv_key = self._int_to_bn(numbers.x) - self._dsa_cdata_set_values(dsa_cdata, p, q, g, pub_key, priv_key) - - evp_pkey = self._dsa_cdata_to_evp_pkey(dsa_cdata) - - return _DSAPrivateKey(self, dsa_cdata, evp_pkey) - - def load_dsa_public_numbers(self, numbers): + def load_dsa_public_numbers( + self, numbers: dsa.DSAPublicNumbers + ) -> dsa.DSAPublicKey: dsa._check_dsa_parameters(numbers.parameter_numbers) - dsa_cdata = self._lib.DSA_new() - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - - p = self._int_to_bn(numbers.parameter_numbers.p) - q = self._int_to_bn(numbers.parameter_numbers.q) - g = self._int_to_bn(numbers.parameter_numbers.g) - pub_key = self._int_to_bn(numbers.y) - priv_key = self._ffi.NULL - self._dsa_cdata_set_values(dsa_cdata, p, q, g, pub_key, priv_key) - - evp_pkey = self._dsa_cdata_to_evp_pkey(dsa_cdata) - - return _DSAPublicKey(self, dsa_cdata, evp_pkey) + return rust_openssl.dsa.from_public_numbers(numbers) - def load_dsa_parameter_numbers(self, numbers): + def load_dsa_parameter_numbers( + self, numbers: dsa.DSAParameterNumbers + ) -> dsa.DSAParameters: dsa._check_dsa_parameters(numbers) - dsa_cdata = self._lib.DSA_new() - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + return rust_openssl.dsa.from_parameter_numbers(numbers) - p = self._int_to_bn(numbers.p) - q = self._int_to_bn(numbers.q) - g = self._int_to_bn(numbers.g) - res = self._lib.DSA_set0_pqg(dsa_cdata, p, q, g) - self.openssl_assert(res == 1) - - return _DSAParameters(self, dsa_cdata) - - def _dsa_cdata_to_evp_pkey(self, dsa_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_DSA(evp_pkey, dsa_cdata) - self.openssl_assert(res == 1) - return evp_pkey - - def dsa_hash_supported(self, algorithm): - return self.hash_supported(algorithm) + def dsa_supported(self) -> bool: + return ( + not self._lib.CRYPTOGRAPHY_IS_BORINGSSL and not self._fips_enabled + ) - def dsa_parameters_supported(self, p, q, g): - return True + def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if not self.dsa_supported(): + return False + return self.signature_hash_supported(algorithm) - def cmac_algorithm_supported(self, algorithm): + def cmac_algorithm_supported(self, algorithm) -> bool: return self.cipher_supported( algorithm, CBC(b"\x00" * algorithm.block_size) ) - def create_cmac_ctx(self, algorithm): + def create_cmac_ctx(self, algorithm: BlockCipherAlgorithm) -> _CMACContext: return _CMACContext(self, algorithm) - def _x509_check_signature_params(self, private_key, algorithm): - if isinstance( - private_key, (ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey) - ): - if algorithm is not None: - raise ValueError( - "algorithm must be None when signing via ed25519 or ed448" - ) - elif not isinstance( - private_key, - (rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey), - ): - raise TypeError( - "Key must be an rsa, dsa, ec, ed25519, or ed448 private key." - ) - elif not isinstance(algorithm, hashes.HashAlgorithm): - raise TypeError("Algorithm must be a registered hash algorithm.") - elif isinstance(algorithm, hashes.MD5) and not isinstance( - private_key, rsa.RSAPrivateKey - ): - raise ValueError( - "MD5 hash algorithm is only supported with RSA keys" - ) - - def create_x509_csr(self, builder, private_key, algorithm): - if not isinstance(builder, x509.CertificateSigningRequestBuilder): - raise TypeError("Builder type mismatch.") - self._x509_check_signature_params(private_key, algorithm) - - # Resolve the signature algorithm. - evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) - - # Create an empty request. - x509_req = self._lib.X509_REQ_new() - self.openssl_assert(x509_req != self._ffi.NULL) - x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) - - # Set x509 version. - res = self._lib.X509_REQ_set_version(x509_req, x509.Version.v1.value) - self.openssl_assert(res == 1) - - # Set subject name. - res = self._lib.X509_REQ_set_subject_name( - x509_req, _encode_name_gc(self, builder._subject_name) - ) - self.openssl_assert(res == 1) - - # Set subject public key. - public_key = private_key.public_key() - res = self._lib.X509_REQ_set_pubkey(x509_req, public_key._evp_pkey) - self.openssl_assert(res == 1) - - # Add extensions. - sk_extension = self._lib.sk_X509_EXTENSION_new_null() - self.openssl_assert(sk_extension != self._ffi.NULL) - sk_extension = self._ffi.gc( - sk_extension, - lambda x: self._lib.sk_X509_EXTENSION_pop_free( - x, - self._ffi.addressof( - self._lib._original_lib, "X509_EXTENSION_free" - ), - ), - ) - # Don't GC individual extensions because the memory is owned by - # sk_extensions and will be freed along with it. - self._create_x509_extensions( - extensions=builder._extensions, - handlers=self._extension_encode_handlers, - x509_obj=sk_extension, - add_func=self._lib.sk_X509_EXTENSION_insert, - gc=False, - ) - res = self._lib.X509_REQ_add_extensions(x509_req, sk_extension) - self.openssl_assert(res == 1) - - # Add attributes (all bytes encoded as ASN1 UTF8_STRING) - for attr_oid, attr_val in builder._attributes: - obj = _txt2obj_gc(self, attr_oid.dotted_string) - res = self._lib.X509_REQ_add1_attr_by_OBJ( - x509_req, - obj, - x509.name._ASN1Type.UTF8String.value, - attr_val, - len(attr_val), - ) - self.openssl_assert(res == 1) - - # Sign the request using the requester's private key. - res = self._lib.X509_REQ_sign(x509_req, private_key._evp_pkey, evp_md) - if res == 0: - errors = self._consume_errors_with_text() - raise ValueError("Signing failed", errors) - - return _CertificateSigningRequest(self, x509_req) - - def create_x509_certificate(self, builder, private_key, algorithm): - if not isinstance(builder, x509.CertificateBuilder): - raise TypeError("Builder type mismatch.") - self._x509_check_signature_params(private_key, algorithm) - - # Resolve the signature algorithm. - evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) - - # Create an empty certificate. - x509_cert = self._lib.X509_new() - x509_cert = self._ffi.gc(x509_cert, self._lib.X509_free) - - # Set the x509 version. - res = self._lib.X509_set_version(x509_cert, builder._version.value) - self.openssl_assert(res == 1) - - # Set the subject's name. - res = self._lib.X509_set_subject_name( - x509_cert, _encode_name_gc(self, builder._subject_name) - ) - self.openssl_assert(res == 1) - - # Set the subject's public key. - res = self._lib.X509_set_pubkey( - x509_cert, builder._public_key._evp_pkey - ) - self.openssl_assert(res == 1) - - # Set the certificate serial number. - serial_number = _encode_asn1_int_gc(self, builder._serial_number) - res = self._lib.X509_set_serialNumber(x509_cert, serial_number) - self.openssl_assert(res == 1) - - # Set the "not before" time. - self._set_asn1_time( - self._lib.X509_getm_notBefore(x509_cert), builder._not_valid_before - ) - - # Set the "not after" time. - self._set_asn1_time( - self._lib.X509_getm_notAfter(x509_cert), builder._not_valid_after - ) - - # Add extensions. - self._create_x509_extensions( - extensions=builder._extensions, - handlers=self._extension_encode_handlers, - x509_obj=x509_cert, - add_func=self._lib.X509_add_ext, - gc=True, - ) - - # Set the issuer name. - res = self._lib.X509_set_issuer_name( - x509_cert, _encode_name_gc(self, builder._issuer_name) - ) - self.openssl_assert(res == 1) - - # Sign the certificate with the issuer's private key. - res = self._lib.X509_sign(x509_cert, private_key._evp_pkey, evp_md) - if res == 0: - errors = self._consume_errors_with_text() - raise ValueError("Signing failed", errors) - - return _Certificate(self, x509_cert) - - def _evp_md_x509_null_if_eddsa(self, private_key, algorithm): - if isinstance( - private_key, (ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey) - ): - # OpenSSL requires us to pass NULL for EVP_MD for ed25519/ed448 - return self._ffi.NULL - else: - return self._evp_md_non_null_from_algorithm(algorithm) - - def _set_asn1_time(self, asn1_time, time): - if time.year >= 2050: - asn1_str = time.strftime("%Y%m%d%H%M%SZ").encode("ascii") - else: - asn1_str = time.strftime("%y%m%d%H%M%SZ").encode("ascii") - res = self._lib.ASN1_TIME_set_string(asn1_time, asn1_str) - self.openssl_assert(res == 1) - - def _create_asn1_time(self, time): - asn1_time = self._lib.ASN1_TIME_new() - self.openssl_assert(asn1_time != self._ffi.NULL) - asn1_time = self._ffi.gc(asn1_time, self._lib.ASN1_TIME_free) - self._set_asn1_time(asn1_time, time) - return asn1_time - - def create_x509_crl(self, builder, private_key, algorithm): - if not isinstance(builder, x509.CertificateRevocationListBuilder): - raise TypeError("Builder type mismatch.") - self._x509_check_signature_params(private_key, algorithm) - - evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) - - # Create an empty CRL. - x509_crl = self._lib.X509_CRL_new() - x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) - - # Set the x509 CRL version. We only support v2 (integer value 1). - res = self._lib.X509_CRL_set_version(x509_crl, 1) - self.openssl_assert(res == 1) - - # Set the issuer name. - res = self._lib.X509_CRL_set_issuer_name( - x509_crl, _encode_name_gc(self, builder._issuer_name) - ) - self.openssl_assert(res == 1) - - # Set the last update time. - last_update = self._create_asn1_time(builder._last_update) - res = self._lib.X509_CRL_set_lastUpdate(x509_crl, last_update) - self.openssl_assert(res == 1) - - # Set the next update time. - next_update = self._create_asn1_time(builder._next_update) - res = self._lib.X509_CRL_set_nextUpdate(x509_crl, next_update) - self.openssl_assert(res == 1) - - # Add extensions. - self._create_x509_extensions( - extensions=builder._extensions, - handlers=self._crl_extension_encode_handlers, - x509_obj=x509_crl, - add_func=self._lib.X509_CRL_add_ext, - gc=True, - ) - - # add revoked certificates - for revoked_cert in builder._revoked_certificates: - # Duplicating because the X509_CRL takes ownership and will free - # this memory when X509_CRL_free is called. - revoked = self._lib.X509_REVOKED_dup(revoked_cert._x509_revoked) - self.openssl_assert(revoked != self._ffi.NULL) - res = self._lib.X509_CRL_add0_revoked(x509_crl, revoked) - self.openssl_assert(res == 1) - - res = self._lib.X509_CRL_sign(x509_crl, private_key._evp_pkey, evp_md) - if res == 0: - errors = self._consume_errors_with_text() - raise ValueError("Signing failed", errors) - - return _CertificateRevocationList(self, x509_crl) - - def _create_x509_extensions( - self, extensions, handlers, x509_obj, add_func, gc - ): - for i, extension in enumerate(extensions): - x509_extension = self._create_x509_extension(handlers, extension) - self.openssl_assert(x509_extension != self._ffi.NULL) - - if gc: - x509_extension = self._ffi.gc( - x509_extension, self._lib.X509_EXTENSION_free - ) - res = add_func(x509_obj, x509_extension, i) - self.openssl_assert(res >= 1) - - def _create_raw_x509_extension(self, extension, value): - obj = _txt2obj_gc(self, extension.oid.dotted_string) - return self._lib.X509_EXTENSION_create_by_OBJ( - self._ffi.NULL, obj, 1 if extension.critical else 0, value - ) - - def _create_x509_extension(self, handlers, extension): - if isinstance(extension.value, x509.UnrecognizedExtension): - value = _encode_asn1_str_gc(self, extension.value.value) - return self._create_raw_x509_extension(extension, value) - elif isinstance(extension.value, x509.TLSFeature): - asn1 = encode_der( - SEQUENCE, - *[ - encode_der(INTEGER, encode_der_integer(x.value)) - for x in extension.value - ] - ) - value = _encode_asn1_str_gc(self, asn1) - return self._create_raw_x509_extension(extension, value) - elif isinstance(extension.value, x509.PrecertPoison): - value = _encode_asn1_str_gc(self, encode_der(NULL)) - return self._create_raw_x509_extension(extension, value) - else: - try: - encode = handlers[extension.oid] - except KeyError: - raise NotImplementedError( - "Extension not supported: {}".format(extension.oid) - ) - - ext_struct = encode(self, extension.value) - nid = self._lib.OBJ_txt2nid( - extension.oid.dotted_string.encode("ascii") - ) - self.openssl_assert(nid != self._lib.NID_undef) - return self._lib.X509V3_EXT_i2d( - nid, 1 if extension.critical else 0, ext_struct - ) - - def create_x509_revoked_certificate(self, builder): - if not isinstance(builder, x509.RevokedCertificateBuilder): - raise TypeError("Builder type mismatch.") - - x509_revoked = self._lib.X509_REVOKED_new() - self.openssl_assert(x509_revoked != self._ffi.NULL) - x509_revoked = self._ffi.gc(x509_revoked, self._lib.X509_REVOKED_free) - serial_number = _encode_asn1_int_gc(self, builder._serial_number) - res = self._lib.X509_REVOKED_set_serialNumber( - x509_revoked, serial_number - ) - self.openssl_assert(res == 1) - rev_date = self._create_asn1_time(builder._revocation_date) - res = self._lib.X509_REVOKED_set_revocationDate(x509_revoked, rev_date) - self.openssl_assert(res == 1) - # add CRL entry extensions - self._create_x509_extensions( - extensions=builder._extensions, - handlers=self._crl_entry_extension_encode_handlers, - x509_obj=x509_revoked, - add_func=self._lib.X509_REVOKED_add_ext, - gc=True, - ) - return _RevokedCertificate(self, None, x509_revoked) - - def load_pem_private_key(self, data, password): + def load_pem_private_key( + self, + data: bytes, + password: typing.Optional[bytes], + unsafe_skip_rsa_key_validation: bool, + ) -> PrivateKeyTypes: return self._load_key( self._lib.PEM_read_bio_PrivateKey, - self._evp_pkey_to_private_key, data, password, + unsafe_skip_rsa_key_validation, ) - def load_pem_public_key(self, data): + def load_pem_public_key(self, data: bytes) -> PublicKeyTypes: mem_bio = self._bytes_to_bio(data) + # In OpenSSL 3.0.x the PEM_read_bio_PUBKEY function will invoke + # the default password callback if you pass an encrypted private + # key. This is very, very, very bad as the default callback can + # trigger an interactive console prompt, which will hang the + # Python process. We therefore provide our own callback to + # catch this and error out properly. + userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") evp_pkey = self._lib.PEM_read_bio_PUBKEY( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + mem_bio.bio, + self._ffi.NULL, + self._ffi.addressof( + self._lib._original_lib, "Cryptography_pem_password_cb" + ), + userdata, ) if evp_pkey != self._ffi.NULL: evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) @@ -1263,7 +779,12 @@ def load_pem_public_key(self, data): res = self._lib.BIO_reset(mem_bio.bio) self.openssl_assert(res == 1) rsa_cdata = self._lib.PEM_read_bio_RSAPublicKey( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + mem_bio.bio, + self._ffi.NULL, + self._ffi.addressof( + self._lib._original_lib, "Cryptography_pem_password_cb" + ), + userdata, ) if rsa_cdata != self._ffi.NULL: rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) @@ -1272,19 +793,15 @@ def load_pem_public_key(self, data): else: self._handle_key_loading_error() - def load_pem_parameters(self, data): - mem_bio = self._bytes_to_bio(data) - # only DH is supported currently - dh_cdata = self._lib.PEM_read_bio_DHparams( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL - ) - if dh_cdata != self._ffi.NULL: - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHParameters(self, dh_cdata) - else: - self._handle_key_loading_error() + def load_pem_parameters(self, data: bytes) -> dh.DHParameters: + return rust_openssl.dh.from_pem_parameters(data) - def load_der_private_key(self, data, password): + def load_der_private_key( + self, + data: bytes, + password: typing.Optional[bytes], + unsafe_skip_rsa_key_validation: bool, + ) -> PrivateKeyTypes: # OpenSSL has a function called d2i_AutoPrivateKey that in theory # handles this automatically, however it doesn't handle encrypted # private keys. Instead we try to load the key two different ways. @@ -1292,15 +809,17 @@ def load_der_private_key(self, data, password): bio_data = self._bytes_to_bio(data) key = self._evp_pkey_from_der_traditional_key(bio_data, password) if key: - return self._evp_pkey_to_private_key(key) + return self._evp_pkey_to_private_key( + key, unsafe_skip_rsa_key_validation + ) else: # Finally we try to load it with the method that handles encrypted # PKCS8 properly. return self._load_key( self._lib.d2i_PKCS8PrivateKey_bio, - self._evp_pkey_to_private_key, data, password, + unsafe_skip_rsa_key_validation, ) def _evp_pkey_from_der_traditional_key(self, bio_data, password): @@ -1317,7 +836,7 @@ def _evp_pkey_from_der_traditional_key(self, bio_data, password): self._consume_errors() return None - def load_der_public_key(self, data): + def load_der_public_key(self, data: bytes) -> PublicKeyTypes: mem_bio = self._bytes_to_bio(data) evp_pkey = self._lib.d2i_PUBKEY_bio(mem_bio.bio, self._ffi.NULL) if evp_pkey != self._ffi.NULL: @@ -1340,102 +859,41 @@ def load_der_public_key(self, data): else: self._handle_key_loading_error() - def load_der_parameters(self, data): - mem_bio = self._bytes_to_bio(data) - dh_cdata = self._lib.d2i_DHparams_bio(mem_bio.bio, self._ffi.NULL) - if dh_cdata != self._ffi.NULL: - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHParameters(self, dh_cdata) - elif self._lib.Cryptography_HAS_EVP_PKEY_DHX: - # We check to see if the is dhx. - self._consume_errors() - res = self._lib.BIO_reset(mem_bio.bio) - self.openssl_assert(res == 1) - dh_cdata = self._lib.Cryptography_d2i_DHxparams_bio( - mem_bio.bio, self._ffi.NULL - ) - if dh_cdata != self._ffi.NULL: - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHParameters(self, dh_cdata) - - self._handle_key_loading_error() - - def load_pem_x509_certificate(self, data): - mem_bio = self._bytes_to_bio(data) - x509 = self._lib.PEM_read_bio_X509( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL - ) - if x509 == self._ffi.NULL: - self._consume_errors() - raise ValueError( - "Unable to load certificate. See https://cryptography.io/en/la" - "test/faq/#why-can-t-i-import-my-pem-file for more details." - ) - - x509 = self._ffi.gc(x509, self._lib.X509_free) - return _Certificate(self, x509) + def load_der_parameters(self, data: bytes) -> dh.DHParameters: + return rust_openssl.dh.from_der_parameters(data) - def load_der_x509_certificate(self, data): + def _cert2ossl(self, cert: x509.Certificate) -> typing.Any: + data = cert.public_bytes(serialization.Encoding.DER) mem_bio = self._bytes_to_bio(data) x509 = self._lib.d2i_X509_bio(mem_bio.bio, self._ffi.NULL) - if x509 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to load certificate") - + self.openssl_assert(x509 != self._ffi.NULL) x509 = self._ffi.gc(x509, self._lib.X509_free) - return _Certificate(self, x509) - - def load_pem_x509_crl(self, data): - mem_bio = self._bytes_to_bio(data) - x509_crl = self._lib.PEM_read_bio_X509_CRL( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL - ) - if x509_crl == self._ffi.NULL: - self._consume_errors() - raise ValueError( - "Unable to load CRL. See https://cryptography.io/en/la" - "test/faq/#why-can-t-i-import-my-pem-file for more details." - ) - - x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) - return _CertificateRevocationList(self, x509_crl) - - def load_der_x509_crl(self, data): - mem_bio = self._bytes_to_bio(data) - x509_crl = self._lib.d2i_X509_CRL_bio(mem_bio.bio, self._ffi.NULL) - if x509_crl == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to load CRL") + return x509 - x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) - return _CertificateRevocationList(self, x509_crl) + def _ossl2cert(self, x509_ptr: typing.Any) -> x509.Certificate: + bio = self._create_mem_bio_gc() + res = self._lib.i2d_X509_bio(bio, x509_ptr) + self.openssl_assert(res == 1) + return x509.load_der_x509_certificate(self._read_mem_bio(bio)) - def load_pem_x509_csr(self, data): - mem_bio = self._bytes_to_bio(data) - x509_req = self._lib.PEM_read_bio_X509_REQ( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + def _key2ossl(self, key: PKCS12PrivateKeyTypes) -> typing.Any: + data = key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), ) - if x509_req == self._ffi.NULL: - self._consume_errors() - raise ValueError( - "Unable to load request. See https://cryptography.io/en/la" - "test/faq/#why-can-t-i-import-my-pem-file for more details." - ) - - x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) - return _CertificateSigningRequest(self, x509_req) - - def load_der_x509_csr(self, data): mem_bio = self._bytes_to_bio(data) - x509_req = self._lib.d2i_X509_REQ_bio(mem_bio.bio, self._ffi.NULL) - if x509_req == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to load request") - x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) - return _CertificateSigningRequest(self, x509_req) + evp_pkey = self._lib.d2i_PrivateKey_bio( + mem_bio.bio, + self._ffi.NULL, + ) + self.openssl_assert(evp_pkey != self._ffi.NULL) + return self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - def _load_key(self, openssl_read_func, convert_func, data, password): + def _load_key( + self, openssl_read_func, data, password, unsafe_skip_rsa_key_validation + ) -> PrivateKeyTypes: mem_bio = self._bytes_to_bio(data) userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") @@ -1456,8 +914,7 @@ def _load_key(self, openssl_read_func, convert_func, data, password): if evp_pkey == self._ffi.NULL: if userdata.error != 0: - errors = self._consume_errors() - self.openssl_assert(errors) + self._consume_errors() if userdata.error == -1: raise TypeError( "Password was not given but private key is encrypted" @@ -1482,32 +939,38 @@ def _load_key(self, openssl_read_func, convert_func, data, password): password is not None and userdata.called == 1 ) or password is None - return convert_func(evp_pkey) + return self._evp_pkey_to_private_key( + evp_pkey, unsafe_skip_rsa_key_validation + ) - def _handle_key_loading_error(self): + def _handle_key_loading_error(self) -> typing.NoReturn: errors = self._consume_errors() if not errors: - raise ValueError("Could not deserialize key data.") + raise ValueError( + "Could not deserialize key data. The data may be in an " + "incorrect format or it may be encrypted with an unsupported " + "algorithm." + ) - elif errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT - ) or errors[0]._lib_reason_match( - self._lib.ERR_LIB_PKCS12, - self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, + elif ( + errors[0]._lib_reason_match( + self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT + ) + or errors[0]._lib_reason_match( + self._lib.ERR_LIB_PKCS12, + self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, + ) + or ( + self._lib.Cryptography_HAS_PROVIDERS + and errors[0]._lib_reason_match( + self._lib.ERR_LIB_PROV, + self._lib.PROV_R_BAD_DECRYPT, + ) + ) ): raise ValueError("Bad decrypt. Incorrect password?") - elif errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, self._lib.EVP_R_UNKNOWN_PBE_ALGORITHM - ) or errors[0]._lib_reason_match( - self._lib.ERR_LIB_PEM, self._lib.PEM_R_UNSUPPORTED_ENCRYPTION - ): - raise UnsupportedAlgorithm( - "PEM data is encrypted with an unsupported cipher", - _Reasons.UNSUPPORTED_CIPHER, - ) - elif any( error._lib_reason_match( self._lib.ERR_LIB_EVP, @@ -1518,14 +981,15 @@ def _handle_key_loading_error(self): raise ValueError("Unsupported public key algorithm.") else: - assert errors[0].lib in ( - self._lib.ERR_LIB_EVP, - self._lib.ERR_LIB_PEM, - self._lib.ERR_LIB_ASN1, + raise ValueError( + "Could not deserialize key data. The data may be in an " + "incorrect format, it may be encrypted with an unsupported " + "algorithm, or it may be an unsupported key type (e.g. EC " + "curves with explicit parameters).", + errors, ) - raise ValueError("Could not deserialize key data.") - def elliptic_curve_supported(self, curve): + def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: try: curve_nid = self._elliptic_curve_to_nid(curve) except UnsupportedAlgorithm: @@ -1542,15 +1006,19 @@ def elliptic_curve_supported(self, curve): return True def elliptic_curve_signature_algorithm_supported( - self, signature_algorithm, curve - ): + self, + signature_algorithm: ec.EllipticCurveSignatureAlgorithm, + curve: ec.EllipticCurve, + ) -> bool: # We only support ECDSA right now. if not isinstance(signature_algorithm, ec.ECDSA): return False return self.elliptic_curve_supported(curve) - def generate_elliptic_curve_private_key(self, curve): + def generate_elliptic_curve_private_key( + self, curve: ec.EllipticCurve + ) -> ec.EllipticCurvePrivateKey: """ Generate a new private key on the named curve. """ @@ -1566,11 +1034,13 @@ def generate_elliptic_curve_private_key(self, curve): return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) else: raise UnsupportedAlgorithm( - "Backend object does not support {}.".format(curve.name), + f"Backend object does not support {curve.name}.", _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) - def load_elliptic_curve_private_numbers(self, numbers): + def load_elliptic_curve_private_numbers( + self, numbers: ec.EllipticCurvePrivateNumbers + ) -> ec.EllipticCurvePrivateKey: public = numbers.public_numbers ec_cdata = self._ec_key_new_by_curve(public.curve) @@ -1579,26 +1049,62 @@ def load_elliptic_curve_private_numbers(self, numbers): self._int_to_bn(numbers.private_value), self._lib.BN_clear_free ) res = self._lib.EC_KEY_set_private_key(ec_cdata, private_value) - self.openssl_assert(res == 1) + if res != 1: + self._consume_errors() + raise ValueError("Invalid EC key.") - ec_cdata = self._ec_key_set_public_key_affine_coordinates( - ec_cdata, public.x, public.y - ) + with self._tmp_bn_ctx() as bn_ctx: + self._ec_key_set_public_key_affine_coordinates( + ec_cdata, public.x, public.y, bn_ctx + ) + # derive the expected public point and compare it to the one we + # just set based on the values we were given. If they don't match + # this isn't a valid key pair. + group = self._lib.EC_KEY_get0_group(ec_cdata) + self.openssl_assert(group != self._ffi.NULL) + set_point = backend._lib.EC_KEY_get0_public_key(ec_cdata) + self.openssl_assert(set_point != self._ffi.NULL) + computed_point = self._lib.EC_POINT_new(group) + self.openssl_assert(computed_point != self._ffi.NULL) + computed_point = self._ffi.gc( + computed_point, self._lib.EC_POINT_free + ) + res = self._lib.EC_POINT_mul( + group, + computed_point, + private_value, + self._ffi.NULL, + self._ffi.NULL, + bn_ctx, + ) + self.openssl_assert(res == 1) + if ( + self._lib.EC_POINT_cmp( + group, set_point, computed_point, bn_ctx + ) + != 0 + ): + raise ValueError("Invalid EC key.") evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - def load_elliptic_curve_public_numbers(self, numbers): + def load_elliptic_curve_public_numbers( + self, numbers: ec.EllipticCurvePublicNumbers + ) -> ec.EllipticCurvePublicKey: ec_cdata = self._ec_key_new_by_curve(numbers.curve) - ec_cdata = self._ec_key_set_public_key_affine_coordinates( - ec_cdata, numbers.x, numbers.y - ) + with self._tmp_bn_ctx() as bn_ctx: + self._ec_key_set_public_key_affine_coordinates( + ec_cdata, numbers.x, numbers.y, bn_ctx + ) evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - def load_elliptic_curve_public_bytes(self, curve, point_bytes): + def load_elliptic_curve_public_bytes( + self, curve: ec.EllipticCurve, point_bytes: bytes + ) -> ec.EllipticCurvePublicKey: ec_cdata = self._ec_key_new_by_curve(curve) group = self._lib.EC_KEY_get0_group(ec_cdata) self.openssl_assert(group != self._ffi.NULL) @@ -1618,10 +1124,13 @@ def load_elliptic_curve_public_bytes(self, curve, point_bytes): evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - def derive_elliptic_curve_private_key(self, private_value, curve): + def derive_elliptic_curve_private_key( + self, private_value: int, curve: ec.EllipticCurve + ) -> ec.EllipticCurvePrivateKey: ec_cdata = self._ec_key_new_by_curve(curve) - get_func, group = self._ec_key_determine_group_get_func(ec_cdata) + group = self._lib.EC_KEY_get0_group(ec_cdata) + self.openssl_assert(group != self._ffi.NULL) point = self._lib.EC_POINT_new(group) self.openssl_assert(point != self._ffi.NULL) @@ -1639,8 +1148,12 @@ def derive_elliptic_curve_private_key(self, private_value, curve): bn_x = self._lib.BN_CTX_get(bn_ctx) bn_y = self._lib.BN_CTX_get(bn_ctx) - res = get_func(group, point, bn_x, bn_y, bn_ctx) - self.openssl_assert(res == 1) + res = self._lib.EC_POINT_get_affine_coordinates( + group, point, bn_x, bn_y, bn_ctx + ) + if res != 1: + self._consume_errors() + raise ValueError("Unable to derive key from private_value") res = self._lib.EC_KEY_set_public_key(ec_cdata, point) self.openssl_assert(res == 1) @@ -1653,165 +1166,23 @@ def derive_elliptic_curve_private_key(self, private_value, curve): return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - def _ec_key_new_by_curve(self, curve): + def _ec_key_new_by_curve(self, curve: ec.EllipticCurve): curve_nid = self._elliptic_curve_to_nid(curve) return self._ec_key_new_by_curve_nid(curve_nid) - def _ec_key_new_by_curve_nid(self, curve_nid): + def _ec_key_new_by_curve_nid(self, curve_nid: int): ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) self.openssl_assert(ec_cdata != self._ffi.NULL) - # Setting the ASN.1 flag to OPENSSL_EC_NAMED_CURVE is - # only necessary on OpenSSL 1.0.2t/u. Once we drop support for 1.0.2 - # we can remove this as it's done automatically when getting an EC_KEY - # from new_by_curve_name - # CRYPTOGRAPHY_OPENSSL_102U_OR_GREATER - self._lib.EC_KEY_set_asn1_flag( - ec_cdata, backend._lib.OPENSSL_EC_NAMED_CURVE - ) return self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) - def load_der_ocsp_request(self, data): - mem_bio = self._bytes_to_bio(data) - request = self._lib.d2i_OCSP_REQUEST_bio(mem_bio.bio, self._ffi.NULL) - if request == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to load OCSP request") - - request = self._ffi.gc(request, self._lib.OCSP_REQUEST_free) - return _OCSPRequest(self, request) - - def load_der_ocsp_response(self, data): - mem_bio = self._bytes_to_bio(data) - response = self._lib.d2i_OCSP_RESPONSE_bio(mem_bio.bio, self._ffi.NULL) - if response == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to load OCSP response") - - response = self._ffi.gc(response, self._lib.OCSP_RESPONSE_free) - return _OCSPResponse(self, response) - - def create_ocsp_request(self, builder): - ocsp_req = self._lib.OCSP_REQUEST_new() - self.openssl_assert(ocsp_req != self._ffi.NULL) - ocsp_req = self._ffi.gc(ocsp_req, self._lib.OCSP_REQUEST_free) - cert, issuer, algorithm = builder._request - evp_md = self._evp_md_non_null_from_algorithm(algorithm) - certid = self._lib.OCSP_cert_to_id(evp_md, cert._x509, issuer._x509) - self.openssl_assert(certid != self._ffi.NULL) - onereq = self._lib.OCSP_request_add0_id(ocsp_req, certid) - self.openssl_assert(onereq != self._ffi.NULL) - self._create_x509_extensions( - extensions=builder._extensions, - handlers=self._ocsp_request_extension_encode_handlers, - x509_obj=ocsp_req, - add_func=self._lib.OCSP_REQUEST_add_ext, - gc=True, - ) - return _OCSPRequest(self, ocsp_req) - - def _create_ocsp_basic_response(self, builder, private_key, algorithm): - self._x509_check_signature_params(private_key, algorithm) - - basic = self._lib.OCSP_BASICRESP_new() - self.openssl_assert(basic != self._ffi.NULL) - basic = self._ffi.gc(basic, self._lib.OCSP_BASICRESP_free) - evp_md = self._evp_md_non_null_from_algorithm( - builder._response._algorithm - ) - certid = self._lib.OCSP_cert_to_id( - evp_md, - builder._response._cert._x509, - builder._response._issuer._x509, - ) - self.openssl_assert(certid != self._ffi.NULL) - certid = self._ffi.gc(certid, self._lib.OCSP_CERTID_free) - if builder._response._revocation_reason is None: - reason = -1 - else: - reason = _CRL_ENTRY_REASON_ENUM_TO_CODE[ - builder._response._revocation_reason - ] - if builder._response._revocation_time is None: - rev_time = self._ffi.NULL - else: - rev_time = self._create_asn1_time( - builder._response._revocation_time - ) - - next_update = self._ffi.NULL - if builder._response._next_update is not None: - next_update = self._create_asn1_time( - builder._response._next_update - ) - - this_update = self._create_asn1_time(builder._response._this_update) - - res = self._lib.OCSP_basic_add1_status( - basic, - certid, - builder._response._cert_status.value, - reason, - rev_time, - this_update, - next_update, - ) - self.openssl_assert(res != self._ffi.NULL) - # okay, now sign the basic structure - evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) - responder_cert, responder_encoding = builder._responder_id - flags = self._lib.OCSP_NOCERTS - if responder_encoding is ocsp.OCSPResponderEncoding.HASH: - flags |= self._lib.OCSP_RESPID_KEY - - if builder._certs is not None: - for cert in builder._certs: - res = self._lib.OCSP_basic_add1_cert(basic, cert._x509) - self.openssl_assert(res == 1) - - self._create_x509_extensions( - extensions=builder._extensions, - handlers=self._ocsp_basicresp_extension_encode_handlers, - x509_obj=basic, - add_func=self._lib.OCSP_BASICRESP_add_ext, - gc=True, - ) - - res = self._lib.OCSP_basic_sign( - basic, - responder_cert._x509, - private_key._evp_pkey, - evp_md, - self._ffi.NULL, - flags, - ) - if res != 1: - errors = self._consume_errors_with_text() - raise ValueError( - "Error while signing. responder_cert must be signed " - "by private_key", - errors, - ) - - return basic - - def create_ocsp_response( - self, response_status, builder, private_key, algorithm - ): - if response_status is ocsp.OCSPResponseStatus.SUCCESSFUL: - basic = self._create_ocsp_basic_response( - builder, private_key, algorithm - ) - else: - basic = self._ffi.NULL - - ocsp_resp = self._lib.OCSP_response_create( - response_status.value, basic - ) - self.openssl_assert(ocsp_resp != self._ffi.NULL) - ocsp_resp = self._ffi.gc(ocsp_resp, self._lib.OCSP_RESPONSE_free) - return _OCSPResponse(self, ocsp_resp) + def elliptic_curve_exchange_algorithm_supported( + self, algorithm: ec.ECDH, curve: ec.EllipticCurve + ) -> bool: + if self._fips_enabled and not isinstance( + curve, self._fips_ecdh_curves + ): + return False - def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): return self.elliptic_curve_supported(curve) and isinstance( algorithm, ec.ECDH ) @@ -1822,7 +1193,7 @@ def _ec_cdata_to_evp_pkey(self, ec_cdata): self.openssl_assert(res == 1) return evp_pkey - def _elliptic_curve_to_nid(self, curve): + def _elliptic_curve_to_nid(self, curve: ec.EllipticCurve) -> int: """ Get the NID for a curve name. """ @@ -1834,7 +1205,7 @@ def _elliptic_curve_to_nid(self, curve): curve_nid = self._lib.OBJ_sn2nid(curve_name.encode()) if curve_nid == self._lib.NID_undef: raise UnsupportedAlgorithm( - "{} is not a supported elliptic curve".format(curve.name), + f"{curve.name} is not a supported elliptic curve", _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) return curve_nid @@ -1850,35 +1221,13 @@ def _tmp_bn_ctx(self): finally: self._lib.BN_CTX_end(bn_ctx) - def _ec_key_determine_group_get_func(self, ctx): - """ - Given an EC_KEY determine the group and what function is required to - get point coordinates. - """ - self.openssl_assert(ctx != self._ffi.NULL) - - nid_two_field = self._lib.OBJ_sn2nid(b"characteristic-two-field") - self.openssl_assert(nid_two_field != self._lib.NID_undef) - - group = self._lib.EC_KEY_get0_group(ctx) - self.openssl_assert(group != self._ffi.NULL) - - method = self._lib.EC_GROUP_method_of(group) - self.openssl_assert(method != self._ffi.NULL) - - nid = self._lib.EC_METHOD_get_field_type(method) - self.openssl_assert(nid != self._lib.NID_undef) - - if nid == nid_two_field and self._lib.Cryptography_HAS_EC2M: - get_func = self._lib.EC_POINT_get_affine_coordinates_GF2m - else: - get_func = self._lib.EC_POINT_get_affine_coordinates_GFp - - assert get_func - - return get_func, group - - def _ec_key_set_public_key_affine_coordinates(self, ctx, x, y): + def _ec_key_set_public_key_affine_coordinates( + self, + ec_cdata, + x: int, + y: int, + bn_ctx, + ) -> None: """ Sets the public key point in the EC_KEY context to the affine x and y values. @@ -1891,16 +1240,29 @@ def _ec_key_set_public_key_affine_coordinates(self, ctx, x, y): x = self._ffi.gc(self._int_to_bn(x), self._lib.BN_free) y = self._ffi.gc(self._int_to_bn(y), self._lib.BN_free) - res = self._lib.EC_KEY_set_public_key_affine_coordinates(ctx, x, y) + group = self._lib.EC_KEY_get0_group(ec_cdata) + self.openssl_assert(group != self._ffi.NULL) + point = self._lib.EC_POINT_new(group) + self.openssl_assert(point != self._ffi.NULL) + point = self._ffi.gc(point, self._lib.EC_POINT_free) + res = self._lib.EC_POINT_set_affine_coordinates( + group, point, x, y, bn_ctx + ) if res != 1: self._consume_errors() raise ValueError("Invalid EC key.") - - return ctx + res = self._lib.EC_KEY_set_public_key(ec_cdata, point) + self.openssl_assert(res == 1) def _private_key_bytes( - self, encoding, format, encryption_algorithm, key, evp_pkey, cdata - ): + 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") @@ -1928,6 +1290,15 @@ def _private_key_bytes( "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") @@ -1957,14 +1328,9 @@ def _private_key_bytes( if encoding is serialization.Encoding.PEM: if key_type == self._lib.EVP_PKEY_RSA: write_bio = self._lib.PEM_write_bio_RSAPrivateKey - elif key_type == self._lib.EVP_PKEY_DSA: - write_bio = self._lib.PEM_write_bio_DSAPrivateKey - elif key_type == self._lib.EVP_PKEY_EC: - write_bio = self._lib.PEM_write_bio_ECPrivateKey else: - raise ValueError( - "Unsupported key type for TraditionalOpenSSL" - ) + assert key_type == self._lib.EVP_PKEY_EC + write_bio = self._lib.PEM_write_bio_ECPrivateKey return self._private_key_bytes_via_bio( write_bio, cdata, password ) @@ -1977,14 +1343,9 @@ def _private_key_bytes( ) if key_type == self._lib.EVP_PKEY_RSA: write_bio = self._lib.i2d_RSAPrivateKey_bio - elif key_type == self._lib.EVP_PKEY_EC: - write_bio = self._lib.i2d_ECPrivateKey_bio - elif key_type == self._lib.EVP_PKEY_DSA: - write_bio = self._lib.i2d_DSAPrivateKey_bio else: - raise ValueError( - "Unsupported key type for TraditionalOpenSSL" - ) + assert key_type == self._lib.EVP_PKEY_EC + write_bio = self._lib.i2d_ECPrivateKey_bio return self._bio_func_output(write_bio, cdata) raise ValueError("Unsupported encoding for TraditionalOpenSSL") @@ -1992,7 +1353,9 @@ def _private_key_bytes( # OpenSSH + PEM if format is serialization.PrivateFormat.OpenSSH: if encoding is serialization.Encoding.PEM: - return ssh.serialize_ssh_private_key(key, password) + return ssh._serialize_ssh_private_key( + key, password, encryption_algorithm + ) raise ValueError( "OpenSSH private key format can only be used" @@ -2003,7 +1366,9 @@ def _private_key_bytes( # like Raw. raise ValueError("format is invalid with this key") - def _private_key_bytes_via_bio(self, write_bio, evp_pkey, password): + def _private_key_bytes_via_bio( + self, write_bio, evp_pkey, password + ) -> bytes: if not password: evp_cipher = self._ffi.NULL else: @@ -2020,13 +1385,20 @@ def _private_key_bytes_via_bio(self, write_bio, evp_pkey, password): self._ffi.NULL, ) - def _bio_func_output(self, write_bio, *args): + 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, format, key, evp_pkey, cdata): + 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): @@ -2074,420 +1446,128 @@ def _public_key_bytes(self, encoding, format, key, evp_pkey, cdata): # like Raw, CompressedPoint, UncompressedPoint raise ValueError("format is invalid with this key") - def _parameter_bytes(self, encoding, format, cdata): - if encoding is serialization.Encoding.OpenSSH: - raise TypeError("OpenSSH encoding is not supported") - - # Only DH is supported here currently. - q = self._ffi.new("BIGNUM **") - self._lib.DH_get0_pqg(cdata, self._ffi.NULL, q, self._ffi.NULL) - if encoding is serialization.Encoding.PEM: - if q[0] != self._ffi.NULL: - write_bio = self._lib.PEM_write_bio_DHxparams - else: - write_bio = self._lib.PEM_write_bio_DHparams - elif encoding is serialization.Encoding.DER: - if q[0] != self._ffi.NULL: - write_bio = self._lib.Cryptography_i2d_DHxparams_bio - else: - write_bio = self._lib.i2d_DHparams_bio - else: - raise TypeError("encoding must be an item from the Encoding enum") - - bio = self._create_mem_bio_gc() - res = write_bio(bio, cdata) - self.openssl_assert(res == 1) - return self._read_mem_bio(bio) - - def generate_dh_parameters(self, generator, key_size): - if key_size < 512: - raise ValueError("DH key_size must be at least 512 bits") - - if generator not in (2, 5): - raise ValueError("DH generator must be 2 or 5") - - dh_param_cdata = self._lib.DH_new() - self.openssl_assert(dh_param_cdata != self._ffi.NULL) - dh_param_cdata = self._ffi.gc(dh_param_cdata, self._lib.DH_free) - - res = self._lib.DH_generate_parameters_ex( - dh_param_cdata, key_size, generator, self._ffi.NULL - ) - self.openssl_assert(res == 1) - - return _DHParameters(self, dh_param_cdata) - - def _dh_cdata_to_evp_pkey(self, dh_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_DH(evp_pkey, dh_cdata) - self.openssl_assert(res == 1) - return evp_pkey - - def generate_dh_private_key(self, parameters): - dh_key_cdata = _dh_params_dup(parameters._dh_cdata, self) + def dh_supported(self) -> bool: + return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - res = self._lib.DH_generate_key(dh_key_cdata) - self.openssl_assert(res == 1) - - evp_pkey = self._dh_cdata_to_evp_pkey(dh_key_cdata) + def generate_dh_parameters( + self, generator: int, key_size: int + ) -> dh.DHParameters: + return rust_openssl.dh.generate_parameters(generator, key_size) - return _DHPrivateKey(self, dh_key_cdata, evp_pkey) + def generate_dh_private_key( + self, parameters: dh.DHParameters + ) -> dh.DHPrivateKey: + return parameters.generate_private_key() - def generate_dh_private_key_and_parameters(self, generator, key_size): + def generate_dh_private_key_and_parameters( + self, generator: int, key_size: int + ) -> dh.DHPrivateKey: return self.generate_dh_private_key( self.generate_dh_parameters(generator, key_size) ) - def load_dh_private_numbers(self, numbers): - parameter_numbers = numbers.public_numbers.parameter_numbers - - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - - p = self._int_to_bn(parameter_numbers.p) - g = self._int_to_bn(parameter_numbers.g) - - if parameter_numbers.q is not None: - q = self._int_to_bn(parameter_numbers.q) - else: - q = self._ffi.NULL - - pub_key = self._int_to_bn(numbers.public_numbers.y) - priv_key = self._int_to_bn(numbers.x) - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - res = self._lib.DH_set0_key(dh_cdata, pub_key, priv_key) - self.openssl_assert(res == 1) - - codes = self._ffi.new("int[]", 1) - res = self._lib.Cryptography_DH_check(dh_cdata, codes) - self.openssl_assert(res == 1) - - # DH_check will return DH_NOT_SUITABLE_GENERATOR if p % 24 does not - # equal 11 when the generator is 2 (a quadratic nonresidue). - # We want to ignore that error because p % 24 == 23 is also fine. - # Specifically, g is then a quadratic residue. Within the context of - # Diffie-Hellman this means it can only generate half the possible - # values. That sounds bad, but quadratic nonresidues leak a bit of - # the key to the attacker in exchange for having the full key space - # available. See: https://crypto.stackexchange.com/questions/12961 - if codes[0] != 0 and not ( - parameter_numbers.g == 2 - and codes[0] ^ self._lib.DH_NOT_SUITABLE_GENERATOR == 0 - ): - raise ValueError("DH private numbers did not pass safety checks.") - - evp_pkey = self._dh_cdata_to_evp_pkey(dh_cdata) - - return _DHPrivateKey(self, dh_cdata, evp_pkey) - - def load_dh_public_numbers(self, numbers): - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - - parameter_numbers = numbers.parameter_numbers - - p = self._int_to_bn(parameter_numbers.p) - g = self._int_to_bn(parameter_numbers.g) - - if parameter_numbers.q is not None: - q = self._int_to_bn(parameter_numbers.q) - else: - q = self._ffi.NULL - - pub_key = self._int_to_bn(numbers.y) - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - res = self._lib.DH_set0_key(dh_cdata, pub_key, self._ffi.NULL) - self.openssl_assert(res == 1) - - evp_pkey = self._dh_cdata_to_evp_pkey(dh_cdata) - - return _DHPublicKey(self, dh_cdata, evp_pkey) - - def load_dh_parameter_numbers(self, numbers): - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - - p = self._int_to_bn(numbers.p) - g = self._int_to_bn(numbers.g) - - if numbers.q is not None: - q = self._int_to_bn(numbers.q) - else: - q = self._ffi.NULL - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - return _DHParameters(self, dh_cdata) + def load_dh_private_numbers( + self, numbers: dh.DHPrivateNumbers + ) -> dh.DHPrivateKey: + return rust_openssl.dh.from_private_numbers(numbers) - def dh_parameters_supported(self, p, g, q=None): - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + def load_dh_public_numbers( + self, numbers: dh.DHPublicNumbers + ) -> dh.DHPublicKey: + return rust_openssl.dh.from_public_numbers(numbers) - p = self._int_to_bn(p) - g = self._int_to_bn(g) + def load_dh_parameter_numbers( + self, numbers: dh.DHParameterNumbers + ) -> dh.DHParameters: + return rust_openssl.dh.from_parameter_numbers(numbers) - if q is not None: - q = self._int_to_bn(q) + def dh_parameters_supported( + self, p: int, g: int, q: typing.Optional[int] = None + ) -> bool: + try: + rust_openssl.dh.from_parameter_numbers( + dh.DHParameterNumbers(p=p, g=g, q=q) + ) + except ValueError: + return False else: - q = self._ffi.NULL - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - codes = self._ffi.new("int[]", 1) - res = self._lib.Cryptography_DH_check(dh_cdata, codes) - self.openssl_assert(res == 1) - - return codes[0] == 0 + return True - def dh_x942_serialization_supported(self): + def dh_x942_serialization_supported(self) -> bool: return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 - def x509_name_bytes(self, name): - x509_name = _encode_name_gc(self, name) - pp = self._ffi.new("unsigned char **") - res = self._lib.i2d_X509_NAME(x509_name, pp) - self.openssl_assert(pp[0] != self._ffi.NULL) - pp = self._ffi.gc( - pp, lambda pointer: self._lib.OPENSSL_free(pointer[0]) - ) - self.openssl_assert(res > 0) - return self._ffi.buffer(pp[0], res)[:] - - def x25519_load_public_bytes(self, data): - # When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 we can - # switch this to EVP_PKEY_new_raw_public_key - if len(data) != 32: - raise ValueError("An X25519 public key is 32 bytes long") - - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set_type(evp_pkey, self._lib.NID_X25519) - self.openssl_assert(res == 1) - res = self._lib.EVP_PKEY_set1_tls_encodedpoint( - evp_pkey, data, len(data) - ) - self.openssl_assert(res == 1) - return _X25519PublicKey(self, evp_pkey) - - def x25519_load_private_bytes(self, data): - # When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 we can - # switch this to EVP_PKEY_new_raw_private_key and drop the - # zeroed_bytearray garbage. - # OpenSSL only has facilities for loading PKCS8 formatted private - # keys using the algorithm identifiers specified in - # https://tools.ietf.org/html/draft-ietf-curdle-pkix-09. - # This is the standard PKCS8 prefix for a 32 byte X25519 key. - # The form is: - # 0:d=0 hl=2 l= 46 cons: SEQUENCE - # 2:d=1 hl=2 l= 1 prim: INTEGER :00 - # 5:d=1 hl=2 l= 5 cons: SEQUENCE - # 7:d=2 hl=2 l= 3 prim: OBJECT :1.3.101.110 - # 12:d=1 hl=2 l= 34 prim: OCTET STRING (the key) - # Of course there's a bit more complexity. In reality OCTET STRING - # contains an OCTET STRING of length 32! So the last two bytes here - # are \x04\x20, which is an OCTET STRING of length 32. - if len(data) != 32: - raise ValueError("An X25519 private key is 32 bytes long") - - pkcs8_prefix = b'0.\x02\x01\x000\x05\x06\x03+en\x04"\x04 ' - with self._zeroed_bytearray(48) as ba: - ba[0:16] = pkcs8_prefix - ba[16:] = data - bio = self._bytes_to_bio(ba) - evp_pkey = self._lib.d2i_PrivateKey_bio(bio.bio, self._ffi.NULL) - - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - self.openssl_assert( - self._lib.EVP_PKEY_id(evp_pkey) == self._lib.EVP_PKEY_X25519 - ) - return _X25519PrivateKey(self, evp_pkey) + def x25519_load_public_bytes(self, data: bytes) -> x25519.X25519PublicKey: + return rust_openssl.x25519.from_public_bytes(data) - def _evp_pkey_keygen_gc(self, nid): - evp_pkey_ctx = self._lib.EVP_PKEY_CTX_new_id(nid, self._ffi.NULL) - self.openssl_assert(evp_pkey_ctx != self._ffi.NULL) - evp_pkey_ctx = self._ffi.gc(evp_pkey_ctx, self._lib.EVP_PKEY_CTX_free) - res = self._lib.EVP_PKEY_keygen_init(evp_pkey_ctx) - self.openssl_assert(res == 1) - evp_ppkey = self._ffi.new("EVP_PKEY **") - res = self._lib.EVP_PKEY_keygen(evp_pkey_ctx, evp_ppkey) - self.openssl_assert(res == 1) - self.openssl_assert(evp_ppkey[0] != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_ppkey[0], self._lib.EVP_PKEY_free) - return evp_pkey + def x25519_load_private_bytes( + self, data: bytes + ) -> x25519.X25519PrivateKey: + return rust_openssl.x25519.from_private_bytes(data) - def x25519_generate_key(self): - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_X25519) - return _X25519PrivateKey(self, evp_pkey) + def x25519_generate_key(self) -> x25519.X25519PrivateKey: + return rust_openssl.x25519.generate_key() - def x25519_supported(self): + def x25519_supported(self) -> bool: if self._fips_enabled: return False - return self._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - - def x448_load_public_bytes(self, data): - if len(data) != 56: - raise ValueError("An X448 public key is 56 bytes long") - - evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( - self._lib.NID_X448, self._ffi.NULL, data, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return _X448PublicKey(self, evp_pkey) + return not self._lib.CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 - def x448_load_private_bytes(self, data): - if len(data) != 56: - raise ValueError("An X448 private key is 56 bytes long") + def x448_load_public_bytes(self, data: bytes) -> x448.X448PublicKey: + return rust_openssl.x448.from_public_bytes(data) - data_ptr = self._ffi.from_buffer(data) - evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( - self._lib.NID_X448, self._ffi.NULL, data_ptr, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return _X448PrivateKey(self, evp_pkey) + def x448_load_private_bytes(self, data: bytes) -> x448.X448PrivateKey: + return rust_openssl.x448.from_private_bytes(data) - def x448_generate_key(self): - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_X448) - return _X448PrivateKey(self, evp_pkey) + def x448_generate_key(self) -> x448.X448PrivateKey: + return rust_openssl.x448.generate_key() - def x448_supported(self): + def x448_supported(self) -> bool: if self._fips_enabled: return False - return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 + return ( + not self._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + ) - def ed25519_supported(self): + def ed25519_supported(self) -> bool: if self._fips_enabled: return False - return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B - - def ed25519_load_public_bytes(self, data): - utils._check_bytes("data", data) - - if len(data) != ed25519._ED25519_KEY_SIZE: - raise ValueError("An Ed25519 public key is 32 bytes long") - - evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( - self._lib.NID_ED25519, self._ffi.NULL, data, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - return _Ed25519PublicKey(self, evp_pkey) + return self._lib.CRYPTOGRAPHY_HAS_WORKING_ED25519 - def ed25519_load_private_bytes(self, data): - if len(data) != ed25519._ED25519_KEY_SIZE: - raise ValueError("An Ed25519 private key is 32 bytes long") + def ed25519_load_public_bytes( + self, data: bytes + ) -> ed25519.Ed25519PublicKey: + return rust_openssl.ed25519.from_public_bytes(data) - utils._check_byteslike("data", data) - data_ptr = self._ffi.from_buffer(data) - evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( - self._lib.NID_ED25519, self._ffi.NULL, data_ptr, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + def ed25519_load_private_bytes( + self, data: bytes + ) -> ed25519.Ed25519PrivateKey: + return rust_openssl.ed25519.from_private_bytes(data) - return _Ed25519PrivateKey(self, evp_pkey) + def ed25519_generate_key(self) -> ed25519.Ed25519PrivateKey: + return rust_openssl.ed25519.generate_key() - def ed25519_generate_key(self): - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED25519) - return _Ed25519PrivateKey(self, evp_pkey) - - def ed448_supported(self): + def ed448_supported(self) -> bool: if self._fips_enabled: return False - return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B - - def ed448_load_public_bytes(self, data): - utils._check_bytes("data", data) - if len(data) != _ED448_KEY_SIZE: - raise ValueError("An Ed448 public key is 57 bytes long") - - evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( - self._lib.NID_ED448, self._ffi.NULL, data, len(data) + return ( + not self._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - return _Ed448PublicKey(self, evp_pkey) - def ed448_load_private_bytes(self, data): - utils._check_byteslike("data", data) - if len(data) != _ED448_KEY_SIZE: - raise ValueError("An Ed448 private key is 57 bytes long") - - data_ptr = self._ffi.from_buffer(data) - evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( - self._lib.NID_ED448, self._ffi.NULL, data_ptr, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + def ed448_load_public_bytes(self, data: bytes) -> ed448.Ed448PublicKey: + return rust_openssl.ed448.from_public_bytes(data) - return _Ed448PrivateKey(self, evp_pkey) - - def ed448_generate_key(self): - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED448) - return _Ed448PrivateKey(self, evp_pkey) - - def derive_scrypt(self, key_material, salt, length, n, r, p): - buf = self._ffi.new("unsigned char[]", length) - key_material_ptr = self._ffi.from_buffer(key_material) - res = self._lib.EVP_PBE_scrypt( - key_material_ptr, - len(key_material), - salt, - len(salt), - n, - r, - p, - scrypt._MEM_LIMIT, - buf, - length, - ) - if res != 1: - errors = self._consume_errors_with_text() - # memory required formula explained here: - # https://blog.filippo.io/the-scrypt-parameters/ - min_memory = 128 * n * r // (1024 ** 2) - raise MemoryError( - "Not enough memory to derive key. These parameters require" - " {} MB of memory.".format(min_memory), - errors, - ) - return self._ffi.buffer(buf)[:] + def ed448_load_private_bytes(self, data: bytes) -> ed448.Ed448PrivateKey: + return rust_openssl.ed448.from_private_bytes(data) - def aead_cipher_supported(self, cipher): - cipher_name = aead._aead_cipher_name(cipher) - if self._fips_enabled and cipher_name not in self._fips_aead: - return False - return self._lib.EVP_get_cipherbyname(cipher_name) != self._ffi.NULL + def ed448_generate_key(self) -> ed448.Ed448PrivateKey: + return rust_openssl.ed448.generate_key() - @contextlib.contextmanager - def _zeroed_bytearray(self, length): - """ - This method creates a bytearray, which we copy data into (hopefully - also from a mutable buffer that can be dynamically erased!), and then - zero when we're done. - """ - ba = bytearray(length) - try: - yield ba - finally: - self._zero_data(ba, length) + def aead_cipher_supported(self, cipher) -> bool: + return aead._aead_cipher_supported(self, cipher) - def _zero_data(self, data, length): + def _zero_data(self, data, length: int) -> None: # We clear things this way because at the moment we're not # sure of a better way that can guarantee it overwrites the # memory of a bytearray and doesn't just replace the underlying char *. @@ -2517,7 +1597,23 @@ def _zeroed_null_terminated_buf(self, data): # Cast to a uint8_t * so we can assign by integer self._zero_data(self._ffi.cast("uint8_t *", buf), data_len) - def load_key_and_certificates_from_pkcs12(self, data, password): + def load_key_and_certificates_from_pkcs12( + self, data: bytes, password: typing.Optional[bytes] + ) -> typing.Tuple[ + typing.Optional[PrivateKeyTypes], + typing.Optional[x509.Certificate], + typing.List[x509.Certificate], + ]: + pkcs12 = self.load_pkcs12(data, password) + return ( + pkcs12.key, + pkcs12.cert.certificate if pkcs12.cert else None, + [cert.certificate for cert in pkcs12.additional_certs], + ) + + def load_pkcs12( + self, data: bytes, password: typing.Optional[bytes] + ) -> PKCS12KeyAndCertificates: if password is not None: utils._check_byteslike("password", password) @@ -2535,7 +1631,6 @@ def load_key_and_certificates_from_pkcs12(self, data, password): res = self._lib.PKCS12_parse( p12, password_buf, evp_pkey_ptr, x509_ptr, sk_x509_ptr ) - if res == 0: self._consume_errors() raise ValueError("Invalid password or PKCS12 data") @@ -2546,26 +1641,59 @@ def load_key_and_certificates_from_pkcs12(self, data, password): if evp_pkey_ptr[0] != self._ffi.NULL: evp_pkey = self._ffi.gc(evp_pkey_ptr[0], self._lib.EVP_PKEY_free) - key = self._evp_pkey_to_private_key(evp_pkey) + # We don't support turning off RSA key validation when loading + # PKCS12 keys + key = self._evp_pkey_to_private_key( + evp_pkey, unsafe_skip_rsa_key_validation=False + ) if x509_ptr[0] != self._ffi.NULL: x509 = self._ffi.gc(x509_ptr[0], self._lib.X509_free) - cert = _Certificate(self, x509) + cert_obj = self._ossl2cert(x509) + name = None + maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL) + if maybe_name != self._ffi.NULL: + name = self._ffi.string(maybe_name) + cert = PKCS12Certificate(cert_obj, name) if sk_x509_ptr[0] != self._ffi.NULL: sk_x509 = self._ffi.gc(sk_x509_ptr[0], self._lib.sk_X509_free) num = self._lib.sk_X509_num(sk_x509_ptr[0]) - for i in range(num): + + # In OpenSSL < 3.0.0 PKCS12 parsing reverses the order of the + # certificates. + indices: typing.Iterable[int] + if ( + self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER + or self._lib.CRYPTOGRAPHY_IS_BORINGSSL + ): + indices = range(num) + else: + indices = reversed(range(num)) + + for i in indices: x509 = self._lib.sk_X509_value(sk_x509, i) self.openssl_assert(x509 != self._ffi.NULL) x509 = self._ffi.gc(x509, self._lib.X509_free) - additional_certificates.append(_Certificate(self, x509)) + addl_cert = self._ossl2cert(x509) + addl_name = None + maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL) + if maybe_name != self._ffi.NULL: + addl_name = self._ffi.string(maybe_name) + additional_certificates.append( + PKCS12Certificate(addl_cert, addl_name) + ) - return (key, cert, additional_certificates) + return PKCS12KeyAndCertificates(key, cert, additional_certificates) def serialize_key_and_certificates_to_pkcs12( - self, name, key, cert, cas, encryption_algorithm - ): + self, + name: typing.Optional[bytes], + key: typing.Optional[PKCS12PrivateKeyTypes], + cert: typing.Optional[x509.Certificate], + cas: typing.Optional[typing.List[_PKCS12CATypes]], + encryption_algorithm: serialization.KeySerializationEncryption, + ) -> bytes: password = None if name is not None: utils._check_bytes("name", name) @@ -2575,20 +1703,75 @@ def serialize_key_and_certificates_to_pkcs12( nid_key = -1 pkcs12_iter = 0 mac_iter = 0 + mac_alg = self._ffi.NULL elif isinstance( encryption_algorithm, serialization.BestAvailableEncryption ): # PKCS12 encryption is hopeless trash and can never be fixed. - # This is the least terrible option. - nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + # OpenSSL 3 supports PBESv2, but Libre and Boring do not, so + # we use PBESv1 with 3DES on the older paths. + if self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + nid_cert = self._lib.NID_aes_256_cbc + nid_key = self._lib.NID_aes_256_cbc + else: + nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC # At least we can set this higher than OpenSSL's default pkcs12_iter = 20000 # mac_iter chosen for compatibility reasons, see: # https://www.openssl.org/docs/man1.1.1/man3/PKCS12_create.html # Did we mention how lousy PKCS12 encryption is? mac_iter = 1 + # MAC algorithm can only be set on OpenSSL 3.0.0+ + mac_alg = self._ffi.NULL + password = encryption_algorithm.password + elif ( + isinstance( + encryption_algorithm, serialization._KeySerializationEncryption + ) + and encryption_algorithm._format + is serialization.PrivateFormat.PKCS12 + ): + # Default to OpenSSL's defaults. Behavior will vary based on the + # version of OpenSSL cryptography is compiled against. + nid_cert = 0 + nid_key = 0 + # Use the default iters we use in best available + pkcs12_iter = 20000 + # See the Best Available comment for why this is 1 + mac_iter = 1 password = encryption_algorithm.password + keycertalg = encryption_algorithm._key_cert_algorithm + if keycertalg is PBES.PBESv1SHA1And3KeyTripleDESCBC: + nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + elif keycertalg is PBES.PBESv2SHA256AndAES256CBC: + if not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + raise UnsupportedAlgorithm( + "PBESv2 is not supported by this version of OpenSSL" + ) + nid_cert = self._lib.NID_aes_256_cbc + nid_key = self._lib.NID_aes_256_cbc + else: + assert keycertalg is None + # We use OpenSSL's defaults + + if encryption_algorithm._hmac_hash is not None: + if not self._lib.Cryptography_HAS_PKCS12_SET_MAC: + raise UnsupportedAlgorithm( + "Setting MAC algorithm is not supported by this " + "version of OpenSSL." + ) + mac_alg = self._evp_md_non_null_from_algorithm( + encryption_algorithm._hmac_hash + ) + self.openssl_assert(mac_alg != self._ffi.NULL) + else: + mac_alg = self._ffi.NULL + + if encryption_algorithm._kdf_rounds is not None: + pkcs12_iter = encryption_algorithm._kdf_rounds + else: raise ValueError("Unsupported key encryption type") @@ -2598,19 +1781,39 @@ def serialize_key_and_certificates_to_pkcs12( sk_x509 = self._lib.sk_X509_new_null() sk_x509 = self._ffi.gc(sk_x509, self._lib.sk_X509_free) - # reverse the list when building the stack so that they're encoded - # in the order they were originally provided. it is a mystery - for ca in reversed(cas): - res = self._lib.sk_X509_push(sk_x509, ca._x509) + # This list is to keep the x509 values alive until end of function + ossl_cas = [] + for ca in cas: + if isinstance(ca, PKCS12Certificate): + ca_alias = ca.friendly_name + ossl_ca = self._cert2ossl(ca.certificate) + if ca_alias is None: + res = self._lib.X509_alias_set1( + ossl_ca, self._ffi.NULL, -1 + ) + else: + res = self._lib.X509_alias_set1( + ossl_ca, ca_alias, len(ca_alias) + ) + self.openssl_assert(res == 1) + else: + ossl_ca = self._cert2ossl(ca) + ossl_cas.append(ossl_ca) + res = self._lib.sk_X509_push(sk_x509, ossl_ca) backend.openssl_assert(res >= 1) with self._zeroed_null_terminated_buf(password) as password_buf: with self._zeroed_null_terminated_buf(name) as name_buf: + ossl_cert = self._cert2ossl(cert) if cert else self._ffi.NULL + ossl_pkey = ( + self._key2ossl(key) if key is not None else self._ffi.NULL + ) + p12 = self._lib.PKCS12_create( password_buf, name_buf, - key._evp_pkey if key else self._ffi.NULL, - cert._x509 if cert else self._ffi.NULL, + ossl_pkey, + ossl_cert, sk_x509, nid_key, nid_cert, @@ -2619,6 +1822,20 @@ def serialize_key_and_certificates_to_pkcs12( 0, ) + if ( + self._lib.Cryptography_HAS_PKCS12_SET_MAC + and mac_alg != self._ffi.NULL + ): + self._lib.PKCS12_set_mac( + p12, + password_buf, + -1, + self._ffi.NULL, + 0, + mac_iter, + mac_alg, + ) + self.openssl_assert(p12 != self._ffi.NULL) p12 = self._ffi.gc(p12, self._lib.PKCS12_free) @@ -2627,19 +1844,17 @@ def serialize_key_and_certificates_to_pkcs12( self.openssl_assert(res > 0) return self._read_mem_bio(bio) - def poly1305_supported(self): + def poly1305_supported(self) -> bool: if self._fips_enabled: return False return self._lib.Cryptography_HAS_POLY1305 == 1 - def create_poly1305_ctx(self, key): - utils._check_byteslike("key", key) - if len(key) != _POLY1305_KEY_SIZE: - raise ValueError("A poly1305 key is 32 bytes long") - - return _Poly1305Context(self, key) + def pkcs7_supported(self) -> bool: + return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - def load_pem_pkcs7_certificates(self, data): + def load_pem_pkcs7_certificates( + self, data: bytes + ) -> typing.List[x509.Certificate]: utils._check_bytes("data", data) bio = self._bytes_to_bio(data) p7 = self._lib.PEM_read_bio_PKCS7( @@ -2652,7 +1867,9 @@ def load_pem_pkcs7_certificates(self, data): p7 = self._ffi.gc(p7, self._lib.PKCS7_free) return self._load_pkcs7_certificates(p7) - def load_der_pkcs7_certificates(self, data): + def load_der_pkcs7_certificates( + self, data: bytes + ) -> typing.List[x509.Certificate]: utils._check_bytes("data", data) bio = self._bytes_to_bio(data) p7 = self._lib.d2i_PKCS7_bio(bio.bio, self._ffi.NULL) @@ -2663,7 +1880,7 @@ def load_der_pkcs7_certificates(self, data): p7 = self._ffi.gc(p7, self._lib.PKCS7_free) return self._load_pkcs7_certificates(p7) - def _load_pkcs7_certificates(self, p7): + def _load_pkcs7_certificates(self, p7) -> typing.List[x509.Certificate]: nid = self._lib.OBJ_obj2nid(p7.type) self.openssl_assert(nid != self._lib.NID_undef) if nid != self._lib.NID_pkcs7_signed: @@ -2679,106 +1896,39 @@ def _load_pkcs7_certificates(self, p7): for i in range(num): x509 = self._lib.sk_X509_value(sk_x509, i) self.openssl_assert(x509 != self._ffi.NULL) - res = self._lib.X509_up_ref(x509) - # When OpenSSL is less than 1.1.0 up_ref returns the current - # refcount. On 1.1.0+ it returns 1 for success. - self.openssl_assert(res >= 1) - x509 = self._ffi.gc(x509, self._lib.X509_free) - certs.append(_Certificate(self, x509)) + cert = self._ossl2cert(x509) + certs.append(cert) return certs - def pkcs7_sign(self, builder, encoding, options): - bio = self._bytes_to_bio(builder._data) - init_flags = self._lib.PKCS7_PARTIAL - final_flags = 0 - - if len(builder._additional_certs) == 0: - certs = self._ffi.NULL - else: - certs = self._lib.sk_X509_new_null() - certs = self._ffi.gc(certs, self._lib.sk_X509_free) - for cert in builder._additional_certs: - res = self._lib.sk_X509_push(certs, cert._x509) - self.openssl_assert(res >= 1) - - if pkcs7.PKCS7Options.DetachedSignature in options: - # Don't embed the data in the PKCS7 structure - init_flags |= self._lib.PKCS7_DETACHED - final_flags |= self._lib.PKCS7_DETACHED - - # This just inits a structure for us. However, there - # are flags we need to set, joy. - p7 = self._lib.PKCS7_sign( - self._ffi.NULL, - self._ffi.NULL, - certs, - self._ffi.NULL, - init_flags, - ) - self.openssl_assert(p7 != self._ffi.NULL) - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - signer_flags = 0 - # These flags are configurable on a per-signature basis - # but we've deliberately chosen to make the API only allow - # setting it across all signatures for now. - if pkcs7.PKCS7Options.NoCapabilities in options: - signer_flags |= self._lib.PKCS7_NOSMIMECAP - elif pkcs7.PKCS7Options.NoAttributes in options: - signer_flags |= self._lib.PKCS7_NOATTR - - if pkcs7.PKCS7Options.NoCerts in options: - signer_flags |= self._lib.PKCS7_NOCERTS - - for certificate, private_key, hash_algorithm in builder._signers: - md = self._evp_md_non_null_from_algorithm(hash_algorithm) - p7signerinfo = self._lib.PKCS7_sign_add_signer( - p7, certificate._x509, private_key._evp_pkey, md, signer_flags - ) - self.openssl_assert(p7signerinfo != self._ffi.NULL) - - for option in options: - # DetachedSignature, NoCapabilities, and NoAttributes are already - # handled so we just need to check these last two options. - if option is pkcs7.PKCS7Options.Text: - final_flags |= self._lib.PKCS7_TEXT - elif option is pkcs7.PKCS7Options.Binary: - final_flags |= self._lib.PKCS7_BINARY - - bio_out = self._create_mem_bio_gc() - if encoding is serialization.Encoding.SMIME: - # This finalizes the structure - res = self._lib.SMIME_write_PKCS7( - bio_out, p7, bio.bio, final_flags - ) - elif encoding is serialization.Encoding.PEM: - res = self._lib.PKCS7_final(p7, bio.bio, final_flags) - self.openssl_assert(res == 1) - res = self._lib.PEM_write_bio_PKCS7_stream( - bio_out, p7, bio.bio, final_flags - ) - else: - assert encoding is serialization.Encoding.DER - # We need to call finalize here becauase i2d_PKCS7_bio does not - # finalize. - res = self._lib.PKCS7_final(p7, bio.bio, final_flags) - self.openssl_assert(res == 1) - res = self._lib.i2d_PKCS7_bio(bio_out, p7) - self.openssl_assert(res == 1) - return self._read_mem_bio(bio_out) - -class GetCipherByName(object): - def __init__(self, fmt): +class GetCipherByName: + def __init__(self, fmt: str): self._fmt = fmt - def __call__(self, backend, cipher, mode): + def __call__(self, backend: Backend, cipher: CipherAlgorithm, mode: Mode): cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower() - return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) + evp_cipher = backend._lib.EVP_get_cipherbyname( + cipher_name.encode("ascii") + ) + + # try EVP_CIPHER_fetch if present + if ( + evp_cipher == backend._ffi.NULL + and backend._lib.Cryptography_HAS_300_EVP_CIPHER + ): + evp_cipher = backend._lib.EVP_CIPHER_fetch( + backend._ffi.NULL, + cipher_name.encode("ascii"), + backend._ffi.NULL, + ) + + backend._consume_errors() + return evp_cipher -def _get_xts_cipher(backend, cipher, mode): - cipher_name = "aes-{}-xts".format(cipher.key_size // 2) +def _get_xts_cipher(backend: Backend, cipher: AES, mode): + cipher_name = f"aes-{cipher.key_size // 2}-xts" return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py index 1e805d235aa2..bc42adbd49a5 100644 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -2,29 +2,29 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import typing -from cryptography import utils from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons from cryptography.hazmat.primitives import ciphers -from cryptography.hazmat.primitives.ciphers import modes +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.backend import Backend -@utils.register_interface(ciphers.CipherContext) -@utils.register_interface(ciphers.AEADCipherContext) -@utils.register_interface(ciphers.AEADEncryptionContext) -@utils.register_interface(ciphers.AEADDecryptionContext) -class _CipherContext(object): +class _CipherContext: _ENCRYPT = 1 _DECRYPT = 0 - _MAX_CHUNK_SIZE = 2 ** 31 - 1 + _MAX_CHUNK_SIZE = 2**30 - 1 - def __init__(self, backend, cipher, mode, operation): + def __init__(self, backend: Backend, cipher, mode, operation: int) -> None: self._backend = backend self._cipher = cipher self._mode = mode self._operation = operation - self._tag = None + self._tag: typing.Optional[bytes] = None if isinstance(self._cipher, ciphers.BlockCipherAlgorithm): self._block_size_bytes = self._cipher.block_size // 8 @@ -50,9 +50,9 @@ def __init__(self, backend, cipher, mode, operation): evp_cipher = adapter(self._backend, cipher, mode) if evp_cipher == self._backend._ffi.NULL: - msg = "cipher {0.name} ".format(cipher) + msg = f"cipher {cipher.name} " if mode is not None: - msg += "in {0.name} mode ".format(mode) + msg += f"in {mode.name} mode " msg += ( "is not supported by this backend (Your version of OpenSSL " "may be too old. Current version: {}.)" @@ -67,7 +67,7 @@ def __init__(self, backend, cipher, mode, operation): iv_nonce = self._backend._ffi.from_buffer(mode.tweak) elif isinstance(mode, modes.ModeWithNonce): iv_nonce = self._backend._ffi.from_buffer(mode.nonce) - elif isinstance(cipher, modes.ModeWithNonce): + elif isinstance(cipher, algorithms.ChaCha20): iv_nonce = self._backend._ffi.from_buffer(cipher.nonce) else: iv_nonce = self._backend._ffi.NULL @@ -113,18 +113,39 @@ def __init__(self, backend, cipher, mode, operation): iv_nonce, operation, ) - self._backend.openssl_assert(res != 0) + + # Check for XTS mode duplicate keys error + errors = self._backend._consume_errors() + lib = self._backend._lib + if res == 0 and ( + ( + not lib.CRYPTOGRAPHY_IS_LIBRESSL + and errors[0]._lib_reason_match( + lib.ERR_LIB_EVP, lib.EVP_R_XTS_DUPLICATED_KEYS + ) + ) + or ( + lib.Cryptography_HAS_PROVIDERS + and errors[0]._lib_reason_match( + lib.ERR_LIB_PROV, lib.PROV_R_XTS_DUPLICATED_KEYS + ) + ) + ): + raise ValueError("In XTS mode duplicated keys are not allowed") + + self._backend.openssl_assert(res != 0, errors=errors) + # We purposely disable padding here as it's handled higher up in the # API. self._backend._lib.EVP_CIPHER_CTX_set_padding(ctx, 0) self._ctx = ctx - def update(self, data): + def update(self, data: bytes) -> bytes: buf = bytearray(len(data) + self._block_size_bytes - 1) n = self.update_into(data, buf) return bytes(buf[:n]) - def update_into(self, data, buf): + def update_into(self, data: bytes, buf: bytes) -> int: total_data_len = len(data) if len(buf) < (total_data_len + self._block_size_bytes - 1): raise ValueError( @@ -135,7 +156,7 @@ def update_into(self, data, buf): data_processed = 0 total_out = 0 outlen = self._backend._ffi.new("int *") - baseoutbuf = self._backend._ffi.from_buffer(buf) + baseoutbuf = self._backend._ffi.from_buffer(buf, require_writable=True) baseinbuf = self._backend._ffi.from_buffer(data) while data_processed != total_data_len: @@ -146,13 +167,20 @@ def update_into(self, data, buf): res = self._backend._lib.EVP_CipherUpdate( self._ctx, outbuf, outlen, inbuf, inlen ) - self._backend.openssl_assert(res != 0) + if res == 0 and isinstance(self._mode, modes.XTS): + self._backend._consume_errors() + raise ValueError( + "In XTS mode you must supply at least a full block in the " + "first update call. For AES this is 16 bytes." + ) + else: + self._backend.openssl_assert(res != 0) data_processed += inlen total_out += outlen[0] return total_out - def finalize(self): + def finalize(self) -> bytes: if ( self._operation == self._DECRYPT and isinstance(self._mode, modes.ModeWithAuthenticationTag) @@ -171,10 +199,23 @@ def finalize(self): if not errors and isinstance(self._mode, modes.GCM): raise InvalidTag + lib = self._backend._lib self._backend.openssl_assert( errors[0]._lib_reason_match( - self._backend._lib.ERR_LIB_EVP, - self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + lib.ERR_LIB_EVP, + lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + ) + or ( + lib.Cryptography_HAS_PROVIDERS + and errors[0]._lib_reason_match( + lib.ERR_LIB_PROV, + lib.PROV_R_WRONG_FINAL_BLOCK_LENGTH, + ) + ) + or ( + lib.CRYPTOGRAPHY_IS_BORINGSSL + and errors[0].reason + == lib.CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH ), errors=errors, ) @@ -199,17 +240,24 @@ def finalize(self): self._backend.openssl_assert(res != 0) self._tag = self._backend._ffi.buffer(tag_buf)[:] - res = self._backend._lib.EVP_CIPHER_CTX_cleanup(self._ctx) + res = self._backend._lib.EVP_CIPHER_CTX_reset(self._ctx) self._backend.openssl_assert(res == 1) return self._backend._ffi.buffer(buf)[: outlen[0]] - def finalize_with_tag(self, tag): - if len(tag) < self._mode._min_tag_length: + def finalize_with_tag(self, tag: bytes) -> bytes: + tag_len = len(tag) + if tag_len < self._mode._min_tag_length: raise ValueError( "Authentication tag must be {} bytes or longer.".format( self._mode._min_tag_length ) ) + elif tag_len > self._block_size_bytes: + raise ValueError( + "Authentication tag cannot be more than {} bytes.".format( + self._block_size_bytes + ) + ) res = self._backend._lib.EVP_CIPHER_CTX_ctrl( self._ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag ) @@ -217,7 +265,7 @@ def finalize_with_tag(self, tag): self._tag = tag return self.finalize() - def authenticate_additional_data(self, data): + def authenticate_additional_data(self, data: bytes) -> None: outlen = self._backend._ffi.new("int *") res = self._backend._lib.EVP_CipherUpdate( self._ctx, @@ -228,4 +276,6 @@ def authenticate_additional_data(self, data): ) self._backend.openssl_assert(res != 0) - tag = utils.read_only_property("_tag") + @property + def tag(self) -> typing.Optional[bytes]: + return self._tag diff --git a/src/cryptography/hazmat/backends/openssl/cmac.py b/src/cryptography/hazmat/backends/openssl/cmac.py index 195fc230f2b2..bdd7fec611d1 100644 --- a/src/cryptography/hazmat/backends/openssl/cmac.py +++ b/src/cryptography/hazmat/backends/openssl/cmac.py @@ -2,10 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations +import typing -from cryptography import utils from cryptography.exceptions import ( InvalidSignature, UnsupportedAlgorithm, @@ -14,9 +14,18 @@ from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.ciphers.modes import CBC +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.backend import Backend + from cryptography.hazmat.primitives import ciphers -class _CMACContext(object): - def __init__(self, backend, algorithm, ctx=None): + +class _CMACContext: + def __init__( + self, + backend: Backend, + algorithm: ciphers.BlockCipherAlgorithm, + ctx=None, + ) -> None: if not backend.cmac_algorithm_supported(algorithm): raise UnsupportedAlgorithm( "This backend does not support CMAC.", @@ -51,13 +60,11 @@ def __init__(self, backend, algorithm, ctx=None): self._ctx = ctx - algorithm = utils.read_only_property("_algorithm") - - def update(self, data): + def update(self, data: bytes) -> None: res = self._backend._lib.CMAC_Update(self._ctx, data, len(data)) self._backend.openssl_assert(res == 1) - def finalize(self): + def finalize(self) -> bytes: buf = self._backend._ffi.new("unsigned char[]", self._output_length) length = self._backend._ffi.new("size_t *", self._output_length) res = self._backend._lib.CMAC_Final(self._ctx, buf, length) @@ -67,7 +74,7 @@ def finalize(self): return self._backend._ffi.buffer(buf)[:] - def copy(self): + def copy(self) -> _CMACContext: copied_ctx = self._backend._lib.CMAC_CTX_new() copied_ctx = self._backend._ffi.gc( copied_ctx, self._backend._lib.CMAC_CTX_free @@ -76,7 +83,7 @@ def copy(self): self._backend.openssl_assert(res == 1) return _CMACContext(self._backend, self._algorithm, ctx=copied_ctx) - def verify(self, signature): + def verify(self, signature: bytes) -> None: digest = self.finalize() if not constant_time.bytes_eq(digest, signature): raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index 279b00ca5c10..bf123b6285b6 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -2,673 +2,9 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - -import datetime -import ipaddress - -import six +from __future__ import annotations from cryptography import x509 -from cryptography.hazmat._der import DERReader, INTEGER, NULL, SEQUENCE -from cryptography.x509.extensions import _TLS_FEATURE_TYPE_TO_ENUM -from cryptography.x509.name import _ASN1_TYPE_TO_ENUM -from cryptography.x509.oid import ( - CRLEntryExtensionOID, - CertificatePoliciesOID, - ExtensionOID, - OCSPExtensionOID, -) - - -def _obj2txt(backend, obj): - # Set to 80 on the recommendation of - # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values - # - # But OIDs longer than this occur in real life (e.g. Active - # Directory makes some very long OIDs). So we need to detect - # and properly handle the case where the default buffer is not - # big enough. - # - buf_len = 80 - buf = backend._ffi.new("char[]", buf_len) - - # 'res' is the number of bytes that *would* be written if the - # buffer is large enough. If 'res' > buf_len - 1, we need to - # alloc a big-enough buffer and go again. - res = backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1) - if res > buf_len - 1: # account for terminating null byte - buf_len = res + 1 - buf = backend._ffi.new("char[]", buf_len) - res = backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1) - backend.openssl_assert(res > 0) - return backend._ffi.buffer(buf, res)[:].decode() - - -def _decode_x509_name_entry(backend, x509_name_entry): - obj = backend._lib.X509_NAME_ENTRY_get_object(x509_name_entry) - backend.openssl_assert(obj != backend._ffi.NULL) - data = backend._lib.X509_NAME_ENTRY_get_data(x509_name_entry) - backend.openssl_assert(data != backend._ffi.NULL) - value = _asn1_string_to_utf8(backend, data) - oid = _obj2txt(backend, obj) - type = _ASN1_TYPE_TO_ENUM[data.type] - - return x509.NameAttribute(x509.ObjectIdentifier(oid), value, type) - - -def _decode_x509_name(backend, x509_name): - count = backend._lib.X509_NAME_entry_count(x509_name) - attributes = [] - prev_set_id = -1 - for x in range(count): - entry = backend._lib.X509_NAME_get_entry(x509_name, x) - attribute = _decode_x509_name_entry(backend, entry) - set_id = backend._lib.Cryptography_X509_NAME_ENTRY_set(entry) - if set_id != prev_set_id: - attributes.append({attribute}) - else: - # is in the same RDN a previous entry - attributes[-1].add(attribute) - prev_set_id = set_id - - return x509.Name(x509.RelativeDistinguishedName(rdn) for rdn in attributes) - - -def _decode_general_names(backend, gns): - num = backend._lib.sk_GENERAL_NAME_num(gns) - names = [] - for i in range(num): - gn = backend._lib.sk_GENERAL_NAME_value(gns, i) - backend.openssl_assert(gn != backend._ffi.NULL) - names.append(_decode_general_name(backend, gn)) - - return names - - -def _decode_general_name(backend, gn): - if gn.type == backend._lib.GEN_DNS: - # Convert to bytes and then decode to utf8. We don't use - # asn1_string_to_utf8 here because it doesn't properly convert - # utf8 from ia5strings. - data = _asn1_string_to_bytes(backend, gn.d.dNSName).decode("utf8") - # We don't use the constructor for DNSName so we can bypass validation - # This allows us to create DNSName objects that have unicode chars - # when a certificate (against the RFC) contains them. - return x509.DNSName._init_without_validation(data) - elif gn.type == backend._lib.GEN_URI: - # Convert to bytes and then decode to utf8. We don't use - # asn1_string_to_utf8 here because it doesn't properly convert - # utf8 from ia5strings. - data = _asn1_string_to_bytes( - backend, gn.d.uniformResourceIdentifier - ).decode("utf8") - # We don't use the constructor for URI so we can bypass validation - # This allows us to create URI objects that have unicode chars - # when a certificate (against the RFC) contains them. - return x509.UniformResourceIdentifier._init_without_validation(data) - elif gn.type == backend._lib.GEN_RID: - oid = _obj2txt(backend, gn.d.registeredID) - return x509.RegisteredID(x509.ObjectIdentifier(oid)) - elif gn.type == backend._lib.GEN_IPADD: - data = _asn1_string_to_bytes(backend, gn.d.iPAddress) - data_len = len(data) - if data_len == 8 or data_len == 32: - # This is an IPv4 or IPv6 Network and not a single IP. This - # type of data appears in Name Constraints. Unfortunately, - # ipaddress doesn't support packed bytes + netmask. Additionally, - # IPv6Network can only handle CIDR rather than the full 16 byte - # netmask. To handle this we convert the netmask to integer, then - # find the first 0 bit, which will be the prefix. If another 1 - # bit is present after that the netmask is invalid. - base = ipaddress.ip_address(data[: data_len // 2]) - netmask = ipaddress.ip_address(data[data_len // 2 :]) - bits = bin(int(netmask))[2:] - prefix = bits.find("0") - # If no 0 bits are found it is a /32 or /128 - if prefix == -1: - prefix = len(bits) - - if "1" in bits[prefix:]: - raise ValueError("Invalid netmask") - - ip = ipaddress.ip_network(base.exploded + u"/{}".format(prefix)) - else: - ip = ipaddress.ip_address(data) - - return x509.IPAddress(ip) - elif gn.type == backend._lib.GEN_DIRNAME: - return x509.DirectoryName( - _decode_x509_name(backend, gn.d.directoryName) - ) - elif gn.type == backend._lib.GEN_EMAIL: - # Convert to bytes and then decode to utf8. We don't use - # asn1_string_to_utf8 here because it doesn't properly convert - # utf8 from ia5strings. - data = _asn1_string_to_bytes(backend, gn.d.rfc822Name).decode("utf8") - # We don't use the constructor for RFC822Name so we can bypass - # validation. This allows us to create RFC822Name objects that have - # unicode chars when a certificate (against the RFC) contains them. - return x509.RFC822Name._init_without_validation(data) - elif gn.type == backend._lib.GEN_OTHERNAME: - type_id = _obj2txt(backend, gn.d.otherName.type_id) - value = _asn1_to_der(backend, gn.d.otherName.value) - return x509.OtherName(x509.ObjectIdentifier(type_id), value) - else: - # x400Address or ediPartyName - raise x509.UnsupportedGeneralNameType( - "{} is not a supported type".format( - x509._GENERAL_NAMES.get(gn.type, gn.type) - ), - gn.type, - ) - - -def _decode_ocsp_no_check(backend, ext): - return x509.OCSPNoCheck() - - -def _decode_crl_number(backend, ext): - asn1_int = backend._ffi.cast("ASN1_INTEGER *", ext) - asn1_int = backend._ffi.gc(asn1_int, backend._lib.ASN1_INTEGER_free) - return x509.CRLNumber(_asn1_integer_to_int(backend, asn1_int)) - - -def _decode_delta_crl_indicator(backend, ext): - asn1_int = backend._ffi.cast("ASN1_INTEGER *", ext) - asn1_int = backend._ffi.gc(asn1_int, backend._lib.ASN1_INTEGER_free) - return x509.DeltaCRLIndicator(_asn1_integer_to_int(backend, asn1_int)) - - -class _X509ExtensionParser(object): - def __init__(self, backend, ext_count, get_ext, handlers): - self.ext_count = ext_count - self.get_ext = get_ext - self.handlers = handlers - self._backend = backend - - def parse(self, x509_obj): - extensions = [] - seen_oids = set() - for i in range(self.ext_count(x509_obj)): - ext = self.get_ext(x509_obj, i) - self._backend.openssl_assert(ext != self._backend._ffi.NULL) - crit = self._backend._lib.X509_EXTENSION_get_critical(ext) - critical = crit == 1 - oid = x509.ObjectIdentifier( - _obj2txt( - self._backend, - self._backend._lib.X509_EXTENSION_get_object(ext), - ) - ) - if oid in seen_oids: - raise x509.DuplicateExtension( - "Duplicate {} extension found".format(oid), oid - ) - - # These OIDs are only supported in OpenSSL 1.1.0+ but we want - # to support them in all versions of OpenSSL so we decode them - # ourselves. - if oid == ExtensionOID.TLS_FEATURE: - # The extension contents are a SEQUENCE OF INTEGERs. - data = self._backend._lib.X509_EXTENSION_get_data(ext) - data_bytes = _asn1_string_to_bytes(self._backend, data) - features = DERReader(data_bytes).read_single_element(SEQUENCE) - parsed = [] - while not features.is_empty(): - parsed.append(features.read_element(INTEGER).as_integer()) - # Map the features to their enum value. - value = x509.TLSFeature( - [_TLS_FEATURE_TYPE_TO_ENUM[x] for x in parsed] - ) - extensions.append(x509.Extension(oid, critical, value)) - seen_oids.add(oid) - continue - elif oid == ExtensionOID.PRECERT_POISON: - data = self._backend._lib.X509_EXTENSION_get_data(ext) - # The contents of the extension must be an ASN.1 NULL. - reader = DERReader(_asn1_string_to_bytes(self._backend, data)) - reader.read_single_element(NULL).check_empty() - extensions.append( - x509.Extension(oid, critical, x509.PrecertPoison()) - ) - seen_oids.add(oid) - continue - - try: - handler = self.handlers[oid] - except KeyError: - # Dump the DER payload into an UnrecognizedExtension object - data = self._backend._lib.X509_EXTENSION_get_data(ext) - self._backend.openssl_assert(data != self._backend._ffi.NULL) - der = self._backend._ffi.buffer(data.data, data.length)[:] - unrecognized = x509.UnrecognizedExtension(oid, der) - extensions.append(x509.Extension(oid, critical, unrecognized)) - else: - ext_data = self._backend._lib.X509V3_EXT_d2i(ext) - if ext_data == self._backend._ffi.NULL: - self._backend._consume_errors() - raise ValueError( - "The {} extension is invalid and can't be " - "parsed".format(oid) - ) - - value = handler(self._backend, ext_data) - extensions.append(x509.Extension(oid, critical, value)) - - seen_oids.add(oid) - - return x509.Extensions(extensions) - - -def _decode_certificate_policies(backend, cp): - cp = backend._ffi.cast("Cryptography_STACK_OF_POLICYINFO *", cp) - cp = backend._ffi.gc(cp, backend._lib.CERTIFICATEPOLICIES_free) - - num = backend._lib.sk_POLICYINFO_num(cp) - certificate_policies = [] - for i in range(num): - qualifiers = None - pi = backend._lib.sk_POLICYINFO_value(cp, i) - oid = x509.ObjectIdentifier(_obj2txt(backend, pi.policyid)) - if pi.qualifiers != backend._ffi.NULL: - qnum = backend._lib.sk_POLICYQUALINFO_num(pi.qualifiers) - qualifiers = [] - for j in range(qnum): - pqi = backend._lib.sk_POLICYQUALINFO_value(pi.qualifiers, j) - pqualid = x509.ObjectIdentifier(_obj2txt(backend, pqi.pqualid)) - if pqualid == CertificatePoliciesOID.CPS_QUALIFIER: - cpsuri = backend._ffi.buffer( - pqi.d.cpsuri.data, pqi.d.cpsuri.length - )[:].decode("ascii") - qualifiers.append(cpsuri) - else: - assert pqualid == CertificatePoliciesOID.CPS_USER_NOTICE - user_notice = _decode_user_notice( - backend, pqi.d.usernotice - ) - qualifiers.append(user_notice) - - certificate_policies.append(x509.PolicyInformation(oid, qualifiers)) - - return x509.CertificatePolicies(certificate_policies) - - -def _decode_user_notice(backend, un): - explicit_text = None - notice_reference = None - - if un.exptext != backend._ffi.NULL: - explicit_text = _asn1_string_to_utf8(backend, un.exptext) - - if un.noticeref != backend._ffi.NULL: - organization = _asn1_string_to_utf8(backend, un.noticeref.organization) - - num = backend._lib.sk_ASN1_INTEGER_num(un.noticeref.noticenos) - notice_numbers = [] - for i in range(num): - asn1_int = backend._lib.sk_ASN1_INTEGER_value( - un.noticeref.noticenos, i - ) - notice_num = _asn1_integer_to_int(backend, asn1_int) - notice_numbers.append(notice_num) - - notice_reference = x509.NoticeReference(organization, notice_numbers) - - return x509.UserNotice(notice_reference, explicit_text) - - -def _decode_basic_constraints(backend, bc_st): - basic_constraints = backend._ffi.cast("BASIC_CONSTRAINTS *", bc_st) - basic_constraints = backend._ffi.gc( - basic_constraints, backend._lib.BASIC_CONSTRAINTS_free - ) - # The byte representation of an ASN.1 boolean true is \xff. OpenSSL - # chooses to just map this to its ordinal value, so true is 255 and - # false is 0. - ca = basic_constraints.ca == 255 - path_length = _asn1_integer_to_int_or_none( - backend, basic_constraints.pathlen - ) - - return x509.BasicConstraints(ca, path_length) - - -def _decode_subject_key_identifier(backend, asn1_string): - asn1_string = backend._ffi.cast("ASN1_OCTET_STRING *", asn1_string) - asn1_string = backend._ffi.gc( - asn1_string, backend._lib.ASN1_OCTET_STRING_free - ) - return x509.SubjectKeyIdentifier( - backend._ffi.buffer(asn1_string.data, asn1_string.length)[:] - ) - - -def _decode_authority_key_identifier(backend, akid): - akid = backend._ffi.cast("AUTHORITY_KEYID *", akid) - akid = backend._ffi.gc(akid, backend._lib.AUTHORITY_KEYID_free) - key_identifier = None - authority_cert_issuer = None - - if akid.keyid != backend._ffi.NULL: - key_identifier = backend._ffi.buffer( - akid.keyid.data, akid.keyid.length - )[:] - - if akid.issuer != backend._ffi.NULL: - authority_cert_issuer = _decode_general_names(backend, akid.issuer) - - authority_cert_serial_number = _asn1_integer_to_int_or_none( - backend, akid.serial - ) - - return x509.AuthorityKeyIdentifier( - key_identifier, authority_cert_issuer, authority_cert_serial_number - ) - - -def _decode_information_access(backend, ia): - ia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", ia) - ia = backend._ffi.gc( - ia, - lambda x: backend._lib.sk_ACCESS_DESCRIPTION_pop_free( - x, - backend._ffi.addressof( - backend._lib._original_lib, "ACCESS_DESCRIPTION_free" - ), - ), - ) - num = backend._lib.sk_ACCESS_DESCRIPTION_num(ia) - access_descriptions = [] - for i in range(num): - ad = backend._lib.sk_ACCESS_DESCRIPTION_value(ia, i) - backend.openssl_assert(ad.method != backend._ffi.NULL) - oid = x509.ObjectIdentifier(_obj2txt(backend, ad.method)) - backend.openssl_assert(ad.location != backend._ffi.NULL) - gn = _decode_general_name(backend, ad.location) - access_descriptions.append(x509.AccessDescription(oid, gn)) - - return access_descriptions - - -def _decode_authority_information_access(backend, aia): - access_descriptions = _decode_information_access(backend, aia) - return x509.AuthorityInformationAccess(access_descriptions) - - -def _decode_subject_information_access(backend, aia): - access_descriptions = _decode_information_access(backend, aia) - return x509.SubjectInformationAccess(access_descriptions) - - -def _decode_key_usage(backend, bit_string): - bit_string = backend._ffi.cast("ASN1_BIT_STRING *", bit_string) - bit_string = backend._ffi.gc(bit_string, backend._lib.ASN1_BIT_STRING_free) - get_bit = backend._lib.ASN1_BIT_STRING_get_bit - digital_signature = get_bit(bit_string, 0) == 1 - content_commitment = get_bit(bit_string, 1) == 1 - key_encipherment = get_bit(bit_string, 2) == 1 - data_encipherment = get_bit(bit_string, 3) == 1 - key_agreement = get_bit(bit_string, 4) == 1 - key_cert_sign = get_bit(bit_string, 5) == 1 - crl_sign = get_bit(bit_string, 6) == 1 - encipher_only = get_bit(bit_string, 7) == 1 - decipher_only = get_bit(bit_string, 8) == 1 - return x509.KeyUsage( - digital_signature, - content_commitment, - key_encipherment, - data_encipherment, - key_agreement, - key_cert_sign, - crl_sign, - encipher_only, - decipher_only, - ) - - -def _decode_general_names_extension(backend, gns): - gns = backend._ffi.cast("GENERAL_NAMES *", gns) - gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free) - general_names = _decode_general_names(backend, gns) - return general_names - - -def _decode_subject_alt_name(backend, ext): - return x509.SubjectAlternativeName( - _decode_general_names_extension(backend, ext) - ) - - -def _decode_issuer_alt_name(backend, ext): - return x509.IssuerAlternativeName( - _decode_general_names_extension(backend, ext) - ) - - -def _decode_name_constraints(backend, nc): - nc = backend._ffi.cast("NAME_CONSTRAINTS *", nc) - nc = backend._ffi.gc(nc, backend._lib.NAME_CONSTRAINTS_free) - permitted = _decode_general_subtrees(backend, nc.permittedSubtrees) - excluded = _decode_general_subtrees(backend, nc.excludedSubtrees) - return x509.NameConstraints( - permitted_subtrees=permitted, excluded_subtrees=excluded - ) - - -def _decode_general_subtrees(backend, stack_subtrees): - if stack_subtrees == backend._ffi.NULL: - return None - - num = backend._lib.sk_GENERAL_SUBTREE_num(stack_subtrees) - subtrees = [] - - for i in range(num): - obj = backend._lib.sk_GENERAL_SUBTREE_value(stack_subtrees, i) - backend.openssl_assert(obj != backend._ffi.NULL) - name = _decode_general_name(backend, obj.base) - subtrees.append(name) - - return subtrees - - -def _decode_issuing_dist_point(backend, idp): - idp = backend._ffi.cast("ISSUING_DIST_POINT *", idp) - idp = backend._ffi.gc(idp, backend._lib.ISSUING_DIST_POINT_free) - if idp.distpoint != backend._ffi.NULL: - full_name, relative_name = _decode_distpoint(backend, idp.distpoint) - else: - full_name = None - relative_name = None - - only_user = idp.onlyuser == 255 - only_ca = idp.onlyCA == 255 - indirect_crl = idp.indirectCRL == 255 - only_attr = idp.onlyattr == 255 - if idp.onlysomereasons != backend._ffi.NULL: - only_some_reasons = _decode_reasons(backend, idp.onlysomereasons) - else: - only_some_reasons = None - - return x509.IssuingDistributionPoint( - full_name, - relative_name, - only_user, - only_ca, - only_some_reasons, - indirect_crl, - only_attr, - ) - - -def _decode_policy_constraints(backend, pc): - pc = backend._ffi.cast("POLICY_CONSTRAINTS *", pc) - pc = backend._ffi.gc(pc, backend._lib.POLICY_CONSTRAINTS_free) - - require_explicit_policy = _asn1_integer_to_int_or_none( - backend, pc.requireExplicitPolicy - ) - inhibit_policy_mapping = _asn1_integer_to_int_or_none( - backend, pc.inhibitPolicyMapping - ) - - return x509.PolicyConstraints( - require_explicit_policy, inhibit_policy_mapping - ) - - -def _decode_extended_key_usage(backend, sk): - sk = backend._ffi.cast("Cryptography_STACK_OF_ASN1_OBJECT *", sk) - sk = backend._ffi.gc(sk, backend._lib.sk_ASN1_OBJECT_free) - num = backend._lib.sk_ASN1_OBJECT_num(sk) - ekus = [] - - for i in range(num): - obj = backend._lib.sk_ASN1_OBJECT_value(sk, i) - backend.openssl_assert(obj != backend._ffi.NULL) - oid = x509.ObjectIdentifier(_obj2txt(backend, obj)) - ekus.append(oid) - - return x509.ExtendedKeyUsage(ekus) - - -_DISTPOINT_TYPE_FULLNAME = 0 -_DISTPOINT_TYPE_RELATIVENAME = 1 - - -def _decode_dist_points(backend, cdps): - cdps = backend._ffi.cast("Cryptography_STACK_OF_DIST_POINT *", cdps) - cdps = backend._ffi.gc(cdps, backend._lib.CRL_DIST_POINTS_free) - - num = backend._lib.sk_DIST_POINT_num(cdps) - dist_points = [] - for i in range(num): - full_name = None - relative_name = None - crl_issuer = None - reasons = None - cdp = backend._lib.sk_DIST_POINT_value(cdps, i) - if cdp.reasons != backend._ffi.NULL: - reasons = _decode_reasons(backend, cdp.reasons) - - if cdp.CRLissuer != backend._ffi.NULL: - crl_issuer = _decode_general_names(backend, cdp.CRLissuer) - - # Certificates may have a crl_issuer/reasons and no distribution - # point so make sure it's not null. - if cdp.distpoint != backend._ffi.NULL: - full_name, relative_name = _decode_distpoint( - backend, cdp.distpoint - ) - - dist_points.append( - x509.DistributionPoint( - full_name, relative_name, reasons, crl_issuer - ) - ) - - return dist_points - - -# ReasonFlags ::= BIT STRING { -# unused (0), -# keyCompromise (1), -# cACompromise (2), -# affiliationChanged (3), -# superseded (4), -# cessationOfOperation (5), -# certificateHold (6), -# privilegeWithdrawn (7), -# aACompromise (8) } -_REASON_BIT_MAPPING = { - 1: x509.ReasonFlags.key_compromise, - 2: x509.ReasonFlags.ca_compromise, - 3: x509.ReasonFlags.affiliation_changed, - 4: x509.ReasonFlags.superseded, - 5: x509.ReasonFlags.cessation_of_operation, - 6: x509.ReasonFlags.certificate_hold, - 7: x509.ReasonFlags.privilege_withdrawn, - 8: x509.ReasonFlags.aa_compromise, -} - - -def _decode_reasons(backend, reasons): - # We will check each bit from RFC 5280 - enum_reasons = [] - for bit_position, reason in six.iteritems(_REASON_BIT_MAPPING): - if backend._lib.ASN1_BIT_STRING_get_bit(reasons, bit_position): - enum_reasons.append(reason) - - return frozenset(enum_reasons) - - -def _decode_distpoint(backend, distpoint): - if distpoint.type == _DISTPOINT_TYPE_FULLNAME: - full_name = _decode_general_names(backend, distpoint.name.fullname) - return full_name, None - - # OpenSSL code doesn't test for a specific type for - # relativename, everything that isn't fullname is considered - # relativename. Per RFC 5280: - # - # DistributionPointName ::= CHOICE { - # fullName [0] GeneralNames, - # nameRelativeToCRLIssuer [1] RelativeDistinguishedName } - rns = distpoint.name.relativename - rnum = backend._lib.sk_X509_NAME_ENTRY_num(rns) - attributes = set() - for i in range(rnum): - rn = backend._lib.sk_X509_NAME_ENTRY_value(rns, i) - backend.openssl_assert(rn != backend._ffi.NULL) - attributes.add(_decode_x509_name_entry(backend, rn)) - - relative_name = x509.RelativeDistinguishedName(attributes) - - return None, relative_name - - -def _decode_crl_distribution_points(backend, cdps): - dist_points = _decode_dist_points(backend, cdps) - return x509.CRLDistributionPoints(dist_points) - - -def _decode_freshest_crl(backend, cdps): - dist_points = _decode_dist_points(backend, cdps) - return x509.FreshestCRL(dist_points) - - -def _decode_inhibit_any_policy(backend, asn1_int): - asn1_int = backend._ffi.cast("ASN1_INTEGER *", asn1_int) - asn1_int = backend._ffi.gc(asn1_int, backend._lib.ASN1_INTEGER_free) - skip_certs = _asn1_integer_to_int(backend, asn1_int) - return x509.InhibitAnyPolicy(skip_certs) - - -def _decode_scts(backend, asn1_scts): - from cryptography.hazmat.backends.openssl.x509 import ( - _SignedCertificateTimestamp, - ) - - asn1_scts = backend._ffi.cast("Cryptography_STACK_OF_SCT *", asn1_scts) - asn1_scts = backend._ffi.gc(asn1_scts, backend._lib.SCT_LIST_free) - - scts = [] - for i in range(backend._lib.sk_SCT_num(asn1_scts)): - sct = backend._lib.sk_SCT_value(asn1_scts, i) - - scts.append(_SignedCertificateTimestamp(backend, asn1_scts, sct)) - return scts - - -def _decode_precert_signed_certificate_timestamps(backend, asn1_scts): - return x509.PrecertificateSignedCertificateTimestamps( - _decode_scts(backend, asn1_scts) - ) - - -def _decode_signed_certificate_timestamps(backend, asn1_scts): - return x509.SignedCertificateTimestamps(_decode_scts(backend, asn1_scts)) - # CRLReason ::= ENUMERATED { # unspecified (0), @@ -682,20 +18,6 @@ def _decode_signed_certificate_timestamps(backend, asn1_scts): # removeFromCRL (8), # privilegeWithdrawn (9), # aACompromise (10) } -_CRL_ENTRY_REASON_CODE_TO_ENUM = { - 0: x509.ReasonFlags.unspecified, - 1: x509.ReasonFlags.key_compromise, - 2: x509.ReasonFlags.ca_compromise, - 3: x509.ReasonFlags.affiliation_changed, - 4: x509.ReasonFlags.superseded, - 5: x509.ReasonFlags.cessation_of_operation, - 6: x509.ReasonFlags.certificate_hold, - 8: x509.ReasonFlags.remove_from_crl, - 9: x509.ReasonFlags.privilege_withdrawn, - 10: x509.ReasonFlags.aa_compromise, -} - - _CRL_ENTRY_REASON_ENUM_TO_CODE = { x509.ReasonFlags.unspecified: 0, x509.ReasonFlags.key_compromise: 1, @@ -708,171 +30,3 @@ def _decode_signed_certificate_timestamps(backend, asn1_scts): x509.ReasonFlags.privilege_withdrawn: 9, x509.ReasonFlags.aa_compromise: 10, } - - -def _decode_crl_reason(backend, enum): - enum = backend._ffi.cast("ASN1_ENUMERATED *", enum) - enum = backend._ffi.gc(enum, backend._lib.ASN1_ENUMERATED_free) - code = backend._lib.ASN1_ENUMERATED_get(enum) - - try: - return x509.CRLReason(_CRL_ENTRY_REASON_CODE_TO_ENUM[code]) - except KeyError: - raise ValueError("Unsupported reason code: {}".format(code)) - - -def _decode_invalidity_date(backend, inv_date): - generalized_time = backend._ffi.cast("ASN1_GENERALIZEDTIME *", inv_date) - generalized_time = backend._ffi.gc( - generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free - ) - return x509.InvalidityDate( - _parse_asn1_generalized_time(backend, generalized_time) - ) - - -def _decode_cert_issuer(backend, gns): - gns = backend._ffi.cast("GENERAL_NAMES *", gns) - gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free) - general_names = _decode_general_names(backend, gns) - return x509.CertificateIssuer(general_names) - - -def _asn1_to_der(backend, asn1_type): - buf = backend._ffi.new("unsigned char **") - res = backend._lib.i2d_ASN1_TYPE(asn1_type, buf) - backend.openssl_assert(res >= 0) - backend.openssl_assert(buf[0] != backend._ffi.NULL) - buf = backend._ffi.gc( - buf, lambda buffer: backend._lib.OPENSSL_free(buffer[0]) - ) - return backend._ffi.buffer(buf[0], res)[:] - - -def _asn1_integer_to_int(backend, asn1_int): - bn = backend._lib.ASN1_INTEGER_to_BN(asn1_int, backend._ffi.NULL) - backend.openssl_assert(bn != backend._ffi.NULL) - bn = backend._ffi.gc(bn, backend._lib.BN_free) - return backend._bn_to_int(bn) - - -def _asn1_integer_to_int_or_none(backend, asn1_int): - if asn1_int == backend._ffi.NULL: - return None - else: - return _asn1_integer_to_int(backend, asn1_int) - - -def _asn1_string_to_bytes(backend, asn1_string): - return backend._ffi.buffer(asn1_string.data, asn1_string.length)[:] - - -def _asn1_string_to_ascii(backend, asn1_string): - return _asn1_string_to_bytes(backend, asn1_string).decode("ascii") - - -def _asn1_string_to_utf8(backend, asn1_string): - buf = backend._ffi.new("unsigned char **") - res = backend._lib.ASN1_STRING_to_UTF8(buf, asn1_string) - if res == -1: - raise ValueError( - "Unsupported ASN1 string type. Type: {}".format(asn1_string.type) - ) - - backend.openssl_assert(buf[0] != backend._ffi.NULL) - buf = backend._ffi.gc( - buf, lambda buffer: backend._lib.OPENSSL_free(buffer[0]) - ) - return backend._ffi.buffer(buf[0], res)[:].decode("utf8") - - -def _parse_asn1_time(backend, asn1_time): - backend.openssl_assert(asn1_time != backend._ffi.NULL) - generalized_time = backend._lib.ASN1_TIME_to_generalizedtime( - asn1_time, backend._ffi.NULL - ) - if generalized_time == backend._ffi.NULL: - raise ValueError( - "Couldn't parse ASN.1 time as generalizedtime {!r}".format( - _asn1_string_to_bytes(backend, asn1_time) - ) - ) - - generalized_time = backend._ffi.gc( - generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free - ) - return _parse_asn1_generalized_time(backend, generalized_time) - - -def _parse_asn1_generalized_time(backend, generalized_time): - time = _asn1_string_to_ascii( - backend, backend._ffi.cast("ASN1_STRING *", generalized_time) - ) - return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") - - -def _decode_nonce(backend, nonce): - nonce = backend._ffi.cast("ASN1_OCTET_STRING *", nonce) - nonce = backend._ffi.gc(nonce, backend._lib.ASN1_OCTET_STRING_free) - return x509.OCSPNonce(_asn1_string_to_bytes(backend, nonce)) - - -_EXTENSION_HANDLERS_BASE = { - ExtensionOID.BASIC_CONSTRAINTS: _decode_basic_constraints, - ExtensionOID.SUBJECT_KEY_IDENTIFIER: _decode_subject_key_identifier, - ExtensionOID.KEY_USAGE: _decode_key_usage, - ExtensionOID.SUBJECT_ALTERNATIVE_NAME: _decode_subject_alt_name, - ExtensionOID.EXTENDED_KEY_USAGE: _decode_extended_key_usage, - ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _decode_authority_key_identifier, - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( - _decode_authority_information_access - ), - ExtensionOID.SUBJECT_INFORMATION_ACCESS: ( - _decode_subject_information_access - ), - ExtensionOID.CERTIFICATE_POLICIES: _decode_certificate_policies, - ExtensionOID.CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points, - ExtensionOID.FRESHEST_CRL: _decode_freshest_crl, - ExtensionOID.OCSP_NO_CHECK: _decode_ocsp_no_check, - ExtensionOID.INHIBIT_ANY_POLICY: _decode_inhibit_any_policy, - ExtensionOID.ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, - ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints, - ExtensionOID.POLICY_CONSTRAINTS: _decode_policy_constraints, -} -_EXTENSION_HANDLERS_SCT = { - ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( - _decode_precert_signed_certificate_timestamps - ) -} - -_REVOKED_EXTENSION_HANDLERS = { - CRLEntryExtensionOID.CRL_REASON: _decode_crl_reason, - CRLEntryExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, - CRLEntryExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, -} - -_CRL_EXTENSION_HANDLERS = { - ExtensionOID.CRL_NUMBER: _decode_crl_number, - ExtensionOID.DELTA_CRL_INDICATOR: _decode_delta_crl_indicator, - ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _decode_authority_key_identifier, - ExtensionOID.ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( - _decode_authority_information_access - ), - ExtensionOID.ISSUING_DISTRIBUTION_POINT: _decode_issuing_dist_point, - ExtensionOID.FRESHEST_CRL: _decode_freshest_crl, -} - -_OCSP_REQ_EXTENSION_HANDLERS = { - OCSPExtensionOID.NONCE: _decode_nonce, -} - -_OCSP_BASICRESP_EXTENSION_HANDLERS = { - OCSPExtensionOID.NONCE: _decode_nonce, -} - -_OCSP_SINGLERESP_EXTENSION_HANDLERS_SCT = { - ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: ( - _decode_signed_certificate_timestamps - ) -} diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py deleted file mode 100644 index 2862676c65ea..000000000000 --- a/src/cryptography/hazmat/backends/openssl/dh.py +++ /dev/null @@ -1,271 +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 absolute_import, division, print_function - -from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import dh - - -def _dh_params_dup(dh_cdata, backend): - lib = backend._lib - ffi = backend._ffi - - param_cdata = lib.DHparams_dup(dh_cdata) - backend.openssl_assert(param_cdata != ffi.NULL) - param_cdata = ffi.gc(param_cdata, lib.DH_free) - if lib.CRYPTOGRAPHY_IS_LIBRESSL: - # In libressl DHparams_dup don't copy q - q = ffi.new("BIGNUM **") - lib.DH_get0_pqg(dh_cdata, ffi.NULL, q, ffi.NULL) - q_dup = lib.BN_dup(q[0]) - res = lib.DH_set0_pqg(param_cdata, ffi.NULL, q_dup, ffi.NULL) - backend.openssl_assert(res == 1) - - return param_cdata - - -def _dh_cdata_to_parameters(dh_cdata, backend): - param_cdata = _dh_params_dup(dh_cdata, backend) - return _DHParameters(backend, param_cdata) - - -@utils.register_interface(dh.DHParametersWithSerialization) -class _DHParameters(object): - def __init__(self, backend, dh_cdata): - self._backend = backend - self._dh_cdata = dh_cdata - - def parameter_numbers(self): - p = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - if q[0] == self._backend._ffi.NULL: - q_val = None - else: - q_val = self._backend._bn_to_int(q[0]) - return dh.DHParameterNumbers( - p=self._backend._bn_to_int(p[0]), - g=self._backend._bn_to_int(g[0]), - q=q_val, - ) - - def generate_private_key(self): - return self._backend.generate_dh_private_key(self) - - def parameter_bytes(self, encoding, format): - if format is not serialization.ParameterFormat.PKCS3: - raise ValueError("Only PKCS3 serialization is supported") - if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg( - self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL, - ) - if q[0] != self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - return self._backend._parameter_bytes(encoding, format, self._dh_cdata) - - -def _get_dh_num_bits(backend, dh_cdata): - p = backend._ffi.new("BIGNUM **") - backend._lib.DH_get0_pqg(dh_cdata, p, backend._ffi.NULL, backend._ffi.NULL) - backend.openssl_assert(p[0] != backend._ffi.NULL) - return backend._lib.BN_num_bits(p[0]) - - -@utils.register_interface(dh.DHPrivateKeyWithSerialization) -class _DHPrivateKey(object): - def __init__(self, backend, dh_cdata, evp_pkey): - self._backend = backend - self._dh_cdata = dh_cdata - self._evp_pkey = evp_pkey - self._key_size_bytes = self._backend._lib.DH_size(dh_cdata) - - @property - def key_size(self): - return _get_dh_num_bits(self._backend, self._dh_cdata) - - def private_numbers(self): - p = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - if q[0] == self._backend._ffi.NULL: - q_val = None - else: - q_val = self._backend._bn_to_int(q[0]) - pub_key = self._backend._ffi.new("BIGNUM **") - priv_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key(self._dh_cdata, pub_key, priv_key) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) - return dh.DHPrivateNumbers( - public_numbers=dh.DHPublicNumbers( - parameter_numbers=dh.DHParameterNumbers( - p=self._backend._bn_to_int(p[0]), - g=self._backend._bn_to_int(g[0]), - q=q_val, - ), - y=self._backend._bn_to_int(pub_key[0]), - ), - x=self._backend._bn_to_int(priv_key[0]), - ) - - def exchange(self, peer_public_key): - - buf = self._backend._ffi.new("unsigned char[]", self._key_size_bytes) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key( - peer_public_key._dh_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - res = self._backend._lib.DH_compute_key( - buf, pub_key[0], self._dh_cdata - ) - - if res == -1: - errors_with_text = self._backend._consume_errors_with_text() - raise ValueError( - "Error computing shared key. Public key is likely invalid " - "for this exchange.", - errors_with_text, - ) - else: - self._backend.openssl_assert(res >= 1) - - key = self._backend._ffi.buffer(buf)[:res] - pad = self._key_size_bytes - len(key) - - if pad > 0: - key = (b"\x00" * pad) + key - - return key - - def public_key(self): - dh_cdata = _dh_params_dup(self._dh_cdata, self._backend) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key( - self._dh_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) - self._backend.openssl_assert(pub_key_dup != self._backend._ffi.NULL) - - res = self._backend._lib.DH_set0_key( - dh_cdata, pub_key_dup, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res == 1) - evp_pkey = self._backend._dh_cdata_to_evp_pkey(dh_cdata) - return _DHPublicKey(self._backend, dh_cdata, evp_pkey) - - def parameters(self): - return _dh_cdata_to_parameters(self._dh_cdata, self._backend) - - def private_bytes(self, encoding, format, encryption_algorithm): - if format is not serialization.PrivateFormat.PKCS8: - raise ValueError( - "DH private keys support only PKCS8 serialization" - ) - if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg( - self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL, - ) - if q[0] != self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._dh_cdata, - ) - - -@utils.register_interface(dh.DHPublicKeyWithSerialization) -class _DHPublicKey(object): - def __init__(self, backend, dh_cdata, evp_pkey): - self._backend = backend - self._dh_cdata = dh_cdata - self._evp_pkey = evp_pkey - self._key_size_bits = _get_dh_num_bits(self._backend, self._dh_cdata) - - @property - def key_size(self): - return self._key_size_bits - - def public_numbers(self): - p = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - if q[0] == self._backend._ffi.NULL: - q_val = None - else: - q_val = self._backend._bn_to_int(q[0]) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key( - self._dh_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - return dh.DHPublicNumbers( - parameter_numbers=dh.DHParameterNumbers( - p=self._backend._bn_to_int(p[0]), - g=self._backend._bn_to_int(g[0]), - q=q_val, - ), - y=self._backend._bn_to_int(pub_key[0]), - ) - - def parameters(self): - return _dh_cdata_to_parameters(self._dh_cdata, self._backend) - - def public_bytes(self, encoding, format): - if format is not serialization.PublicFormat.SubjectPublicKeyInfo: - raise ValueError( - "DH public keys support only " - "SubjectPublicKeyInfo serialization" - ) - - if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg( - self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL, - ) - if q[0] != self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) diff --git a/src/cryptography/hazmat/backends/openssl/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py deleted file mode 100644 index 0c5faba18ac9..000000000000 --- a/src/cryptography/hazmat/backends/openssl/dsa.py +++ /dev/null @@ -1,263 +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 absolute_import, division, print_function - -from cryptography import utils -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, - _check_not_prehashed, - _warn_sign_verify_deprecated, -) -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ( - AsymmetricSignatureContext, - AsymmetricVerificationContext, - dsa, -) - - -def _dsa_sig_sign(backend, private_key, data): - sig_buf_len = backend._lib.DSA_size(private_key._dsa_cdata) - sig_buf = backend._ffi.new("unsigned char[]", sig_buf_len) - buflen = backend._ffi.new("unsigned int *") - - # The first parameter passed to DSA_sign is unused by OpenSSL but - # must be an integer. - res = backend._lib.DSA_sign( - 0, data, len(data), sig_buf, buflen, private_key._dsa_cdata - ) - backend.openssl_assert(res == 1) - backend.openssl_assert(buflen[0]) - - return backend._ffi.buffer(sig_buf)[: buflen[0]] - - -def _dsa_sig_verify(backend, public_key, signature, data): - # The first parameter passed to DSA_verify is unused by OpenSSL but - # must be an integer. - res = backend._lib.DSA_verify( - 0, data, len(data), signature, len(signature), public_key._dsa_cdata - ) - - if res != 1: - backend._consume_errors() - raise InvalidSignature - - -@utils.register_interface(AsymmetricVerificationContext) -class _DSAVerificationContext(object): - def __init__(self, backend, public_key, signature, algorithm): - self._backend = backend - self._public_key = public_key - self._signature = signature - self._algorithm = algorithm - - self._hash_ctx = hashes.Hash(self._algorithm, self._backend) - - def update(self, data): - self._hash_ctx.update(data) - - def verify(self): - data_to_verify = self._hash_ctx.finalize() - - _dsa_sig_verify( - self._backend, self._public_key, self._signature, data_to_verify - ) - - -@utils.register_interface(AsymmetricSignatureContext) -class _DSASignatureContext(object): - def __init__(self, backend, private_key, algorithm): - self._backend = backend - self._private_key = private_key - self._algorithm = algorithm - self._hash_ctx = hashes.Hash(self._algorithm, self._backend) - - def update(self, data): - self._hash_ctx.update(data) - - def finalize(self): - data_to_sign = self._hash_ctx.finalize() - return _dsa_sig_sign(self._backend, self._private_key, data_to_sign) - - -@utils.register_interface(dsa.DSAParametersWithNumbers) -class _DSAParameters(object): - def __init__(self, backend, dsa_cdata): - self._backend = backend - self._dsa_cdata = dsa_cdata - - def parameter_numbers(self): - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - return dsa.DSAParameterNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]), - ) - - def generate_private_key(self): - return self._backend.generate_dsa_private_key(self) - - -@utils.register_interface(dsa.DSAPrivateKeyWithSerialization) -class _DSAPrivateKey(object): - def __init__(self, backend, dsa_cdata, evp_pkey): - self._backend = backend - self._dsa_cdata = dsa_cdata - self._evp_pkey = evp_pkey - - p = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg( - dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL - ) - self._backend.openssl_assert(p[0] != backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(p[0]) - - key_size = utils.read_only_property("_key_size") - - def signer(self, signature_algorithm): - _warn_sign_verify_deprecated() - _check_not_prehashed(signature_algorithm) - return _DSASignatureContext(self._backend, self, signature_algorithm) - - def private_numbers(self): - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - pub_key = self._backend._ffi.new("BIGNUM **") - priv_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - self._backend._lib.DSA_get0_key(self._dsa_cdata, pub_key, priv_key) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) - return dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]), - ), - y=self._backend._bn_to_int(pub_key[0]), - ), - x=self._backend._bn_to_int(priv_key[0]), - ) - - def public_key(self): - dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) - self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL) - dsa_cdata = self._backend._ffi.gc( - dsa_cdata, self._backend._lib.DSA_free - ) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_key( - self._dsa_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) - res = self._backend._lib.DSA_set0_key( - dsa_cdata, pub_key_dup, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res == 1) - evp_pkey = self._backend._dsa_cdata_to_evp_pkey(dsa_cdata) - return _DSAPublicKey(self._backend, dsa_cdata, evp_pkey) - - def parameters(self): - dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) - self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL) - dsa_cdata = self._backend._ffi.gc( - dsa_cdata, self._backend._lib.DSA_free - ) - return _DSAParameters(self._backend, dsa_cdata) - - def private_bytes(self, encoding, format, encryption_algorithm): - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._dsa_cdata, - ) - - def sign(self, data, algorithm): - data, algorithm = _calculate_digest_and_algorithm( - self._backend, data, algorithm - ) - return _dsa_sig_sign(self._backend, self, data) - - -@utils.register_interface(dsa.DSAPublicKeyWithSerialization) -class _DSAPublicKey(object): - def __init__(self, backend, dsa_cdata, evp_pkey): - self._backend = backend - self._dsa_cdata = dsa_cdata - self._evp_pkey = evp_pkey - p = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg( - dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL - ) - self._backend.openssl_assert(p[0] != backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(p[0]) - - key_size = utils.read_only_property("_key_size") - - def verifier(self, signature, signature_algorithm): - _warn_sign_verify_deprecated() - utils._check_bytes("signature", signature) - - _check_not_prehashed(signature_algorithm) - return _DSAVerificationContext( - self._backend, self, signature, signature_algorithm - ) - - def public_numbers(self): - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - self._backend._lib.DSA_get0_key( - self._dsa_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - return dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]), - ), - y=self._backend._bn_to_int(pub_key[0]), - ) - - def parameters(self): - dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) - dsa_cdata = self._backend._ffi.gc( - dsa_cdata, self._backend._lib.DSA_free - ) - return _DSAParameters(self._backend, dsa_cdata) - - def public_bytes(self, encoding, format): - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def verify(self, signature, data, algorithm): - data, algorithm = _calculate_digest_and_algorithm( - self._backend, data, algorithm - ) - return _dsa_sig_verify(self._backend, self, signature, data) diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py index bf61bcf16b20..9821bd193e29 100644 --- a/src/cryptography/hazmat/backends/openssl/ec.py +++ b/src/cryptography/hazmat/backends/openssl/ec.py @@ -2,9 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import typing -from cryptography import utils from cryptography.exceptions import ( InvalidSignature, UnsupportedAlgorithm, @@ -12,18 +13,18 @@ ) from cryptography.hazmat.backends.openssl.utils import ( _calculate_digest_and_algorithm, - _check_not_prehashed, - _warn_sign_verify_deprecated, -) -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ( - AsymmetricSignatureContext, - AsymmetricVerificationContext, - ec, + _evp_pkey_derive, ) +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec + +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.backend import Backend -def _check_signature_algorithm(signature_algorithm): +def _check_signature_algorithm( + signature_algorithm: ec.EllipticCurveSignatureAlgorithm, +) -> None: if not isinstance(signature_algorithm, ec.ECDSA): raise UnsupportedAlgorithm( "Unsupported elliptic curve signature algorithm.", @@ -31,7 +32,7 @@ def _check_signature_algorithm(signature_algorithm): ) -def _ec_key_curve_sn(backend, ec_key): +def _ec_key_curve_sn(backend: Backend, ec_key) -> str: group = backend._lib.EC_KEY_get0_group(ec_key) backend.openssl_assert(group != backend._ffi.NULL) @@ -39,19 +40,19 @@ def _ec_key_curve_sn(backend, ec_key): # The following check is to find EC keys with unnamed curves and raise # an error for now. if nid == backend._lib.NID_undef: - raise NotImplementedError( - "ECDSA keys with unnamed curves are unsupported " "at this time" + raise ValueError( + "ECDSA keys with explicit parameters are unsupported at this time" ) # This is like the above check, but it also catches the case where you # explicitly encoded a curve with the same parameters as a named curve. # Don't do that. if ( - backend._lib.CRYPTOGRAPHY_OPENSSL_102U_OR_GREATER + not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL and backend._lib.EC_GROUP_get_asn1_flag(group) == 0 ): - raise NotImplementedError( - "ECDSA keys with unnamed curves are unsupported " "at this time" + raise ValueError( + "ECDSA keys with explicit parameters are unsupported at this time" ) curve_name = backend._lib.OBJ_nid2sn(nid) @@ -61,7 +62,7 @@ def _ec_key_curve_sn(backend, ec_key): return sn -def _mark_asn1_named_ec_curve(backend, ec_cdata): +def _mark_asn1_named_ec_curve(backend: Backend, ec_cdata): """ Set the named curve flag on the EC_KEY. This causes OpenSSL to serialize EC keys along with their curve OID which makes @@ -73,17 +74,30 @@ def _mark_asn1_named_ec_curve(backend, ec_cdata): ) -def _sn_to_elliptic_curve(backend, sn): +def _check_key_infinity(backend: Backend, ec_cdata) -> None: + point = backend._lib.EC_KEY_get0_public_key(ec_cdata) + backend.openssl_assert(point != backend._ffi.NULL) + group = backend._lib.EC_KEY_get0_group(ec_cdata) + backend.openssl_assert(group != backend._ffi.NULL) + if backend._lib.EC_POINT_is_at_infinity(group, point): + raise ValueError( + "Cannot load an EC public key where the point is at infinity" + ) + + +def _sn_to_elliptic_curve(backend: Backend, sn: str) -> ec.EllipticCurve: try: return ec._CURVE_TYPES[sn]() except KeyError: raise UnsupportedAlgorithm( - "{} is not a supported elliptic curve".format(sn), + f"{sn} is not a supported elliptic curve", _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) -def _ecdsa_sig_sign(backend, private_key, data): +def _ecdsa_sig_sign( + backend: Backend, private_key: _EllipticCurvePrivateKey, data: bytes +) -> bytes: max_size = backend._lib.ECDSA_size(private_key._ec_key) backend.openssl_assert(max_size > 0) @@ -96,7 +110,12 @@ def _ecdsa_sig_sign(backend, private_key, data): return backend._ffi.buffer(sigbuf)[: siglen_ptr[0]] -def _ecdsa_sig_verify(backend, public_key, signature, data): +def _ecdsa_sig_verify( + backend: Backend, + public_key: _EllipticCurvePublicKey, + signature: bytes, + data: bytes, +) -> None: res = backend._lib.ECDSA_verify( 0, data, len(data), signature, len(signature), public_key._ec_key ) @@ -105,43 +124,8 @@ def _ecdsa_sig_verify(backend, public_key, signature, data): raise InvalidSignature -@utils.register_interface(AsymmetricSignatureContext) -class _ECDSASignatureContext(object): - def __init__(self, backend, private_key, algorithm): - self._backend = backend - self._private_key = private_key - self._digest = hashes.Hash(algorithm, backend) - - def update(self, data): - self._digest.update(data) - - def finalize(self): - digest = self._digest.finalize() - - return _ecdsa_sig_sign(self._backend, self._private_key, digest) - - -@utils.register_interface(AsymmetricVerificationContext) -class _ECDSAVerificationContext(object): - def __init__(self, backend, public_key, signature, algorithm): - self._backend = backend - self._public_key = public_key - self._signature = signature - self._digest = hashes.Hash(algorithm, backend) - - def update(self, data): - self._digest.update(data) - - def verify(self): - digest = self._digest.finalize() - _ecdsa_sig_verify( - self._backend, self._public_key, self._signature, digest - ) - - -@utils.register_interface(ec.EllipticCurvePrivateKeyWithSerialization) -class _EllipticCurvePrivateKey(object): - def __init__(self, backend, ec_key_cdata, evp_pkey): +class _EllipticCurvePrivateKey(ec.EllipticCurvePrivateKey): + def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): self._backend = backend self._ec_key = ec_key_cdata self._evp_pkey = evp_pkey @@ -149,22 +133,19 @@ def __init__(self, backend, ec_key_cdata, evp_pkey): sn = _ec_key_curve_sn(backend, ec_key_cdata) self._curve = _sn_to_elliptic_curve(backend, sn) _mark_asn1_named_ec_curve(backend, ec_key_cdata) + _check_key_infinity(backend, ec_key_cdata) - curve = utils.read_only_property("_curve") + @property + def curve(self) -> ec.EllipticCurve: + return self._curve @property - def key_size(self): + def key_size(self) -> int: return self.curve.key_size - def signer(self, signature_algorithm): - _warn_sign_verify_deprecated() - _check_signature_algorithm(signature_algorithm) - _check_not_prehashed(signature_algorithm.algorithm) - return _ECDSASignatureContext( - self._backend, self, signature_algorithm.algorithm - ) - - def exchange(self, algorithm, peer_public_key): + def exchange( + self, algorithm: ec.ECDH, peer_public_key: ec.EllipticCurvePublicKey + ) -> bytes: if not ( self._backend.elliptic_curve_exchange_algorithm_supported( algorithm, self.curve @@ -180,21 +161,9 @@ def exchange(self, algorithm, peer_public_key): "peer_public_key and self are not on the same curve" ) - group = self._backend._lib.EC_KEY_get0_group(self._ec_key) - z_len = (self._backend._lib.EC_GROUP_get_degree(group) + 7) // 8 - self._backend.openssl_assert(z_len > 0) - z_buf = self._backend._ffi.new("uint8_t[]", z_len) - peer_key = self._backend._lib.EC_KEY_get0_public_key( - peer_public_key._ec_key - ) - - r = self._backend._lib.ECDH_compute_key( - z_buf, z_len, peer_key, self._ec_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(r > 0) - return self._backend._ffi.buffer(z_buf)[:z_len] + return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) - def public_key(self): + def public_key(self) -> ec.EllipticCurvePublicKey: group = self._backend._lib.EC_KEY_get0_group(self._ec_key) self._backend.openssl_assert(group != self._backend._ffi.NULL) @@ -211,7 +180,7 @@ def public_key(self): return _EllipticCurvePublicKey(self._backend, public_ec_key, evp_pkey) - def private_numbers(self): + def private_numbers(self) -> ec.EllipticCurvePrivateNumbers: bn = self._backend._lib.EC_KEY_get0_private_key(self._ec_key) private_value = self._backend._bn_to_int(bn) return ec.EllipticCurvePrivateNumbers( @@ -219,7 +188,12 @@ def private_numbers(self): public_numbers=self.public_key().public_numbers(), ) - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: serialization.Encoding, + format: serialization.PrivateFormat, + encryption_algorithm: serialization.KeySerializationEncryption, + ) -> bytes: return self._backend._private_key_bytes( encoding, format, @@ -229,17 +203,21 @@ def private_bytes(self, encoding, format, encryption_algorithm): self._ec_key, ) - def sign(self, data, signature_algorithm): + def sign( + self, + data: bytes, + signature_algorithm: ec.EllipticCurveSignatureAlgorithm, + ) -> bytes: _check_signature_algorithm(signature_algorithm) - data, algorithm = _calculate_digest_and_algorithm( - self._backend, data, signature_algorithm._algorithm + data, _ = _calculate_digest_and_algorithm( + data, + signature_algorithm.algorithm, ) return _ecdsa_sig_sign(self._backend, self, data) -@utils.register_interface(ec.EllipticCurvePublicKeyWithSerialization) -class _EllipticCurvePublicKey(object): - def __init__(self, backend, ec_key_cdata, evp_pkey): +class _EllipticCurvePublicKey(ec.EllipticCurvePublicKey): + def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): self._backend = backend self._ec_key = ec_key_cdata self._evp_pkey = evp_pkey @@ -247,27 +225,29 @@ def __init__(self, backend, ec_key_cdata, evp_pkey): sn = _ec_key_curve_sn(backend, ec_key_cdata) self._curve = _sn_to_elliptic_curve(backend, sn) _mark_asn1_named_ec_curve(backend, ec_key_cdata) + _check_key_infinity(backend, ec_key_cdata) - curve = utils.read_only_property("_curve") + @property + def curve(self) -> ec.EllipticCurve: + return self._curve @property - def key_size(self): + def key_size(self) -> int: return self.curve.key_size - def verifier(self, signature, signature_algorithm): - _warn_sign_verify_deprecated() - utils._check_bytes("signature", signature) + def __eq__(self, other: object) -> bool: + if not isinstance(other, _EllipticCurvePublicKey): + return NotImplemented - _check_signature_algorithm(signature_algorithm) - _check_not_prehashed(signature_algorithm.algorithm) - return _ECDSAVerificationContext( - self._backend, self, signature, signature_algorithm.algorithm + return ( + self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) + == 1 ) - def public_numbers(self): - get_func, group = self._backend._ec_key_determine_group_get_func( - self._ec_key - ) + def public_numbers(self) -> ec.EllipticCurvePublicNumbers: + group = self._backend._lib.EC_KEY_get0_group(self._ec_key) + self._backend.openssl_assert(group != self._backend._ffi.NULL) + point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) self._backend.openssl_assert(point != self._backend._ffi.NULL) @@ -275,7 +255,9 @@ def public_numbers(self): bn_x = self._backend._lib.BN_CTX_get(bn_ctx) bn_y = self._backend._lib.BN_CTX_get(bn_ctx) - res = get_func(group, point, bn_x, bn_y, bn_ctx) + res = self._backend._lib.EC_POINT_get_affine_coordinates( + group, point, bn_x, bn_y, bn_ctx + ) self._backend.openssl_assert(res == 1) x = self._backend._bn_to_int(bn_x) @@ -283,7 +265,7 @@ def public_numbers(self): return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=self._curve) - def _encode_point(self, format): + def _encode_point(self, format: serialization.PublicFormat) -> bytes: if format is serialization.PublicFormat.CompressedPoint: conversion = self._backend._lib.POINT_CONVERSION_COMPRESSED else: @@ -307,8 +289,11 @@ def _encode_point(self, format): return self._backend._ffi.buffer(buf)[:] - def public_bytes(self, encoding, format): - + def public_bytes( + self, + encoding: serialization.Encoding, + format: serialization.PublicFormat, + ) -> bytes: if ( encoding is serialization.Encoding.X962 or format is serialization.PublicFormat.CompressedPoint @@ -329,9 +314,15 @@ def public_bytes(self, encoding, format): encoding, format, self, self._evp_pkey, None ) - def verify(self, signature, data, signature_algorithm): + def verify( + self, + signature: bytes, + data: bytes, + signature_algorithm: ec.EllipticCurveSignatureAlgorithm, + ) -> None: _check_signature_algorithm(signature_algorithm) - data, algorithm = _calculate_digest_and_algorithm( - self._backend, data, signature_algorithm._algorithm + data, _ = _calculate_digest_and_algorithm( + data, + signature_algorithm.algorithm, ) _ecdsa_sig_verify(self._backend, self, signature, data) diff --git a/src/cryptography/hazmat/backends/openssl/ed25519.py b/src/cryptography/hazmat/backends/openssl/ed25519.py deleted file mode 100644 index 75653373b3cf..000000000000 --- a/src/cryptography/hazmat/backends/openssl/ed25519.py +++ /dev/null @@ -1,145 +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 absolute_import, division, print_function - -from cryptography import exceptions, utils -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.ed25519 import ( - Ed25519PrivateKey, - Ed25519PublicKey, - _ED25519_KEY_SIZE, - _ED25519_SIG_SIZE, -) - - -@utils.register_interface(Ed25519PublicKey) -class _Ed25519PublicKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes(self, encoding, format): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self): - buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:] - - def verify(self, signature, data): - evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestVerifyInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - res = self._backend._lib.EVP_DigestVerify( - evp_md_ctx, signature, len(signature), data, len(data) - ) - if res != 1: - self._backend._consume_errors() - raise exceptions.InvalidSignature - - -@utils.register_interface(Ed25519PrivateKey) -class _Ed25519PrivateKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self): - buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) - public_bytes = self._backend._ffi.buffer(buf)[:] - return self._backend.ed25519_load_public_bytes(public_bytes) - - def sign(self, data): - evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestSignInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - buf = self._backend._ffi.new("unsigned char[]", _ED25519_SIG_SIZE) - buflen = self._backend._ffi.new("size_t *", len(buf)) - res = self._backend._lib.EVP_DigestSign( - evp_md_ctx, buf, buflen, data, len(data) - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_SIG_SIZE) - return self._backend._ffi.buffer(buf, buflen[0])[:] - - def private_bytes(self, encoding, format, encryption_algorithm): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self): - buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_private_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/ed448.py b/src/cryptography/hazmat/backends/openssl/ed448.py deleted file mode 100644 index 4a8dab1a8115..000000000000 --- a/src/cryptography/hazmat/backends/openssl/ed448.py +++ /dev/null @@ -1,146 +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 absolute_import, division, print_function - -from cryptography import exceptions, utils -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.ed448 import ( - Ed448PrivateKey, - Ed448PublicKey, -) - -_ED448_KEY_SIZE = 57 -_ED448_SIG_SIZE = 114 - - -@utils.register_interface(Ed448PublicKey) -class _Ed448PublicKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes(self, encoding, format): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self): - buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:] - - def verify(self, signature, data): - evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestVerifyInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - res = self._backend._lib.EVP_DigestVerify( - evp_md_ctx, signature, len(signature), data, len(data) - ) - if res != 1: - self._backend._consume_errors() - raise exceptions.InvalidSignature - - -@utils.register_interface(Ed448PrivateKey) -class _Ed448PrivateKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self): - buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) - public_bytes = self._backend._ffi.buffer(buf)[:] - return self._backend.ed448_load_public_bytes(public_bytes) - - def sign(self, data): - evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestSignInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - buf = self._backend._ffi.new("unsigned char[]", _ED448_SIG_SIZE) - buflen = self._backend._ffi.new("size_t *", len(buf)) - res = self._backend._lib.EVP_DigestSign( - evp_md_ctx, buf, buflen, data, len(data) - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_SIG_SIZE) - return self._backend._ffi.buffer(buf, buflen[0])[:] - - def private_bytes(self, encoding, format, encryption_algorithm): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self): - buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_private_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py deleted file mode 100644 index 88d709d21457..000000000000 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ /dev/null @@ -1,657 +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 absolute_import, division, print_function - -import calendar -import ipaddress - -import six - -from cryptography import utils, x509 -from cryptography.hazmat.backends.openssl.decode_asn1 import ( - _CRL_ENTRY_REASON_ENUM_TO_CODE, - _DISTPOINT_TYPE_FULLNAME, - _DISTPOINT_TYPE_RELATIVENAME, -) -from cryptography.x509.name import _ASN1Type -from cryptography.x509.oid import ( - CRLEntryExtensionOID, - ExtensionOID, - OCSPExtensionOID, -) - - -def _encode_asn1_int(backend, x): - """ - Converts a python integer to an ASN1_INTEGER. The returned ASN1_INTEGER - will not be garbage collected (to support adding them to structs that take - ownership of the object). Be sure to register it for GC if it will be - discarded after use. - - """ - # Convert Python integer to OpenSSL "bignum" in case value exceeds - # machine's native integer limits (note: `int_to_bn` doesn't automatically - # GC). - i = backend._int_to_bn(x) - i = backend._ffi.gc(i, backend._lib.BN_free) - - # Wrap in an ASN.1 integer. Don't GC -- as documented. - i = backend._lib.BN_to_ASN1_INTEGER(i, backend._ffi.NULL) - backend.openssl_assert(i != backend._ffi.NULL) - return i - - -def _encode_asn1_int_gc(backend, x): - i = _encode_asn1_int(backend, x) - i = backend._ffi.gc(i, backend._lib.ASN1_INTEGER_free) - return i - - -def _encode_asn1_str(backend, data): - """ - Create an ASN1_OCTET_STRING from a Python byte string. - """ - s = backend._lib.ASN1_OCTET_STRING_new() - res = backend._lib.ASN1_OCTET_STRING_set(s, data, len(data)) - backend.openssl_assert(res == 1) - return s - - -def _encode_asn1_utf8_str(backend, string): - """ - Create an ASN1_UTF8STRING from a Python unicode string. - This object will be an ASN1_STRING with UTF8 type in OpenSSL and - can be decoded with ASN1_STRING_to_UTF8. - """ - s = backend._lib.ASN1_UTF8STRING_new() - res = backend._lib.ASN1_STRING_set( - s, string.encode("utf8"), len(string.encode("utf8")) - ) - backend.openssl_assert(res == 1) - return s - - -def _encode_asn1_str_gc(backend, data): - s = _encode_asn1_str(backend, data) - s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free) - return s - - -def _encode_inhibit_any_policy(backend, inhibit_any_policy): - return _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs) - - -def _encode_name(backend, name): - """ - The X509_NAME created will not be gc'd. Use _encode_name_gc if needed. - """ - subject = backend._lib.X509_NAME_new() - for rdn in name.rdns: - set_flag = 0 # indicate whether to add to last RDN or create new RDN - for attribute in rdn: - name_entry = _encode_name_entry(backend, attribute) - # X509_NAME_add_entry dups the object so we need to gc this copy - name_entry = backend._ffi.gc( - name_entry, backend._lib.X509_NAME_ENTRY_free - ) - res = backend._lib.X509_NAME_add_entry( - subject, name_entry, -1, set_flag - ) - backend.openssl_assert(res == 1) - set_flag = -1 - return subject - - -def _encode_name_gc(backend, attributes): - subject = _encode_name(backend, attributes) - subject = backend._ffi.gc(subject, backend._lib.X509_NAME_free) - return subject - - -def _encode_sk_name_entry(backend, attributes): - """ - The sk_X509_NAME_ENTRY created will not be gc'd. - """ - stack = backend._lib.sk_X509_NAME_ENTRY_new_null() - for attribute in attributes: - name_entry = _encode_name_entry(backend, attribute) - res = backend._lib.sk_X509_NAME_ENTRY_push(stack, name_entry) - backend.openssl_assert(res >= 1) - return stack - - -def _encode_name_entry(backend, attribute): - if attribute._type is _ASN1Type.BMPString: - value = attribute.value.encode("utf_16_be") - elif attribute._type is _ASN1Type.UniversalString: - value = attribute.value.encode("utf_32_be") - else: - value = attribute.value.encode("utf8") - - obj = _txt2obj_gc(backend, attribute.oid.dotted_string) - - name_entry = backend._lib.X509_NAME_ENTRY_create_by_OBJ( - backend._ffi.NULL, obj, attribute._type.value, value, len(value) - ) - return name_entry - - -def _encode_crl_number_delta_crl_indicator(backend, ext): - return _encode_asn1_int_gc(backend, ext.crl_number) - - -def _encode_issuing_dist_point(backend, ext): - idp = backend._lib.ISSUING_DIST_POINT_new() - backend.openssl_assert(idp != backend._ffi.NULL) - idp = backend._ffi.gc(idp, backend._lib.ISSUING_DIST_POINT_free) - idp.onlyuser = 255 if ext.only_contains_user_certs else 0 - idp.onlyCA = 255 if ext.only_contains_ca_certs else 0 - idp.indirectCRL = 255 if ext.indirect_crl else 0 - idp.onlyattr = 255 if ext.only_contains_attribute_certs else 0 - if ext.only_some_reasons: - idp.onlysomereasons = _encode_reasonflags( - backend, ext.only_some_reasons - ) - - if ext.full_name: - idp.distpoint = _encode_full_name(backend, ext.full_name) - - if ext.relative_name: - idp.distpoint = _encode_relative_name(backend, ext.relative_name) - - return idp - - -def _encode_crl_reason(backend, crl_reason): - asn1enum = backend._lib.ASN1_ENUMERATED_new() - backend.openssl_assert(asn1enum != backend._ffi.NULL) - asn1enum = backend._ffi.gc(asn1enum, backend._lib.ASN1_ENUMERATED_free) - res = backend._lib.ASN1_ENUMERATED_set( - asn1enum, _CRL_ENTRY_REASON_ENUM_TO_CODE[crl_reason.reason] - ) - backend.openssl_assert(res == 1) - - return asn1enum - - -def _encode_invalidity_date(backend, invalidity_date): - time = backend._lib.ASN1_GENERALIZEDTIME_set( - backend._ffi.NULL, - calendar.timegm(invalidity_date.invalidity_date.timetuple()), - ) - backend.openssl_assert(time != backend._ffi.NULL) - time = backend._ffi.gc(time, backend._lib.ASN1_GENERALIZEDTIME_free) - - return time - - -def _encode_certificate_policies(backend, certificate_policies): - cp = backend._lib.sk_POLICYINFO_new_null() - backend.openssl_assert(cp != backend._ffi.NULL) - cp = backend._ffi.gc(cp, backend._lib.sk_POLICYINFO_free) - for policy_info in certificate_policies: - pi = backend._lib.POLICYINFO_new() - backend.openssl_assert(pi != backend._ffi.NULL) - res = backend._lib.sk_POLICYINFO_push(cp, pi) - backend.openssl_assert(res >= 1) - oid = _txt2obj(backend, policy_info.policy_identifier.dotted_string) - pi.policyid = oid - if policy_info.policy_qualifiers: - pqis = backend._lib.sk_POLICYQUALINFO_new_null() - backend.openssl_assert(pqis != backend._ffi.NULL) - for qualifier in policy_info.policy_qualifiers: - pqi = backend._lib.POLICYQUALINFO_new() - backend.openssl_assert(pqi != backend._ffi.NULL) - res = backend._lib.sk_POLICYQUALINFO_push(pqis, pqi) - backend.openssl_assert(res >= 1) - if isinstance(qualifier, six.text_type): - pqi.pqualid = _txt2obj( - backend, x509.OID_CPS_QUALIFIER.dotted_string - ) - pqi.d.cpsuri = _encode_asn1_str( - backend, - qualifier.encode("ascii"), - ) - else: - assert isinstance(qualifier, x509.UserNotice) - pqi.pqualid = _txt2obj( - backend, x509.OID_CPS_USER_NOTICE.dotted_string - ) - un = backend._lib.USERNOTICE_new() - backend.openssl_assert(un != backend._ffi.NULL) - pqi.d.usernotice = un - if qualifier.explicit_text: - un.exptext = _encode_asn1_utf8_str( - backend, qualifier.explicit_text - ) - - un.noticeref = _encode_notice_reference( - backend, qualifier.notice_reference - ) - - pi.qualifiers = pqis - - return cp - - -def _encode_notice_reference(backend, notice): - if notice is None: - return backend._ffi.NULL - else: - nr = backend._lib.NOTICEREF_new() - backend.openssl_assert(nr != backend._ffi.NULL) - # organization is a required field - nr.organization = _encode_asn1_utf8_str(backend, notice.organization) - - notice_stack = backend._lib.sk_ASN1_INTEGER_new_null() - nr.noticenos = notice_stack - for number in notice.notice_numbers: - num = _encode_asn1_int(backend, number) - res = backend._lib.sk_ASN1_INTEGER_push(notice_stack, num) - backend.openssl_assert(res >= 1) - - return nr - - -def _txt2obj(backend, name): - """ - Converts a Python string with an ASN.1 object ID in dotted form to a - ASN1_OBJECT. - """ - name = name.encode("ascii") - obj = backend._lib.OBJ_txt2obj(name, 1) - backend.openssl_assert(obj != backend._ffi.NULL) - return obj - - -def _txt2obj_gc(backend, name): - obj = _txt2obj(backend, name) - obj = backend._ffi.gc(obj, backend._lib.ASN1_OBJECT_free) - return obj - - -def _encode_ocsp_nocheck(backend, ext): - # Doesn't need to be GC'd - return backend._lib.ASN1_NULL_new() - - -def _encode_key_usage(backend, key_usage): - set_bit = backend._lib.ASN1_BIT_STRING_set_bit - ku = backend._lib.ASN1_BIT_STRING_new() - ku = backend._ffi.gc(ku, backend._lib.ASN1_BIT_STRING_free) - res = set_bit(ku, 0, key_usage.digital_signature) - backend.openssl_assert(res == 1) - res = set_bit(ku, 1, key_usage.content_commitment) - backend.openssl_assert(res == 1) - res = set_bit(ku, 2, key_usage.key_encipherment) - backend.openssl_assert(res == 1) - res = set_bit(ku, 3, key_usage.data_encipherment) - backend.openssl_assert(res == 1) - res = set_bit(ku, 4, key_usage.key_agreement) - backend.openssl_assert(res == 1) - res = set_bit(ku, 5, key_usage.key_cert_sign) - backend.openssl_assert(res == 1) - res = set_bit(ku, 6, key_usage.crl_sign) - backend.openssl_assert(res == 1) - if key_usage.key_agreement: - res = set_bit(ku, 7, key_usage.encipher_only) - backend.openssl_assert(res == 1) - res = set_bit(ku, 8, key_usage.decipher_only) - backend.openssl_assert(res == 1) - else: - res = set_bit(ku, 7, 0) - backend.openssl_assert(res == 1) - res = set_bit(ku, 8, 0) - backend.openssl_assert(res == 1) - - return ku - - -def _encode_authority_key_identifier(backend, authority_keyid): - akid = backend._lib.AUTHORITY_KEYID_new() - backend.openssl_assert(akid != backend._ffi.NULL) - akid = backend._ffi.gc(akid, backend._lib.AUTHORITY_KEYID_free) - if authority_keyid.key_identifier is not None: - akid.keyid = _encode_asn1_str( - backend, - authority_keyid.key_identifier, - ) - - if authority_keyid.authority_cert_issuer is not None: - akid.issuer = _encode_general_names( - backend, authority_keyid.authority_cert_issuer - ) - - if authority_keyid.authority_cert_serial_number is not None: - akid.serial = _encode_asn1_int( - backend, authority_keyid.authority_cert_serial_number - ) - - return akid - - -def _encode_basic_constraints(backend, basic_constraints): - constraints = backend._lib.BASIC_CONSTRAINTS_new() - constraints = backend._ffi.gc( - constraints, backend._lib.BASIC_CONSTRAINTS_free - ) - constraints.ca = 255 if basic_constraints.ca else 0 - if basic_constraints.ca and basic_constraints.path_length is not None: - constraints.pathlen = _encode_asn1_int( - backend, basic_constraints.path_length - ) - - return constraints - - -def _encode_information_access(backend, info_access): - aia = backend._lib.sk_ACCESS_DESCRIPTION_new_null() - backend.openssl_assert(aia != backend._ffi.NULL) - aia = backend._ffi.gc( - aia, - lambda x: backend._lib.sk_ACCESS_DESCRIPTION_pop_free( - x, - backend._ffi.addressof( - backend._lib._original_lib, "ACCESS_DESCRIPTION_free" - ), - ), - ) - for access_description in info_access: - ad = backend._lib.ACCESS_DESCRIPTION_new() - method = _txt2obj( - backend, access_description.access_method.dotted_string - ) - _encode_general_name_preallocated( - backend, access_description.access_location, ad.location - ) - ad.method = method - res = backend._lib.sk_ACCESS_DESCRIPTION_push(aia, ad) - backend.openssl_assert(res >= 1) - - return aia - - -def _encode_general_names(backend, names): - general_names = backend._lib.GENERAL_NAMES_new() - backend.openssl_assert(general_names != backend._ffi.NULL) - for name in names: - gn = _encode_general_name(backend, name) - res = backend._lib.sk_GENERAL_NAME_push(general_names, gn) - backend.openssl_assert(res != 0) - - return general_names - - -def _encode_alt_name(backend, san): - general_names = _encode_general_names(backend, san) - general_names = backend._ffi.gc( - general_names, backend._lib.GENERAL_NAMES_free - ) - return general_names - - -def _encode_subject_key_identifier(backend, ski): - return _encode_asn1_str_gc(backend, ski.digest) - - -def _encode_general_name(backend, name): - gn = backend._lib.GENERAL_NAME_new() - _encode_general_name_preallocated(backend, name, gn) - return gn - - -def _encode_general_name_preallocated(backend, name, gn): - if isinstance(name, x509.DNSName): - backend.openssl_assert(gn != backend._ffi.NULL) - gn.type = backend._lib.GEN_DNS - - ia5 = backend._lib.ASN1_IA5STRING_new() - backend.openssl_assert(ia5 != backend._ffi.NULL) - # ia5strings are supposed to be ITU T.50 but to allow round-tripping - # of broken certs that encode utf8 we'll encode utf8 here too. - value = name.value.encode("utf8") - - res = backend._lib.ASN1_STRING_set(ia5, value, len(value)) - backend.openssl_assert(res == 1) - gn.d.dNSName = ia5 - elif isinstance(name, x509.RegisteredID): - backend.openssl_assert(gn != backend._ffi.NULL) - gn.type = backend._lib.GEN_RID - obj = backend._lib.OBJ_txt2obj( - name.value.dotted_string.encode("ascii"), 1 - ) - backend.openssl_assert(obj != backend._ffi.NULL) - gn.d.registeredID = obj - elif isinstance(name, x509.DirectoryName): - backend.openssl_assert(gn != backend._ffi.NULL) - dir_name = _encode_name(backend, name.value) - gn.type = backend._lib.GEN_DIRNAME - gn.d.directoryName = dir_name - elif isinstance(name, x509.IPAddress): - backend.openssl_assert(gn != backend._ffi.NULL) - if isinstance(name.value, ipaddress.IPv4Network): - packed = name.value.network_address.packed + utils.int_to_bytes( - ((1 << 32) - name.value.num_addresses), 4 - ) - elif isinstance(name.value, ipaddress.IPv6Network): - packed = name.value.network_address.packed + utils.int_to_bytes( - (1 << 128) - name.value.num_addresses, 16 - ) - else: - packed = name.value.packed - ipaddr = _encode_asn1_str(backend, packed) - gn.type = backend._lib.GEN_IPADD - gn.d.iPAddress = ipaddr - elif isinstance(name, x509.OtherName): - backend.openssl_assert(gn != backend._ffi.NULL) - other_name = backend._lib.OTHERNAME_new() - backend.openssl_assert(other_name != backend._ffi.NULL) - - type_id = backend._lib.OBJ_txt2obj( - name.type_id.dotted_string.encode("ascii"), 1 - ) - backend.openssl_assert(type_id != backend._ffi.NULL) - data = backend._ffi.new("unsigned char[]", name.value) - data_ptr_ptr = backend._ffi.new("unsigned char **") - data_ptr_ptr[0] = data - value = backend._lib.d2i_ASN1_TYPE( - backend._ffi.NULL, data_ptr_ptr, len(name.value) - ) - if value == backend._ffi.NULL: - backend._consume_errors() - raise ValueError("Invalid ASN.1 data") - other_name.type_id = type_id - other_name.value = value - gn.type = backend._lib.GEN_OTHERNAME - gn.d.otherName = other_name - elif isinstance(name, x509.RFC822Name): - backend.openssl_assert(gn != backend._ffi.NULL) - # ia5strings are supposed to be ITU T.50 but to allow round-tripping - # of broken certs that encode utf8 we'll encode utf8 here too. - data = name.value.encode("utf8") - asn1_str = _encode_asn1_str(backend, data) - gn.type = backend._lib.GEN_EMAIL - gn.d.rfc822Name = asn1_str - elif isinstance(name, x509.UniformResourceIdentifier): - backend.openssl_assert(gn != backend._ffi.NULL) - # ia5strings are supposed to be ITU T.50 but to allow round-tripping - # of broken certs that encode utf8 we'll encode utf8 here too. - data = name.value.encode("utf8") - asn1_str = _encode_asn1_str(backend, data) - gn.type = backend._lib.GEN_URI - gn.d.uniformResourceIdentifier = asn1_str - else: - raise ValueError("{} is an unknown GeneralName type".format(name)) - - -def _encode_extended_key_usage(backend, extended_key_usage): - eku = backend._lib.sk_ASN1_OBJECT_new_null() - eku = backend._ffi.gc(eku, backend._lib.sk_ASN1_OBJECT_free) - for oid in extended_key_usage: - obj = _txt2obj(backend, oid.dotted_string) - res = backend._lib.sk_ASN1_OBJECT_push(eku, obj) - backend.openssl_assert(res >= 1) - - return eku - - -_CRLREASONFLAGS = { - x509.ReasonFlags.key_compromise: 1, - x509.ReasonFlags.ca_compromise: 2, - x509.ReasonFlags.affiliation_changed: 3, - x509.ReasonFlags.superseded: 4, - x509.ReasonFlags.cessation_of_operation: 5, - x509.ReasonFlags.certificate_hold: 6, - x509.ReasonFlags.privilege_withdrawn: 7, - x509.ReasonFlags.aa_compromise: 8, -} - - -def _encode_reasonflags(backend, reasons): - bitmask = backend._lib.ASN1_BIT_STRING_new() - backend.openssl_assert(bitmask != backend._ffi.NULL) - for reason in reasons: - res = backend._lib.ASN1_BIT_STRING_set_bit( - bitmask, _CRLREASONFLAGS[reason], 1 - ) - backend.openssl_assert(res == 1) - - return bitmask - - -def _encode_full_name(backend, full_name): - dpn = backend._lib.DIST_POINT_NAME_new() - backend.openssl_assert(dpn != backend._ffi.NULL) - dpn.type = _DISTPOINT_TYPE_FULLNAME - dpn.name.fullname = _encode_general_names(backend, full_name) - return dpn - - -def _encode_relative_name(backend, relative_name): - dpn = backend._lib.DIST_POINT_NAME_new() - backend.openssl_assert(dpn != backend._ffi.NULL) - dpn.type = _DISTPOINT_TYPE_RELATIVENAME - dpn.name.relativename = _encode_sk_name_entry(backend, relative_name) - return dpn - - -def _encode_cdps_freshest_crl(backend, cdps): - cdp = backend._lib.sk_DIST_POINT_new_null() - cdp = backend._ffi.gc(cdp, backend._lib.sk_DIST_POINT_free) - for point in cdps: - dp = backend._lib.DIST_POINT_new() - backend.openssl_assert(dp != backend._ffi.NULL) - - if point.reasons: - dp.reasons = _encode_reasonflags(backend, point.reasons) - - if point.full_name: - dp.distpoint = _encode_full_name(backend, point.full_name) - - if point.relative_name: - dp.distpoint = _encode_relative_name(backend, point.relative_name) - - if point.crl_issuer: - dp.CRLissuer = _encode_general_names(backend, point.crl_issuer) - - res = backend._lib.sk_DIST_POINT_push(cdp, dp) - backend.openssl_assert(res >= 1) - - return cdp - - -def _encode_name_constraints(backend, name_constraints): - nc = backend._lib.NAME_CONSTRAINTS_new() - backend.openssl_assert(nc != backend._ffi.NULL) - nc = backend._ffi.gc(nc, backend._lib.NAME_CONSTRAINTS_free) - permitted = _encode_general_subtree( - backend, name_constraints.permitted_subtrees - ) - nc.permittedSubtrees = permitted - excluded = _encode_general_subtree( - backend, name_constraints.excluded_subtrees - ) - nc.excludedSubtrees = excluded - - return nc - - -def _encode_policy_constraints(backend, policy_constraints): - pc = backend._lib.POLICY_CONSTRAINTS_new() - backend.openssl_assert(pc != backend._ffi.NULL) - pc = backend._ffi.gc(pc, backend._lib.POLICY_CONSTRAINTS_free) - if policy_constraints.require_explicit_policy is not None: - pc.requireExplicitPolicy = _encode_asn1_int( - backend, policy_constraints.require_explicit_policy - ) - - if policy_constraints.inhibit_policy_mapping is not None: - pc.inhibitPolicyMapping = _encode_asn1_int( - backend, policy_constraints.inhibit_policy_mapping - ) - - return pc - - -def _encode_general_subtree(backend, subtrees): - if subtrees is None: - return backend._ffi.NULL - else: - general_subtrees = backend._lib.sk_GENERAL_SUBTREE_new_null() - for name in subtrees: - gs = backend._lib.GENERAL_SUBTREE_new() - gs.base = _encode_general_name(backend, name) - res = backend._lib.sk_GENERAL_SUBTREE_push(general_subtrees, gs) - assert res >= 1 - - return general_subtrees - - -def _encode_nonce(backend, nonce): - return _encode_asn1_str_gc(backend, nonce.nonce) - - -_EXTENSION_ENCODE_HANDLERS = { - ExtensionOID.BASIC_CONSTRAINTS: _encode_basic_constraints, - ExtensionOID.SUBJECT_KEY_IDENTIFIER: _encode_subject_key_identifier, - ExtensionOID.KEY_USAGE: _encode_key_usage, - ExtensionOID.SUBJECT_ALTERNATIVE_NAME: _encode_alt_name, - ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, - ExtensionOID.EXTENDED_KEY_USAGE: _encode_extended_key_usage, - ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, - ExtensionOID.CERTIFICATE_POLICIES: _encode_certificate_policies, - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access, - ExtensionOID.SUBJECT_INFORMATION_ACCESS: _encode_information_access, - ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_cdps_freshest_crl, - ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, - ExtensionOID.INHIBIT_ANY_POLICY: _encode_inhibit_any_policy, - ExtensionOID.OCSP_NO_CHECK: _encode_ocsp_nocheck, - ExtensionOID.NAME_CONSTRAINTS: _encode_name_constraints, - ExtensionOID.POLICY_CONSTRAINTS: _encode_policy_constraints, -} - -_CRL_EXTENSION_ENCODE_HANDLERS = { - ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, - ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access, - ExtensionOID.CRL_NUMBER: _encode_crl_number_delta_crl_indicator, - ExtensionOID.DELTA_CRL_INDICATOR: _encode_crl_number_delta_crl_indicator, - ExtensionOID.ISSUING_DISTRIBUTION_POINT: _encode_issuing_dist_point, - ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, -} - -_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS = { - CRLEntryExtensionOID.CERTIFICATE_ISSUER: _encode_alt_name, - CRLEntryExtensionOID.CRL_REASON: _encode_crl_reason, - CRLEntryExtensionOID.INVALIDITY_DATE: _encode_invalidity_date, -} - -_OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS = { - OCSPExtensionOID.NONCE: _encode_nonce, -} - -_OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS = { - OCSPExtensionOID.NONCE: _encode_nonce, -} diff --git a/src/cryptography/hazmat/backends/openssl/hashes.py b/src/cryptography/hazmat/backends/openssl/hashes.py deleted file mode 100644 index 44033993e166..000000000000 --- a/src/cryptography/hazmat/backends/openssl/hashes.py +++ /dev/null @@ -1,82 +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 absolute_import, division, print_function - - -from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.primitives import hashes - - -@utils.register_interface(hashes.HashContext) -class _HashContext(object): - def __init__(self, backend, algorithm, ctx=None): - self._algorithm = algorithm - - self._backend = backend - - if ctx is None: - ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() - ctx = self._backend._ffi.gc( - ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free - ) - evp_md = self._backend._evp_md_from_algorithm(algorithm) - if evp_md == self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "{} is not a supported hash on this backend.".format( - algorithm.name - ), - _Reasons.UNSUPPORTED_HASH, - ) - res = self._backend._lib.EVP_DigestInit_ex( - ctx, evp_md, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res != 0) - - self._ctx = ctx - - algorithm = utils.read_only_property("_algorithm") - - def copy(self): - copied_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() - copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx) - self._backend.openssl_assert(res != 0) - return _HashContext(self._backend, self.algorithm, ctx=copied_ctx) - - def update(self, data): - data_ptr = self._backend._ffi.from_buffer(data) - res = self._backend._lib.EVP_DigestUpdate( - self._ctx, data_ptr, len(data) - ) - self._backend.openssl_assert(res != 0) - - def finalize(self): - if isinstance(self.algorithm, hashes.ExtendableOutputFunction): - # extendable output functions use a different finalize - return self._finalize_xof() - else: - buf = self._backend._ffi.new( - "unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE - ) - outlen = self._backend._ffi.new("unsigned int *") - res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen) - self._backend.openssl_assert(res != 0) - self._backend.openssl_assert( - outlen[0] == self.algorithm.digest_size - ) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def _finalize_xof(self): - buf = self._backend._ffi.new( - "unsigned char[]", self.algorithm.digest_size - ) - res = self._backend._lib.EVP_DigestFinalXOF( - self._ctx, buf, self.algorithm.digest_size - ) - self._backend.openssl_assert(res != 0) - return self._backend._ffi.buffer(buf)[: self.algorithm.digest_size] diff --git a/src/cryptography/hazmat/backends/openssl/hmac.py b/src/cryptography/hazmat/backends/openssl/hmac.py deleted file mode 100644 index 5024223b219b..000000000000 --- a/src/cryptography/hazmat/backends/openssl/hmac.py +++ /dev/null @@ -1,78 +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 absolute_import, division, print_function - - -from cryptography import utils -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.primitives import constant_time, hashes - - -@utils.register_interface(hashes.HashContext) -class _HMACContext(object): - def __init__(self, backend, key, algorithm, ctx=None): - self._algorithm = algorithm - self._backend = backend - - if ctx is None: - ctx = self._backend._lib.Cryptography_HMAC_CTX_new() - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc( - ctx, self._backend._lib.Cryptography_HMAC_CTX_free - ) - evp_md = self._backend._evp_md_from_algorithm(algorithm) - if evp_md == self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "{} is not a supported hash on this backend".format( - algorithm.name - ), - _Reasons.UNSUPPORTED_HASH, - ) - key_ptr = self._backend._ffi.from_buffer(key) - res = self._backend._lib.HMAC_Init_ex( - ctx, key_ptr, len(key), evp_md, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res != 0) - - self._ctx = ctx - self._key = key - - algorithm = utils.read_only_property("_algorithm") - - def copy(self): - copied_ctx = self._backend._lib.Cryptography_HMAC_CTX_new() - self._backend.openssl_assert(copied_ctx != self._backend._ffi.NULL) - copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.Cryptography_HMAC_CTX_free - ) - res = self._backend._lib.HMAC_CTX_copy(copied_ctx, self._ctx) - self._backend.openssl_assert(res != 0) - return _HMACContext( - self._backend, self._key, self.algorithm, ctx=copied_ctx - ) - - def update(self, data): - data_ptr = self._backend._ffi.from_buffer(data) - res = self._backend._lib.HMAC_Update(self._ctx, data_ptr, len(data)) - self._backend.openssl_assert(res != 0) - - def finalize(self): - buf = self._backend._ffi.new( - "unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE - ) - outlen = self._backend._ffi.new("unsigned int *") - res = self._backend._lib.HMAC_Final(self._ctx, buf, outlen) - self._backend.openssl_assert(res != 0) - self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def verify(self, signature): - digest = self.finalize() - if not constant_time.bytes_eq(digest, signature): - raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/backends/openssl/ocsp.py b/src/cryptography/hazmat/backends/openssl/ocsp.py deleted file mode 100644 index 50c02e7a8018..000000000000 --- a/src/cryptography/hazmat/backends/openssl/ocsp.py +++ /dev/null @@ -1,401 +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 absolute_import, division, print_function - -import functools - -from cryptography import utils, x509 -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends.openssl.decode_asn1 import ( - _CRL_ENTRY_REASON_CODE_TO_ENUM, - _asn1_integer_to_int, - _asn1_string_to_bytes, - _decode_x509_name, - _obj2txt, - _parse_asn1_generalized_time, -) -from cryptography.hazmat.backends.openssl.x509 import _Certificate -from cryptography.hazmat.primitives import serialization -from cryptography.x509.ocsp import ( - OCSPCertStatus, - OCSPRequest, - OCSPResponse, - OCSPResponseStatus, - _CERT_STATUS_TO_ENUM, - _OIDS_TO_HASH, - _RESPONSE_STATUS_TO_ENUM, -) - - -def _requires_successful_response(func): - @functools.wraps(func) - def wrapper(self, *args): - if self.response_status != OCSPResponseStatus.SUCCESSFUL: - raise ValueError( - "OCSP response status is not successful so the property " - "has no value" - ) - else: - return func(self, *args) - - return wrapper - - -def _issuer_key_hash(backend, cert_id): - key_hash = backend._ffi.new("ASN1_OCTET_STRING **") - res = backend._lib.OCSP_id_get0_info( - backend._ffi.NULL, - backend._ffi.NULL, - key_hash, - backend._ffi.NULL, - cert_id, - ) - backend.openssl_assert(res == 1) - backend.openssl_assert(key_hash[0] != backend._ffi.NULL) - return _asn1_string_to_bytes(backend, key_hash[0]) - - -def _issuer_name_hash(backend, cert_id): - name_hash = backend._ffi.new("ASN1_OCTET_STRING **") - res = backend._lib.OCSP_id_get0_info( - name_hash, - backend._ffi.NULL, - backend._ffi.NULL, - backend._ffi.NULL, - cert_id, - ) - backend.openssl_assert(res == 1) - backend.openssl_assert(name_hash[0] != backend._ffi.NULL) - return _asn1_string_to_bytes(backend, name_hash[0]) - - -def _serial_number(backend, cert_id): - num = backend._ffi.new("ASN1_INTEGER **") - res = backend._lib.OCSP_id_get0_info( - backend._ffi.NULL, backend._ffi.NULL, backend._ffi.NULL, num, cert_id - ) - backend.openssl_assert(res == 1) - backend.openssl_assert(num[0] != backend._ffi.NULL) - return _asn1_integer_to_int(backend, num[0]) - - -def _hash_algorithm(backend, cert_id): - asn1obj = backend._ffi.new("ASN1_OBJECT **") - res = backend._lib.OCSP_id_get0_info( - backend._ffi.NULL, - asn1obj, - backend._ffi.NULL, - backend._ffi.NULL, - cert_id, - ) - backend.openssl_assert(res == 1) - backend.openssl_assert(asn1obj[0] != backend._ffi.NULL) - oid = _obj2txt(backend, asn1obj[0]) - try: - return _OIDS_TO_HASH[oid] - except KeyError: - raise UnsupportedAlgorithm( - "Signature algorithm OID: {} not recognized".format(oid) - ) - - -@utils.register_interface(OCSPResponse) -class _OCSPResponse(object): - def __init__(self, backend, ocsp_response): - self._backend = backend - self._ocsp_response = ocsp_response - status = self._backend._lib.OCSP_response_status(self._ocsp_response) - self._backend.openssl_assert(status in _RESPONSE_STATUS_TO_ENUM) - self._status = _RESPONSE_STATUS_TO_ENUM[status] - if self._status is OCSPResponseStatus.SUCCESSFUL: - basic = self._backend._lib.OCSP_response_get1_basic( - self._ocsp_response - ) - self._backend.openssl_assert(basic != self._backend._ffi.NULL) - self._basic = self._backend._ffi.gc( - basic, self._backend._lib.OCSP_BASICRESP_free - ) - num_resp = self._backend._lib.OCSP_resp_count(self._basic) - if num_resp != 1: - raise ValueError( - "OCSP response contains more than one SINGLERESP structure" - ", which this library does not support. " - "{} found".format(num_resp) - ) - self._single = self._backend._lib.OCSP_resp_get0(self._basic, 0) - self._backend.openssl_assert( - self._single != self._backend._ffi.NULL - ) - self._cert_id = self._backend._lib.OCSP_SINGLERESP_get0_id( - self._single - ) - self._backend.openssl_assert( - self._cert_id != self._backend._ffi.NULL - ) - - response_status = utils.read_only_property("_status") - - @property - @_requires_successful_response - def signature_algorithm_oid(self): - alg = self._backend._lib.OCSP_resp_get0_tbs_sigalg(self._basic) - self._backend.openssl_assert(alg != self._backend._ffi.NULL) - oid = _obj2txt(self._backend, alg.algorithm) - return x509.ObjectIdentifier(oid) - - @property - @_requires_successful_response - def signature_hash_algorithm(self): - oid = self.signature_algorithm_oid - try: - return x509._SIG_OIDS_TO_HASH[oid] - except KeyError: - raise UnsupportedAlgorithm( - "Signature algorithm OID:{} not recognized".format(oid) - ) - - @property - @_requires_successful_response - def signature(self): - sig = self._backend._lib.OCSP_resp_get0_signature(self._basic) - self._backend.openssl_assert(sig != self._backend._ffi.NULL) - return _asn1_string_to_bytes(self._backend, sig) - - @property - @_requires_successful_response - def tbs_response_bytes(self): - respdata = self._backend._lib.OCSP_resp_get0_respdata(self._basic) - self._backend.openssl_assert(respdata != self._backend._ffi.NULL) - pp = self._backend._ffi.new("unsigned char **") - res = self._backend._lib.i2d_OCSP_RESPDATA(respdata, pp) - self._backend.openssl_assert(pp[0] != self._backend._ffi.NULL) - pp = self._backend._ffi.gc( - pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) - ) - self._backend.openssl_assert(res > 0) - return self._backend._ffi.buffer(pp[0], res)[:] - - @property - @_requires_successful_response - def certificates(self): - sk_x509 = self._backend._lib.OCSP_resp_get0_certs(self._basic) - num = self._backend._lib.sk_X509_num(sk_x509) - certs = [] - for i in range(num): - x509 = self._backend._lib.sk_X509_value(sk_x509, i) - self._backend.openssl_assert(x509 != self._backend._ffi.NULL) - cert = _Certificate(self._backend, x509) - # We need to keep the OCSP response that the certificate came from - # alive until the Certificate object itself goes out of scope, so - # we give it a private reference. - cert._ocsp_resp = self - certs.append(cert) - - return certs - - @property - @_requires_successful_response - def responder_key_hash(self): - _, asn1_string = self._responder_key_name() - if asn1_string == self._backend._ffi.NULL: - return None - else: - return _asn1_string_to_bytes(self._backend, asn1_string) - - @property - @_requires_successful_response - def responder_name(self): - x509_name, _ = self._responder_key_name() - if x509_name == self._backend._ffi.NULL: - return None - else: - return _decode_x509_name(self._backend, x509_name) - - def _responder_key_name(self): - asn1_string = self._backend._ffi.new("ASN1_OCTET_STRING **") - x509_name = self._backend._ffi.new("X509_NAME **") - res = self._backend._lib.OCSP_resp_get0_id( - self._basic, asn1_string, x509_name - ) - self._backend.openssl_assert(res == 1) - return x509_name[0], asn1_string[0] - - @property - @_requires_successful_response - def produced_at(self): - produced_at = self._backend._lib.OCSP_resp_get0_produced_at( - self._basic - ) - return _parse_asn1_generalized_time(self._backend, produced_at) - - @property - @_requires_successful_response - def certificate_status(self): - status = self._backend._lib.OCSP_single_get0_status( - self._single, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(status in _CERT_STATUS_TO_ENUM) - return _CERT_STATUS_TO_ENUM[status] - - @property - @_requires_successful_response - def revocation_time(self): - if self.certificate_status is not OCSPCertStatus.REVOKED: - return None - - asn1_time = self._backend._ffi.new("ASN1_GENERALIZEDTIME **") - self._backend._lib.OCSP_single_get0_status( - self._single, - self._backend._ffi.NULL, - asn1_time, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(asn1_time[0] != self._backend._ffi.NULL) - return _parse_asn1_generalized_time(self._backend, asn1_time[0]) - - @property - @_requires_successful_response - def revocation_reason(self): - if self.certificate_status is not OCSPCertStatus.REVOKED: - return None - - reason_ptr = self._backend._ffi.new("int *") - self._backend._lib.OCSP_single_get0_status( - self._single, - reason_ptr, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - # If no reason is encoded OpenSSL returns -1 - if reason_ptr[0] == -1: - return None - else: - self._backend.openssl_assert( - reason_ptr[0] in _CRL_ENTRY_REASON_CODE_TO_ENUM - ) - return _CRL_ENTRY_REASON_CODE_TO_ENUM[reason_ptr[0]] - - @property - @_requires_successful_response - def this_update(self): - asn1_time = self._backend._ffi.new("ASN1_GENERALIZEDTIME **") - self._backend._lib.OCSP_single_get0_status( - self._single, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - asn1_time, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(asn1_time[0] != self._backend._ffi.NULL) - return _parse_asn1_generalized_time(self._backend, asn1_time[0]) - - @property - @_requires_successful_response - def next_update(self): - asn1_time = self._backend._ffi.new("ASN1_GENERALIZEDTIME **") - self._backend._lib.OCSP_single_get0_status( - self._single, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - asn1_time, - ) - if asn1_time[0] != self._backend._ffi.NULL: - return _parse_asn1_generalized_time(self._backend, asn1_time[0]) - else: - return None - - @property - @_requires_successful_response - def issuer_key_hash(self): - return _issuer_key_hash(self._backend, self._cert_id) - - @property - @_requires_successful_response - def issuer_name_hash(self): - return _issuer_name_hash(self._backend, self._cert_id) - - @property - @_requires_successful_response - def hash_algorithm(self): - return _hash_algorithm(self._backend, self._cert_id) - - @property - @_requires_successful_response - def serial_number(self): - return _serial_number(self._backend, self._cert_id) - - @utils.cached_property - @_requires_successful_response - def extensions(self): - return self._backend._ocsp_basicresp_ext_parser.parse(self._basic) - - @utils.cached_property - @_requires_successful_response - def single_extensions(self): - return self._backend._ocsp_singleresp_ext_parser.parse(self._single) - - def public_bytes(self, encoding): - if encoding is not serialization.Encoding.DER: - raise ValueError("The only allowed encoding value is Encoding.DER") - - bio = self._backend._create_mem_bio_gc() - res = self._backend._lib.i2d_OCSP_RESPONSE_bio( - bio, self._ocsp_response - ) - self._backend.openssl_assert(res > 0) - return self._backend._read_mem_bio(bio) - - -@utils.register_interface(OCSPRequest) -class _OCSPRequest(object): - def __init__(self, backend, ocsp_request): - if backend._lib.OCSP_request_onereq_count(ocsp_request) > 1: - raise NotImplementedError( - "OCSP request contains more than one request" - ) - self._backend = backend - self._ocsp_request = ocsp_request - self._request = self._backend._lib.OCSP_request_onereq_get0( - self._ocsp_request, 0 - ) - self._backend.openssl_assert(self._request != self._backend._ffi.NULL) - self._cert_id = self._backend._lib.OCSP_onereq_get0_id(self._request) - self._backend.openssl_assert(self._cert_id != self._backend._ffi.NULL) - - @property - def issuer_key_hash(self): - return _issuer_key_hash(self._backend, self._cert_id) - - @property - def issuer_name_hash(self): - return _issuer_name_hash(self._backend, self._cert_id) - - @property - def serial_number(self): - return _serial_number(self._backend, self._cert_id) - - @property - def hash_algorithm(self): - return _hash_algorithm(self._backend, self._cert_id) - - @utils.cached_property - def extensions(self): - return self._backend._ocsp_req_ext_parser.parse(self._ocsp_request) - - def public_bytes(self, encoding): - if encoding is not serialization.Encoding.DER: - raise ValueError("The only allowed encoding value is Encoding.DER") - - bio = self._backend._create_mem_bio_gc() - res = self._backend._lib.i2d_OCSP_REQUEST_bio(bio, self._ocsp_request) - self._backend.openssl_assert(res > 0) - return self._backend._read_mem_bio(bio) diff --git a/src/cryptography/hazmat/backends/openssl/poly1305.py b/src/cryptography/hazmat/backends/openssl/poly1305.py deleted file mode 100644 index 17493ca60ce8..000000000000 --- a/src/cryptography/hazmat/backends/openssl/poly1305.py +++ /dev/null @@ -1,65 +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 absolute_import, division, print_function - - -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives import constant_time - - -_POLY1305_TAG_SIZE = 16 -_POLY1305_KEY_SIZE = 32 - - -class _Poly1305Context(object): - def __init__(self, backend, key): - self._backend = backend - - key_ptr = self._backend._ffi.from_buffer(key) - # This function copies the key into OpenSSL-owned memory so we don't - # need to retain it ourselves - evp_pkey = self._backend._lib.EVP_PKEY_new_raw_private_key( - self._backend._lib.NID_poly1305, - self._backend._ffi.NULL, - key_ptr, - len(key), - ) - self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL) - self._evp_pkey = self._backend._ffi.gc( - evp_pkey, self._backend._lib.EVP_PKEY_free - ) - ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - self._ctx = self._backend._ffi.gc( - ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestSignInit( - self._ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - - def update(self, data): - data_ptr = self._backend._ffi.from_buffer(data) - res = self._backend._lib.EVP_DigestSignUpdate( - self._ctx, data_ptr, len(data) - ) - self._backend.openssl_assert(res != 0) - - def finalize(self): - buf = self._backend._ffi.new("unsigned char[]", _POLY1305_TAG_SIZE) - outlen = self._backend._ffi.new("size_t *") - res = self._backend._lib.EVP_DigestSignFinal(self._ctx, buf, outlen) - self._backend.openssl_assert(res != 0) - self._backend.openssl_assert(outlen[0] == _POLY1305_TAG_SIZE) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def verify(self, tag): - mac = self.finalize() - if not constant_time.bytes_eq(mac, tag): - raise InvalidSignature("Value did not match computed tag.") diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 66b37224e443..ef27d4ead570 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -2,9 +2,11 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import threading +import typing -from cryptography import utils from cryptography.exceptions import ( InvalidSignature, UnsupportedAlgorithm, @@ -12,39 +14,59 @@ ) from cryptography.hazmat.backends.openssl.utils import ( _calculate_digest_and_algorithm, - _check_not_prehashed, - _warn_sign_verify_deprecated, -) -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ( - AsymmetricSignatureContext, - AsymmetricVerificationContext, - rsa, ) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.asymmetric.padding import ( - AsymmetricPadding, MGF1, OAEP, - PKCS1v15, PSS, + AsymmetricPadding, + PKCS1v15, + _Auto, + _DigestLength, + _MaxLength, calculate_max_pss_salt_length, ) from cryptography.hazmat.primitives.asymmetric.rsa import ( - RSAPrivateKeyWithSerialization, - RSAPublicKeyWithSerialization, + RSAPrivateKey, + RSAPrivateNumbers, + RSAPublicKey, + RSAPublicNumbers, ) +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.backend import Backend -def _get_rsa_pss_salt_length(pss, key, hash_algorithm): + +def _get_rsa_pss_salt_length( + backend: Backend, + pss: PSS, + key: typing.Union[RSAPrivateKey, RSAPublicKey], + hash_algorithm: hashes.HashAlgorithm, +) -> int: salt = pss._salt_length - if salt is MGF1.MAX_LENGTH or salt is PSS.MAX_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, key, data, padding): +def _enc_dec_rsa( + backend: Backend, + key: typing.Union[_RSAPrivateKey, _RSAPublicKey], + data: bytes, + padding: AsymmetricPadding, +) -> bytes: if not isinstance(padding, AsymmetricPadding): raise TypeError("Padding must be an instance of AsymmetricPadding.") @@ -68,14 +90,22 @@ def _enc_dec_rsa(backend, key, data, padding): else: raise UnsupportedAlgorithm( - "{} is not supported by this backend.".format(padding.name), + 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, key, data, padding_enum, padding): +def _enc_dec_rsa_pkey_ctx( + backend: Backend, + key: typing.Union[_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 @@ -92,7 +122,7 @@ def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding): 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) and backend._lib.Cryptography_HAS_RSA_OAEP_MD: + if isinstance(padding, OAEP): mgf1_md = backend._evp_md_non_null_from_algorithm( padding._mgf._algorithm ) @@ -134,7 +164,12 @@ def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding): return resbuf -def _rsa_sig_determine_padding(backend, key, padding, algorithm): +def _rsa_sig_determine_padding( + backend: Backend, + key: typing.Union[_RSAPrivateKey, _RSAPublicKey], + padding: AsymmetricPadding, + algorithm: typing.Optional[hashes.HashAlgorithm], +) -> int: if not isinstance(padding, AsymmetricPadding): raise TypeError("Expected provider of AsymmetricPadding.") @@ -142,6 +177,7 @@ def _rsa_sig_determine_padding(backend, key, padding, algorithm): 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): @@ -150,6 +186,10 @@ def _rsa_sig_determine_padding(backend, key, padding, algorithm): _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: @@ -161,35 +201,58 @@ def _rsa_sig_determine_padding(backend, key, padding, algorithm): padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING else: raise UnsupportedAlgorithm( - "{} is not supported by this backend.".format(padding.name), + f"{padding.name} is not supported by this backend.", _Reasons.UNSUPPORTED_PADDING, ) return padding_enum -def _rsa_sig_setup(backend, padding, algorithm, key, data, init_func): +# 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: typing.Optional[hashes.HashAlgorithm], + key: typing.Union[_RSAPublicKey, _RSAPrivateKey], + init_func: typing.Callable[[typing.Any], int], +): padding_enum = _rsa_sig_determine_padding(backend, key, padding, algorithm) - evp_md = backend._evp_md_non_null_from_algorithm(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) - backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_CTX_set_signature_md(pkey_ctx, evp_md) - if res == 0: + 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( - "{} is not supported by this backend for RSA signing.".format( - algorithm.name + "{} is not supported for the RSA signature operation.".format( + padding.name ), - _Reasons.UNSUPPORTED_HASH, + _Reasons.UNSUPPORTED_PADDING, ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) - backend.openssl_assert(res > 0) 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(padding, key, algorithm) + pkey_ctx, + _get_rsa_pss_salt_length(backend, padding, key, algorithm), ) backend.openssl_assert(res > 0) @@ -202,13 +265,18 @@ def _rsa_sig_setup(backend, padding, algorithm, key, data, init_func): return pkey_ctx -def _rsa_sig_sign(backend, padding, algorithm, private_key, data): +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, - data, backend._lib.EVP_PKEY_sign_init, ) buflen = backend._ffi.new("size_t *") @@ -219,7 +287,7 @@ def _rsa_sig_sign(backend, padding, algorithm, private_key, data): 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_with_text() + 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", @@ -229,13 +297,19 @@ def _rsa_sig_sign(backend, padding, algorithm, private_key, data): return backend._ffi.buffer(buf)[:] -def _rsa_sig_verify(backend, padding, algorithm, public_key, signature, data): +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, - data, backend._lib.EVP_PKEY_verify_init, ) res = backend._lib.EVP_PKEY_verify( @@ -250,79 +324,85 @@ def _rsa_sig_verify(backend, padding, algorithm, public_key, signature, data): raise InvalidSignature -@utils.register_interface(AsymmetricSignatureContext) -class _RSASignatureContext(object): - def __init__(self, backend, private_key, padding, algorithm): - self._backend = backend - self._private_key = private_key - - # We now call _rsa_sig_determine_padding in _rsa_sig_setup. However - # we need to make a pointless call to it here so we maintain the - # API of erroring on init with this context if the values are invalid. - _rsa_sig_determine_padding(backend, private_key, padding, algorithm) - self._padding = padding - self._algorithm = algorithm - self._hash_ctx = hashes.Hash(self._algorithm, self._backend) - - def update(self, data): - self._hash_ctx.update(data) - - def finalize(self): - return _rsa_sig_sign( - self._backend, - self._padding, - self._algorithm, - self._private_key, - self._hash_ctx.finalize(), - ) - +def _rsa_sig_recover( + backend: Backend, + padding: AsymmetricPadding, + algorithm: typing.Optional[hashes.HashAlgorithm], + public_key: _RSAPublicKey, + signature: bytes, +) -> bytes: + pkey_ctx = _rsa_sig_setup( + backend, + padding, + algorithm, + public_key, + backend._lib.EVP_PKEY_verify_recover_init, + ) -@utils.register_interface(AsymmetricVerificationContext) -class _RSAVerificationContext(object): - def __init__(self, backend, public_key, signature, padding, algorithm): - self._backend = backend - self._public_key = public_key - self._signature = signature - self._padding = padding - # We now call _rsa_sig_determine_padding in _rsa_sig_setup. However - # we need to make a pointless call to it here so we maintain the - # API of erroring on init with this context if the values are invalid. - _rsa_sig_determine_padding(backend, public_key, padding, algorithm) - - padding = padding - self._algorithm = algorithm - self._hash_ctx = hashes.Hash(self._algorithm, self._backend) - - def update(self, data): - self._hash_ctx.update(data) - - def verify(self): - return _rsa_sig_verify( - self._backend, - self._padding, - self._algorithm, - self._public_key, - self._signature, - self._hash_ctx.finalize(), - ) + # 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 -@utils.register_interface(RSAPrivateKeyWithSerialization) -class _RSAPrivateKey(object): - def __init__(self, backend, rsa_cdata, evp_pkey): - res = backend._lib.RSA_check_key(rsa_cdata) - if res != 1: - errors = backend._consume_errors_with_text() - raise ValueError("Invalid private key", errors) +class _RSAPrivateKey(RSAPrivateKey): + _evp_pkey: object + _rsa_cdata: object + _key_size: int - # Blinding is on by default in many versions of OpenSSL, but let's - # just be conservative here. - res = backend._lib.RSA_blinding_on(rsa_cdata, backend._ffi.NULL) - backend.openssl_assert(res == 1) + 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 + # Used for lazy blinding + self._blinded = False + self._blinding_lock = threading.Lock() n = self._backend._ffi.new("BIGNUM **") self._backend._lib.RSA_get0_key( @@ -334,28 +414,45 @@ def __init__(self, backend, rsa_cdata, evp_pkey): self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) self._key_size = self._backend._lib.BN_num_bits(n[0]) - key_size = utils.read_only_property("_key_size") + def _enable_blinding(self) -> None: + # If you call blind on an already blinded RSA key OpenSSL will turn + # it off and back on, which is a performance hit we want to avoid. + if not self._blinded: + with self._blinding_lock: + self._non_threadsafe_enable_blinding() + + def _non_threadsafe_enable_blinding(self) -> None: + # This is only a separate function to allow for testing to cover both + # branches. It should never be invoked except through _enable_blinding. + # Check if it's not True again in case another thread raced past the + # first non-locked check. + if not self._blinded: + res = self._backend._lib.RSA_blinding_on( + self._rsa_cdata, self._backend._ffi.NULL + ) + self._backend.openssl_assert(res == 1) + self._blinded = True - def signer(self, padding, algorithm): - _warn_sign_verify_deprecated() - _check_not_prehashed(algorithm) - return _RSASignatureContext(self._backend, self, padding, algorithm) + @property + def key_size(self) -> int: + return self._key_size - def decrypt(self, ciphertext, padding): + def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: + self._enable_blinding() 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): + 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): + def private_numbers(self) -> RSAPrivateNumbers: n = self._backend._ffi.new("BIGNUM **") e = self._backend._ffi.new("BIGNUM **") d = self._backend._ffi.new("BIGNUM **") @@ -377,20 +474,25 @@ def private_numbers(self): 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 rsa.RSAPrivateNumbers( + 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=rsa.RSAPublicNumbers( + public_numbers=RSAPublicNumbers( e=self._backend._bn_to_int(e[0]), n=self._backend._bn_to_int(n[0]), ), ) - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: serialization.Encoding, + format: serialization.PrivateFormat, + encryption_algorithm: serialization.KeySerializationEncryption, + ) -> bytes: return self._backend._private_key_bytes( encoding, format, @@ -400,21 +502,23 @@ def private_bytes(self, encoding, format, encryption_algorithm): self._rsa_cdata, ) - def sign(self, data, padding, algorithm): - data, algorithm = _calculate_digest_and_algorithm( - self._backend, data, algorithm - ) + def sign( + self, + data: bytes, + padding: AsymmetricPadding, + algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + ) -> bytes: + self._enable_blinding() + data, algorithm = _calculate_digest_and_algorithm(data, algorithm) return _rsa_sig_sign(self._backend, padding, algorithm, self, data) -@utils.register_interface(RSAPublicKeyWithSerialization) -class _RSAPublicKey(object): - def __init__(self, backend, rsa_cdata, evp_pkey): - # Blinding is on by default in many versions of OpenSSL, but let's - # just be conservative here. - res = backend._lib.RSA_blinding_on(rsa_cdata, backend._ffi.NULL) - backend.openssl_assert(res == 1) +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 @@ -429,21 +533,23 @@ def __init__(self, backend, rsa_cdata, evp_pkey): self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) self._key_size = self._backend._lib.BN_num_bits(n[0]) - key_size = utils.read_only_property("_key_size") + @property + def key_size(self) -> int: + return self._key_size - def verifier(self, signature, padding, algorithm): - _warn_sign_verify_deprecated() - utils._check_bytes("signature", signature) + def __eq__(self, other: object) -> bool: + if not isinstance(other, _RSAPublicKey): + return NotImplemented - _check_not_prehashed(algorithm) - return _RSAVerificationContext( - self._backend, self, signature, padding, algorithm + return ( + self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) + == 1 ) - def encrypt(self, plaintext, padding): + def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: return _enc_dec_rsa(self._backend, self, plaintext, padding) - def public_numbers(self): + def public_numbers(self) -> RSAPublicNumbers: n = self._backend._ffi.new("BIGNUM **") e = self._backend._ffi.new("BIGNUM **") self._backend._lib.RSA_get0_key( @@ -451,20 +557,43 @@ def public_numbers(self): ) self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) self._backend.openssl_assert(e[0] != self._backend._ffi.NULL) - return rsa.RSAPublicNumbers( + return RSAPublicNumbers( e=self._backend._bn_to_int(e[0]), n=self._backend._bn_to_int(n[0]), ) - def public_bytes(self, encoding, format): + 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, data, padding, algorithm): - data, algorithm = _calculate_digest_and_algorithm( - self._backend, data, algorithm - ) - return _rsa_sig_verify( + def verify( + self, + signature: bytes, + data: bytes, + padding: AsymmetricPadding, + algorithm: typing.Union[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: typing.Optional[hashes.HashAlgorithm], + ) -> 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/backends/openssl/utils.py b/src/cryptography/hazmat/backends/openssl/utils.py index ec0b947a44c6..5b404defde33 100644 --- a/src/cryptography/hazmat/backends/openssl/utils.py +++ b/src/cryptography/hazmat/backends/openssl/utils.py @@ -2,23 +2,34 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import warnings +import typing -from cryptography import utils from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import Prehashed +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.backend import Backend -def _evp_pkey_derive(backend, evp_pkey, peer_public_key): + +def _evp_pkey_derive(backend: Backend, evp_pkey, peer_public_key) -> bytes: ctx = backend._lib.EVP_PKEY_CTX_new(evp_pkey, backend._ffi.NULL) backend.openssl_assert(ctx != backend._ffi.NULL) ctx = backend._ffi.gc(ctx, backend._lib.EVP_PKEY_CTX_free) res = backend._lib.EVP_PKEY_derive_init(ctx) backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_derive_set_peer(ctx, peer_public_key._evp_pkey) + + if backend._lib.Cryptography_HAS_EVP_PKEY_SET_PEER_EX: + res = backend._lib.EVP_PKEY_derive_set_peer_ex( + ctx, peer_public_key._evp_pkey, 0 + ) + else: + res = backend._lib.EVP_PKEY_derive_set_peer( + ctx, peer_public_key._evp_pkey + ) backend.openssl_assert(res == 1) + keylen = backend._ffi.new("size_t *") res = backend._lib.EVP_PKEY_derive(ctx, backend._ffi.NULL, keylen) backend.openssl_assert(res == 1) @@ -26,14 +37,18 @@ def _evp_pkey_derive(backend, evp_pkey, peer_public_key): buf = backend._ffi.new("unsigned char[]", keylen[0]) res = backend._lib.EVP_PKEY_derive(ctx, buf, keylen) if res != 1: - raise ValueError("Null shared key derived from public/private pair.") + errors = backend._consume_errors() + raise ValueError("Error computing shared key.", errors) return backend._ffi.buffer(buf, keylen[0])[:] -def _calculate_digest_and_algorithm(backend, data, algorithm): +def _calculate_digest_and_algorithm( + data: bytes, + algorithm: typing.Union[Prehashed, hashes.HashAlgorithm], +) -> typing.Tuple[bytes, hashes.HashAlgorithm]: if not isinstance(algorithm, Prehashed): - hash_ctx = hashes.Hash(algorithm, backend) + hash_ctx = hashes.Hash(algorithm) hash_ctx.update(data) data = hash_ctx.finalize() else: @@ -46,20 +61,3 @@ def _calculate_digest_and_algorithm(backend, data, algorithm): ) return (data, algorithm) - - -def _check_not_prehashed(signature_algorithm): - if isinstance(signature_algorithm, Prehashed): - raise TypeError( - "Prehashed is only supported in the sign and verify methods. " - "It cannot be used with signer or verifier." - ) - - -def _warn_sign_verify_deprecated(): - warnings.warn( - "signer and verifier have been deprecated. Please use sign " - "and verify instead.", - utils.PersistentlyDeprecated2017, - stacklevel=3, - ) diff --git a/src/cryptography/hazmat/backends/openssl/x25519.py b/src/cryptography/hazmat/backends/openssl/x25519.py deleted file mode 100644 index 4971c5481402..000000000000 --- a/src/cryptography/hazmat/backends/openssl/x25519.py +++ /dev/null @@ -1,123 +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 absolute_import, division, print_function - -from cryptography import utils -from cryptography.hazmat.backends.openssl.utils import _evp_pkey_derive -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.x25519 import ( - X25519PrivateKey, - X25519PublicKey, -) - - -_X25519_KEY_SIZE = 32 - - -@utils.register_interface(X25519PublicKey) -class _X25519PublicKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes(self, encoding, format): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self): - ucharpp = self._backend._ffi.new("unsigned char **") - res = self._backend._lib.EVP_PKEY_get1_tls_encodedpoint( - self._evp_pkey, ucharpp - ) - self._backend.openssl_assert(res == 32) - self._backend.openssl_assert(ucharpp[0] != self._backend._ffi.NULL) - data = self._backend._ffi.gc( - ucharpp[0], self._backend._lib.OPENSSL_free - ) - return self._backend._ffi.buffer(data, res)[:] - - -@utils.register_interface(X25519PrivateKey) -class _X25519PrivateKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self): - bio = self._backend._create_mem_bio_gc() - res = self._backend._lib.i2d_PUBKEY_bio(bio, self._evp_pkey) - self._backend.openssl_assert(res == 1) - evp_pkey = self._backend._lib.d2i_PUBKEY_bio( - bio, self._backend._ffi.NULL - ) - self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL) - evp_pkey = self._backend._ffi.gc( - evp_pkey, self._backend._lib.EVP_PKEY_free - ) - return _X25519PublicKey(self._backend, evp_pkey) - - def exchange(self, peer_public_key): - if not isinstance(peer_public_key, X25519PublicKey): - raise TypeError("peer_public_key must be X25519PublicKey.") - - return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) - - def private_bytes(self, encoding, format, encryption_algorithm): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self): - # When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 we can - # switch this to EVP_PKEY_new_raw_private_key - # The trick we use here is serializing to a PKCS8 key and just - # using the last 32 bytes, which is the key itself. - bio = self._backend._create_mem_bio_gc() - res = self._backend._lib.i2d_PKCS8PrivateKey_bio( - bio, - self._evp_pkey, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - 0, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(res == 1) - pkcs8 = self._backend._read_mem_bio(bio) - self._backend.openssl_assert(len(pkcs8) == 48) - return pkcs8[-_X25519_KEY_SIZE:] diff --git a/src/cryptography/hazmat/backends/openssl/x448.py b/src/cryptography/hazmat/backends/openssl/x448.py deleted file mode 100644 index 7ebcdf84bcc2..000000000000 --- a/src/cryptography/hazmat/backends/openssl/x448.py +++ /dev/null @@ -1,107 +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 absolute_import, division, print_function - -from cryptography import utils -from cryptography.hazmat.backends.openssl.utils import _evp_pkey_derive -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.x448 import ( - X448PrivateKey, - X448PublicKey, -) - -_X448_KEY_SIZE = 56 - - -@utils.register_interface(X448PublicKey) -class _X448PublicKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes(self, encoding, format): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self): - buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _X448_KEY_SIZE)[:] - - -@utils.register_interface(X448PrivateKey) -class _X448PrivateKey(object): - def __init__(self, backend, evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self): - buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE) - return self._backend.x448_load_public_bytes(buf) - - def exchange(self, peer_public_key): - if not isinstance(peer_public_key, X448PublicKey): - raise TypeError("peer_public_key must be X448PublicKey.") - - return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) - - def private_bytes(self, encoding, format, encryption_algorithm): - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self): - buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_private_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _X448_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py deleted file mode 100644 index 4d0dac7649a6..000000000000 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ /dev/null @@ -1,587 +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 absolute_import, division, print_function - -import datetime -import operator - -from cryptography import utils, x509 -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends.openssl.decode_asn1 import ( - _asn1_integer_to_int, - _asn1_string_to_bytes, - _decode_x509_name, - _obj2txt, - _parse_asn1_time, -) -from cryptography.hazmat.backends.openssl.encode_asn1 import ( - _encode_asn1_int_gc, - _txt2obj_gc, -) -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa -from cryptography.x509.name import _ASN1Type - - -@utils.register_interface(x509.Certificate) -class _Certificate(object): - def __init__(self, backend, x509_cert): - self._backend = backend - self._x509 = x509_cert - - version = self._backend._lib.X509_get_version(self._x509) - if version == 0: - self._version = x509.Version.v1 - elif version == 2: - self._version = x509.Version.v3 - else: - raise x509.InvalidVersion( - "{} is not a valid X509 version".format(version), version - ) - - def __repr__(self): - return "".format(self.subject) - - def __eq__(self, other): - if not isinstance(other, x509.Certificate): - return NotImplemented - - res = self._backend._lib.X509_cmp(self._x509, other._x509) - return res == 0 - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(self.public_bytes(serialization.Encoding.DER)) - - def __deepcopy__(self, memo): - return self - - def fingerprint(self, algorithm): - h = hashes.Hash(algorithm, self._backend) - h.update(self.public_bytes(serialization.Encoding.DER)) - return h.finalize() - - version = utils.read_only_property("_version") - - @property - def serial_number(self): - asn1_int = self._backend._lib.X509_get_serialNumber(self._x509) - self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) - return _asn1_integer_to_int(self._backend, asn1_int) - - def public_key(self): - pkey = self._backend._lib.X509_get_pubkey(self._x509) - if pkey == self._backend._ffi.NULL: - # Remove errors from the stack. - self._backend._consume_errors() - raise ValueError("Certificate public key is of an unknown type") - - pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) - - return self._backend._evp_pkey_to_public_key(pkey) - - @property - def not_valid_before(self): - asn1_time = self._backend._lib.X509_getm_notBefore(self._x509) - return _parse_asn1_time(self._backend, asn1_time) - - @property - def not_valid_after(self): - asn1_time = self._backend._lib.X509_getm_notAfter(self._x509) - return _parse_asn1_time(self._backend, asn1_time) - - @property - def issuer(self): - issuer = self._backend._lib.X509_get_issuer_name(self._x509) - self._backend.openssl_assert(issuer != self._backend._ffi.NULL) - return _decode_x509_name(self._backend, issuer) - - @property - def subject(self): - subject = self._backend._lib.X509_get_subject_name(self._x509) - self._backend.openssl_assert(subject != self._backend._ffi.NULL) - return _decode_x509_name(self._backend, subject) - - @property - def signature_hash_algorithm(self): - oid = self.signature_algorithm_oid - try: - return x509._SIG_OIDS_TO_HASH[oid] - except KeyError: - raise UnsupportedAlgorithm( - "Signature algorithm OID:{} not recognized".format(oid) - ) - - @property - def signature_algorithm_oid(self): - alg = self._backend._ffi.new("X509_ALGOR **") - self._backend._lib.X509_get0_signature( - self._backend._ffi.NULL, alg, self._x509 - ) - self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) - oid = _obj2txt(self._backend, alg[0].algorithm) - return x509.ObjectIdentifier(oid) - - @utils.cached_property - def extensions(self): - return self._backend._certificate_extension_parser.parse(self._x509) - - @property - def signature(self): - sig = self._backend._ffi.new("ASN1_BIT_STRING **") - self._backend._lib.X509_get0_signature( - sig, self._backend._ffi.NULL, self._x509 - ) - self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) - return _asn1_string_to_bytes(self._backend, sig[0]) - - @property - def tbs_certificate_bytes(self): - pp = self._backend._ffi.new("unsigned char **") - res = self._backend._lib.i2d_re_X509_tbs(self._x509, pp) - self._backend.openssl_assert(res > 0) - pp = self._backend._ffi.gc( - pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) - ) - return self._backend._ffi.buffer(pp[0], res)[:] - - def public_bytes(self, encoding): - bio = self._backend._create_mem_bio_gc() - if encoding is serialization.Encoding.PEM: - res = self._backend._lib.PEM_write_bio_X509(bio, self._x509) - elif encoding is serialization.Encoding.DER: - res = self._backend._lib.i2d_X509_bio(bio, self._x509) - else: - raise TypeError("encoding must be an item from the Encoding enum") - - self._backend.openssl_assert(res == 1) - return self._backend._read_mem_bio(bio) - - -@utils.register_interface(x509.RevokedCertificate) -class _RevokedCertificate(object): - def __init__(self, backend, crl, x509_revoked): - self._backend = backend - # The X509_REVOKED_value is a X509_REVOKED * that has - # no reference counting. This means when X509_CRL_free is - # called then the CRL and all X509_REVOKED * are freed. Since - # you can retain a reference to a single revoked certificate - # and let the CRL fall out of scope we need to retain a - # private reference to the CRL inside the RevokedCertificate - # object to prevent the gc from being called inappropriately. - self._crl = crl - self._x509_revoked = x509_revoked - - @property - def serial_number(self): - asn1_int = self._backend._lib.X509_REVOKED_get0_serialNumber( - self._x509_revoked - ) - self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) - return _asn1_integer_to_int(self._backend, asn1_int) - - @property - def revocation_date(self): - return _parse_asn1_time( - self._backend, - self._backend._lib.X509_REVOKED_get0_revocationDate( - self._x509_revoked - ), - ) - - @utils.cached_property - def extensions(self): - return self._backend._revoked_cert_extension_parser.parse( - self._x509_revoked - ) - - -@utils.register_interface(x509.CertificateRevocationList) -class _CertificateRevocationList(object): - def __init__(self, backend, x509_crl): - self._backend = backend - self._x509_crl = x509_crl - - def __eq__(self, other): - if not isinstance(other, x509.CertificateRevocationList): - return NotImplemented - - res = self._backend._lib.X509_CRL_cmp(self._x509_crl, other._x509_crl) - return res == 0 - - def __ne__(self, other): - return not self == other - - def fingerprint(self, algorithm): - h = hashes.Hash(algorithm, self._backend) - bio = self._backend._create_mem_bio_gc() - res = self._backend._lib.i2d_X509_CRL_bio(bio, self._x509_crl) - self._backend.openssl_assert(res == 1) - der = self._backend._read_mem_bio(bio) - h.update(der) - return h.finalize() - - @utils.cached_property - def _sorted_crl(self): - # X509_CRL_get0_by_serial sorts in place, which breaks a variety of - # things we don't want to break (like iteration and the signature). - # Let's dupe it and sort that instead. - dup = self._backend._lib.X509_CRL_dup(self._x509_crl) - self._backend.openssl_assert(dup != self._backend._ffi.NULL) - dup = self._backend._ffi.gc(dup, self._backend._lib.X509_CRL_free) - return dup - - def get_revoked_certificate_by_serial_number(self, serial_number): - revoked = self._backend._ffi.new("X509_REVOKED **") - asn1_int = _encode_asn1_int_gc(self._backend, serial_number) - res = self._backend._lib.X509_CRL_get0_by_serial( - self._sorted_crl, revoked, asn1_int - ) - if res == 0: - return None - else: - self._backend.openssl_assert(revoked[0] != self._backend._ffi.NULL) - return _RevokedCertificate( - self._backend, self._sorted_crl, revoked[0] - ) - - @property - def signature_hash_algorithm(self): - oid = self.signature_algorithm_oid - try: - return x509._SIG_OIDS_TO_HASH[oid] - except KeyError: - raise UnsupportedAlgorithm( - "Signature algorithm OID:{} not recognized".format(oid) - ) - - @property - def signature_algorithm_oid(self): - alg = self._backend._ffi.new("X509_ALGOR **") - self._backend._lib.X509_CRL_get0_signature( - self._x509_crl, self._backend._ffi.NULL, alg - ) - self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) - oid = _obj2txt(self._backend, alg[0].algorithm) - return x509.ObjectIdentifier(oid) - - @property - def issuer(self): - issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) - self._backend.openssl_assert(issuer != self._backend._ffi.NULL) - return _decode_x509_name(self._backend, issuer) - - @property - def next_update(self): - nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) - self._backend.openssl_assert(nu != self._backend._ffi.NULL) - return _parse_asn1_time(self._backend, nu) - - @property - def last_update(self): - lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) - self._backend.openssl_assert(lu != self._backend._ffi.NULL) - return _parse_asn1_time(self._backend, lu) - - @property - def signature(self): - sig = self._backend._ffi.new("ASN1_BIT_STRING **") - self._backend._lib.X509_CRL_get0_signature( - self._x509_crl, sig, self._backend._ffi.NULL - ) - self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) - return _asn1_string_to_bytes(self._backend, sig[0]) - - @property - def tbs_certlist_bytes(self): - pp = self._backend._ffi.new("unsigned char **") - res = self._backend._lib.i2d_re_X509_CRL_tbs(self._x509_crl, pp) - self._backend.openssl_assert(res > 0) - pp = self._backend._ffi.gc( - pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) - ) - return self._backend._ffi.buffer(pp[0], res)[:] - - def public_bytes(self, encoding): - bio = self._backend._create_mem_bio_gc() - if encoding is serialization.Encoding.PEM: - res = self._backend._lib.PEM_write_bio_X509_CRL( - bio, self._x509_crl - ) - elif encoding is serialization.Encoding.DER: - res = self._backend._lib.i2d_X509_CRL_bio(bio, self._x509_crl) - else: - raise TypeError("encoding must be an item from the Encoding enum") - - self._backend.openssl_assert(res == 1) - return self._backend._read_mem_bio(bio) - - def _revoked_cert(self, idx): - revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) - r = self._backend._lib.sk_X509_REVOKED_value(revoked, idx) - self._backend.openssl_assert(r != self._backend._ffi.NULL) - return _RevokedCertificate(self._backend, self, r) - - def __iter__(self): - for i in range(len(self)): - yield self._revoked_cert(i) - - def __getitem__(self, idx): - if isinstance(idx, slice): - start, stop, step = idx.indices(len(self)) - return [self._revoked_cert(i) for i in range(start, stop, step)] - else: - idx = operator.index(idx) - if idx < 0: - idx += len(self) - if not 0 <= idx < len(self): - raise IndexError - return self._revoked_cert(idx) - - def __len__(self): - revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) - if revoked == self._backend._ffi.NULL: - return 0 - else: - return self._backend._lib.sk_X509_REVOKED_num(revoked) - - @utils.cached_property - def extensions(self): - return self._backend._crl_extension_parser.parse(self._x509_crl) - - def is_signature_valid(self, public_key): - if not isinstance( - public_key, - (dsa.DSAPublicKey, rsa.RSAPublicKey, ec.EllipticCurvePublicKey), - ): - raise TypeError( - "Expecting one of DSAPublicKey, RSAPublicKey," - " or EllipticCurvePublicKey." - ) - res = self._backend._lib.X509_CRL_verify( - self._x509_crl, public_key._evp_pkey - ) - - if res != 1: - self._backend._consume_errors() - return False - - return True - - -@utils.register_interface(x509.CertificateSigningRequest) -class _CertificateSigningRequest(object): - def __init__(self, backend, x509_req): - self._backend = backend - self._x509_req = x509_req - - def __eq__(self, other): - if not isinstance(other, _CertificateSigningRequest): - return NotImplemented - - self_bytes = self.public_bytes(serialization.Encoding.DER) - other_bytes = other.public_bytes(serialization.Encoding.DER) - return self_bytes == other_bytes - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(self.public_bytes(serialization.Encoding.DER)) - - def public_key(self): - pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req) - self._backend.openssl_assert(pkey != self._backend._ffi.NULL) - pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) - return self._backend._evp_pkey_to_public_key(pkey) - - @property - def subject(self): - subject = self._backend._lib.X509_REQ_get_subject_name(self._x509_req) - self._backend.openssl_assert(subject != self._backend._ffi.NULL) - return _decode_x509_name(self._backend, subject) - - @property - def signature_hash_algorithm(self): - oid = self.signature_algorithm_oid - try: - return x509._SIG_OIDS_TO_HASH[oid] - except KeyError: - raise UnsupportedAlgorithm( - "Signature algorithm OID:{} not recognized".format(oid) - ) - - @property - def signature_algorithm_oid(self): - alg = self._backend._ffi.new("X509_ALGOR **") - self._backend._lib.X509_REQ_get0_signature( - self._x509_req, self._backend._ffi.NULL, alg - ) - self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) - oid = _obj2txt(self._backend, alg[0].algorithm) - return x509.ObjectIdentifier(oid) - - @utils.cached_property - def extensions(self): - x509_exts = self._backend._lib.X509_REQ_get_extensions(self._x509_req) - x509_exts = self._backend._ffi.gc( - x509_exts, - lambda x: self._backend._lib.sk_X509_EXTENSION_pop_free( - x, - self._backend._ffi.addressof( - self._backend._lib._original_lib, "X509_EXTENSION_free" - ), - ), - ) - return self._backend._csr_extension_parser.parse(x509_exts) - - def public_bytes(self, encoding): - bio = self._backend._create_mem_bio_gc() - if encoding is serialization.Encoding.PEM: - res = self._backend._lib.PEM_write_bio_X509_REQ( - bio, self._x509_req - ) - elif encoding is serialization.Encoding.DER: - res = self._backend._lib.i2d_X509_REQ_bio(bio, self._x509_req) - else: - raise TypeError("encoding must be an item from the Encoding enum") - - self._backend.openssl_assert(res == 1) - return self._backend._read_mem_bio(bio) - - @property - def tbs_certrequest_bytes(self): - pp = self._backend._ffi.new("unsigned char **") - res = self._backend._lib.i2d_re_X509_REQ_tbs(self._x509_req, pp) - self._backend.openssl_assert(res > 0) - pp = self._backend._ffi.gc( - pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) - ) - return self._backend._ffi.buffer(pp[0], res)[:] - - @property - def signature(self): - sig = self._backend._ffi.new("ASN1_BIT_STRING **") - self._backend._lib.X509_REQ_get0_signature( - self._x509_req, sig, self._backend._ffi.NULL - ) - self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) - return _asn1_string_to_bytes(self._backend, sig[0]) - - @property - def is_signature_valid(self): - pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req) - self._backend.openssl_assert(pkey != self._backend._ffi.NULL) - pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) - res = self._backend._lib.X509_REQ_verify(self._x509_req, pkey) - - if res != 1: - self._backend._consume_errors() - return False - - return True - - def get_attribute_for_oid(self, oid): - obj = _txt2obj_gc(self._backend, oid.dotted_string) - pos = self._backend._lib.X509_REQ_get_attr_by_OBJ( - self._x509_req, obj, -1 - ) - if pos == -1: - raise x509.AttributeNotFound( - "No {} attribute was found".format(oid), oid - ) - - attr = self._backend._lib.X509_REQ_get_attr(self._x509_req, pos) - self._backend.openssl_assert(attr != self._backend._ffi.NULL) - # We don't support multiple valued attributes for now. - self._backend.openssl_assert( - self._backend._lib.X509_ATTRIBUTE_count(attr) == 1 - ) - asn1_type = self._backend._lib.X509_ATTRIBUTE_get0_type(attr, 0) - self._backend.openssl_assert(asn1_type != self._backend._ffi.NULL) - # We need this to ensure that our C type cast is safe. - # Also this should always be a sane string type, but we'll see if - # that is true in the real world... - if asn1_type.type not in ( - _ASN1Type.UTF8String.value, - _ASN1Type.PrintableString.value, - _ASN1Type.IA5String.value, - ): - raise ValueError( - "OID {} has a disallowed ASN.1 type: {}".format( - oid, asn1_type.type - ) - ) - - data = self._backend._lib.X509_ATTRIBUTE_get0_data( - attr, 0, asn1_type.type, self._backend._ffi.NULL - ) - self._backend.openssl_assert(data != self._backend._ffi.NULL) - # This cast is safe iff we assert on the type above to ensure - # that it is always a type of ASN1_STRING - data = self._backend._ffi.cast("ASN1_STRING *", data) - return _asn1_string_to_bytes(self._backend, data) - - -@utils.register_interface( - x509.certificate_transparency.SignedCertificateTimestamp -) -class _SignedCertificateTimestamp(object): - def __init__(self, backend, sct_list, sct): - self._backend = backend - # Keep the SCT_LIST that this SCT came from alive. - self._sct_list = sct_list - self._sct = sct - - @property - def version(self): - version = self._backend._lib.SCT_get_version(self._sct) - assert version == self._backend._lib.SCT_VERSION_V1 - return x509.certificate_transparency.Version.v1 - - @property - def log_id(self): - out = self._backend._ffi.new("unsigned char **") - log_id_length = self._backend._lib.SCT_get0_log_id(self._sct, out) - assert log_id_length >= 0 - return self._backend._ffi.buffer(out[0], log_id_length)[:] - - @property - def timestamp(self): - timestamp = self._backend._lib.SCT_get_timestamp(self._sct) - milliseconds = timestamp % 1000 - return datetime.datetime.utcfromtimestamp(timestamp // 1000).replace( - microsecond=milliseconds * 1000 - ) - - @property - def entry_type(self): - entry_type = self._backend._lib.SCT_get_log_entry_type(self._sct) - # We currently only support loading SCTs from the X.509 extension, so - # we only have precerts. - assert entry_type == self._backend._lib.CT_LOG_ENTRY_TYPE_PRECERT - return x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE - - @property - def _signature(self): - ptrptr = self._backend._ffi.new("unsigned char **") - res = self._backend._lib.SCT_get0_signature(self._sct, ptrptr) - self._backend.openssl_assert(res > 0) - self._backend.openssl_assert(ptrptr[0] != self._backend._ffi.NULL) - return self._backend._ffi.buffer(ptrptr[0], res)[:] - - def __hash__(self): - return hash(self._signature) - - def __eq__(self, other): - if not isinstance(other, _SignedCertificateTimestamp): - return NotImplemented - - return self._signature == other._signature - - def __ne__(self, other): - return not self == other diff --git a/src/cryptography/hazmat/bindings/__init__.py b/src/cryptography/hazmat/bindings/__init__.py index 4b540884df72..b509336233c2 100644 --- a/src/cryptography/hazmat/bindings/__init__.py +++ b/src/cryptography/hazmat/bindings/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/src/cryptography/hazmat/bindings/_rust/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/__init__.pyi new file mode 100644 index 000000000000..94a37a20aa96 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -0,0 +1,34 @@ +# 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 types +import typing + +def check_pkcs7_padding(data: bytes) -> bool: ... +def check_ansix923_padding(data: bytes) -> bool: ... + +class ObjectIdentifier: + def __init__(self, val: str) -> None: ... + @property + def dotted_string(self) -> str: ... + @property + def _name(self) -> str: ... + +T = typing.TypeVar("T") + +class FixedPool(typing.Generic[T]): + def __init__( + self, + create: typing.Callable[[], T], + ) -> None: ... + def acquire(self) -> PoolAcquisition[T]: ... + +class PoolAcquisition(typing.Generic[T]): + def __enter__(self) -> T: ... + def __exit__( + self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc_value: typing.Optional[BaseException], + exc_tb: typing.Optional[types.TracebackType], + ) -> None: ... diff --git a/tests/hypothesis/__init__.py b/src/cryptography/hazmat/bindings/_rust/_openssl.pyi similarity index 73% rename from tests/hypothesis/__init__.py rename to src/cryptography/hazmat/bindings/_rust/_openssl.pyi index 4b540884df72..80100082acd3 100644 --- a/tests/hypothesis/__init__.py +++ b/src/cryptography/hazmat/bindings/_rust/_openssl.pyi @@ -2,4 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +import typing + +lib = typing.Any +ffi = typing.Any diff --git a/src/cryptography/hazmat/bindings/_rust/asn1.pyi b/src/cryptography/hazmat/bindings/_rust/asn1.pyi new file mode 100644 index 000000000000..a8369ba8383e --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/asn1.pyi @@ -0,0 +1,16 @@ +# 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 typing + +class TestCertificate: + not_after_tag: int + not_before_tag: int + issuer_value_tags: typing.List[int] + subject_value_tags: typing.List[int] + +def decode_dss_signature(signature: bytes) -> typing.Tuple[int, int]: ... +def encode_dss_signature(r: int, s: int) -> bytes: ... +def parse_spki_for_data(data: bytes) -> bytes: ... +def test_parse_certificate(data: bytes) -> TestCertificate: ... diff --git a/src/cryptography/hazmat/bindings/_rust/exceptions.pyi b/src/cryptography/hazmat/bindings/_rust/exceptions.pyi new file mode 100644 index 000000000000..09f46b1e817f --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/exceptions.pyi @@ -0,0 +1,17 @@ +# 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. + +class _Reasons: + BACKEND_MISSING_INTERFACE: _Reasons + UNSUPPORTED_HASH: _Reasons + UNSUPPORTED_CIPHER: _Reasons + UNSUPPORTED_PADDING: _Reasons + UNSUPPORTED_MGF: _Reasons + UNSUPPORTED_PUBLIC_KEY_ALGORITHM: _Reasons + UNSUPPORTED_ELLIPTIC_CURVE: _Reasons + UNSUPPORTED_SERIALIZATION: _Reasons + UNSUPPORTED_X509: _Reasons + UNSUPPORTED_EXCHANGE_ALGORITHM: _Reasons + UNSUPPORTED_DIFFIE_HELLMAN: _Reasons + UNSUPPORTED_MAC: _Reasons diff --git a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi new file mode 100644 index 000000000000..4671eb9ba34d --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -0,0 +1,25 @@ +# 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 typing + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.x509.ocsp import ( + OCSPRequest, + OCSPRequestBuilder, + OCSPResponse, + OCSPResponseBuilder, + OCSPResponseStatus, +) + +def load_der_ocsp_request(data: bytes) -> OCSPRequest: ... +def load_der_ocsp_response(data: bytes) -> OCSPResponse: ... +def create_ocsp_request(builder: OCSPRequestBuilder) -> OCSPRequest: ... +def create_ocsp_response( + status: OCSPResponseStatus, + builder: typing.Optional[OCSPResponseBuilder], + private_key: typing.Optional[PrivateKeyTypes], + hash_algorithm: typing.Optional[hashes.HashAlgorithm], +) -> OCSPResponse: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi new file mode 100644 index 000000000000..82f30d20b0ab --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -0,0 +1,47 @@ +# 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 typing + +from cryptography.hazmat.bindings._rust.openssl import ( + dh, + dsa, + ed448, + ed25519, + hashes, + hmac, + kdf, + poly1305, + x448, + x25519, +) + +__all__ = [ + "openssl_version", + "raise_openssl_error", + "dh", + "dsa", + "hashes", + "hmac", + "kdf", + "ed448", + "ed25519", + "poly1305", + "x448", + "x25519", +] + +def openssl_version() -> int: ... +def raise_openssl_error() -> typing.NoReturn: ... +def capture_error_stack() -> typing.List[OpenSSLError]: ... +def is_fips_enabled() -> bool: ... + +class OpenSSLError: + @property + def lib(self) -> int: ... + @property + def reason(self) -> int: ... + @property + def reason_text(self) -> bytes: ... + def _lib_reason_match(self, lib: int, reason: int) -> bool: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi new file mode 100644 index 000000000000..bfd005d99fec --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi @@ -0,0 +1,22 @@ +# 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 dh + +MIN_MODULUS_SIZE: int + +class DHPrivateKey: ... +class DHPublicKey: ... +class DHParameters: ... + +def generate_parameters(generator: int, key_size: int) -> dh.DHParameters: ... +def private_key_from_ptr(ptr: int) -> dh.DHPrivateKey: ... +def public_key_from_ptr(ptr: int) -> dh.DHPublicKey: ... +def from_pem_parameters(data: bytes) -> dh.DHParameters: ... +def from_der_parameters(data: bytes) -> dh.DHParameters: ... +def from_private_numbers(numbers: dh.DHPrivateNumbers) -> dh.DHPrivateKey: ... +def from_public_numbers(numbers: dh.DHPublicNumbers) -> dh.DHPublicKey: ... +def from_parameter_numbers( + numbers: dh.DHParameterNumbers, +) -> dh.DHParameters: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi new file mode 100644 index 000000000000..5a56f256d52d --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi @@ -0,0 +1,20 @@ +# 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 dsa + +class DSAPrivateKey: ... +class DSAPublicKey: ... +class DSAParameters: ... + +def generate_parameters(key_size: int) -> dsa.DSAParameters: ... +def private_key_from_ptr(ptr: int) -> dsa.DSAPrivateKey: ... +def public_key_from_ptr(ptr: int) -> dsa.DSAPublicKey: ... +def from_private_numbers( + numbers: dsa.DSAPrivateNumbers, +) -> dsa.DSAPrivateKey: ... +def from_public_numbers(numbers: dsa.DSAPublicNumbers) -> dsa.DSAPublicKey: ... +def from_parameter_numbers( + numbers: dsa.DSAParameterNumbers, +) -> dsa.DSAParameters: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi new file mode 100644 index 000000000000..c7f127f0b157 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi @@ -0,0 +1,14 @@ +# 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 ed25519 + +class Ed25519PrivateKey: ... +class Ed25519PublicKey: ... + +def generate_key() -> ed25519.Ed25519PrivateKey: ... +def private_key_from_ptr(ptr: int) -> ed25519.Ed25519PrivateKey: ... +def public_key_from_ptr(ptr: int) -> ed25519.Ed25519PublicKey: ... +def from_private_bytes(data: bytes) -> ed25519.Ed25519PrivateKey: ... +def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi new file mode 100644 index 000000000000..1cf5f1773a0b --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi @@ -0,0 +1,14 @@ +# 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 ed448 + +class Ed448PrivateKey: ... +class Ed448PublicKey: ... + +def generate_key() -> ed448.Ed448PrivateKey: ... +def private_key_from_ptr(ptr: int) -> ed448.Ed448PrivateKey: ... +def public_key_from_ptr(ptr: int) -> ed448.Ed448PublicKey: ... +def from_private_bytes(data: bytes) -> ed448.Ed448PrivateKey: ... +def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi new file mode 100644 index 000000000000..ca5f42a00615 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi @@ -0,0 +1,17 @@ +# 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 typing + +from cryptography.hazmat.primitives import hashes + +class Hash(hashes.HashContext): + def __init__( + self, algorithm: hashes.HashAlgorithm, backend: typing.Any = None + ) -> None: ... + @property + def algorithm(self) -> hashes.HashAlgorithm: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def copy(self) -> Hash: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi new file mode 100644 index 000000000000..e38d9b54d01b --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi @@ -0,0 +1,21 @@ +# 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 typing + +from cryptography.hazmat.primitives import hashes + +class HMAC(hashes.HashContext): + def __init__( + self, + key: bytes, + algorithm: hashes.HashAlgorithm, + backend: typing.Any = None, + ) -> None: ... + @property + def algorithm(self) -> hashes.HashAlgorithm: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> HMAC: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi new file mode 100644 index 000000000000..034a8fed2e78 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi @@ -0,0 +1,22 @@ +# 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.hashes import HashAlgorithm + +def derive_pbkdf2_hmac( + key_material: bytes, + algorithm: HashAlgorithm, + salt: bytes, + iterations: int, + length: int, +) -> bytes: ... +def derive_scrypt( + key_material: bytes, + salt: bytes, + n: int, + r: int, + p: int, + max_mem: int, + length: int, +) -> bytes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi new file mode 100644 index 000000000000..2e9b0a9e1254 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi @@ -0,0 +1,13 @@ +# 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. + +class Poly1305: + def __init__(self, key: bytes) -> None: ... + @staticmethod + def generate_tag(key: bytes, data: bytes) -> bytes: ... + @staticmethod + def verify_tag(key: bytes, data: bytes, tag: bytes) -> None: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, tag: bytes) -> None: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi new file mode 100644 index 000000000000..90f7cbdda950 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi @@ -0,0 +1,14 @@ +# 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 x25519 + +class X25519PrivateKey: ... +class X25519PublicKey: ... + +def generate_key() -> x25519.X25519PrivateKey: ... +def private_key_from_ptr(ptr: int) -> x25519.X25519PrivateKey: ... +def public_key_from_ptr(ptr: int) -> x25519.X25519PublicKey: ... +def from_private_bytes(data: bytes) -> x25519.X25519PrivateKey: ... +def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi new file mode 100644 index 000000000000..d326c8d2d7c5 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi @@ -0,0 +1,14 @@ +# 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 x448 + +class X448PrivateKey: ... +class X448PublicKey: ... + +def generate_key() -> x448.X448PrivateKey: ... +def private_key_from_ptr(ptr: int) -> x448.X448PrivateKey: ... +def public_key_from_ptr(ptr: int) -> x448.X448PublicKey: ... +def from_private_bytes(data: bytes) -> x448.X448PrivateKey: ... +def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi new file mode 100644 index 000000000000..66bd850981a6 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -0,0 +1,15 @@ +import typing + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import pkcs7 + +def serialize_certificates( + certs: typing.List[x509.Certificate], + encoding: serialization.Encoding, +) -> bytes: ... +def sign_and_serialize( + builder: pkcs7.PKCS7SignatureBuilder, + encoding: serialization.Encoding, + options: typing.Iterable[pkcs7.PKCS7Options], +) -> bytes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi new file mode 100644 index 000000000000..24b2f5e3a78c --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi @@ -0,0 +1,44 @@ +# 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 typing + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes + +def load_pem_x509_certificate(data: bytes) -> x509.Certificate: ... +def load_pem_x509_certificates( + data: bytes, +) -> typing.List[x509.Certificate]: ... +def load_der_x509_certificate(data: bytes) -> x509.Certificate: ... +def load_pem_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... +def load_der_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... +def load_pem_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ... +def load_der_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ... +def encode_name_bytes(name: x509.Name) -> bytes: ... +def encode_extension_value(extension: x509.ExtensionType) -> bytes: ... +def create_x509_certificate( + builder: x509.CertificateBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: typing.Optional[hashes.HashAlgorithm], + padding: typing.Optional[typing.Union[PKCS1v15, PSS]], +) -> x509.Certificate: ... +def create_x509_csr( + builder: x509.CertificateSigningRequestBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: typing.Optional[hashes.HashAlgorithm], +) -> x509.CertificateSigningRequest: ... +def create_x509_crl( + builder: x509.CertificateRevocationListBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: typing.Optional[hashes.HashAlgorithm], +) -> x509.CertificateRevocationList: ... + +class Sct: ... +class Certificate: ... +class RevokedCertificate: ... +class CertificateRevocationList: ... +class CertificateSigningRequest: ... diff --git a/src/cryptography/hazmat/bindings/openssl/__init__.py b/src/cryptography/hazmat/bindings/openssl/__init__.py index 4b540884df72..b509336233c2 100644 --- a/src/cryptography/hazmat/bindings/openssl/__init__.py +++ b/src/cryptography/hazmat/bindings/openssl/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index cdc18eab6848..5e8ecd04182c 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -2,63 +2,19 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations - -def cryptography_has_ec2m(): - return [ - "EC_POINT_set_affine_coordinates_GF2m", - "EC_POINT_get_affine_coordinates_GF2m", - "EC_POINT_set_compressed_coordinates_GF2m", - ] - - -def cryptography_has_rsa_oaep_md(): - return [ - "EVP_PKEY_CTX_set_rsa_oaep_md", - ] - - -def cryptography_has_rsa_oaep_label(): - return [ - "EVP_PKEY_CTX_set0_rsa_oaep_label", - ] - - -def cryptography_has_ssl3_method(): - return [ - "SSLv3_method", - "SSLv3_client_method", - "SSLv3_server_method", - ] +import typing -def cryptography_has_102_verification(): - return [ - "X509_V_ERR_SUITE_B_INVALID_VERSION", - "X509_V_ERR_SUITE_B_INVALID_ALGORITHM", - "X509_V_ERR_SUITE_B_INVALID_CURVE", - "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM", - "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED", - "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256", - "X509_V_FLAG_SUITEB_128_LOS_ONLY", - "X509_V_FLAG_SUITEB_192_LOS", - "X509_V_FLAG_SUITEB_128_LOS", - ] - - -def cryptography_has_110_verification_params(): - return ["X509_CHECK_FLAG_NEVER_CHECK_SUBJECT"] - - -def cryptography_has_set_cert_cb(): +def cryptography_has_set_cert_cb() -> typing.List[str]: return [ "SSL_CTX_set_cert_cb", "SSL_set_cert_cb", ] -def cryptography_has_ssl_st(): +def cryptography_has_ssl_st() -> typing.List[str]: return [ "SSL_ST_BEFORE", "SSL_ST_OK", @@ -67,135 +23,72 @@ def cryptography_has_ssl_st(): ] -def cryptography_has_tls_st(): +def cryptography_has_tls_st() -> typing.List[str]: return [ "TLS_ST_BEFORE", "TLS_ST_OK", ] -def cryptography_has_locking_callbacks(): - return [ - "Cryptography_setup_ssl_threads", - ] - - -def cryptography_has_scrypt(): - return [ - "EVP_PBE_scrypt", - ] - - -def cryptography_has_evp_pkey_dhx(): +def cryptography_has_evp_pkey_dhx() -> typing.List[str]: return [ "EVP_PKEY_DHX", ] -def cryptography_has_mem_functions(): +def cryptography_has_mem_functions() -> typing.List[str]: return [ "Cryptography_CRYPTO_set_mem_functions", ] -def cryptography_has_sct(): - return [ - "SCT_get_version", - "SCT_get_log_entry_type", - "SCT_get0_log_id", - "SCT_get0_signature", - "SCT_get_timestamp", - "SCT_set_source", - "sk_SCT_num", - "sk_SCT_value", - "SCT_LIST_free", - "sk_SCT_push", - "sk_SCT_new_null", - "SCT_new", - "SCT_set1_log_id", - "SCT_set_timestamp", - "SCT_set_version", - "SCT_set_log_entry_type", - ] - - -def cryptography_has_x509_store_ctx_get_issuer(): +def cryptography_has_x509_store_ctx_get_issuer() -> typing.List[str]: return [ - "X509_STORE_get_get_issuer", "X509_STORE_set_get_issuer", ] -def cryptography_has_x25519(): - return [ - "EVP_PKEY_X25519", - "NID_X25519", - ] - - -def cryptography_has_x448(): - return [ - "EVP_PKEY_X448", - "NID_X448", - ] - - -def cryptography_has_ed448(): +def cryptography_has_ed448() -> typing.List[str]: return [ "EVP_PKEY_ED448", "NID_ED448", ] -def cryptography_has_ed25519(): +def cryptography_has_ed25519() -> typing.List[str]: return [ "NID_ED25519", "EVP_PKEY_ED25519", ] -def cryptography_has_poly1305(): +def cryptography_has_poly1305() -> typing.List[str]: return [ "NID_poly1305", "EVP_PKEY_POLY1305", ] -def cryptography_has_oneshot_evp_digest_sign_verify(): - return [ - "EVP_DigestSign", - "EVP_DigestVerify", - ] - - -def cryptography_has_evp_digestfinal_xof(): +def cryptography_has_evp_digestfinal_xof() -> typing.List[str]: return [ "EVP_DigestFinalXOF", ] -def cryptography_has_evp_pkey_get_set_tls_encodedpoint(): - return [ - "EVP_PKEY_get1_tls_encodedpoint", - "EVP_PKEY_set1_tls_encodedpoint", - ] - - -def cryptography_has_fips(): +def cryptography_has_fips() -> typing.List[str]: return [ "FIPS_mode_set", "FIPS_mode", ] -def cryptography_has_ssl_sigalgs(): +def cryptography_has_ssl_sigalgs() -> typing.List[str]: return [ "SSL_CTX_set1_sigalgs_list", - "SSL_get_sigalgs", ] -def cryptography_has_psk(): +def cryptography_has_psk() -> typing.List[str]: return [ "SSL_CTX_use_psk_identity_hint", "SSL_CTX_set_psk_server_callback", @@ -203,33 +96,28 @@ def cryptography_has_psk(): ] -def cryptography_has_custom_ext(): +def cryptography_has_psk_tlsv13() -> typing.List[str]: return [ - "SSL_CTX_add_client_custom_ext", - "SSL_CTX_add_server_custom_ext", - "SSL_extension_supported", + "SSL_CTX_set_psk_find_session_callback", + "SSL_CTX_set_psk_use_session_callback", + "Cryptography_SSL_SESSION_new", + "SSL_CIPHER_find", + "SSL_SESSION_set1_master_key", + "SSL_SESSION_set_cipher", + "SSL_SESSION_set_protocol_version", ] -def cryptography_has_openssl_cleanup(): +def cryptography_has_custom_ext() -> typing.List[str]: return [ - "OPENSSL_cleanup", - ] - - -def cryptography_has_cipher_details(): - return [ - "SSL_CIPHER_is_aead", - "SSL_CIPHER_get_cipher_nid", - "SSL_CIPHER_get_digest_nid", - "SSL_CIPHER_get_kx_nid", - "SSL_CIPHER_get_auth_nid", + "SSL_CTX_add_client_custom_ext", + "SSL_CTX_add_server_custom_ext", + "SSL_extension_supported", ] -def cryptography_has_tlsv13(): +def cryptography_has_tlsv13_functions() -> typing.List[str]: return [ - "SSL_OP_NO_TLSv1_3", "SSL_VERIFY_POST_HANDSHAKE", "SSL_CTX_set_ciphersuites", "SSL_verify_client_post_handshake", @@ -242,14 +130,7 @@ def cryptography_has_tlsv13(): ] -def cryptography_has_keylog(): - return [ - "SSL_CTX_set_keylog_callback", - "SSL_CTX_get_keylog_callback", - ] - - -def cryptography_has_raw_key(): +def cryptography_has_raw_key() -> typing.List[str]: return [ "EVP_PKEY_new_raw_private_key", "EVP_PKEY_new_raw_public_key", @@ -258,7 +139,7 @@ def cryptography_has_raw_key(): ] -def cryptography_has_engine(): +def cryptography_has_engine() -> typing.List[str]: return [ "ENGINE_by_id", "ENGINE_init", @@ -269,21 +150,21 @@ def cryptography_has_engine(): "ENGINE_ctrl_cmd", "ENGINE_free", "ENGINE_get_name", - "Cryptography_add_osrandom_engine", "ENGINE_ctrl_cmd_string", "ENGINE_load_builtin_engines", "ENGINE_load_private_key", "ENGINE_load_public_key", + "SSL_CTX_set_client_cert_engine", ] -def cryptography_has_verified_chain(): +def cryptography_has_verified_chain() -> typing.List[str]: return [ "SSL_get0_verified_chain", ] -def cryptography_has_srtp(): +def cryptography_has_srtp() -> typing.List[str]: return [ "SSL_CTX_set_tlsext_use_srtp", "SSL_set_tlsext_use_srtp", @@ -291,50 +172,130 @@ def cryptography_has_srtp(): ] +def cryptography_has_providers() -> typing.List[str]: + return [ + "OSSL_PROVIDER_load", + "OSSL_PROVIDER_unload", + "ERR_LIB_PROV", + "PROV_R_WRONG_FINAL_BLOCK_LENGTH", + "PROV_R_BAD_DECRYPT", + ] + + +def cryptography_has_op_no_renegotiation() -> typing.List[str]: + return [ + "SSL_OP_NO_RENEGOTIATION", + ] + + +def cryptography_has_dtls_get_data_mtu() -> typing.List[str]: + return [ + "DTLS_get_data_mtu", + ] + + +def cryptography_has_300_fips() -> typing.List[str]: + return [ + "EVP_default_properties_is_fips_enabled", + "EVP_default_properties_enable_fips", + ] + + +def cryptography_has_ssl_cookie() -> typing.List[str]: + return [ + "SSL_OP_COOKIE_EXCHANGE", + "DTLSv1_listen", + "SSL_CTX_set_cookie_generate_cb", + "SSL_CTX_set_cookie_verify_cb", + ] + + +def cryptography_has_pkcs7_funcs() -> typing.List[str]: + return [ + "SMIME_write_PKCS7", + "PEM_write_bio_PKCS7_stream", + "PKCS7_sign_add_signer", + "PKCS7_final", + "PKCS7_verify", + "SMIME_read_PKCS7", + "PKCS7_get0_signers", + ] + + +def cryptography_has_bn_flags() -> typing.List[str]: + return [ + "BN_FLG_CONSTTIME", + "BN_set_flags", + "BN_prime_checks_for_size", + ] + + +def cryptography_has_evp_pkey_dh() -> typing.List[str]: + return [ + "EVP_PKEY_set1_DH", + ] + + +def cryptography_has_300_evp_cipher() -> typing.List[str]: + return ["EVP_CIPHER_fetch", "EVP_CIPHER_free"] + + +def cryptography_has_unexpected_eof_while_reading() -> typing.List[str]: + return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"] + + +def cryptography_has_pkcs12_set_mac() -> typing.List[str]: + return ["PKCS12_set_mac"] + + +def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]: + return [ + "SSL_OP_IGNORE_UNEXPECTED_EOF", + ] + + +def cryptography_has_get_extms_support() -> typing.List[str]: + return ["SSL_get_extms_support"] + + +def cryptography_has_evp_pkey_set_peer_ex() -> typing.List[str]: + return ["EVP_PKEY_derive_set_peer_ex"] + + +def cryptography_has_evp_aead() -> typing.List[str]: + return [ + "EVP_aead_chacha20_poly1305", + "EVP_AEAD_CTX_free", + "EVP_AEAD_CTX_seal", + "EVP_AEAD_CTX_open", + "EVP_AEAD_max_overhead", + "Cryptography_EVP_AEAD_CTX_new", + ] + + # This is a mapping of # {condition: function-returning-names-dependent-on-that-condition} so we can # loop over them and delete unsupported names at runtime. It will be removed # when cffi supports #if in cdef. We use functions instead of just a dict of # lists so we can use coverage to measure which are used. CONDITIONAL_NAMES = { - "Cryptography_HAS_EC2M": cryptography_has_ec2m, - "Cryptography_HAS_RSA_OAEP_MD": cryptography_has_rsa_oaep_md, - "Cryptography_HAS_RSA_OAEP_LABEL": cryptography_has_rsa_oaep_label, - "Cryptography_HAS_SSL3_METHOD": cryptography_has_ssl3_method, - "Cryptography_HAS_102_VERIFICATION": cryptography_has_102_verification, - "Cryptography_HAS_110_VERIFICATION_PARAMS": ( - cryptography_has_110_verification_params - ), "Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb, "Cryptography_HAS_SSL_ST": cryptography_has_ssl_st, "Cryptography_HAS_TLS_ST": cryptography_has_tls_st, - "Cryptography_HAS_LOCKING_CALLBACKS": cryptography_has_locking_callbacks, - "Cryptography_HAS_SCRYPT": cryptography_has_scrypt, "Cryptography_HAS_EVP_PKEY_DHX": cryptography_has_evp_pkey_dhx, "Cryptography_HAS_MEM_FUNCTIONS": cryptography_has_mem_functions, - "Cryptography_HAS_SCT": cryptography_has_sct, "Cryptography_HAS_X509_STORE_CTX_GET_ISSUER": ( cryptography_has_x509_store_ctx_get_issuer ), - "Cryptography_HAS_X25519": cryptography_has_x25519, - "Cryptography_HAS_X448": cryptography_has_x448, "Cryptography_HAS_ED448": cryptography_has_ed448, "Cryptography_HAS_ED25519": cryptography_has_ed25519, "Cryptography_HAS_POLY1305": cryptography_has_poly1305, - "Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY": ( - cryptography_has_oneshot_evp_digest_sign_verify - ), - "Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint": ( - cryptography_has_evp_pkey_get_set_tls_encodedpoint - ), "Cryptography_HAS_FIPS": cryptography_has_fips, "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs, "Cryptography_HAS_PSK": cryptography_has_psk, + "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, - "Cryptography_HAS_OPENSSL_CLEANUP": cryptography_has_openssl_cleanup, - "Cryptography_HAS_CIPHER_DETAILS": cryptography_has_cipher_details, - "Cryptography_HAS_TLSv1_3": cryptography_has_tlsv13, - "Cryptography_HAS_KEYLOG": cryptography_has_keylog, + "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, "Cryptography_HAS_RAW_KEY": cryptography_has_raw_key, "Cryptography_HAS_EVP_DIGESTFINAL_XOF": ( cryptography_has_evp_digestfinal_xof @@ -342,4 +303,27 @@ def cryptography_has_srtp(): "Cryptography_HAS_ENGINE": cryptography_has_engine, "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, + "Cryptography_HAS_PROVIDERS": cryptography_has_providers, + "Cryptography_HAS_OP_NO_RENEGOTIATION": ( + cryptography_has_op_no_renegotiation + ), + "Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu, + "Cryptography_HAS_300_FIPS": cryptography_has_300_fips, + "Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie, + "Cryptography_HAS_PKCS7_FUNCS": cryptography_has_pkcs7_funcs, + "Cryptography_HAS_BN_FLAGS": cryptography_has_bn_flags, + "Cryptography_HAS_EVP_PKEY_DH": cryptography_has_evp_pkey_dh, + "Cryptography_HAS_300_EVP_CIPHER": cryptography_has_300_evp_cipher, + "Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": ( + cryptography_has_unexpected_eof_while_reading + ), + "Cryptography_HAS_PKCS12_SET_MAC": cryptography_has_pkcs12_set_mac, + "Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": ( + cryptography_has_ssl_op_ignore_unexpected_eof + ), + "Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support, + "Cryptography_HAS_EVP_PKEY_SET_PEER_EX": ( + cryptography_has_evp_pkey_set_peer_ex + ), + "Cryptography_HAS_EVP_AEAD": (cryptography_has_evp_aead), } diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index f6bf93729118..b50d631518c1 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -2,82 +2,29 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import collections import os +import sys import threading import types +import typing import warnings import cryptography -from cryptography import utils from cryptography.exceptions import InternalError -from cryptography.hazmat.bindings._openssl import ffi, lib +from cryptography.hazmat.bindings._rust import _openssl, openssl from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES -_OpenSSLErrorWithText = collections.namedtuple( - "_OpenSSLErrorWithText", ["code", "lib", "func", "reason", "reason_text"] -) - -class _OpenSSLError(object): - def __init__(self, code, lib, func, reason): - self._code = code - self._lib = lib - self._func = func - self._reason = reason - - def _lib_reason_match(self, lib, reason): - return lib == self.lib and reason == self.reason - - code = utils.read_only_property("_code") - lib = utils.read_only_property("_lib") - func = utils.read_only_property("_func") - reason = utils.read_only_property("_reason") - - -def _consume_errors(lib): - errors = [] - while True: - code = lib.ERR_get_error() - if code == 0: - break - - err_lib = lib.ERR_GET_LIB(code) - err_func = lib.ERR_GET_FUNC(code) - err_reason = lib.ERR_GET_REASON(code) - - errors.append(_OpenSSLError(code, err_lib, err_func, err_reason)) - - return errors - - -def _errors_with_text(errors): - errors_with_text = [] - for err in errors: - buf = ffi.new("char[]", 256) - lib.ERR_error_string_n(err.code, buf, len(buf)) - err_text_reason = ffi.string(buf) - - errors_with_text.append( - _OpenSSLErrorWithText( - err.code, err.lib, err.func, err.reason, err_text_reason - ) - ) - - return errors_with_text - - -def _consume_errors_with_text(lib): - return _errors_with_text(_consume_errors(lib)) - - -def _openssl_assert(lib, ok, errors=None): +def _openssl_assert( + lib, + ok: bool, + errors: typing.Optional[typing.List[openssl.OpenSSLError]] = None, +) -> None: if not ok: if errors is None: - errors = _consume_errors(lib) - errors_with_text = _errors_with_text(errors) + errors = openssl.capture_error_stack() raise InternalError( "Unknown OpenSSL error. This error is commonly encountered when " @@ -86,14 +33,28 @@ def _openssl_assert(lib, ok, errors=None): "OpenSSL try disabling it before reporting a bug. Otherwise " "please file an issue at https://github.com/pyca/cryptography/" "issues with information on how to reproduce " - "this. ({0!r})".format(errors_with_text), - errors_with_text, + "this. ({!r})".format(errors), + errors, + ) + + +def _legacy_provider_error(loaded: bool) -> None: + if not loaded: + raise RuntimeError( + "OpenSSL 3.0's legacy provider failed to load. This is a fatal " + "error by default, but cryptography supports running without " + "legacy algorithms by setting the environment variable " + "CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error," + " you have likely made a mistake with your OpenSSL configuration." ) -def build_conditional_library(lib, conditional_names): +def build_conditional_library( + lib: typing.Any, + conditional_names: typing.Dict[str, typing.Callable[[], typing.List[str]]], +) -> typing.Any: conditional_lib = types.ModuleType("lib") - conditional_lib._original_lib = lib + conditional_lib._original_lib = lib # type: ignore[attr-defined] excluded_names = set() for condition, names_cb in conditional_names.items(): if not getattr(lib, condition): @@ -106,90 +67,74 @@ def build_conditional_library(lib, conditional_names): return conditional_lib -class Binding(object): +class Binding: """ OpenSSL API wrapper. """ - lib = None - ffi = ffi + lib: typing.ClassVar = None + ffi = _openssl.ffi _lib_loaded = False _init_lock = threading.Lock() - _lock_init_lock = threading.Lock() + _legacy_provider: typing.Any = ffi.NULL + _legacy_provider_loaded = False + _default_provider: typing.Any = ffi.NULL - def __init__(self): + def __init__(self) -> None: self._ensure_ffi_initialized() - @classmethod - def _register_osrandom_engine(cls): - # Clear any errors extant in the queue before we start. In many - # scenarios other things may be interacting with OpenSSL in the same - # process space and it has proven untenable to assume that they will - # reliably clear the error queue. Once we clear it here we will - # error on any subsequent unexpected item in the stack. - cls.lib.ERR_clear_error() - if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - result = cls.lib.Cryptography_add_osrandom_engine() - _openssl_assert(cls.lib, result in (1, 2)) + def _enable_fips(self) -> None: + # This function enables FIPS mode for OpenSSL 3.0.0 on installs that + # have the FIPS provider installed properly. + _openssl_assert(self.lib, self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER) + self._base_provider = self.lib.OSSL_PROVIDER_load( + self.ffi.NULL, b"base" + ) + _openssl_assert(self.lib, self._base_provider != self.ffi.NULL) + self.lib._fips_provider = self.lib.OSSL_PROVIDER_load( + self.ffi.NULL, b"fips" + ) + _openssl_assert(self.lib, self.lib._fips_provider != self.ffi.NULL) + + res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1) + _openssl_assert(self.lib, res == 1) @classmethod - def _ensure_ffi_initialized(cls): + def _ensure_ffi_initialized(cls) -> None: with cls._init_lock: if not cls._lib_loaded: - cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES) + cls.lib = build_conditional_library( + _openssl.lib, CONDITIONAL_NAMES + ) cls._lib_loaded = True - # initialize the SSL library - cls.lib.SSL_library_init() - # adds all ciphers/digests for EVP - cls.lib.OpenSSL_add_all_algorithms() - # loads error strings for libcrypto and libssl functions - cls.lib.SSL_load_error_strings() - cls._register_osrandom_engine() + # As of OpenSSL 3.0.0 we must register a legacy cipher provider + # to get RC2 (needed for junk asymmetric private key + # serialization), RC4, Blowfish, IDEA, SEED, etc. These things + # are ugly legacy, but we aren't going to get rid of them + # any time soon. + if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + if not os.environ.get("CRYPTOGRAPHY_OPENSSL_NO_LEGACY"): + cls._legacy_provider = cls.lib.OSSL_PROVIDER_load( + cls.ffi.NULL, b"legacy" + ) + cls._legacy_provider_loaded = ( + cls._legacy_provider != cls.ffi.NULL + ) + _legacy_provider_error(cls._legacy_provider_loaded) + + cls._default_provider = cls.lib.OSSL_PROVIDER_load( + cls.ffi.NULL, b"default" + ) + _openssl_assert( + cls.lib, cls._default_provider != cls.ffi.NULL + ) @classmethod - def init_static_locks(cls): - with cls._lock_init_lock: - cls._ensure_ffi_initialized() - # Use Python's implementation if available, importing _ssl triggers - # the setup for this. - __import__("_ssl") - - if ( - not cls.lib.Cryptography_HAS_LOCKING_CALLBACKS - or cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL - ): - return - - # If nothing else has setup a locking callback already, we set up - # our own - res = lib.Cryptography_setup_ssl_threads() - _openssl_assert(cls.lib, res == 1) - - -def _verify_openssl_version(lib): - if ( - lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 - and not lib.CRYPTOGRAPHY_IS_LIBRESSL - ): - if os.environ.get("CRYPTOGRAPHY_ALLOW_OPENSSL_102"): - warnings.warn( - "OpenSSL version 1.0.2 is no longer supported by the OpenSSL " - "project, please upgrade. The next version of cryptography " - "will completely remove support for it.", - utils.CryptographyDeprecationWarning, - ) - else: - raise RuntimeError( - "You are linking against OpenSSL 1.0.2, which is no longer " - "supported by the OpenSSL project. To use this version of " - "cryptography you need to upgrade to a newer version of " - "OpenSSL. For this version only you can also set the " - "environment variable CRYPTOGRAPHY_ALLOW_OPENSSL_102 to " - "allow OpenSSL 1.0.2." - ) + def init_static_locks(cls) -> None: + cls._ensure_ffi_initialized() -def _verify_package_version(version): +def _verify_package_version(version: str) -> None: # Occasionally we run into situations where the version of the Python # package does not match the version of the shared object that is loaded. # This may occur in environments where multiple versions of cryptography @@ -197,7 +142,9 @@ def _verify_package_version(version): # up later this code checks that the currently imported package and the # shared object that were loaded have the same version and raise an # ImportError if they do not - so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION) + so_package_version = _openssl.ffi.string( + _openssl.lib.CRYPTOGRAPHY_PACKAGE_VERSION + ) if version.encode("ascii") != so_package_version: raise ImportError( "The version of cryptography does not match the loaded " @@ -209,14 +156,24 @@ def _verify_package_version(version): ) ) + _openssl_assert( + _openssl.lib, + _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), + ) + _verify_package_version(cryptography.__version__) -# OpenSSL is not thread safe until the locks are initialized. We call this -# method in module scope so that it executes with the import lock. On -# Pythons < 3.4 this import lock is a global lock, which can prevent a race -# condition registering the OpenSSL locks. On Python 3.4+ the import lock -# is per module so this approach will not work. Binding.init_static_locks() -_verify_openssl_version(Binding.lib) +if ( + sys.platform == "win32" + and os.environ.get("PROCESSOR_ARCHITEW6432") is not None +): + warnings.warn( + "You are using cryptography on a 32-bit Python on a 64-bit Windows " + "Operating System. Cryptography will be significantly faster if you " + "switch to using a 64-bit Python.", + UserWarning, + stacklevel=2, + ) diff --git a/src/cryptography/hazmat/primitives/__init__.py b/src/cryptography/hazmat/primitives/__init__.py index 4b540884df72..b509336233c2 100644 --- a/src/cryptography/hazmat/primitives/__init__.py +++ b/src/cryptography/hazmat/primitives/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/src/cryptography/hazmat/primitives/_asymmetric.py b/src/cryptography/hazmat/primitives/_asymmetric.py new file mode 100644 index 000000000000..ea55ffdf1a72 --- /dev/null +++ b/src/cryptography/hazmat/primitives/_asymmetric.py @@ -0,0 +1,19 @@ +# 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 abc + +# This exists to break an import cycle. It is normally accessible from the +# asymmetric padding module. + + +class AsymmetricPadding(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this padding (e.g. "PSS", "PKCS1"). + """ diff --git a/src/cryptography/hazmat/primitives/_cipheralgorithm.py b/src/cryptography/hazmat/primitives/_cipheralgorithm.py new file mode 100644 index 000000000000..3b880b648849 --- /dev/null +++ b/src/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -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. + +from __future__ import annotations + +import abc +import typing + +# This exists to break an import cycle. It is normally accessible from the +# ciphers module. + + +class CipherAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this mode (e.g. "AES", "Camellia"). + """ + + @property + @abc.abstractmethod + def key_sizes(self) -> typing.FrozenSet[int]: + """ + Valid key sizes for this algorithm in bits + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The size of the key being used as an integer in bits (e.g. 128, 256). + """ + + +class BlockCipherAlgorithm(CipherAlgorithm): + key: bytes + + @property + @abc.abstractmethod + def block_size(self) -> int: + """ + The size of a block as an integer in bits (e.g. 64, 128). + """ diff --git a/src/cryptography/hazmat/primitives/_serialization.py b/src/cryptography/hazmat/primitives/_serialization.py new file mode 100644 index 000000000000..34f3fbc86026 --- /dev/null +++ b/src/cryptography/hazmat/primitives/_serialization.py @@ -0,0 +1,170 @@ +# 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 abc +import typing + +from cryptography import utils +from cryptography.hazmat.primitives.hashes import HashAlgorithm + +# This exists to break an import cycle. These classes are normally accessible +# from the serialization module. + + +class PBES(utils.Enum): + PBESv1SHA1And3KeyTripleDESCBC = "PBESv1 using SHA1 and 3-Key TripleDES" + PBESv2SHA256AndAES256CBC = "PBESv2 using SHA256 PBKDF2 and AES256 CBC" + + +class Encoding(utils.Enum): + PEM = "PEM" + DER = "DER" + OpenSSH = "OpenSSH" + Raw = "Raw" + X962 = "ANSI X9.62" + SMIME = "S/MIME" + + +class PrivateFormat(utils.Enum): + PKCS8 = "PKCS8" + TraditionalOpenSSL = "TraditionalOpenSSL" + Raw = "Raw" + OpenSSH = "OpenSSH" + PKCS12 = "PKCS12" + + def encryption_builder(self) -> KeySerializationEncryptionBuilder: + if self not in (PrivateFormat.OpenSSH, PrivateFormat.PKCS12): + raise ValueError( + "encryption_builder only supported with PrivateFormat.OpenSSH" + " and PrivateFormat.PKCS12" + ) + return KeySerializationEncryptionBuilder(self) + + +class PublicFormat(utils.Enum): + SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1" + PKCS1 = "Raw PKCS#1" + OpenSSH = "OpenSSH" + Raw = "Raw" + CompressedPoint = "X9.62 Compressed Point" + UncompressedPoint = "X9.62 Uncompressed Point" + + +class ParameterFormat(utils.Enum): + PKCS3 = "PKCS3" + + +class KeySerializationEncryption(metaclass=abc.ABCMeta): + pass + + +class BestAvailableEncryption(KeySerializationEncryption): + def __init__(self, password: bytes): + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + self.password = password + + +class NoEncryption(KeySerializationEncryption): + pass + + +class KeySerializationEncryptionBuilder: + def __init__( + self, + format: PrivateFormat, + *, + _kdf_rounds: typing.Optional[int] = None, + _hmac_hash: typing.Optional[HashAlgorithm] = None, + _key_cert_algorithm: typing.Optional[PBES] = None, + ) -> None: + self._format = format + + self._kdf_rounds = _kdf_rounds + self._hmac_hash = _hmac_hash + self._key_cert_algorithm = _key_cert_algorithm + + def kdf_rounds(self, rounds: int) -> KeySerializationEncryptionBuilder: + if self._kdf_rounds is not None: + raise ValueError("kdf_rounds already set") + + if not isinstance(rounds, int): + raise TypeError("kdf_rounds must be an integer") + + if rounds < 1: + raise ValueError("kdf_rounds must be a positive integer") + + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=rounds, + _hmac_hash=self._hmac_hash, + _key_cert_algorithm=self._key_cert_algorithm, + ) + + def hmac_hash( + self, algorithm: HashAlgorithm + ) -> KeySerializationEncryptionBuilder: + if self._format is not PrivateFormat.PKCS12: + raise TypeError( + "hmac_hash only supported with PrivateFormat.PKCS12" + ) + + if self._hmac_hash is not None: + raise ValueError("hmac_hash already set") + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=self._kdf_rounds, + _hmac_hash=algorithm, + _key_cert_algorithm=self._key_cert_algorithm, + ) + + def key_cert_algorithm( + self, algorithm: PBES + ) -> KeySerializationEncryptionBuilder: + if self._format is not PrivateFormat.PKCS12: + raise TypeError( + "key_cert_algorithm only supported with " + "PrivateFormat.PKCS12" + ) + if self._key_cert_algorithm is not None: + raise ValueError("key_cert_algorithm already set") + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=self._kdf_rounds, + _hmac_hash=self._hmac_hash, + _key_cert_algorithm=algorithm, + ) + + def build(self, password: bytes) -> KeySerializationEncryption: + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + return _KeySerializationEncryption( + self._format, + password, + kdf_rounds=self._kdf_rounds, + hmac_hash=self._hmac_hash, + key_cert_algorithm=self._key_cert_algorithm, + ) + + +class _KeySerializationEncryption(KeySerializationEncryption): + def __init__( + self, + format: PrivateFormat, + password: bytes, + *, + kdf_rounds: typing.Optional[int], + hmac_hash: typing.Optional[HashAlgorithm], + key_cert_algorithm: typing.Optional[PBES], + ): + self._format = format + self.password = password + + self._kdf_rounds = kdf_rounds + self._hmac_hash = hmac_hash + self._key_cert_algorithm = key_cert_algorithm diff --git a/src/cryptography/hazmat/primitives/asymmetric/__init__.py b/src/cryptography/hazmat/primitives/asymmetric/__init__.py index 494a7a13504b..b509336233c2 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/__init__.py +++ b/src/cryptography/hazmat/primitives/asymmetric/__init__.py @@ -1,40 +1,3 @@ # 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 absolute_import, division, print_function - -import abc - -import six - - -@six.add_metaclass(abc.ABCMeta) -class AsymmetricSignatureContext(object): - @abc.abstractmethod - def update(self, data): - """ - Processes the provided bytes and returns nothing. - """ - - @abc.abstractmethod - def finalize(self): - """ - Returns the signature as bytes. - """ - - -@six.add_metaclass(abc.ABCMeta) -class AsymmetricVerificationContext(object): - @abc.abstractmethod - def update(self, data): - """ - Processes the provided bytes and returns nothing. - """ - - @abc.abstractmethod - def verify(self): - """ - Raises an exception if the bytes provided to update do not match the - signature or the signature does not match the public key. - """ diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py index cd9fbfab4600..751bcc402e94 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -2,57 +2,74 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc +import typing -import six +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization -from cryptography import utils -from cryptography.hazmat.backends import _get_backend +def generate_parameters( + generator: int, key_size: int, backend: typing.Any = None +) -> DHParameters: + from cryptography.hazmat.backends.openssl.backend import backend as ossl -def generate_parameters(generator, key_size, backend=None): - backend = _get_backend(backend) - return backend.generate_dh_parameters(generator, key_size) + return ossl.generate_dh_parameters(generator, key_size) -class DHPrivateNumbers(object): - def __init__(self, x, public_numbers): - if not isinstance(x, six.integer_types): - raise TypeError("x must be an integer.") +class DHParameterNumbers: + def __init__(self, p: int, g: int, q: typing.Optional[int] = None) -> None: + if not isinstance(p, int) or not isinstance(g, int): + raise TypeError("p and g must be integers") + if q is not None and not isinstance(q, int): + raise TypeError("q must be integer or None") - if not isinstance(public_numbers, DHPublicNumbers): - raise TypeError( - "public_numbers must be an instance of " "DHPublicNumbers." + if g < 2: + raise ValueError("DH generator must be 2 or greater") + + if p.bit_length() < rust_openssl.dh.MIN_MODULUS_SIZE: + raise ValueError( + f"p (modulus) must be at least " + f"{rust_openssl.dh.MIN_MODULUS_SIZE}-bit" ) - self._x = x - self._public_numbers = public_numbers + self._p = p + self._g = g + self._q = q - def __eq__(self, other): - if not isinstance(other, DHPrivateNumbers): + def __eq__(self, other: object) -> bool: + if not isinstance(other, DHParameterNumbers): return NotImplemented return ( - self._x == other._x - and self._public_numbers == other._public_numbers + self._p == other._p and self._g == other._g and self._q == other._q ) - def __ne__(self, other): - return not self == other + def parameters(self, backend: typing.Any = None) -> DHParameters: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + return ossl.load_dh_parameter_numbers(self) + + @property + def p(self) -> int: + return self._p - def private_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_dh_private_numbers(self) + @property + def g(self) -> int: + return self._g - public_numbers = utils.read_only_property("_public_numbers") - x = utils.read_only_property("_x") + @property + def q(self) -> typing.Optional[int]: + return self._q -class DHPublicNumbers(object): - def __init__(self, y, parameter_numbers): - if not isinstance(y, six.integer_types): +class DHPublicNumbers: + def __init__(self, y: int, parameter_numbers: DHParameterNumbers) -> None: + if not isinstance(y, int): raise TypeError("y must be an integer.") if not isinstance(parameter_numbers, DHParameterNumbers): @@ -63,7 +80,7 @@ def __init__(self, y, parameter_numbers): self._y = y self._parameter_numbers = parameter_numbers - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DHPublicNumbers): return NotImplemented @@ -72,145 +89,173 @@ def __eq__(self, other): and self._parameter_numbers == other._parameter_numbers ) - def __ne__(self, other): - return not self == other + def public_key(self, backend: typing.Any = None) -> DHPublicKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) - def public_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_dh_public_numbers(self) + return ossl.load_dh_public_numbers(self) - y = utils.read_only_property("_y") - parameter_numbers = utils.read_only_property("_parameter_numbers") + @property + def y(self) -> int: + return self._y + @property + def parameter_numbers(self) -> DHParameterNumbers: + return self._parameter_numbers -class DHParameterNumbers(object): - def __init__(self, p, g, q=None): - if not isinstance(p, six.integer_types) or not isinstance( - g, six.integer_types - ): - raise TypeError("p and g must be integers") - if q is not None and not isinstance(q, six.integer_types): - raise TypeError("q must be integer or None") - if g < 2: - raise ValueError("DH generator must be 2 or greater") +class DHPrivateNumbers: + def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: + if not isinstance(x, int): + raise TypeError("x must be an integer.") - self._p = p - self._g = g - self._q = q + if not isinstance(public_numbers, DHPublicNumbers): + raise TypeError( + "public_numbers must be an instance of " "DHPublicNumbers." + ) - def __eq__(self, other): - if not isinstance(other, DHParameterNumbers): + self._x = x + self._public_numbers = public_numbers + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DHPrivateNumbers): return NotImplemented return ( - self._p == other._p and self._g == other._g and self._q == other._q + self._x == other._x + and self._public_numbers == other._public_numbers + ) + + def private_key(self, backend: typing.Any = None) -> DHPrivateKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, ) - def __ne__(self, other): - return not self == other + return ossl.load_dh_private_numbers(self) - def parameters(self, backend=None): - backend = _get_backend(backend) - return backend.load_dh_parameter_numbers(self) + @property + def public_numbers(self) -> DHPublicNumbers: + return self._public_numbers - p = utils.read_only_property("_p") - g = utils.read_only_property("_g") - q = utils.read_only_property("_q") + @property + def x(self) -> int: + return self._x -@six.add_metaclass(abc.ABCMeta) -class DHParameters(object): +class DHParameters(metaclass=abc.ABCMeta): @abc.abstractmethod - def generate_private_key(self): + def generate_private_key(self) -> DHPrivateKey: """ Generates and returns a DHPrivateKey. """ @abc.abstractmethod - def parameter_bytes(self, encoding, format): + def parameter_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.ParameterFormat, + ) -> bytes: """ Returns the parameters serialized as bytes. """ @abc.abstractmethod - def parameter_numbers(self): + def parameter_numbers(self) -> DHParameterNumbers: """ Returns a DHParameterNumbers. """ DHParametersWithSerialization = DHParameters +DHParameters.register(rust_openssl.dh.DHParameters) -@six.add_metaclass(abc.ABCMeta) -class DHPrivateKey(object): - @abc.abstractproperty - def key_size(self): +class DHPublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod - def public_key(self): + def parameters(self) -> DHParameters: """ - The DHPublicKey associated with this private key. + The DHParameters object associated with this public key. """ @abc.abstractmethod - def parameters(self): + def public_numbers(self) -> DHPublicNumbers: """ - The DHParameters object associated with this private key. + Returns a DHPublicNumbers. """ @abc.abstractmethod - def exchange(self, peer_public_key): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ - Given peer's DHPublicKey, carry out the key exchange and - return shared key as bytes. + Returns the key serialized as bytes. """ - -@six.add_metaclass(abc.ABCMeta) -class DHPrivateKeyWithSerialization(DHPrivateKey): @abc.abstractmethod - def private_numbers(self): + def __eq__(self, other: object) -> bool: """ - Returns a DHPrivateNumbers. + Checks equality. """ + +DHPublicKeyWithSerialization = DHPublicKey +DHPublicKey.register(rust_openssl.dh.DHPublicKey) + + +class DHPrivateKey(metaclass=abc.ABCMeta): + @property @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def key_size(self) -> int: """ - Returns the key serialized as bytes. + The bit length of the prime modulus. """ + @abc.abstractmethod + def public_key(self) -> DHPublicKey: + """ + The DHPublicKey associated with this private key. + """ -@six.add_metaclass(abc.ABCMeta) -class DHPublicKey(object): - @abc.abstractproperty - def key_size(self): + @abc.abstractmethod + def parameters(self) -> DHParameters: """ - The bit length of the prime modulus. + The DHParameters object associated with this private key. """ @abc.abstractmethod - def parameters(self): + def exchange(self, peer_public_key: DHPublicKey) -> bytes: """ - The DHParameters object associated with this public key. + Given peer's DHPublicKey, carry out the key exchange and + return shared key as bytes. """ @abc.abstractmethod - def public_numbers(self): + def private_numbers(self) -> DHPrivateNumbers: """ - Returns a DHPublicNumbers. + Returns a DHPrivateNumbers. """ @abc.abstractmethod - def public_bytes(self, encoding, format): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ Returns the key serialized as bytes. """ -DHPublicKeyWithSerialization = DHPublicKey +DHPrivateKeyWithSerialization = DHPrivateKey +DHPrivateKey.register(rust_openssl.dh.DHPrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py index 8ccc66665f36..a8c52de4fb49 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -2,162 +2,144 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc +import typing -import six +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils -from cryptography import utils -from cryptography.hazmat.backends import _get_backend - -@six.add_metaclass(abc.ABCMeta) -class DSAParameters(object): +class DSAParameters(metaclass=abc.ABCMeta): @abc.abstractmethod - def generate_private_key(self): + def generate_private_key(self) -> DSAPrivateKey: """ Generates and returns a DSAPrivateKey. """ - -@six.add_metaclass(abc.ABCMeta) -class DSAParametersWithNumbers(DSAParameters): @abc.abstractmethod - def parameter_numbers(self): + def parameter_numbers(self) -> DSAParameterNumbers: """ Returns a DSAParameterNumbers. """ -@six.add_metaclass(abc.ABCMeta) -class DSAPrivateKey(object): - @abc.abstractproperty - def key_size(self): +DSAParametersWithNumbers = DSAParameters +DSAParameters.register(rust_openssl.dsa.DSAParameters) + + +class DSAPrivateKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod - def public_key(self): + def public_key(self) -> DSAPublicKey: """ The DSAPublicKey associated with this private key. """ @abc.abstractmethod - def parameters(self): + def parameters(self) -> DSAParameters: """ The DSAParameters object associated with this private key. """ @abc.abstractmethod - def signer(self, signature_algorithm): - """ - Returns an AsymmetricSignatureContext used for signing data. - """ - - @abc.abstractmethod - def sign(self, data, algorithm): + def sign( + self, + data: bytes, + algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + ) -> bytes: """ Signs the data """ - -@six.add_metaclass(abc.ABCMeta) -class DSAPrivateKeyWithSerialization(DSAPrivateKey): @abc.abstractmethod - def private_numbers(self): + def private_numbers(self) -> DSAPrivateNumbers: """ Returns a DSAPrivateNumbers. """ @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ Returns the key serialized as bytes. """ -@six.add_metaclass(abc.ABCMeta) -class DSAPublicKey(object): - @abc.abstractproperty - def key_size(self): +DSAPrivateKeyWithSerialization = DSAPrivateKey +DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey) + + +class DSAPublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod - def parameters(self): + def parameters(self) -> DSAParameters: """ The DSAParameters object associated with this public key. """ @abc.abstractmethod - def verifier(self, signature, signature_algorithm): + def public_numbers(self) -> DSAPublicNumbers: """ - Returns an AsymmetricVerificationContext used for signing data. + Returns a DSAPublicNumbers. """ @abc.abstractmethod - def public_numbers(self): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ - Returns a DSAPublicNumbers. + Returns the key serialized as bytes. """ @abc.abstractmethod - def public_bytes(self, encoding, format): + def verify( + self, + signature: bytes, + data: bytes, + algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + ) -> None: """ - Returns the key serialized as bytes. + Verifies the signature of the data. """ @abc.abstractmethod - def verify(self, signature, data, algorithm): + def __eq__(self, other: object) -> bool: """ - Verifies the signature of the data. + Checks equality. """ DSAPublicKeyWithSerialization = DSAPublicKey +DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) -def generate_parameters(key_size, backend=None): - backend = _get_backend(backend) - return backend.generate_dsa_parameters(key_size) - - -def generate_private_key(key_size, backend=None): - backend = _get_backend(backend) - return backend.generate_dsa_private_key_and_parameters(key_size) - - -def _check_dsa_parameters(parameters): - if parameters.p.bit_length() not in [1024, 2048, 3072, 4096]: - raise ValueError( - "p must be exactly 1024, 2048, 3072, or 4096 bits long" - ) - if parameters.q.bit_length() not in [160, 224, 256]: - raise ValueError("q must be exactly 160, 224, or 256 bits long") - - if not (1 < parameters.g < parameters.p): - raise ValueError("g, p don't satisfy 1 < g < p.") - - -def _check_dsa_private_numbers(numbers): - parameters = numbers.public_numbers.parameter_numbers - _check_dsa_parameters(parameters) - if numbers.x <= 0 or numbers.x >= parameters.q: - raise ValueError("x must be > 0 and < q.") - - if numbers.public_numbers.y != pow(parameters.g, numbers.x, parameters.p): - raise ValueError("y must be equal to (g ** x % p).") - - -class DSAParameterNumbers(object): - def __init__(self, p, q, g): +class DSAParameterNumbers: + def __init__(self, p: int, q: int, g: int): if ( - not isinstance(p, six.integer_types) - or not isinstance(q, six.integer_types) - or not isinstance(g, six.integer_types) + not isinstance(p, int) + or not isinstance(q, int) + or not isinstance(g, int) ): raise TypeError( "DSAParameterNumbers p, q, and g arguments must be integers." @@ -167,33 +149,41 @@ def __init__(self, p, q, g): self._q = q self._g = g - p = utils.read_only_property("_p") - q = utils.read_only_property("_q") - g = utils.read_only_property("_g") + @property + def p(self) -> int: + return self._p + + @property + def q(self) -> int: + return self._q + + @property + def g(self) -> int: + return self._g - def parameters(self, backend=None): - backend = _get_backend(backend) - return backend.load_dsa_parameter_numbers(self) + def parameters(self, backend: typing.Any = None) -> DSAParameters: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + return ossl.load_dsa_parameter_numbers(self) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DSAParameterNumbers): return NotImplemented return self.p == other.p and self.q == other.q and self.g == other.g - def __ne__(self, other): - return not self == other - - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self=self) ) -class DSAPublicNumbers(object): - def __init__(self, y, parameter_numbers): - if not isinstance(y, six.integer_types): +class DSAPublicNumbers: + def __init__(self, y: int, parameter_numbers: DSAParameterNumbers): + if not isinstance(y, int): raise TypeError("DSAPublicNumbers y argument must be an integer.") if not isinstance(parameter_numbers, DSAParameterNumbers): @@ -204,14 +194,22 @@ def __init__(self, y, parameter_numbers): self._y = y self._parameter_numbers = parameter_numbers - y = utils.read_only_property("_y") - parameter_numbers = utils.read_only_property("_parameter_numbers") + @property + def y(self) -> int: + return self._y + + @property + def parameter_numbers(self) -> DSAParameterNumbers: + return self._parameter_numbers + + def public_key(self, backend: typing.Any = None) -> DSAPublicKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) - def public_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_dsa_public_numbers(self) + return ossl.load_dsa_public_numbers(self) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DSAPublicNumbers): return NotImplemented @@ -220,19 +218,16 @@ def __eq__(self, other): and self.parameter_numbers == other.parameter_numbers ) - def __ne__(self, other): - return not self == other - - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self=self) ) -class DSAPrivateNumbers(object): - def __init__(self, x, public_numbers): - if not isinstance(x, six.integer_types): +class DSAPrivateNumbers: + def __init__(self, x: int, public_numbers: DSAPublicNumbers): + if not isinstance(x, int): raise TypeError("DSAPrivateNumbers x argument must be an integer.") if not isinstance(public_numbers, DSAPublicNumbers): @@ -242,14 +237,22 @@ def __init__(self, x, public_numbers): self._public_numbers = public_numbers self._x = x - x = utils.read_only_property("_x") - public_numbers = utils.read_only_property("_public_numbers") + @property + def x(self) -> int: + return self._x + + @property + def public_numbers(self) -> DSAPublicNumbers: + return self._public_numbers + + def private_key(self, backend: typing.Any = None) -> DSAPrivateKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) - def private_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_dsa_private_numbers(self) + return ossl.load_dsa_private_numbers(self) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DSAPrivateNumbers): return NotImplemented @@ -257,5 +260,40 @@ def __eq__(self, other): self.x == other.x and self.public_numbers == other.public_numbers ) - def __ne__(self, other): - return not self == other + +def generate_parameters( + key_size: int, backend: typing.Any = None +) -> DSAParameters: + from cryptography.hazmat.backends.openssl.backend import backend as ossl + + return ossl.generate_dsa_parameters(key_size) + + +def generate_private_key( + key_size: int, backend: typing.Any = None +) -> DSAPrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend as ossl + + return ossl.generate_dsa_private_key_and_parameters(key_size) + + +def _check_dsa_parameters(parameters: DSAParameterNumbers) -> None: + if parameters.p.bit_length() not in [1024, 2048, 3072, 4096]: + raise ValueError( + "p must be exactly 1024, 2048, 3072, or 4096 bits long" + ) + if parameters.q.bit_length() not in [160, 224, 256]: + raise ValueError("q must be exactly 160, 224, or 256 bits long") + + if not (1 < parameters.g < parameters.p): + raise ValueError("g, p don't satisfy 1 < g < p.") + + +def _check_dsa_private_numbers(numbers: DSAPrivateNumbers) -> None: + parameters = numbers.public_numbers.parameter_numbers + _check_dsa_parameters(parameters) + if numbers.x <= 0 or numbers.x >= parameters.q: + raise ValueError("x must be > 0 and < q.") + + if numbers.public_numbers.y != pow(parameters.g, numbers.x, parameters.p): + raise ValueError("y must be equal to (g ** x % p).") diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index c7e694fc5615..ddfaabf4f3e4 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -2,19 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc -import warnings - -import six +import typing from cryptography import utils from cryptography.hazmat._oid import ObjectIdentifier -from cryptography.hazmat.backends import _get_backend +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils -class EllipticCurveOID(object): +class EllipticCurveOID: SECP192R1 = ObjectIdentifier("1.2.840.10045.3.1.1") SECP224R1 = ObjectIdentifier("1.3.132.0.33") SECP256K1 = ObjectIdentifier("1.3.132.0.10") @@ -36,125 +35,140 @@ class EllipticCurveOID(object): SECT571R1 = ObjectIdentifier("1.3.132.0.39") -@six.add_metaclass(abc.ABCMeta) -class EllipticCurve(object): - @abc.abstractproperty - def name(self): +class EllipticCurve(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: """ The name of the curve. e.g. secp256r1. """ - @abc.abstractproperty - def key_size(self): + @property + @abc.abstractmethod + def key_size(self) -> int: """ Bit size of a secret scalar for the curve. """ -@six.add_metaclass(abc.ABCMeta) -class EllipticCurveSignatureAlgorithm(object): - @abc.abstractproperty - def algorithm(self): +class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def algorithm( + self, + ) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]: """ The digest algorithm used with this signature. """ -@six.add_metaclass(abc.ABCMeta) -class EllipticCurvePrivateKey(object): +class EllipticCurvePrivateKey(metaclass=abc.ABCMeta): @abc.abstractmethod - def signer(self, signature_algorithm): - """ - Returns an AsymmetricSignatureContext used for signing data. - """ - - @abc.abstractmethod - def exchange(self, algorithm, peer_public_key): + def exchange( + self, algorithm: ECDH, peer_public_key: EllipticCurvePublicKey + ) -> bytes: """ Performs a key exchange operation using the provided algorithm with the provided peer's public key. """ @abc.abstractmethod - def public_key(self): + def public_key(self) -> EllipticCurvePublicKey: """ The EllipticCurvePublicKey for this private key. """ - @abc.abstractproperty - def curve(self): + @property + @abc.abstractmethod + def curve(self) -> EllipticCurve: """ The EllipticCurve that this key is on. """ - @abc.abstractproperty - def key_size(self): + @property + @abc.abstractmethod + def key_size(self) -> int: """ Bit size of a secret scalar for the curve. """ @abc.abstractmethod - def sign(self, data, signature_algorithm): + def sign( + self, + data: bytes, + signature_algorithm: EllipticCurveSignatureAlgorithm, + ) -> bytes: """ Signs the data """ - -@six.add_metaclass(abc.ABCMeta) -class EllipticCurvePrivateKeyWithSerialization(EllipticCurvePrivateKey): @abc.abstractmethod - def private_numbers(self): + def private_numbers(self) -> EllipticCurvePrivateNumbers: """ Returns an EllipticCurvePrivateNumbers. """ @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ Returns the key serialized as bytes. """ -@six.add_metaclass(abc.ABCMeta) -class EllipticCurvePublicKey(object): - @abc.abstractmethod - def verifier(self, signature, signature_algorithm): - """ - Returns an AsymmetricVerificationContext used for signing data. - """ +EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey - @abc.abstractproperty - def curve(self): + +class EllipticCurvePublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def curve(self) -> EllipticCurve: """ The EllipticCurve that this key is on. """ - @abc.abstractproperty - def key_size(self): + @property + @abc.abstractmethod + def key_size(self) -> int: """ Bit size of a secret scalar for the curve. """ @abc.abstractmethod - def public_numbers(self): + def public_numbers(self) -> EllipticCurvePublicNumbers: """ Returns an EllipticCurvePublicNumbers. """ @abc.abstractmethod - def public_bytes(self, encoding, format): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ Returns the key serialized as bytes. """ @abc.abstractmethod - def verify(self, signature, data, signature_algorithm): + def verify( + self, + signature: bytes, + data: bytes, + signature_algorithm: EllipticCurveSignatureAlgorithm, + ) -> None: """ Verifies the signature of the data. """ @classmethod - def from_encoded_point(cls, curve, data): + def from_encoded_point( + cls, curve: EllipticCurve, data: bytes + ) -> EllipticCurvePublicKey: utils._check_bytes("data", data) if not isinstance(curve, EllipticCurve): @@ -163,132 +177,119 @@ def from_encoded_point(cls, curve, data): if len(data) == 0: raise ValueError("data must not be an empty byte string") - if six.indexbytes(data, 0) not in [0x02, 0x03, 0x04]: + if data[0] not in [0x02, 0x03, 0x04]: raise ValueError("Unsupported elliptic curve point type") from cryptography.hazmat.backends.openssl.backend import backend return backend.load_elliptic_curve_public_bytes(curve, data) + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey -@utils.register_interface(EllipticCurve) -class SECT571R1(object): +class SECT571R1(EllipticCurve): name = "sect571r1" key_size = 570 -@utils.register_interface(EllipticCurve) -class SECT409R1(object): +class SECT409R1(EllipticCurve): name = "sect409r1" key_size = 409 -@utils.register_interface(EllipticCurve) -class SECT283R1(object): +class SECT283R1(EllipticCurve): name = "sect283r1" key_size = 283 -@utils.register_interface(EllipticCurve) -class SECT233R1(object): +class SECT233R1(EllipticCurve): name = "sect233r1" key_size = 233 -@utils.register_interface(EllipticCurve) -class SECT163R2(object): +class SECT163R2(EllipticCurve): name = "sect163r2" key_size = 163 -@utils.register_interface(EllipticCurve) -class SECT571K1(object): +class SECT571K1(EllipticCurve): name = "sect571k1" key_size = 571 -@utils.register_interface(EllipticCurve) -class SECT409K1(object): +class SECT409K1(EllipticCurve): name = "sect409k1" key_size = 409 -@utils.register_interface(EllipticCurve) -class SECT283K1(object): +class SECT283K1(EllipticCurve): name = "sect283k1" key_size = 283 -@utils.register_interface(EllipticCurve) -class SECT233K1(object): +class SECT233K1(EllipticCurve): name = "sect233k1" key_size = 233 -@utils.register_interface(EllipticCurve) -class SECT163K1(object): +class SECT163K1(EllipticCurve): name = "sect163k1" key_size = 163 -@utils.register_interface(EllipticCurve) -class SECP521R1(object): +class SECP521R1(EllipticCurve): name = "secp521r1" key_size = 521 -@utils.register_interface(EllipticCurve) -class SECP384R1(object): +class SECP384R1(EllipticCurve): name = "secp384r1" key_size = 384 -@utils.register_interface(EllipticCurve) -class SECP256R1(object): +class SECP256R1(EllipticCurve): name = "secp256r1" key_size = 256 -@utils.register_interface(EllipticCurve) -class SECP256K1(object): +class SECP256K1(EllipticCurve): name = "secp256k1" key_size = 256 -@utils.register_interface(EllipticCurve) -class SECP224R1(object): +class SECP224R1(EllipticCurve): name = "secp224r1" key_size = 224 -@utils.register_interface(EllipticCurve) -class SECP192R1(object): +class SECP192R1(EllipticCurve): name = "secp192r1" key_size = 192 -@utils.register_interface(EllipticCurve) -class BrainpoolP256R1(object): +class BrainpoolP256R1(EllipticCurve): name = "brainpoolP256r1" key_size = 256 -@utils.register_interface(EllipticCurve) -class BrainpoolP384R1(object): +class BrainpoolP384R1(EllipticCurve): name = "brainpoolP384r1" key_size = 384 -@utils.register_interface(EllipticCurve) -class BrainpoolP512R1(object): +class BrainpoolP512R1(EllipticCurve): name = "brainpoolP512r1" key_size = 512 -_CURVE_TYPES = { +_CURVE_TYPES: typing.Dict[str, typing.Type[EllipticCurve]] = { "prime192v1": SECP192R1, "prime256v1": SECP256R1, "secp192r1": SECP192R1, @@ -313,22 +314,36 @@ class BrainpoolP512R1(object): } -@utils.register_interface(EllipticCurveSignatureAlgorithm) -class ECDSA(object): - def __init__(self, algorithm): +class ECDSA(EllipticCurveSignatureAlgorithm): + def __init__( + self, + algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + ): self._algorithm = algorithm - algorithm = utils.read_only_property("_algorithm") + @property + def algorithm( + self, + ) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]: + return self._algorithm + + +def generate_private_key( + curve: EllipticCurve, backend: typing.Any = None +) -> EllipticCurvePrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend as ossl + return ossl.generate_elliptic_curve_private_key(curve) -def generate_private_key(curve, backend=None): - backend = _get_backend(backend) - return backend.generate_elliptic_curve_private_key(curve) +def derive_private_key( + private_value: int, + curve: EllipticCurve, + backend: typing.Any = None, +) -> EllipticCurvePrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend as ossl -def derive_private_key(private_value, curve, backend=None): - backend = _get_backend(backend) - if not isinstance(private_value, six.integer_types): + if not isinstance(private_value, int): raise TypeError("private_value must be an integer type.") if private_value <= 0: @@ -337,14 +352,12 @@ def derive_private_key(private_value, curve, backend=None): if not isinstance(curve, EllipticCurve): raise TypeError("curve must provide the EllipticCurve interface.") - return backend.derive_elliptic_curve_private_key(private_value, curve) + return ossl.derive_elliptic_curve_private_key(private_value, curve) -class EllipticCurvePublicNumbers(object): - def __init__(self, x, y, curve): - if not isinstance(x, six.integer_types) or not isinstance( - y, six.integer_types - ): +class EllipticCurvePublicNumbers: + def __init__(self, x: int, y: int, curve: EllipticCurve): + if not isinstance(x, int) or not isinstance(y, int): raise TypeError("x and y must be integers.") if not isinstance(curve, EllipticCurve): @@ -354,57 +367,26 @@ def __init__(self, x, y, curve): self._x = x self._curve = curve - def public_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_elliptic_curve_public_numbers(self) - - def encode_point(self): - warnings.warn( - "encode_point has been deprecated on EllipticCurvePublicNumbers" - " and will be removed in a future version. Please use " - "EllipticCurvePublicKey.public_bytes to obtain both " - "compressed and uncompressed point encoding.", - utils.PersistentlyDeprecated2019, - stacklevel=2, - ) - # key_size is in bits. Convert to bytes and round up - byte_length = (self.curve.key_size + 7) // 8 - return ( - b"\x04" - + utils.int_to_bytes(self.x, byte_length) - + utils.int_to_bytes(self.y, byte_length) + def public_key(self, backend: typing.Any = None) -> EllipticCurvePublicKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, ) - @classmethod - def from_encoded_point(cls, curve, data): - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must be an EllipticCurve instance") + return ossl.load_elliptic_curve_public_numbers(self) - warnings.warn( - "Support for unsafe construction of public numbers from " - "encoded data will be removed in a future version. " - "Please use EllipticCurvePublicKey.from_encoded_point", - utils.PersistentlyDeprecated2019, - stacklevel=2, - ) + @property + def curve(self) -> EllipticCurve: + return self._curve - if data.startswith(b"\x04"): - # key_size is in bits. Convert to bytes and round up - byte_length = (curve.key_size + 7) // 8 - if len(data) == 2 * byte_length + 1: - x = utils.int_from_bytes(data[1 : byte_length + 1], "big") - y = utils.int_from_bytes(data[byte_length + 1 :], "big") - return cls(x, y, curve) - else: - raise ValueError("Invalid elliptic curve point data length") - else: - raise ValueError("Unsupported elliptic curve point type") + @property + def x(self) -> int: + return self._x - curve = utils.read_only_property("_curve") - x = utils.read_only_property("_x") - y = utils.read_only_property("_y") + @property + def y(self) -> int: + return self._y - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, EllipticCurvePublicNumbers): return NotImplemented @@ -415,22 +397,21 @@ def __eq__(self, other): and self.curve.key_size == other.curve.key_size ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.x, self.y, self.curve.name, self.curve.key_size)) - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) -class EllipticCurvePrivateNumbers(object): - def __init__(self, private_value, public_numbers): - if not isinstance(private_value, six.integer_types): +class EllipticCurvePrivateNumbers: + def __init__( + self, private_value: int, public_numbers: EllipticCurvePublicNumbers + ): + if not isinstance(private_value, int): raise TypeError("private_value must be an integer.") if not isinstance(public_numbers, EllipticCurvePublicNumbers): @@ -442,14 +423,24 @@ def __init__(self, private_value, public_numbers): self._private_value = private_value self._public_numbers = public_numbers - def private_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_elliptic_curve_private_numbers(self) + def private_key( + self, backend: typing.Any = None + ) -> EllipticCurvePrivateKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + return ossl.load_elliptic_curve_private_numbers(self) - private_value = utils.read_only_property("_private_value") - public_numbers = utils.read_only_property("_public_numbers") + @property + def private_value(self) -> int: + return self._private_value - def __eq__(self, other): + @property + def public_numbers(self) -> EllipticCurvePublicNumbers: + return self._public_numbers + + def __eq__(self, other: object) -> bool: if not isinstance(other, EllipticCurvePrivateNumbers): return NotImplemented @@ -458,14 +449,11 @@ def __eq__(self, other): and self.public_numbers == other.public_numbers ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.private_value, self.public_numbers)) -class ECDH(object): +class ECDH: pass @@ -492,7 +480,7 @@ class ECDH(object): } -def get_curve_for_oid(oid): +def get_curve_for_oid(oid: ObjectIdentifier) -> typing.Type[EllipticCurve]: try: return _OID_TO_CURVE[oid] except KeyError: diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py index 2d07a029bc73..f26e54d24ec5 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -2,23 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc -import six - from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization -_ED25519_KEY_SIZE = 32 -_ED25519_SIG_SIZE = 64 - - -@six.add_metaclass(abc.ABCMeta) -class Ed25519PublicKey(object): +class Ed25519PublicKey(metaclass=abc.ABCMeta): @classmethod - def from_public_bytes(cls, data): + def from_public_bytes(cls, data: bytes) -> Ed25519PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -30,22 +25,42 @@ def from_public_bytes(cls, data): return backend.ed25519_load_public_bytes(data) @abc.abstractmethod - def public_bytes(self, encoding, format): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ The serialized bytes of the public key. """ @abc.abstractmethod - def verify(self, signature, data): + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def verify(self, signature: bytes, data: bytes) -> None: """ Verify the signature. """ + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ -@six.add_metaclass(abc.ABCMeta) -class Ed25519PrivateKey(object): + +if hasattr(rust_openssl, "ed25519"): + Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) + + +class Ed25519PrivateKey(metaclass=abc.ABCMeta): @classmethod - def generate(cls): + def generate(cls) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -57,7 +72,7 @@ def generate(cls): return backend.ed25519_generate_key() @classmethod - def from_private_bytes(cls, data): + def from_private_bytes(cls, data: bytes) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -69,19 +84,35 @@ def from_private_bytes(cls, data): return backend.ed25519_load_private_bytes(data) @abc.abstractmethod - def public_key(self): + def public_key(self) -> Ed25519PublicKey: """ The Ed25519PublicKey derived from the private key. """ @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ The serialized bytes of the private key. """ @abc.abstractmethod - def sign(self, data): + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def sign(self, data: bytes) -> bytes: """ Signs the data. """ + + +if hasattr(rust_openssl, "x25519"): + Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed448.py b/src/cryptography/hazmat/primitives/asymmetric/ed448.py index 520ffcbcbcb6..a9a34b251b01 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -2,19 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc -import six - from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization -@six.add_metaclass(abc.ABCMeta) -class Ed448PublicKey(object): +class Ed448PublicKey(metaclass=abc.ABCMeta): @classmethod - def from_public_bytes(cls, data): + def from_public_bytes(cls, data: bytes) -> Ed448PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -26,22 +25,42 @@ def from_public_bytes(cls, data): return backend.ed448_load_public_bytes(data) @abc.abstractmethod - def public_bytes(self, encoding, format): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ The serialized bytes of the public key. """ @abc.abstractmethod - def verify(self, signature, data): + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def verify(self, signature: bytes, data: bytes) -> None: """ Verify the signature. """ + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + -@six.add_metaclass(abc.ABCMeta) -class Ed448PrivateKey(object): +if hasattr(rust_openssl, "ed448"): + Ed448PublicKey.register(rust_openssl.ed448.Ed448PublicKey) + + +class Ed448PrivateKey(metaclass=abc.ABCMeta): @classmethod - def generate(cls): + def generate(cls) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -52,7 +71,7 @@ def generate(cls): return backend.ed448_generate_key() @classmethod - def from_private_bytes(cls, data): + def from_private_bytes(cls, data: bytes) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -64,19 +83,35 @@ def from_private_bytes(cls, data): return backend.ed448_load_private_bytes(data) @abc.abstractmethod - def public_key(self): + def public_key(self) -> Ed448PublicKey: """ The Ed448PublicKey derived from the private key. """ @abc.abstractmethod - def sign(self, data): + def sign(self, data: bytes) -> bytes: """ Signs the data. """ @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ The serialized bytes of the private key. """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + +if hasattr(rust_openssl, "x448"): + Ed448PrivateKey.register(rust_openssl.ed448.Ed448PrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/padding.py b/src/cryptography/hazmat/primitives/asymmetric/padding.py index fc8f6e26a917..7198808effd0 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/src/cryptography/hazmat/primitives/asymmetric/padding.py @@ -2,56 +2,71 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc +import typing -import six - -from cryptography import utils from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives._asymmetric import ( + AsymmetricPadding as AsymmetricPadding, +) from cryptography.hazmat.primitives.asymmetric import rsa -@six.add_metaclass(abc.ABCMeta) -class AsymmetricPadding(object): - @abc.abstractproperty - def name(self): - """ - A string naming this padding (e.g. "PSS", "PKCS1"). - """ +class PKCS1v15(AsymmetricPadding): + name = "EMSA-PKCS1-v1_5" -@utils.register_interface(AsymmetricPadding) -class PKCS1v15(object): - name = "EMSA-PKCS1-v1_5" +class _MaxLength: + "Sentinel value for `MAX_LENGTH`." + + +class _Auto: + "Sentinel value for `AUTO`." -@utils.register_interface(AsymmetricPadding) -class PSS(object): - MAX_LENGTH = object() +class _DigestLength: + "Sentinel value for `DIGEST_LENGTH`." + + +class PSS(AsymmetricPadding): + MAX_LENGTH = _MaxLength() + AUTO = _Auto() + DIGEST_LENGTH = _DigestLength() name = "EMSA-PSS" + _salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength] - def __init__(self, mgf, salt_length): + def __init__( + self, + mgf: MGF, + salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength], + ) -> None: self._mgf = mgf - if ( - not isinstance(salt_length, six.integer_types) - and salt_length is not self.MAX_LENGTH + if not isinstance( + salt_length, (int, _MaxLength, _Auto, _DigestLength) ): - raise TypeError("salt_length must be an integer.") + raise TypeError( + "salt_length must be an integer, MAX_LENGTH, " + "DIGEST_LENGTH, or AUTO" + ) - if salt_length is not self.MAX_LENGTH and salt_length < 0: + if isinstance(salt_length, int) and salt_length < 0: raise ValueError("salt_length must be zero or greater.") self._salt_length = salt_length -@utils.register_interface(AsymmetricPadding) -class OAEP(object): +class OAEP(AsymmetricPadding): name = "EME-OAEP" - def __init__(self, mgf, algorithm, label): + def __init__( + self, + mgf: MGF, + algorithm: hashes.HashAlgorithm, + label: typing.Optional[bytes], + ): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of hashes.HashAlgorithm.") @@ -60,17 +75,24 @@ def __init__(self, mgf, algorithm, label): self._label = label -class MGF1(object): - MAX_LENGTH = object() +class MGF(metaclass=abc.ABCMeta): + _algorithm: hashes.HashAlgorithm + + +class MGF1(MGF): + MAX_LENGTH = _MaxLength() - def __init__(self, algorithm): + def __init__(self, algorithm: hashes.HashAlgorithm): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of hashes.HashAlgorithm.") self._algorithm = algorithm -def calculate_max_pss_salt_length(key, hash_algorithm): +def calculate_max_pss_salt_length( + key: typing.Union[rsa.RSAPrivateKey, rsa.RSAPublicKey], + hash_algorithm: hashes.HashAlgorithm, +) -> int: if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): raise TypeError("key must be an RSA public or private key") # bit length - 1 per RFC 3447 diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index d8b8ddd914cc..b740f01f7c4c 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -2,127 +2,144 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc +import typing +from math import gcd -try: - # Only available in math in 3.5+ - from math import gcd -except ImportError: - from fractions import gcd +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils -import six - -from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import RSABackend - - -@six.add_metaclass(abc.ABCMeta) -class RSAPrivateKey(object): - @abc.abstractmethod - def signer(self, padding, algorithm): - """ - Returns an AsymmetricSignatureContext used for signing data. - """ +class RSAPrivateKey(metaclass=abc.ABCMeta): @abc.abstractmethod - def decrypt(self, ciphertext, padding): + def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: """ Decrypts the provided ciphertext. """ - @abc.abstractproperty - def key_size(self): + @property + @abc.abstractmethod + def key_size(self) -> int: """ The bit length of the public modulus. """ @abc.abstractmethod - def public_key(self): + def public_key(self) -> RSAPublicKey: """ The RSAPublicKey associated with this private key. """ @abc.abstractmethod - def sign(self, data, padding, algorithm): + def sign( + self, + data: bytes, + padding: AsymmetricPadding, + algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + ) -> bytes: """ Signs the data. """ - -@six.add_metaclass(abc.ABCMeta) -class RSAPrivateKeyWithSerialization(RSAPrivateKey): @abc.abstractmethod - def private_numbers(self): + def private_numbers(self) -> RSAPrivateNumbers: """ Returns an RSAPrivateNumbers. """ @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ Returns the key serialized as bytes. """ -@six.add_metaclass(abc.ABCMeta) -class RSAPublicKey(object): - @abc.abstractmethod - def verifier(self, signature, padding, algorithm): - """ - Returns an AsymmetricVerificationContext used for verifying signatures. - """ +RSAPrivateKeyWithSerialization = RSAPrivateKey + +class RSAPublicKey(metaclass=abc.ABCMeta): @abc.abstractmethod - def encrypt(self, plaintext, padding): + def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: """ Encrypts the given plaintext. """ - @abc.abstractproperty - def key_size(self): + @property + @abc.abstractmethod + def key_size(self) -> int: """ The bit length of the public modulus. """ @abc.abstractmethod - def public_numbers(self): + def public_numbers(self) -> RSAPublicNumbers: """ Returns an RSAPublicNumbers """ @abc.abstractmethod - def public_bytes(self, encoding, format): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ Returns the key serialized as bytes. """ @abc.abstractmethod - def verify(self, signature, data, padding, algorithm): + def verify( + self, + signature: bytes, + data: bytes, + padding: AsymmetricPadding, + algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + ) -> None: """ Verifies the signature of the data. """ + @abc.abstractmethod + def recover_data_from_signature( + self, + signature: bytes, + padding: AsymmetricPadding, + algorithm: typing.Optional[hashes.HashAlgorithm], + ) -> bytes: + """ + Recovers the original data from the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + RSAPublicKeyWithSerialization = RSAPublicKey -def generate_private_key(public_exponent, key_size, backend=None): - backend = _get_backend(backend) - if not isinstance(backend, RSABackend): - raise UnsupportedAlgorithm( - "Backend object does not implement RSABackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) +def generate_private_key( + public_exponent: int, + key_size: int, + backend: typing.Any = None, +) -> RSAPrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend as ossl _verify_rsa_parameters(public_exponent, key_size) - return backend.generate_rsa_private_key(public_exponent, key_size) + return ossl.generate_rsa_private_key(public_exponent, key_size) -def _verify_rsa_parameters(public_exponent, key_size): +def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: if public_exponent not in (3, 65537): raise ValueError( "public_exponent must be either 3 (for legacy compatibility) or " @@ -134,8 +151,15 @@ def _verify_rsa_parameters(public_exponent, key_size): def _check_private_key_components( - p, q, private_exponent, dmp1, dmq1, iqmp, public_exponent, modulus -): + p: int, + q: int, + private_exponent: int, + dmp1: int, + dmq1: int, + iqmp: int, + public_exponent: int, + modulus: int, +) -> None: if modulus < 3: raise ValueError("modulus must be >= 3.") @@ -173,7 +197,7 @@ def _check_private_key_components( raise ValueError("p*q must equal modulus.") -def _check_public_key_components(e, n): +def _check_public_key_components(e: int, n: int) -> None: if n < 3: raise ValueError("n must be >= 3.") @@ -184,7 +208,7 @@ def _check_public_key_components(e, n): raise ValueError("e must be odd.") -def _modinv(e, m): +def _modinv(e: int, m: int) -> int: """ Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1 """ @@ -197,14 +221,14 @@ def _modinv(e, m): return x1 % m -def rsa_crt_iqmp(p, q): +def rsa_crt_iqmp(p: int, q: int) -> int: """ Compute the CRT (q ** -1) % p value from RSA primes p and q. """ return _modinv(q, p) -def rsa_crt_dmp1(private_exponent, p): +def rsa_crt_dmp1(private_exponent: int, p: int) -> int: """ Compute the CRT private_exponent % (p - 1) value from the RSA private_exponent (d) and p. @@ -212,7 +236,7 @@ def rsa_crt_dmp1(private_exponent, p): return private_exponent % (p - 1) -def rsa_crt_dmq1(private_exponent, q): +def rsa_crt_dmq1(private_exponent: int, q: int) -> int: """ Compute the CRT private_exponent % (q - 1) value from the RSA private_exponent (d) and q. @@ -226,7 +250,9 @@ def rsa_crt_dmq1(private_exponent, q): _MAX_RECOVERY_ATTEMPTS = 1000 -def rsa_recover_prime_factors(n, e, d): +def rsa_recover_prime_factors( + n: int, e: int, d: int +) -> typing.Tuple[int, int]: """ Compute factors p and q from the private exponent d. We assume that n has no more than two factors. This function is adapted from code in PyCrypto. @@ -269,15 +295,24 @@ def rsa_recover_prime_factors(n, e, d): return (p, q) -class RSAPrivateNumbers(object): - def __init__(self, p, q, d, dmp1, dmq1, iqmp, public_numbers): +class RSAPrivateNumbers: + def __init__( + self, + p: int, + q: int, + d: int, + dmp1: int, + dmq1: int, + iqmp: int, + public_numbers: RSAPublicNumbers, + ): if ( - not isinstance(p, six.integer_types) - or not isinstance(q, six.integer_types) - or not isinstance(d, six.integer_types) - or not isinstance(dmp1, six.integer_types) - or not isinstance(dmq1, six.integer_types) - or not isinstance(iqmp, six.integer_types) + not isinstance(p, int) + or not isinstance(q, int) + or not isinstance(d, int) + or not isinstance(dmp1, int) + or not isinstance(dmq1, int) + or not isinstance(iqmp, int) ): raise TypeError( "RSAPrivateNumbers p, q, d, dmp1, dmq1, iqmp arguments must" @@ -298,19 +333,49 @@ def __init__(self, p, q, d, dmp1, dmq1, iqmp, public_numbers): self._iqmp = iqmp self._public_numbers = public_numbers - p = utils.read_only_property("_p") - q = utils.read_only_property("_q") - d = utils.read_only_property("_d") - dmp1 = utils.read_only_property("_dmp1") - dmq1 = utils.read_only_property("_dmq1") - iqmp = utils.read_only_property("_iqmp") - public_numbers = utils.read_only_property("_public_numbers") + @property + def p(self) -> int: + return self._p + + @property + def q(self) -> int: + return self._q + + @property + def d(self) -> int: + return self._d + + @property + def dmp1(self) -> int: + return self._dmp1 + + @property + def dmq1(self) -> int: + return self._dmq1 + + @property + def iqmp(self) -> int: + return self._iqmp + + @property + def public_numbers(self) -> RSAPublicNumbers: + return self._public_numbers + + def private_key( + self, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, + ) -> RSAPrivateKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) - def private_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_rsa_private_numbers(self) + return ossl.load_rsa_private_numbers( + self, unsafe_skip_rsa_key_validation + ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, RSAPrivateNumbers): return NotImplemented @@ -324,10 +389,7 @@ def __eq__(self, other): and self.public_numbers == other.public_numbers ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash( ( self.p, @@ -341,34 +403,37 @@ def __hash__(self): ) -class RSAPublicNumbers(object): - def __init__(self, e, n): - if not isinstance(e, six.integer_types) or not isinstance( - n, six.integer_types - ): +class RSAPublicNumbers: + def __init__(self, e: int, n: int): + if not isinstance(e, int) or not isinstance(n, int): raise TypeError("RSAPublicNumbers arguments must be integers.") self._e = e self._n = n - e = utils.read_only_property("_e") - n = utils.read_only_property("_n") + @property + def e(self) -> int: + return self._e - def public_key(self, backend=None): - backend = _get_backend(backend) - return backend.load_rsa_public_numbers(self) + @property + def n(self) -> int: + return self._n - def __repr__(self): + def public_key(self, backend: typing.Any = None) -> RSAPublicKey: + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + return ossl.load_rsa_public_numbers(self) + + def __repr__(self) -> str: return "".format(self) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, RSAPublicNumbers): return NotImplemented return self.e == other.e and self.n == other.n - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.e, self.n)) diff --git a/src/cryptography/hazmat/primitives/asymmetric/types.py b/src/cryptography/hazmat/primitives/asymmetric/types.py new file mode 100644 index 000000000000..1fe4eaf51d85 --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/types.py @@ -0,0 +1,111 @@ +# 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 import utils +from cryptography.hazmat.primitives.asymmetric import ( + dh, + dsa, + ec, + ed448, + ed25519, + rsa, + x448, + x25519, +) + +# Every asymmetric key type +PublicKeyTypes = typing.Union[ + dh.DHPublicKey, + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, +] +PUBLIC_KEY_TYPES = PublicKeyTypes +utils.deprecated( + PUBLIC_KEY_TYPES, + __name__, + "Use PublicKeyTypes instead", + utils.DeprecatedIn40, + name="PUBLIC_KEY_TYPES", +) +# Every asymmetric key type +PrivateKeyTypes = typing.Union[ + dh.DHPrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + x25519.X25519PrivateKey, + x448.X448PrivateKey, +] +PRIVATE_KEY_TYPES = PrivateKeyTypes +utils.deprecated( + PRIVATE_KEY_TYPES, + __name__, + "Use PrivateKeyTypes instead", + utils.DeprecatedIn40, + name="PRIVATE_KEY_TYPES", +) +# Just the key types we allow to be used for x509 signing. This mirrors +# the certificate public key types +CertificateIssuerPrivateKeyTypes = typing.Union[ + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, +] +CERTIFICATE_PRIVATE_KEY_TYPES = CertificateIssuerPrivateKeyTypes +utils.deprecated( + CERTIFICATE_PRIVATE_KEY_TYPES, + __name__, + "Use CertificateIssuerPrivateKeyTypes instead", + utils.DeprecatedIn40, + name="CERTIFICATE_PRIVATE_KEY_TYPES", +) +# Just the key types we allow to be used for x509 signing. This mirrors +# the certificate private key types +CertificateIssuerPublicKeyTypes = typing.Union[ + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, +] +CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES = CertificateIssuerPublicKeyTypes +utils.deprecated( + CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES, + __name__, + "Use CertificateIssuerPublicKeyTypes instead", + utils.DeprecatedIn40, + name="CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES", +) +# This type removes DHPublicKey. x448/x25519 can be a public key +# but cannot be used in signing so they are allowed here. +CertificatePublicKeyTypes = typing.Union[ + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, +] +CERTIFICATE_PUBLIC_KEY_TYPES = CertificatePublicKeyTypes +utils.deprecated( + CERTIFICATE_PUBLIC_KEY_TYPES, + __name__, + "Use CertificatePublicKeyTypes instead", + utils.DeprecatedIn40, + name="CERTIFICATE_PUBLIC_KEY_TYPES", +) diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py index 5f9b677868db..826b9567b47b 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/utils.py +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -2,40 +2,23 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -from cryptography import utils -from cryptography.hazmat._der import ( - DERReader, - INTEGER, - SEQUENCE, - encode_der, - encode_der_integer, -) +from cryptography.hazmat.bindings._rust import asn1 from cryptography.hazmat.primitives import hashes +decode_dss_signature = asn1.decode_dss_signature +encode_dss_signature = asn1.encode_dss_signature -def decode_dss_signature(signature): - with DERReader(signature).read_single_element(SEQUENCE) as seq: - r = seq.read_element(INTEGER).as_integer() - s = seq.read_element(INTEGER).as_integer() - return r, s - -def encode_dss_signature(r, s): - return encode_der( - SEQUENCE, - encode_der(INTEGER, encode_der_integer(r)), - encode_der(INTEGER, encode_der_integer(s)), - ) - - -class Prehashed(object): - def __init__(self, algorithm): +class Prehashed: + def __init__(self, algorithm: hashes.HashAlgorithm): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of HashAlgorithm.") self._algorithm = algorithm self._digest_size = algorithm.digest_size - digest_size = utils.read_only_property("_digest_size") + @property + def digest_size(self) -> int: + return self._digest_size diff --git a/src/cryptography/hazmat/primitives/asymmetric/x25519.py b/src/cryptography/hazmat/primitives/asymmetric/x25519.py index fc63691536e9..699054c9689b 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -2,19 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc -import six - from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization -@six.add_metaclass(abc.ABCMeta) -class X25519PublicKey(object): +class X25519PublicKey(metaclass=abc.ABCMeta): @classmethod - def from_public_bytes(cls, data): + def from_public_bytes(cls, data: bytes) -> X25519PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -26,16 +25,37 @@ def from_public_bytes(cls, data): return backend.x25519_load_public_bytes(data) @abc.abstractmethod - def public_bytes(self, encoding, format): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ The serialized bytes of the public key. """ + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + -@six.add_metaclass(abc.ABCMeta) -class X25519PrivateKey(object): +# For LibreSSL +if hasattr(rust_openssl, "x25519"): + X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) + + +class X25519PrivateKey(metaclass=abc.ABCMeta): @classmethod - def generate(cls): + def generate(cls) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -46,7 +66,7 @@ def generate(cls): return backend.x25519_generate_key() @classmethod - def from_private_bytes(cls, data): + def from_private_bytes(cls, data: bytes) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -58,19 +78,36 @@ def from_private_bytes(cls, data): return backend.x25519_load_private_bytes(data) @abc.abstractmethod - def public_key(self): + def public_key(self) -> X25519PublicKey: """ - The serialized bytes of the public key. + Returns the public key assosciated with this private key """ @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ The serialized bytes of the private key. """ @abc.abstractmethod - def exchange(self, peer_public_key): + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: X25519PublicKey) -> bytes: """ Performs a key exchange operation using the provided peer's public key. """ + + +# For LibreSSL +if hasattr(rust_openssl, "x25519"): + X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/x448.py b/src/cryptography/hazmat/primitives/asymmetric/x448.py index 3ac067bfd5e0..abf7848550b8 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x448.py @@ -2,19 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc -import six - from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization -@six.add_metaclass(abc.ABCMeta) -class X448PublicKey(object): +class X448PublicKey(metaclass=abc.ABCMeta): @classmethod - def from_public_bytes(cls, data): + def from_public_bytes(cls, data: bytes) -> X448PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -26,16 +25,36 @@ def from_public_bytes(cls, data): return backend.x448_load_public_bytes(data) @abc.abstractmethod - def public_bytes(self, encoding, format): + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: """ The serialized bytes of the public key. """ + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + -@six.add_metaclass(abc.ABCMeta) -class X448PrivateKey(object): +if hasattr(rust_openssl, "x448"): + X448PublicKey.register(rust_openssl.x448.X448PublicKey) + + +class X448PrivateKey(metaclass=abc.ABCMeta): @classmethod - def generate(cls): + def generate(cls) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -46,7 +65,7 @@ def generate(cls): return backend.x448_generate_key() @classmethod - def from_private_bytes(cls, data): + def from_private_bytes(cls, data: bytes) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -58,19 +77,35 @@ def from_private_bytes(cls, data): return backend.x448_load_private_bytes(data) @abc.abstractmethod - def public_key(self): + def public_key(self) -> X448PublicKey: """ - The serialized bytes of the public key. + Returns the public key associated with this private key """ @abc.abstractmethod - def private_bytes(self, encoding, format, encryption_algorithm): + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: """ The serialized bytes of the private key. """ @abc.abstractmethod - def exchange(self, peer_public_key): + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: X448PublicKey) -> bytes: """ Performs a key exchange operation using the provided peer's public key. """ + + +if hasattr(rust_openssl, "x448"): + X448PrivateKey.register(rust_openssl.x448.X448PrivateKey) diff --git a/src/cryptography/hazmat/primitives/ciphers/__init__.py b/src/cryptography/hazmat/primitives/ciphers/__init__.py index 4380f72b2e2e..cc88fbf2c4c3 100644 --- a/src/cryptography/hazmat/primitives/ciphers/__init__.py +++ b/src/cryptography/hazmat/primitives/ciphers/__init__.py @@ -2,19 +2,20 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) from cryptography.hazmat.primitives.ciphers.base import ( AEADCipherContext, AEADDecryptionContext, AEADEncryptionContext, - BlockCipherAlgorithm, Cipher, - CipherAlgorithm, CipherContext, ) - __all__ = [ "Cipher", "CipherAlgorithm", diff --git a/src/cryptography/hazmat/primitives/ciphers/aead.py b/src/cryptography/hazmat/primitives/ciphers/aead.py index 4eddc1ee6e37..957b2d221b62 100644 --- a/src/cryptography/hazmat/primitives/ciphers/aead.py +++ b/src/cryptography/hazmat/primitives/ciphers/aead.py @@ -2,19 +2,21 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import os +import typing from cryptography import exceptions, utils from cryptography.hazmat.backends.openssl import aead from cryptography.hazmat.backends.openssl.backend import backend +from cryptography.hazmat.bindings._rust import FixedPool -class ChaCha20Poly1305(object): - _MAX_SIZE = 2 ** 32 +class ChaCha20Poly1305: + _MAX_SIZE = 2**31 - 1 - def __init__(self, key): + def __init__(self, key: bytes): if not backend.aead_cipher_supported(self): raise exceptions.UnsupportedAlgorithm( "ChaCha20Poly1305 is not supported by this version of OpenSSL", @@ -26,43 +28,68 @@ def __init__(self, key): raise ValueError("ChaCha20Poly1305 key must be 32 bytes.") self._key = key + self._pool = FixedPool(self._create_fn) @classmethod - def generate_key(cls): + def generate_key(cls) -> bytes: return os.urandom(32) - def encrypt(self, nonce, data, associated_data): + def _create_fn(self): + return aead._aead_create_ctx(backend, self, self._key) + + def encrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: if associated_data is None: associated_data = b"" if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: # This is OverflowError to match what cffi would raise raise OverflowError( - "Data or associated data too long. Max 2**32 bytes" + "Data or associated data too long. Max 2**31 - 1 bytes" ) self._check_params(nonce, data, associated_data) - return aead._encrypt(backend, self, nonce, data, associated_data, 16) + with self._pool.acquire() as ctx: + return aead._encrypt( + backend, self, nonce, data, [associated_data], 16, ctx + ) - def decrypt(self, nonce, data, associated_data): + def decrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: if associated_data is None: associated_data = b"" self._check_params(nonce, data, associated_data) - return aead._decrypt(backend, self, nonce, data, associated_data, 16) + with self._pool.acquire() as ctx: + return aead._decrypt( + backend, self, nonce, data, [associated_data], 16, ctx + ) - def _check_params(self, nonce, data, associated_data): + def _check_params( + self, + nonce: bytes, + data: bytes, + associated_data: bytes, + ) -> None: utils._check_byteslike("nonce", nonce) - utils._check_bytes("data", data) - utils._check_bytes("associated_data", associated_data) + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) if len(nonce) != 12: raise ValueError("Nonce must be 12 bytes") -class AESCCM(object): - _MAX_SIZE = 2 ** 32 +class AESCCM: + _MAX_SIZE = 2**31 - 1 - def __init__(self, key, tag_length=16): + def __init__(self, key: bytes, tag_length: int = 16): utils._check_byteslike("key", key) if len(key) not in (16, 24, 32): raise ValueError("AESCCM key must be 128, 192, or 256 bits.") @@ -76,8 +103,14 @@ def __init__(self, key, tag_length=16): self._tag_length = tag_length + if not backend.aead_cipher_supported(self): + raise exceptions.UnsupportedAlgorithm( + "AESCCM is not supported by this version of OpenSSL", + exceptions._Reasons.UNSUPPORTED_CIPHER, + ) + @classmethod - def generate_key(cls, bit_length): + def generate_key(cls, bit_length: int) -> bytes: if not isinstance(bit_length, int): raise TypeError("bit_length must be an integer") @@ -86,50 +119,62 @@ def generate_key(cls, bit_length): return os.urandom(bit_length // 8) - def encrypt(self, nonce, data, associated_data): + def encrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: if associated_data is None: associated_data = b"" if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: # This is OverflowError to match what cffi would raise raise OverflowError( - "Data or associated data too long. Max 2**32 bytes" + "Data or associated data too long. Max 2**31 - 1 bytes" ) self._check_params(nonce, data, associated_data) self._validate_lengths(nonce, len(data)) return aead._encrypt( - backend, self, nonce, data, associated_data, self._tag_length + backend, self, nonce, data, [associated_data], self._tag_length ) - def decrypt(self, nonce, data, associated_data): + def decrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: if associated_data is None: associated_data = b"" self._check_params(nonce, data, associated_data) return aead._decrypt( - backend, self, nonce, data, associated_data, self._tag_length + backend, self, nonce, data, [associated_data], self._tag_length ) - def _validate_lengths(self, nonce, data_len): + def _validate_lengths(self, nonce: bytes, data_len: int) -> None: # For information about computing this, see # https://tools.ietf.org/html/rfc3610#section-2.1 l_val = 15 - len(nonce) if 2 ** (8 * l_val) < data_len: raise ValueError("Data too long for nonce") - def _check_params(self, nonce, data, associated_data): + def _check_params( + self, nonce: bytes, data: bytes, associated_data: bytes + ) -> None: utils._check_byteslike("nonce", nonce) - utils._check_bytes("data", data) - utils._check_bytes("associated_data", associated_data) + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) if not 7 <= len(nonce) <= 13: raise ValueError("Nonce must be between 7 and 13 bytes") -class AESGCM(object): - _MAX_SIZE = 2 ** 32 +class AESGCM: + _MAX_SIZE = 2**31 - 1 - def __init__(self, key): + def __init__(self, key: bytes): utils._check_byteslike("key", key) if len(key) not in (16, 24, 32): raise ValueError("AESGCM key must be 128, 192, or 256 bits.") @@ -137,7 +182,7 @@ def __init__(self, key): self._key = key @classmethod - def generate_key(cls, bit_length): + def generate_key(cls, bit_length: int) -> bytes: if not isinstance(bit_length, int): raise TypeError("bit_length must be an integer") @@ -146,29 +191,188 @@ def generate_key(cls, bit_length): return os.urandom(bit_length // 8) - def encrypt(self, nonce, data, associated_data): + def encrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: if associated_data is None: associated_data = b"" if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: # This is OverflowError to match what cffi would raise raise OverflowError( - "Data or associated data too long. Max 2**32 bytes" + "Data or associated data too long. Max 2**31 - 1 bytes" ) self._check_params(nonce, data, associated_data) - return aead._encrypt(backend, self, nonce, data, associated_data, 16) + return aead._encrypt(backend, self, nonce, data, [associated_data], 16) + + def decrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: + if associated_data is None: + associated_data = b"" + + self._check_params(nonce, data, associated_data) + return aead._decrypt(backend, self, nonce, data, [associated_data], 16) + + def _check_params( + self, + nonce: bytes, + data: bytes, + associated_data: bytes, + ) -> None: + utils._check_byteslike("nonce", nonce) + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) + if len(nonce) < 8 or len(nonce) > 128: + raise ValueError("Nonce must be between 8 and 128 bytes") + + +class AESOCB3: + _MAX_SIZE = 2**31 - 1 + + def __init__(self, key: bytes): + utils._check_byteslike("key", key) + if len(key) not in (16, 24, 32): + raise ValueError("AESOCB3 key must be 128, 192, or 256 bits.") + + self._key = key + + if not backend.aead_cipher_supported(self): + raise exceptions.UnsupportedAlgorithm( + "OCB3 is not supported by this version of OpenSSL", + exceptions._Reasons.UNSUPPORTED_CIPHER, + ) + + @classmethod + def generate_key(cls, bit_length: int) -> bytes: + if not isinstance(bit_length, int): + raise TypeError("bit_length must be an integer") - def decrypt(self, nonce, data, associated_data): + if bit_length not in (128, 192, 256): + raise ValueError("bit_length must be 128, 192, or 256") + + return os.urandom(bit_length // 8) + + def encrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: if associated_data is None: associated_data = b"" + if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: + # This is OverflowError to match what cffi would raise + raise OverflowError( + "Data or associated data too long. Max 2**31 - 1 bytes" + ) + self._check_params(nonce, data, associated_data) - return aead._decrypt(backend, self, nonce, data, associated_data, 16) + return aead._encrypt(backend, self, nonce, data, [associated_data], 16) + + def decrypt( + self, + nonce: bytes, + data: bytes, + associated_data: typing.Optional[bytes], + ) -> bytes: + if associated_data is None: + associated_data = b"" - def _check_params(self, nonce, data, associated_data): + self._check_params(nonce, data, associated_data) + return aead._decrypt(backend, self, nonce, data, [associated_data], 16) + + def _check_params( + self, + nonce: bytes, + data: bytes, + associated_data: bytes, + ) -> None: utils._check_byteslike("nonce", nonce) - utils._check_bytes("data", data) - utils._check_bytes("associated_data", associated_data) - if len(nonce) == 0: - raise ValueError("Nonce must be at least 1 byte") + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) + if len(nonce) < 12 or len(nonce) > 15: + raise ValueError("Nonce must be between 12 and 15 bytes") + + +class AESSIV: + _MAX_SIZE = 2**31 - 1 + + def __init__(self, key: bytes): + utils._check_byteslike("key", key) + if len(key) not in (32, 48, 64): + raise ValueError("AESSIV key must be 256, 384, or 512 bits.") + + self._key = key + + if not backend.aead_cipher_supported(self): + raise exceptions.UnsupportedAlgorithm( + "AES-SIV is not supported by this version of OpenSSL", + exceptions._Reasons.UNSUPPORTED_CIPHER, + ) + + @classmethod + def generate_key(cls, bit_length: int) -> bytes: + if not isinstance(bit_length, int): + raise TypeError("bit_length must be an integer") + + if bit_length not in (256, 384, 512): + raise ValueError("bit_length must be 256, 384, or 512") + + return os.urandom(bit_length // 8) + + def encrypt( + self, + data: bytes, + associated_data: typing.Optional[typing.List[bytes]], + ) -> bytes: + if associated_data is None: + associated_data = [] + + self._check_params(data, associated_data) + + if len(data) > self._MAX_SIZE or any( + len(ad) > self._MAX_SIZE for ad in associated_data + ): + # This is OverflowError to match what cffi would raise + raise OverflowError( + "Data or associated data too long. Max 2**31 - 1 bytes" + ) + + return aead._encrypt(backend, self, b"", data, associated_data, 16) + + def decrypt( + self, + data: bytes, + associated_data: typing.Optional[typing.List[bytes]], + ) -> bytes: + if associated_data is None: + associated_data = [] + + self._check_params(data, associated_data) + + return aead._decrypt(backend, self, b"", data, associated_data, 16) + + def _check_params( + self, + data: bytes, + associated_data: typing.List[bytes], + ) -> None: + utils._check_byteslike("data", data) + if len(data) == 0: + raise ValueError("data must not be zero length") + + if not isinstance(associated_data, list): + raise TypeError( + "associated_data must be a list of bytes-like objects or None" + ) + for x in associated_data: + utils._check_byteslike("associated_data elements", x) diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py index 8072cedd1714..4bfc5d840d67 100644 --- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -2,17 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations from cryptography import utils from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, CipherAlgorithm, ) -from cryptography.hazmat.primitives.ciphers.modes import ModeWithNonce -def _verify_key_size(algorithm, key): +def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: # Verify that the key is instance of bytes utils._check_byteslike("key", key) @@ -26,45 +25,59 @@ def _verify_key_size(algorithm, key): return key -@utils.register_interface(BlockCipherAlgorithm) -@utils.register_interface(CipherAlgorithm) -class AES(object): +class AES(BlockCipherAlgorithm): name = "AES" block_size = 128 # 512 added to support AES-256-XTS, which uses 512-bit keys key_sizes = frozenset([128, 192, 256, 512]) - def __init__(self, key): + def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(BlockCipherAlgorithm) -@utils.register_interface(CipherAlgorithm) -class Camellia(object): +class AES128(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + key_sizes = frozenset([128]) + key_size = 128 + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + +class AES256(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + key_sizes = frozenset([256]) + key_size = 256 + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + +class Camellia(BlockCipherAlgorithm): name = "camellia" block_size = 128 key_sizes = frozenset([128, 192, 256]) - def __init__(self, key): + def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(BlockCipherAlgorithm) -@utils.register_interface(CipherAlgorithm) -class TripleDES(object): +class TripleDES(BlockCipherAlgorithm): name = "3DES" block_size = 64 key_sizes = frozenset([64, 128, 192]) - def __init__(self, key): + def __init__(self, key: bytes): if len(key) == 8: key += key + key elif len(key) == 16: @@ -72,89 +85,119 @@ def __init__(self, key): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(BlockCipherAlgorithm) -@utils.register_interface(CipherAlgorithm) -class Blowfish(object): +class Blowfish(BlockCipherAlgorithm): name = "Blowfish" block_size = 64 key_sizes = frozenset(range(32, 449, 8)) - def __init__(self, key): + def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(BlockCipherAlgorithm) -@utils.register_interface(CipherAlgorithm) -class CAST5(object): +_BlowfishInternal = Blowfish +utils.deprecated( + Blowfish, + __name__, + "Blowfish has been deprecated", + utils.DeprecatedIn37, + name="Blowfish", +) + + +class CAST5(BlockCipherAlgorithm): name = "CAST5" block_size = 64 key_sizes = frozenset(range(40, 129, 8)) - def __init__(self, key): + def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(CipherAlgorithm) -class ARC4(object): +_CAST5Internal = CAST5 +utils.deprecated( + CAST5, + __name__, + "CAST5 has been deprecated", + utils.DeprecatedIn37, + name="CAST5", +) + + +class ARC4(CipherAlgorithm): name = "RC4" key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) - def __init__(self, key): + def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(CipherAlgorithm) -class IDEA(object): +class IDEA(BlockCipherAlgorithm): name = "IDEA" block_size = 64 key_sizes = frozenset([128]) - def __init__(self, key): + def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(BlockCipherAlgorithm) -@utils.register_interface(CipherAlgorithm) -class SEED(object): +_IDEAInternal = IDEA +utils.deprecated( + IDEA, + __name__, + "IDEA has been deprecated", + utils.DeprecatedIn37, + name="IDEA", +) + + +class SEED(BlockCipherAlgorithm): name = "SEED" block_size = 128 key_sizes = frozenset([128]) - def __init__(self, key): + def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 -@utils.register_interface(CipherAlgorithm) -@utils.register_interface(ModeWithNonce) -class ChaCha20(object): +_SEEDInternal = SEED +utils.deprecated( + SEED, + __name__, + "SEED has been deprecated", + utils.DeprecatedIn37, + name="SEED", +) + + +class ChaCha20(CipherAlgorithm): name = "ChaCha20" key_sizes = frozenset([256]) - def __init__(self, key, nonce): + def __init__(self, key: bytes, nonce: bytes): self.key = _verify_key_size(self, key) utils._check_byteslike("nonce", nonce) @@ -163,8 +206,23 @@ def __init__(self, key, nonce): self._nonce = nonce - nonce = utils.read_only_property("_nonce") + @property + def nonce(self) -> bytes: + return self._nonce + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class SM4(BlockCipherAlgorithm): + name = "SM4" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) @property - def key_size(self): + def key_size(self) -> int: return len(self.key) * 8 diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index dae425a2993d..38a2ebbe081e 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -2,119 +2,109 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc +import typing -import six - -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, AlreadyUpdated, NotYetFinalized, - UnsupportedAlgorithm, - _Reasons, ) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm from cryptography.hazmat.primitives.ciphers import modes - -@six.add_metaclass(abc.ABCMeta) -class CipherAlgorithm(object): - @abc.abstractproperty - def name(self): - """ - A string naming this mode (e.g. "AES", "Camellia"). - """ - - @abc.abstractproperty - def key_size(self): - """ - The size of the key being used as an integer in bits (e.g. 128, 256). - """ - - -@six.add_metaclass(abc.ABCMeta) -class BlockCipherAlgorithm(object): - @abc.abstractproperty - def block_size(self): - """ - The size of a block as an integer in bits (e.g. 64, 128). - """ +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.ciphers import ( + _CipherContext as _BackendCipherContext, + ) -@six.add_metaclass(abc.ABCMeta) -class CipherContext(object): +class CipherContext(metaclass=abc.ABCMeta): @abc.abstractmethod - def update(self, data): + def update(self, data: bytes) -> bytes: """ Processes the provided bytes through the cipher and returns the results as bytes. """ @abc.abstractmethod - def update_into(self, data, buf): + def update_into(self, data: bytes, buf: bytes) -> int: """ Processes the provided bytes and writes the resulting data into the provided buffer. Returns the number of bytes written. """ @abc.abstractmethod - def finalize(self): + def finalize(self) -> bytes: """ Returns the results of processing the final block as bytes. """ -@six.add_metaclass(abc.ABCMeta) -class AEADCipherContext(object): +class AEADCipherContext(CipherContext, metaclass=abc.ABCMeta): @abc.abstractmethod - def authenticate_additional_data(self, data): + def authenticate_additional_data(self, data: bytes) -> None: """ Authenticates the provided bytes. """ -@six.add_metaclass(abc.ABCMeta) -class AEADDecryptionContext(object): +class AEADDecryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): @abc.abstractmethod - def finalize_with_tag(self, tag): + def finalize_with_tag(self, tag: bytes) -> bytes: """ Returns the results of processing the final block as bytes and allows delayed passing of the authentication tag. """ -@six.add_metaclass(abc.ABCMeta) -class AEADEncryptionContext(object): - @abc.abstractproperty - def tag(self): +class AEADEncryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tag(self) -> bytes: """ Returns tag bytes. This is only available after encryption is finalized. """ -class Cipher(object): - def __init__(self, algorithm, mode, backend=None): - backend = _get_backend(backend) - if not isinstance(backend, CipherBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement CipherBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) +Mode = typing.TypeVar( + "Mode", bound=typing.Optional[modes.Mode], covariant=True +) + +class Cipher(typing.Generic[Mode]): + def __init__( + self, + algorithm: CipherAlgorithm, + mode: Mode, + backend: typing.Any = None, + ) -> None: if not isinstance(algorithm, CipherAlgorithm): raise TypeError("Expected interface of CipherAlgorithm.") if mode is not None: + # mypy needs this assert to narrow the type from our generic + # type. Maybe it won't some time in the future. + assert isinstance(mode, modes.Mode) mode.validate_for_algorithm(algorithm) self.algorithm = algorithm self.mode = mode - self._backend = backend + + @typing.overload + def encryptor( + self: Cipher[modes.ModeWithAuthenticationTag], + ) -> AEADEncryptionContext: + ... + + @typing.overload + def encryptor( + self: _CIPHER_TYPE, + ) -> CipherContext: + ... def encryptor(self): if isinstance(self.mode, modes.ModeWithAuthenticationTag): @@ -122,43 +112,75 @@ def encryptor(self): raise ValueError( "Authentication tag must be None when encrypting." ) - ctx = self._backend.create_symmetric_encryption_ctx( + from cryptography.hazmat.backends.openssl.backend import backend + + ctx = backend.create_symmetric_encryption_ctx( self.algorithm, self.mode ) return self._wrap_ctx(ctx, encrypt=True) + @typing.overload + def decryptor( + self: Cipher[modes.ModeWithAuthenticationTag], + ) -> AEADDecryptionContext: + ... + + @typing.overload + def decryptor( + self: _CIPHER_TYPE, + ) -> CipherContext: + ... + def decryptor(self): - ctx = self._backend.create_symmetric_decryption_ctx( + from cryptography.hazmat.backends.openssl.backend import backend + + ctx = backend.create_symmetric_decryption_ctx( self.algorithm, self.mode ) return self._wrap_ctx(ctx, encrypt=False) - def _wrap_ctx(self, ctx, encrypt): + def _wrap_ctx( + self, ctx: _BackendCipherContext, encrypt: bool + ) -> typing.Union[ + AEADEncryptionContext, AEADDecryptionContext, CipherContext + ]: if isinstance(self.mode, modes.ModeWithAuthenticationTag): if encrypt: return _AEADEncryptionContext(ctx) else: - return _AEADCipherContext(ctx) + return _AEADDecryptionContext(ctx) else: return _CipherContext(ctx) -@utils.register_interface(CipherContext) -class _CipherContext(object): - def __init__(self, ctx): +_CIPHER_TYPE = Cipher[ + typing.Union[ + modes.ModeWithNonce, + modes.ModeWithTweak, + None, + modes.ECB, + modes.ModeWithInitializationVector, + ] +] + + +class _CipherContext(CipherContext): + _ctx: typing.Optional[_BackendCipherContext] + + def __init__(self, ctx: _BackendCipherContext) -> None: self._ctx = ctx - def update(self, data): + def update(self, data: bytes) -> bytes: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") return self._ctx.update(data) - def update_into(self, data, buf): + def update_into(self, data: bytes, buf: bytes) -> int: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") return self._ctx.update_into(data, buf) - def finalize(self): + def finalize(self) -> bytes: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") data = self._ctx.finalize() @@ -166,18 +188,18 @@ def finalize(self): return data -@utils.register_interface(AEADCipherContext) -@utils.register_interface(CipherContext) -@utils.register_interface(AEADDecryptionContext) -class _AEADCipherContext(object): - def __init__(self, ctx): +class _AEADCipherContext(AEADCipherContext): + _ctx: typing.Optional[_BackendCipherContext] + _tag: typing.Optional[bytes] + + def __init__(self, ctx: _BackendCipherContext) -> None: self._ctx = ctx self._bytes_processed = 0 self._aad_bytes_processed = 0 self._tag = None self._updated = False - def _check_limit(self, data_size): + def _check_limit(self, data_size: int) -> None: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") self._updated = True @@ -189,15 +211,19 @@ def _check_limit(self, data_size): ) ) - def update(self, data): + def update(self, data: bytes) -> bytes: self._check_limit(len(data)) + # mypy needs this assert even though _check_limit already checked + assert self._ctx is not None return self._ctx.update(data) - def update_into(self, data, buf): + def update_into(self, data: bytes, buf: bytes) -> int: self._check_limit(len(data)) + # mypy needs this assert even though _check_limit already checked + assert self._ctx is not None return self._ctx.update_into(data, buf) - def finalize(self): + def finalize(self) -> bytes: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") data = self._ctx.finalize() @@ -205,15 +231,7 @@ def finalize(self): self._ctx = None return data - def finalize_with_tag(self, tag): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - data = self._ctx.finalize_with_tag(tag) - self._tag = self._ctx.tag - self._ctx = None - return data - - def authenticate_additional_data(self, data): + def authenticate_additional_data(self, data: bytes) -> None: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") if self._updated: @@ -230,12 +248,22 @@ def authenticate_additional_data(self, data): self._ctx.authenticate_additional_data(data) -@utils.register_interface(AEADEncryptionContext) -class _AEADEncryptionContext(_AEADCipherContext): +class _AEADDecryptionContext(_AEADCipherContext, AEADDecryptionContext): + def finalize_with_tag(self, tag: bytes) -> bytes: + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + data = self._ctx.finalize_with_tag(tag) + self._tag = self._ctx.tag + self._ctx = None + return data + + +class _AEADEncryptionContext(_AEADCipherContext, AEADEncryptionContext): @property - def tag(self): + def tag(self) -> bytes: if self._ctx is not None: raise NotYetFinalized( "You must finalize encryption before " "getting the tag." ) + assert self._tag is not None return self._tag diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index dcb24444214f..d8ea1888d67b 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -2,75 +2,82 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc - -import six +import typing from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) +from cryptography.hazmat.primitives.ciphers import algorithms -@six.add_metaclass(abc.ABCMeta) -class Mode(object): - @abc.abstractproperty - def name(self): +class Mode(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: """ A string naming this mode (e.g. "ECB", "CBC"). """ @abc.abstractmethod - def validate_for_algorithm(self, algorithm): + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: """ Checks that all the necessary invariants of this (mode, algorithm) combination are met. """ -@six.add_metaclass(abc.ABCMeta) -class ModeWithInitializationVector(object): - @abc.abstractproperty - def initialization_vector(self): +class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def initialization_vector(self) -> bytes: """ The value of the initialization vector for this mode as bytes. """ -@six.add_metaclass(abc.ABCMeta) -class ModeWithTweak(object): - @abc.abstractproperty - def tweak(self): +class ModeWithTweak(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tweak(self) -> bytes: """ The value of the tweak for this mode as bytes. """ -@six.add_metaclass(abc.ABCMeta) -class ModeWithNonce(object): - @abc.abstractproperty - def nonce(self): +class ModeWithNonce(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def nonce(self) -> bytes: """ The value of the nonce for this mode as bytes. """ -@six.add_metaclass(abc.ABCMeta) -class ModeWithAuthenticationTag(object): - @abc.abstractproperty - def tag(self): +class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tag(self) -> typing.Optional[bytes]: """ The value of the tag supplied to the constructor of this mode. """ -def _check_aes_key_length(self, algorithm): +def _check_aes_key_length(self: Mode, algorithm: CipherAlgorithm) -> None: if algorithm.key_size > 256 and algorithm.name == "AES": raise ValueError( "Only 128, 192, and 256 bit keys are allowed for this AES mode" ) -def _check_iv_length(self, algorithm): +def _check_iv_length( + self: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm +) -> None: if len(self.initialization_vector) * 8 != algorithm.block_size: raise ValueError( "Invalid IV size ({}) for {}.".format( @@ -79,30 +86,48 @@ def _check_iv_length(self, algorithm): ) -def _check_iv_and_key_length(self, algorithm): +def _check_nonce_length( + nonce: bytes, name: str, algorithm: CipherAlgorithm +) -> None: + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + f"{name} requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + if len(nonce) * 8 != algorithm.block_size: + raise ValueError(f"Invalid nonce size ({len(nonce)}) for {name}.") + + +def _check_iv_and_key_length( + self: ModeWithInitializationVector, algorithm: CipherAlgorithm +) -> None: + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + f"{self} requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) _check_aes_key_length(self, algorithm) _check_iv_length(self, algorithm) -@utils.register_interface(Mode) -@utils.register_interface(ModeWithInitializationVector) -class CBC(object): +class CBC(ModeWithInitializationVector): name = "CBC" - def __init__(self, initialization_vector): + def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector - initialization_vector = utils.read_only_property("_initialization_vector") + @property + def initialization_vector(self) -> bytes: + return self._initialization_vector + validate_for_algorithm = _check_iv_and_key_length -@utils.register_interface(Mode) -@utils.register_interface(ModeWithTweak) -class XTS(object): +class XTS(ModeWithTweak): name = "XTS" - def __init__(self, tweak): + def __init__(self, tweak: bytes): utils._check_byteslike("tweak", tweak) if len(tweak) != 16: @@ -110,9 +135,17 @@ def __init__(self, tweak): self._tweak = tweak - tweak = utils.read_only_property("_tweak") + @property + def tweak(self) -> bytes: + return self._tweak + + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + if isinstance(algorithm, (algorithms.AES128, algorithms.AES256)): + raise TypeError( + "The AES128 and AES256 classes do not support XTS, please use " + "the standard AES class instead." + ) - def validate_for_algorithm(self, algorithm): if algorithm.key_size not in (256, 512): raise ValueError( "The XTS specification requires a 256-bit key for AES-128-XTS" @@ -120,88 +153,89 @@ def validate_for_algorithm(self, algorithm): ) -@utils.register_interface(Mode) -class ECB(object): +class ECB(Mode): name = "ECB" validate_for_algorithm = _check_aes_key_length -@utils.register_interface(Mode) -@utils.register_interface(ModeWithInitializationVector) -class OFB(object): +class OFB(ModeWithInitializationVector): name = "OFB" - def __init__(self, initialization_vector): + def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector - initialization_vector = utils.read_only_property("_initialization_vector") + @property + def initialization_vector(self) -> bytes: + return self._initialization_vector + validate_for_algorithm = _check_iv_and_key_length -@utils.register_interface(Mode) -@utils.register_interface(ModeWithInitializationVector) -class CFB(object): +class CFB(ModeWithInitializationVector): name = "CFB" - def __init__(self, initialization_vector): + def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector - initialization_vector = utils.read_only_property("_initialization_vector") + @property + def initialization_vector(self) -> bytes: + return self._initialization_vector + validate_for_algorithm = _check_iv_and_key_length -@utils.register_interface(Mode) -@utils.register_interface(ModeWithInitializationVector) -class CFB8(object): +class CFB8(ModeWithInitializationVector): name = "CFB8" - def __init__(self, initialization_vector): + def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector - initialization_vector = utils.read_only_property("_initialization_vector") + @property + def initialization_vector(self) -> bytes: + return self._initialization_vector + validate_for_algorithm = _check_iv_and_key_length -@utils.register_interface(Mode) -@utils.register_interface(ModeWithNonce) -class CTR(object): +class CTR(ModeWithNonce): name = "CTR" - def __init__(self, nonce): + def __init__(self, nonce: bytes): utils._check_byteslike("nonce", nonce) self._nonce = nonce - nonce = utils.read_only_property("_nonce") + @property + def nonce(self) -> bytes: + return self._nonce - def validate_for_algorithm(self, algorithm): + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: _check_aes_key_length(self, algorithm) - if len(self.nonce) * 8 != algorithm.block_size: - raise ValueError( - "Invalid nonce size ({}) for {}.".format( - len(self.nonce), self.name - ) - ) + _check_nonce_length(self.nonce, self.name, algorithm) -@utils.register_interface(Mode) -@utils.register_interface(ModeWithInitializationVector) -@utils.register_interface(ModeWithAuthenticationTag) -class GCM(object): +class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): name = "GCM" - _MAX_ENCRYPTED_BYTES = (2 ** 39 - 256) // 8 - _MAX_AAD_BYTES = (2 ** 64) // 8 - - def __init__(self, initialization_vector, tag=None, min_tag_length=16): - # len(initialization_vector) must in [1, 2 ** 64), but it's impossible - # to actually construct a bytes object that large, so we don't check - # for it + _MAX_ENCRYPTED_BYTES = (2**39 - 256) // 8 + _MAX_AAD_BYTES = (2**64) // 8 + + def __init__( + self, + initialization_vector: bytes, + tag: typing.Optional[bytes] = None, + min_tag_length: int = 16, + ): + # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive + # This is a sane limit anyway so we'll enforce it here. utils._check_byteslike("initialization_vector", initialization_vector) - if len(initialization_vector) == 0: - raise ValueError("initialization_vector must be at least 1 byte") + if len(initialization_vector) < 8 or len(initialization_vector) > 128: + raise ValueError( + "initialization_vector must be between 8 and 128 bytes (64 " + "and 1024 bits)." + ) self._initialization_vector = initialization_vector if tag is not None: utils._check_bytes("tag", tag) @@ -216,8 +250,25 @@ def __init__(self, initialization_vector, tag=None, min_tag_length=16): self._tag = tag self._min_tag_length = min_tag_length - tag = utils.read_only_property("_tag") - initialization_vector = utils.read_only_property("_initialization_vector") + @property + def tag(self) -> typing.Optional[bytes]: + return self._tag + + @property + def initialization_vector(self) -> bytes: + return self._initialization_vector - def validate_for_algorithm(self, algorithm): + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: _check_aes_key_length(self, algorithm) + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + "GCM requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + block_size_bytes = algorithm.block_size // 8 + if self._tag is not None and len(self._tag) > block_size_bytes: + raise ValueError( + "Authentication tag cannot be more than {} bytes.".format( + block_size_bytes + ) + ) diff --git a/src/cryptography/hazmat/primitives/cmac.py b/src/cryptography/hazmat/primitives/cmac.py index bf962c906908..8aa1d791acdd 100644 --- a/src/cryptography/hazmat/primitives/cmac.py +++ b/src/cryptography/hazmat/primitives/cmac.py @@ -2,53 +2,56 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import typing from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import CMACBackend +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import ciphers +if typing.TYPE_CHECKING: + from cryptography.hazmat.backends.openssl.cmac import _CMACContext -class CMAC(object): - def __init__(self, algorithm, backend=None, ctx=None): - backend = _get_backend(backend) - if not isinstance(backend, CMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement CMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) +class CMAC: + _ctx: typing.Optional[_CMACContext] + _algorithm: ciphers.BlockCipherAlgorithm + + def __init__( + self, + algorithm: ciphers.BlockCipherAlgorithm, + backend: typing.Any = None, + ctx: typing.Optional[_CMACContext] = None, + ) -> None: if not isinstance(algorithm, ciphers.BlockCipherAlgorithm): raise TypeError("Expected instance of BlockCipherAlgorithm.") self._algorithm = algorithm - self._backend = backend if ctx is None: - self._ctx = self._backend.create_cmac_ctx(self._algorithm) + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + self._ctx = ossl.create_cmac_ctx(self._algorithm) else: self._ctx = ctx - def update(self, data): + def update(self, data: bytes) -> None: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") utils._check_bytes("data", data) self._ctx.update(data) - def finalize(self): + def finalize(self) -> bytes: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") digest = self._ctx.finalize() self._ctx = None return digest - def verify(self, signature): + def verify(self, signature: bytes) -> None: utils._check_bytes("signature", signature) if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") @@ -56,9 +59,7 @@ def verify(self, signature): ctx, self._ctx = self._ctx, None ctx.verify(signature) - def copy(self): + def copy(self) -> CMAC: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") - return CMAC( - self._algorithm, backend=self._backend, ctx=self._ctx.copy() - ) + return CMAC(self._algorithm, ctx=self._ctx.copy()) diff --git a/src/cryptography/hazmat/primitives/constant_time.py b/src/cryptography/hazmat/primitives/constant_time.py index 7f41b9efa5f7..3975c7147eb9 100644 --- a/src/cryptography/hazmat/primitives/constant_time.py +++ b/src/cryptography/hazmat/primitives/constant_time.py @@ -2,12 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import hmac -def bytes_eq(a, b): +def bytes_eq(a: bytes, b: bytes) -> bool: if not isinstance(a, bytes) or not isinstance(b, bytes): raise TypeError("a and b must be bytes.") diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py index 18e2bab36340..b6a7ff140e68 100644 --- a/src/cryptography/hazmat/primitives/hashes.py +++ b/src/cryptography/hazmat/primitives/hashes.py @@ -2,195 +2,171 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc - -import six - -from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HashBackend - - -@six.add_metaclass(abc.ABCMeta) -class HashAlgorithm(object): - @abc.abstractproperty - def name(self): +import typing + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = [ + "HashAlgorithm", + "HashContext", + "Hash", + "ExtendableOutputFunction", + "SHA1", + "SHA512_224", + "SHA512_256", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA3_224", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "SHAKE128", + "SHAKE256", + "MD5", + "BLAKE2b", + "BLAKE2s", + "SM3", +] + + +class HashAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: """ A string naming this algorithm (e.g. "sha256", "md5"). """ - @abc.abstractproperty - def digest_size(self): + @property + @abc.abstractmethod + def digest_size(self) -> int: """ The size of the resulting digest in bytes. """ + @property + @abc.abstractmethod + def block_size(self) -> typing.Optional[int]: + """ + The internal block size of the hash function, or None if the hash + function does not use blocks internally (e.g. SHA3). + """ + -@six.add_metaclass(abc.ABCMeta) -class HashContext(object): - @abc.abstractproperty - def algorithm(self): +class HashContext(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def algorithm(self) -> HashAlgorithm: """ A HashAlgorithm that will be used by this context. """ @abc.abstractmethod - def update(self, data): + def update(self, data: bytes) -> None: """ Processes the provided bytes through the hash. """ @abc.abstractmethod - def finalize(self): + def finalize(self) -> bytes: """ Finalizes the hash context and returns the hash digest as bytes. """ @abc.abstractmethod - def copy(self): + def copy(self) -> HashContext: """ Return a HashContext that is a copy of the current context. """ -@six.add_metaclass(abc.ABCMeta) -class ExtendableOutputFunction(object): +Hash = rust_openssl.hashes.Hash +HashContext.register(Hash) + + +class ExtendableOutputFunction(metaclass=abc.ABCMeta): """ An interface for extendable output functions. """ -@utils.register_interface(HashContext) -class Hash(object): - def __init__(self, algorithm, backend=None, ctx=None): - backend = _get_backend(backend) - if not isinstance(backend, HashBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HashBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - - if not isinstance(algorithm, HashAlgorithm): - raise TypeError("Expected instance of hashes.HashAlgorithm.") - self._algorithm = algorithm - - self._backend = backend - - if ctx is None: - self._ctx = self._backend.create_hash_ctx(self.algorithm) - else: - self._ctx = ctx - - algorithm = utils.read_only_property("_algorithm") - - def update(self, data): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - utils._check_byteslike("data", data) - self._ctx.update(data) - - def copy(self): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return Hash( - self.algorithm, backend=self._backend, ctx=self._ctx.copy() - ) - - def finalize(self): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - digest = self._ctx.finalize() - self._ctx = None - return digest - - -@utils.register_interface(HashAlgorithm) -class SHA1(object): +class SHA1(HashAlgorithm): name = "sha1" digest_size = 20 block_size = 64 -@utils.register_interface(HashAlgorithm) -class SHA512_224(object): # noqa: N801 +class SHA512_224(HashAlgorithm): # noqa: N801 name = "sha512-224" digest_size = 28 block_size = 128 -@utils.register_interface(HashAlgorithm) -class SHA512_256(object): # noqa: N801 +class SHA512_256(HashAlgorithm): # noqa: N801 name = "sha512-256" digest_size = 32 block_size = 128 -@utils.register_interface(HashAlgorithm) -class SHA224(object): +class SHA224(HashAlgorithm): name = "sha224" digest_size = 28 block_size = 64 -@utils.register_interface(HashAlgorithm) -class SHA256(object): +class SHA256(HashAlgorithm): name = "sha256" digest_size = 32 block_size = 64 -@utils.register_interface(HashAlgorithm) -class SHA384(object): +class SHA384(HashAlgorithm): name = "sha384" digest_size = 48 block_size = 128 -@utils.register_interface(HashAlgorithm) -class SHA512(object): +class SHA512(HashAlgorithm): name = "sha512" digest_size = 64 block_size = 128 -@utils.register_interface(HashAlgorithm) -class SHA3_224(object): # noqa: N801 +class SHA3_224(HashAlgorithm): # noqa: N801 name = "sha3-224" digest_size = 28 + block_size = None -@utils.register_interface(HashAlgorithm) -class SHA3_256(object): # noqa: N801 +class SHA3_256(HashAlgorithm): # noqa: N801 name = "sha3-256" digest_size = 32 + block_size = None -@utils.register_interface(HashAlgorithm) -class SHA3_384(object): # noqa: N801 +class SHA3_384(HashAlgorithm): # noqa: N801 name = "sha3-384" digest_size = 48 + block_size = None -@utils.register_interface(HashAlgorithm) -class SHA3_512(object): # noqa: N801 +class SHA3_512(HashAlgorithm): # noqa: N801 name = "sha3-512" digest_size = 64 + block_size = None -@utils.register_interface(HashAlgorithm) -@utils.register_interface(ExtendableOutputFunction) -class SHAKE128(object): +class SHAKE128(HashAlgorithm, ExtendableOutputFunction): name = "shake128" + block_size = None - def __init__(self, digest_size): - if not isinstance(digest_size, six.integer_types): + def __init__(self, digest_size: int): + if not isinstance(digest_size, int): raise TypeError("digest_size must be an integer") if digest_size < 1: @@ -198,16 +174,17 @@ def __init__(self, digest_size): self._digest_size = digest_size - digest_size = utils.read_only_property("_digest_size") + @property + def digest_size(self) -> int: + return self._digest_size -@utils.register_interface(HashAlgorithm) -@utils.register_interface(ExtendableOutputFunction) -class SHAKE256(object): +class SHAKE256(HashAlgorithm, ExtendableOutputFunction): name = "shake256" + block_size = None - def __init__(self, digest_size): - if not isinstance(digest_size, six.integer_types): + def __init__(self, digest_size: int): + if not isinstance(digest_size, int): raise TypeError("digest_size must be an integer") if digest_size < 1: @@ -215,45 +192,52 @@ def __init__(self, digest_size): self._digest_size = digest_size - digest_size = utils.read_only_property("_digest_size") + @property + def digest_size(self) -> int: + return self._digest_size -@utils.register_interface(HashAlgorithm) -class MD5(object): +class MD5(HashAlgorithm): name = "md5" digest_size = 16 block_size = 64 -@utils.register_interface(HashAlgorithm) -class BLAKE2b(object): +class BLAKE2b(HashAlgorithm): name = "blake2b" _max_digest_size = 64 _min_digest_size = 1 block_size = 128 - def __init__(self, digest_size): - + def __init__(self, digest_size: int): if digest_size != 64: raise ValueError("Digest size must be 64") self._digest_size = digest_size - digest_size = utils.read_only_property("_digest_size") + @property + def digest_size(self) -> int: + return self._digest_size -@utils.register_interface(HashAlgorithm) -class BLAKE2s(object): +class BLAKE2s(HashAlgorithm): name = "blake2s" block_size = 64 _max_digest_size = 32 _min_digest_size = 1 - def __init__(self, digest_size): - + def __init__(self, digest_size: int): if digest_size != 32: raise ValueError("Digest size must be 32") self._digest_size = digest_size - digest_size = utils.read_only_property("_digest_size") + @property + def digest_size(self) -> int: + return self._digest_size + + +class SM3(HashAlgorithm): + name = "sm3" + digest_size = 32 + block_size = 64 diff --git a/src/cryptography/hazmat/primitives/hmac.py b/src/cryptography/hazmat/primitives/hmac.py index 8c421dc68df9..a9442d59ab47 100644 --- a/src/cryptography/hazmat/primitives/hmac.py +++ b/src/cryptography/hazmat/primitives/hmac.py @@ -2,69 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes +__all__ = ["HMAC"] -@utils.register_interface(hashes.HashContext) -class HMAC(object): - def __init__(self, key, algorithm, backend=None, ctx=None): - backend = _get_backend(backend) - if not isinstance(backend, HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - - if not isinstance(algorithm, hashes.HashAlgorithm): - raise TypeError("Expected instance of hashes.HashAlgorithm.") - self._algorithm = algorithm - - self._backend = backend - self._key = key - if ctx is None: - self._ctx = self._backend.create_hmac_ctx(key, self.algorithm) - else: - self._ctx = ctx - - algorithm = utils.read_only_property("_algorithm") - - def update(self, data): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - utils._check_byteslike("data", data) - self._ctx.update(data) - - def copy(self): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return HMAC( - self._key, - self.algorithm, - backend=self._backend, - ctx=self._ctx.copy(), - ) - - def finalize(self): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - digest = self._ctx.finalize() - self._ctx = None - return digest - - def verify(self, signature): - utils._check_bytes("signature", signature) - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - ctx, self._ctx = self._ctx, None - ctx.verify(signature) +HMAC = rust_openssl.hmac.HMAC +hashes.HashContext.register(HMAC) diff --git a/src/cryptography/hazmat/primitives/kdf/__init__.py b/src/cryptography/hazmat/primitives/kdf/__init__.py index 2d0724e5daa4..79bb459f01ec 100644 --- a/src/cryptography/hazmat/primitives/kdf/__init__.py +++ b/src/cryptography/hazmat/primitives/kdf/__init__.py @@ -2,24 +2,21 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc -import six - -@six.add_metaclass(abc.ABCMeta) -class KeyDerivationFunction(object): +class KeyDerivationFunction(metaclass=abc.ABCMeta): @abc.abstractmethod - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: """ Deterministically generates and returns a new key based on the existing key material. """ @abc.abstractmethod - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: """ Checks whether the key generated by the key material matches the expected derived key. Raises an exception if they do not match. diff --git a/src/cryptography/hazmat/primitives/kdf/concatkdf.py b/src/cryptography/hazmat/primitives/kdf/concatkdf.py index 7cc0324fc4f5..d5ea58a94522 100644 --- a/src/cryptography/hazmat/primitives/kdf/concatkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -2,39 +2,38 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import struct +import typing from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidKey, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HMACBackend -from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction -def _int_to_u32be(n): - return struct.pack(">I", n) +def _int_to_u32be(n: int) -> bytes: + return n.to_bytes(length=4, byteorder="big") -def _common_args_checks(algorithm, length, otherinfo): - max_length = algorithm.digest_size * (2 ** 32 - 1) +def _common_args_checks( + algorithm: hashes.HashAlgorithm, + length: int, + otherinfo: typing.Optional[bytes], +) -> None: + max_length = algorithm.digest_size * (2**32 - 1) if length > max_length: - raise ValueError( - "Can not derive keys larger than {} bits.".format(max_length) - ) + raise ValueError(f"Cannot derive keys larger than {max_length} bits.") if otherinfo is not None: utils._check_bytes("otherinfo", otherinfo) -def _concatkdf_derive(key_material, length, auxfn, otherinfo): +def _concatkdf_derive( + key_material: bytes, + length: int, + auxfn: typing.Callable[[], hashes.HashContext], + otherinfo: bytes, +) -> bytes: utils._check_byteslike("key_material", key_material) output = [b""] outlen = 0 @@ -52,30 +51,25 @@ def _concatkdf_derive(key_material, length, auxfn, otherinfo): return b"".join(output)[:length] -@utils.register_interface(KeyDerivationFunction) -class ConcatKDFHash(object): - def __init__(self, algorithm, length, otherinfo, backend=None): - backend = _get_backend(backend) - +class ConcatKDFHash(KeyDerivationFunction): + def __init__( + self, + algorithm: hashes.HashAlgorithm, + length: int, + otherinfo: typing.Optional[bytes], + backend: typing.Any = None, + ): _common_args_checks(algorithm, length, otherinfo) self._algorithm = algorithm self._length = length - self._otherinfo = otherinfo - if self._otherinfo is None: - self._otherinfo = b"" - - if not isinstance(backend, HashBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HashBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - self._backend = backend + self._otherinfo: bytes = otherinfo if otherinfo is not None else b"" + self._used = False - def _hash(self): - return hashes.Hash(self._algorithm, self._backend) + def _hash(self) -> hashes.Hash: + return hashes.Hash(self._algorithm) - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized self._used = True @@ -83,22 +77,27 @@ def derive(self, key_material): key_material, self._length, self._hash, self._otherinfo ) - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey -@utils.register_interface(KeyDerivationFunction) -class ConcatKDFHMAC(object): - def __init__(self, algorithm, length, salt, otherinfo, backend=None): - backend = _get_backend(backend) - +class ConcatKDFHMAC(KeyDerivationFunction): + def __init__( + self, + algorithm: hashes.HashAlgorithm, + length: int, + salt: typing.Optional[bytes], + otherinfo: typing.Optional[bytes], + backend: typing.Any = None, + ): _common_args_checks(algorithm, length, otherinfo) self._algorithm = algorithm self._length = length - self._otherinfo = otherinfo - if self._otherinfo is None: - self._otherinfo = b"" + self._otherinfo: bytes = otherinfo if otherinfo is not None else b"" + + if algorithm.block_size is None: + raise TypeError(f"{algorithm.name} is unsupported for ConcatKDF") if salt is None: salt = b"\x00" * algorithm.block_size @@ -107,18 +106,12 @@ def __init__(self, algorithm, length, salt, otherinfo, backend=None): self._salt = salt - if not isinstance(backend, HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - self._backend = backend self._used = False - def _hmac(self): - return hmac.HMAC(self._salt, self._algorithm, self._backend) + def _hmac(self) -> hmac.HMAC: + return hmac.HMAC(self._salt, self._algorithm) - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized self._used = True @@ -126,6 +119,6 @@ def derive(self, key_material): key_material, self._length, self._hmac, self._otherinfo ) - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/kdf/hkdf.py b/src/cryptography/hazmat/primitives/kdf/hkdf.py index 9bb6bc213253..d47689443631 100644 --- a/src/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/hkdf.py @@ -2,33 +2,25 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import six +import typing from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidKey, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HMACBackend -from cryptography.hazmat.primitives import constant_time, hmac +from cryptography.exceptions import AlreadyFinalized, InvalidKey +from cryptography.hazmat.primitives import constant_time, hashes, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction -@utils.register_interface(KeyDerivationFunction) -class HKDF(object): - def __init__(self, algorithm, length, salt, info, backend=None): - backend = _get_backend(backend) - if not isinstance(backend, HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - +class HKDF(KeyDerivationFunction): + def __init__( + self, + algorithm: hashes.HashAlgorithm, + length: int, + salt: typing.Optional[bytes], + info: typing.Optional[bytes], + backend: typing.Any = None, + ): self._algorithm = algorithm if salt is None: @@ -38,43 +30,37 @@ def __init__(self, algorithm, length, salt, info, backend=None): self._salt = salt - self._backend = backend - - self._hkdf_expand = HKDFExpand(self._algorithm, length, info, backend) + self._hkdf_expand = HKDFExpand(self._algorithm, length, info) - def _extract(self, key_material): - h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) + def _extract(self, key_material: bytes) -> bytes: + h = hmac.HMAC(self._salt, self._algorithm) h.update(key_material) return h.finalize() - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: utils._check_byteslike("key_material", key_material) return self._hkdf_expand.derive(self._extract(key_material)) - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey -@utils.register_interface(KeyDerivationFunction) -class HKDFExpand(object): - def __init__(self, algorithm, length, info, backend=None): - backend = _get_backend(backend) - if not isinstance(backend, HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - +class HKDFExpand(KeyDerivationFunction): + def __init__( + self, + algorithm: hashes.HashAlgorithm, + length: int, + info: typing.Optional[bytes], + backend: typing.Any = None, + ): self._algorithm = algorithm - self._backend = backend - max_length = 255 * algorithm.digest_size if length > max_length: raise ValueError( - "Can not derive keys larger than {} octets.".format(max_length) + f"Cannot derive keys larger than {max_length} octets." ) self._length = length @@ -88,21 +74,21 @@ def __init__(self, algorithm, length, info, backend=None): self._used = False - def _expand(self, key_material): + def _expand(self, key_material: bytes) -> bytes: output = [b""] counter = 1 while self._algorithm.digest_size * (len(output) - 1) < self._length: - h = hmac.HMAC(key_material, self._algorithm, backend=self._backend) + h = hmac.HMAC(key_material, self._algorithm) h.update(output[-1]) h.update(self._info) - h.update(six.int2byte(counter)) + h.update(bytes([counter])) output.append(h.finalize()) counter += 1 return b"".join(output)[: self._length] - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: utils._check_byteslike("key_material", key_material) if self._used: raise AlreadyFinalized @@ -110,6 +96,6 @@ def derive(self, key_material): self._used = True return self._expand(key_material) - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py index 864337001c89..967763828f3f 100644 --- a/src/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -2,11 +2,9 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -from enum import Enum - -from six.moves import range +import typing from cryptography import utils from cryptography.exceptions import ( @@ -15,54 +13,41 @@ UnsupportedAlgorithm, _Reasons, ) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HMACBackend -from cryptography.hazmat.primitives import constant_time, hashes, hmac +from cryptography.hazmat.primitives import ( + ciphers, + cmac, + constant_time, + hashes, + hmac, +) from cryptography.hazmat.primitives.kdf import KeyDerivationFunction -class Mode(Enum): +class Mode(utils.Enum): CounterMode = "ctr" -class CounterLocation(Enum): +class CounterLocation(utils.Enum): BeforeFixed = "before_fixed" AfterFixed = "after_fixed" + MiddleFixed = "middle_fixed" -@utils.register_interface(KeyDerivationFunction) -class KBKDFHMAC(object): +class _KBKDFDeriver: def __init__( self, - algorithm, - mode, - length, - rlen, - llen, - location, - label, - context, - fixed, - backend=None, + prf: typing.Callable, + mode: Mode, + length: int, + rlen: int, + llen: typing.Optional[int], + location: CounterLocation, + break_location: typing.Optional[int], + label: typing.Optional[bytes], + context: typing.Optional[bytes], + fixed: typing.Optional[bytes], ): - backend = _get_backend(backend) - if not isinstance(backend, HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - - if not isinstance(algorithm, hashes.HashAlgorithm): - raise UnsupportedAlgorithm( - "Algorithm supplied is not a supported hash algorithm.", - _Reasons.UNSUPPORTED_HASH, - ) - - if not backend.hmac_supported(algorithm): - raise UnsupportedAlgorithm( - "Algorithm supplied is not a supported hmac algorithm.", - _Reasons.UNSUPPORTED_HASH, - ) + assert callable(prf) if not isinstance(mode, Mode): raise TypeError("mode must be of type Mode") @@ -70,6 +55,24 @@ def __init__( if not isinstance(location, CounterLocation): raise TypeError("location must be of type CounterLocation") + if break_location is None and location is CounterLocation.MiddleFixed: + raise ValueError("Please specify a break_location") + + if ( + break_location is not None + and location != CounterLocation.MiddleFixed + ): + raise ValueError( + "break_location is ignored when location is not" + " CounterLocation.MiddleFixed" + ) + + if break_location is not None and not isinstance(break_location, int): + raise TypeError("break_location must be an integer") + + if break_location is not None and break_location < 0: + raise ValueError("break_location must be a positive integer") + if (label or context) and fixed: raise ValueError( "When supplying fixed data, " "label and context are ignored." @@ -92,19 +95,20 @@ def __init__( utils._check_bytes("label", label) utils._check_bytes("context", context) - self._algorithm = algorithm + self._prf = prf self._mode = mode self._length = length self._rlen = rlen self._llen = llen self._location = location + self._break_location = break_location self._label = label self._context = context - self._backend = backend self._used = False self._fixed_data = fixed - def _valid_byte_length(self, value): + @staticmethod + def _valid_byte_length(value: int) -> bool: if not isinstance(value, int): raise TypeError("value must be of type int") @@ -113,7 +117,7 @@ def _valid_byte_length(self, value): return False return True - def derive(self, key_material): + def derive(self, key_material: bytes, prf_output_size: int) -> bytes: if self._used: raise AlreadyFinalized @@ -121,7 +125,7 @@ def derive(self, key_material): self._used = True # inverse floor division (equivalent to ceiling) - rounds = -(-self._length // self._algorithm.digest_size) + rounds = -(-self._length // prf_output_size) output = [b""] @@ -133,23 +137,35 @@ def derive(self, key_material): if rounds > pow(2, len(r_bin) * 8) - 1: raise ValueError("There are too many iterations.") + fixed = self._generate_fixed_input() + + if self._location == CounterLocation.BeforeFixed: + data_before_ctr = b"" + data_after_ctr = fixed + elif self._location == CounterLocation.AfterFixed: + data_before_ctr = fixed + data_after_ctr = b"" + else: + if isinstance( + self._break_location, int + ) and self._break_location > len(fixed): + raise ValueError("break_location offset > len(fixed)") + data_before_ctr = fixed[: self._break_location] + data_after_ctr = fixed[self._break_location :] + for i in range(1, rounds + 1): - h = hmac.HMAC(key_material, self._algorithm, backend=self._backend) + h = self._prf(key_material) counter = utils.int_to_bytes(i, self._rlen) - if self._location == CounterLocation.BeforeFixed: - h.update(counter) - - h.update(self._generate_fixed_input()) + input_data = data_before_ctr + counter + data_after_ctr - if self._location == CounterLocation.AfterFixed: - h.update(counter) + h.update(input_data) output.append(h.finalize()) return b"".join(output)[: self._length] - def _generate_fixed_input(self): + def _generate_fixed_input(self) -> bytes: if self._fixed_data and isinstance(self._fixed_data, bytes): return self._fixed_data @@ -157,6 +173,127 @@ def _generate_fixed_input(self): return b"".join([self._label, b"\x00", self._context, l_val]) - def verify(self, key_material, expected_key): + +class KBKDFHMAC(KeyDerivationFunction): + def __init__( + self, + algorithm: hashes.HashAlgorithm, + mode: Mode, + length: int, + rlen: int, + llen: typing.Optional[int], + location: CounterLocation, + label: typing.Optional[bytes], + context: typing.Optional[bytes], + fixed: typing.Optional[bytes], + backend: typing.Any = None, + *, + break_location: typing.Optional[int] = None, + ): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise UnsupportedAlgorithm( + "Algorithm supplied is not a supported hash algorithm.", + _Reasons.UNSUPPORTED_HASH, + ) + + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + if not ossl.hmac_supported(algorithm): + raise UnsupportedAlgorithm( + "Algorithm supplied is not a supported hmac algorithm.", + _Reasons.UNSUPPORTED_HASH, + ) + + self._algorithm = algorithm + + self._deriver = _KBKDFDeriver( + self._prf, + mode, + length, + rlen, + llen, + location, + break_location, + label, + context, + fixed, + ) + + def _prf(self, key_material: bytes) -> hmac.HMAC: + return hmac.HMAC(key_material, self._algorithm) + + def derive(self, key_material: bytes) -> bytes: + return self._deriver.derive(key_material, self._algorithm.digest_size) + + def verify(self, key_material: bytes, expected_key: bytes) -> None: + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey + + +class KBKDFCMAC(KeyDerivationFunction): + def __init__( + self, + algorithm, + mode: Mode, + length: int, + rlen: int, + llen: typing.Optional[int], + location: CounterLocation, + label: typing.Optional[bytes], + context: typing.Optional[bytes], + fixed: typing.Optional[bytes], + backend: typing.Any = None, + *, + break_location: typing.Optional[int] = None, + ): + if not issubclass( + algorithm, ciphers.BlockCipherAlgorithm + ) or not issubclass(algorithm, ciphers.CipherAlgorithm): + raise UnsupportedAlgorithm( + "Algorithm supplied is not a supported cipher algorithm.", + _Reasons.UNSUPPORTED_CIPHER, + ) + + self._algorithm = algorithm + self._cipher: typing.Optional[ciphers.BlockCipherAlgorithm] = None + + self._deriver = _KBKDFDeriver( + self._prf, + mode, + length, + rlen, + llen, + location, + break_location, + label, + context, + fixed, + ) + + def _prf(self, _: bytes) -> cmac.CMAC: + assert self._cipher is not None + + return cmac.CMAC(self._cipher) + + def derive(self, key_material: bytes) -> bytes: + self._cipher = self._algorithm(key_material) + + assert self._cipher is not None + + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + if not ossl.cmac_algorithm_supported(self._cipher): + raise UnsupportedAlgorithm( + "Algorithm supplied is not a supported cipher algorithm.", + _Reasons.UNSUPPORTED_CIPHER, + ) + + return self._deriver.derive(key_material, self._cipher.block_size // 8) + + def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py index 5b67d48bbab6..623e1ca7f9eb 100644 --- a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -2,7 +2,9 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import typing from cryptography import utils from cryptography.exceptions import ( @@ -11,23 +13,25 @@ UnsupportedAlgorithm, _Reasons, ) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import PBKDF2HMACBackend -from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import constant_time, hashes from cryptography.hazmat.primitives.kdf import KeyDerivationFunction -@utils.register_interface(KeyDerivationFunction) -class PBKDF2HMAC(object): - def __init__(self, algorithm, length, salt, iterations, backend=None): - backend = _get_backend(backend) - if not isinstance(backend, PBKDF2HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement PBKDF2HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) +class PBKDF2HMAC(KeyDerivationFunction): + def __init__( + self, + algorithm: hashes.HashAlgorithm, + length: int, + salt: bytes, + iterations: int, + backend: typing.Any = None, + ): + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) - if not backend.pbkdf2_hmac_supported(algorithm): + if not ossl.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( "{} is not supported for PBKDF2 by this backend.".format( algorithm.name @@ -40,23 +44,21 @@ def __init__(self, algorithm, length, salt, iterations, backend=None): utils._check_bytes("salt", salt) self._salt = salt self._iterations = iterations - self._backend = backend - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized("PBKDF2 instances can only be used once.") self._used = True - utils._check_byteslike("key_material", key_material) - return self._backend.derive_pbkdf2_hmac( + return rust_openssl.kdf.derive_pbkdf2_hmac( + key_material, self._algorithm, - self._length, self._salt, self._iterations, - key_material, + self._length, ) - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: derived_key = self.derive(key_material) if not constant_time.bytes_eq(derived_key, expected_key): raise InvalidKey("Keys do not match.") diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py index f028646aa02d..05a4f675b6ab 100644 --- a/src/cryptography/hazmat/primitives/kdf/scrypt.py +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -2,38 +2,44 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import sys +import typing from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, - _Reasons, ) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.kdf import KeyDerivationFunction - # This is used by the scrypt tests to skip tests that require more memory # than the MEM_LIMIT _MEM_LIMIT = sys.maxsize // 2 -@utils.register_interface(KeyDerivationFunction) -class Scrypt(object): - def __init__(self, salt, length, n, r, p, backend=None): - backend = _get_backend(backend) - if not isinstance(backend, ScryptBackend): +class Scrypt(KeyDerivationFunction): + def __init__( + self, + salt: bytes, + length: int, + n: int, + r: int, + p: int, + backend: typing.Any = None, + ): + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + if not ossl.scrypt_supported(): raise UnsupportedAlgorithm( - "Backend object does not implement ScryptBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, + "This version of OpenSSL does not support scrypt" ) - self._length = length utils._check_bytes("salt", salt) if n < 2 or (n & (n - 1)) != 0: @@ -50,19 +56,25 @@ def __init__(self, salt, length, n, r, p, backend=None): self._n = n self._r = r self._p = p - self._backend = backend - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized("Scrypt instances can only be used once.") self._used = True utils._check_byteslike("key_material", key_material) - return self._backend.derive_scrypt( - key_material, self._salt, self._length, self._n, self._r, self._p + + return rust_openssl.kdf.derive_scrypt( + key_material, + self._salt, + self._n, + self._r, + self._p, + _MEM_LIMIT, + self._length, ) - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: derived_key = self.derive(key_material) if not constant_time.bytes_eq(derived_key, expected_key): raise InvalidKey("Keys do not match.") diff --git a/src/cryptography/hazmat/primitives/kdf/x963kdf.py b/src/cryptography/hazmat/primitives/kdf/x963kdf.py index 1898d526a48f..17acc5174bb0 100644 --- a/src/cryptography/hazmat/primitives/kdf/x963kdf.py +++ b/src/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -2,53 +2,40 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import struct +import typing from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidKey, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes from cryptography.hazmat.primitives.kdf import KeyDerivationFunction -def _int_to_u32be(n): - return struct.pack(">I", n) +def _int_to_u32be(n: int) -> bytes: + return n.to_bytes(length=4, byteorder="big") -@utils.register_interface(KeyDerivationFunction) -class X963KDF(object): - def __init__(self, algorithm, length, sharedinfo, backend=None): - backend = _get_backend(backend) - - max_len = algorithm.digest_size * (2 ** 32 - 1) +class X963KDF(KeyDerivationFunction): + def __init__( + self, + algorithm: hashes.HashAlgorithm, + length: int, + sharedinfo: typing.Optional[bytes], + backend: typing.Any = None, + ): + max_len = algorithm.digest_size * (2**32 - 1) if length > max_len: - raise ValueError( - "Can not derive keys larger than {} bits.".format(max_len) - ) + raise ValueError(f"Cannot derive keys larger than {max_len} bits.") if sharedinfo is not None: utils._check_bytes("sharedinfo", sharedinfo) self._algorithm = algorithm self._length = length self._sharedinfo = sharedinfo - - if not isinstance(backend, HashBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HashBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - self._backend = backend self._used = False - def derive(self, key_material): + def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized self._used = True @@ -58,7 +45,7 @@ def derive(self, key_material): counter = 1 while self._length > outlen: - h = hashes.Hash(self._algorithm, self._backend) + h = hashes.Hash(self._algorithm) h.update(key_material) h.update(_int_to_u32be(counter)) if self._sharedinfo is not None: @@ -69,6 +56,6 @@ def derive(self, key_material): return b"".join(output)[: self._length] - def verify(self, key_material, expected_key): + def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/keywrap.py b/src/cryptography/hazmat/primitives/keywrap.py index 2439cafe6d59..59b0326c2a86 100644 --- a/src/cryptography/hazmat/primitives/keywrap.py +++ b/src/cryptography/hazmat/primitives/keywrap.py @@ -2,20 +2,23 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import struct +import typing -from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import ECB from cryptography.hazmat.primitives.constant_time import bytes_eq -def _wrap_core(wrapping_key, a, r, backend): +def _wrap_core( + wrapping_key: bytes, + a: bytes, + r: typing.List[bytes], +) -> bytes: # RFC 3394 Key Wrap - 2.2.1 (index method) - encryptor = Cipher(AES(wrapping_key), ECB(), backend).encryptor() + encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() n = len(r) for j in range(6): for i in range(n): @@ -23,10 +26,9 @@ def _wrap_core(wrapping_key, a, r, backend): # AES has a 128-bit block size) and since we're using ECB it is # safe to reuse the encryptor for the entire operation b = encryptor.update(a + r[i]) - # pack/unpack are safe as these are always 64-bit chunks - a = struct.pack( - ">Q", struct.unpack(">Q", b[:8])[0] ^ ((n * j) + i + 1) - ) + a = ( + int.from_bytes(b[:8], byteorder="big") ^ ((n * j) + i + 1) + ).to_bytes(length=8, byteorder="big") r[i] = b[-8:] assert encryptor.finalize() == b"" @@ -34,8 +36,11 @@ def _wrap_core(wrapping_key, a, r, backend): return a + b"".join(r) -def aes_key_wrap(wrapping_key, key_to_wrap, backend=None): - backend = _get_backend(backend) +def aes_key_wrap( + wrapping_key: bytes, + key_to_wrap: bytes, + backend: typing.Any = None, +) -> bytes: if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") @@ -47,22 +52,22 @@ def aes_key_wrap(wrapping_key, key_to_wrap, backend=None): a = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] - return _wrap_core(wrapping_key, a, r, backend) + return _wrap_core(wrapping_key, a, r) -def _unwrap_core(wrapping_key, a, r, backend): +def _unwrap_core( + wrapping_key: bytes, + a: bytes, + r: typing.List[bytes], +) -> typing.Tuple[bytes, typing.List[bytes]]: # Implement RFC 3394 Key Unwrap - 2.2.2 (index method) - decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor() + decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() n = len(r) for j in reversed(range(6)): for i in reversed(range(n)): - # pack/unpack are safe as these are always 64-bit chunks atr = ( - struct.pack( - ">Q", struct.unpack(">Q", a)[0] ^ ((n * j) + i + 1) - ) - + r[i] - ) + int.from_bytes(a, byteorder="big") ^ ((n * j) + i + 1) + ).to_bytes(length=8, byteorder="big") + r[i] # every decryption operation is a discrete 16 byte chunk so # it is safe to reuse the decryptor for the entire operation b = decryptor.update(atr) @@ -73,28 +78,36 @@ def _unwrap_core(wrapping_key, a, r, backend): return a, r -def aes_key_wrap_with_padding(wrapping_key, key_to_wrap, backend=None): - backend = _get_backend(backend) +def aes_key_wrap_with_padding( + wrapping_key: bytes, + key_to_wrap: bytes, + backend: typing.Any = None, +) -> bytes: if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") - aiv = b"\xA6\x59\x59\xA6" + struct.pack(">i", len(key_to_wrap)) + aiv = b"\xA6\x59\x59\xA6" + len(key_to_wrap).to_bytes( + length=4, byteorder="big" + ) # pad the key to wrap if necessary pad = (8 - (len(key_to_wrap) % 8)) % 8 key_to_wrap = key_to_wrap + b"\x00" * pad if len(key_to_wrap) == 8: # RFC 5649 - 4.1 - exactly 8 octets after padding - encryptor = Cipher(AES(wrapping_key), ECB(), backend).encryptor() + encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() b = encryptor.update(aiv + key_to_wrap) assert encryptor.finalize() == b"" return b else: r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] - return _wrap_core(wrapping_key, aiv, r, backend) + return _wrap_core(wrapping_key, aiv, r) -def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend=None): - backend = _get_backend(backend) +def aes_key_unwrap_with_padding( + wrapping_key: bytes, + wrapped_key: bytes, + backend: typing.Any = None, +) -> bytes: if len(wrapped_key) < 16: raise InvalidUnwrap("Must be at least 16 bytes") @@ -103,17 +116,17 @@ def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend=None): if len(wrapped_key) == 16: # RFC 5649 - 4.2 - exactly two 64-bit blocks - decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor() - b = decryptor.update(wrapped_key) + decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() + out = decryptor.update(wrapped_key) assert decryptor.finalize() == b"" - a = b[:8] - data = b[8:] + a = out[:8] + data = out[8:] n = 1 else: r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] encrypted_aiv = r.pop(0) n = len(r) - a, r = _unwrap_core(wrapping_key, encrypted_aiv, r, backend) + a, r = _unwrap_core(wrapping_key, encrypted_aiv, r) data = b"".join(r) # 1) Check that MSB(32,A) = A65959A6. @@ -121,7 +134,7 @@ def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend=None): # MLI = LSB(32,A). # 3) Let b = (8*n)-MLI, and then check that the rightmost b octets of # the output data are zero. - (mli,) = struct.unpack(">I", a[4:]) + mli = int.from_bytes(a[4:], byteorder="big") b = (8 * n) - mli if ( not bytes_eq(a[:4], b"\xa6\x59\x59\xa6") @@ -136,8 +149,11 @@ def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend=None): return data[:-b] -def aes_key_unwrap(wrapping_key, wrapped_key, backend=None): - backend = _get_backend(backend) +def aes_key_unwrap( + wrapping_key: bytes, + wrapped_key: bytes, + backend: typing.Any = None, +) -> bytes: if len(wrapped_key) < 24: raise InvalidUnwrap("Must be at least 24 bytes") @@ -150,7 +166,7 @@ def aes_key_unwrap(wrapping_key, wrapped_key, backend=None): aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] a = r.pop(0) - a, r = _unwrap_core(wrapping_key, a, r, backend) + a, r = _unwrap_core(wrapping_key, a, r) if not bytes_eq(a, aiv): raise InvalidUnwrap() diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index d3dc7093ae51..fde3094b00ae 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -2,33 +2,34 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc - -import six +import typing from cryptography import utils from cryptography.exceptions import AlreadyFinalized -from cryptography.hazmat.bindings._padding import lib +from cryptography.hazmat.bindings._rust import ( + check_ansix923_padding, + check_pkcs7_padding, +) -@six.add_metaclass(abc.ABCMeta) -class PaddingContext(object): +class PaddingContext(metaclass=abc.ABCMeta): @abc.abstractmethod - def update(self, data): + def update(self, data: bytes) -> bytes: """ Pads the provided bytes and returns any available data as bytes. """ @abc.abstractmethod - def finalize(self): + def finalize(self) -> bytes: """ Finalize the padding, returns bytes. """ -def _byte_padding_check(block_size): +def _byte_padding_check(block_size: int) -> None: if not (0 <= block_size <= 2040): raise ValueError("block_size must be in range(0, 2041).") @@ -36,7 +37,9 @@ def _byte_padding_check(block_size): raise ValueError("block_size must be a multiple of 8.") -def _byte_padding_update(buffer_, data, block_size): +def _byte_padding_update( + buffer_: typing.Optional[bytes], data: bytes, block_size: int +) -> typing.Tuple[bytes, bytes]: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") @@ -52,7 +55,11 @@ def _byte_padding_update(buffer_, data, block_size): return buffer_, result -def _byte_padding_pad(buffer_, block_size, paddingfn): +def _byte_padding_pad( + buffer_: typing.Optional[bytes], + block_size: int, + paddingfn: typing.Callable[[int], bytes], +) -> bytes: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") @@ -60,7 +67,9 @@ def _byte_padding_pad(buffer_, block_size, paddingfn): return buffer_ + paddingfn(pad_size) -def _byte_unpadding_update(buffer_, data, block_size): +def _byte_unpadding_update( + buffer_: typing.Optional[bytes], data: bytes, block_size: int +) -> typing.Tuple[bytes, bytes]: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") @@ -76,51 +85,56 @@ def _byte_unpadding_update(buffer_, data, block_size): return buffer_, result -def _byte_unpadding_check(buffer_, block_size, checkfn): +def _byte_unpadding_check( + buffer_: typing.Optional[bytes], + block_size: int, + checkfn: typing.Callable[[bytes], int], +) -> bytes: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") if len(buffer_) != block_size // 8: raise ValueError("Invalid padding bytes.") - valid = checkfn(buffer_, block_size // 8) + valid = checkfn(buffer_) if not valid: raise ValueError("Invalid padding bytes.") - pad_size = six.indexbytes(buffer_, -1) + pad_size = buffer_[-1] return buffer_[:-pad_size] -class PKCS7(object): - def __init__(self, block_size): +class PKCS7: + def __init__(self, block_size: int): _byte_padding_check(block_size) self.block_size = block_size - def padder(self): + def padder(self) -> PaddingContext: return _PKCS7PaddingContext(self.block_size) - def unpadder(self): + def unpadder(self) -> PaddingContext: return _PKCS7UnpaddingContext(self.block_size) -@utils.register_interface(PaddingContext) -class _PKCS7PaddingContext(object): - def __init__(self, block_size): +class _PKCS7PaddingContext(PaddingContext): + _buffer: typing.Optional[bytes] + + def __init__(self, block_size: int): self.block_size = block_size # TODO: more copies than necessary, we should use zero-buffer (#193) self._buffer = b"" - def update(self, data): + def update(self, data: bytes) -> bytes: self._buffer, result = _byte_padding_update( self._buffer, data, self.block_size ) return result - def _padding(self, size): - return six.int2byte(size) * size + def _padding(self, size: int) -> bytes: + return bytes([size]) * size - def finalize(self): + def finalize(self) -> bytes: result = _byte_padding_pad( self._buffer, self.block_size, self._padding ) @@ -128,56 +142,58 @@ def finalize(self): return result -@utils.register_interface(PaddingContext) -class _PKCS7UnpaddingContext(object): - def __init__(self, block_size): +class _PKCS7UnpaddingContext(PaddingContext): + _buffer: typing.Optional[bytes] + + def __init__(self, block_size: int): self.block_size = block_size # TODO: more copies than necessary, we should use zero-buffer (#193) self._buffer = b"" - def update(self, data): + def update(self, data: bytes) -> bytes: self._buffer, result = _byte_unpadding_update( self._buffer, data, self.block_size ) return result - def finalize(self): + def finalize(self) -> bytes: result = _byte_unpadding_check( - self._buffer, self.block_size, lib.Cryptography_check_pkcs7_padding + self._buffer, self.block_size, check_pkcs7_padding ) self._buffer = None return result -class ANSIX923(object): - def __init__(self, block_size): +class ANSIX923: + def __init__(self, block_size: int): _byte_padding_check(block_size) self.block_size = block_size - def padder(self): + def padder(self) -> PaddingContext: return _ANSIX923PaddingContext(self.block_size) - def unpadder(self): + def unpadder(self) -> PaddingContext: return _ANSIX923UnpaddingContext(self.block_size) -@utils.register_interface(PaddingContext) -class _ANSIX923PaddingContext(object): - def __init__(self, block_size): +class _ANSIX923PaddingContext(PaddingContext): + _buffer: typing.Optional[bytes] + + def __init__(self, block_size: int): self.block_size = block_size # TODO: more copies than necessary, we should use zero-buffer (#193) self._buffer = b"" - def update(self, data): + def update(self, data: bytes) -> bytes: self._buffer, result = _byte_padding_update( self._buffer, data, self.block_size ) return result - def _padding(self, size): - return six.int2byte(0) * (size - 1) + six.int2byte(size) + def _padding(self, size: int) -> bytes: + return bytes([0]) * (size - 1) + bytes([size]) - def finalize(self): + def finalize(self) -> bytes: result = _byte_padding_pad( self._buffer, self.block_size, self._padding ) @@ -185,24 +201,25 @@ def finalize(self): return result -@utils.register_interface(PaddingContext) -class _ANSIX923UnpaddingContext(object): - def __init__(self, block_size): +class _ANSIX923UnpaddingContext(PaddingContext): + _buffer: typing.Optional[bytes] + + def __init__(self, block_size: int): self.block_size = block_size # TODO: more copies than necessary, we should use zero-buffer (#193) self._buffer = b"" - def update(self, data): + def update(self, data: bytes) -> bytes: self._buffer, result = _byte_unpadding_update( self._buffer, data, self.block_size ) return result - def finalize(self): + def finalize(self) -> bytes: result = _byte_unpadding_check( self._buffer, self.block_size, - lib.Cryptography_check_ansix923_padding, + check_ansix923_padding, ) self._buffer = None return result diff --git a/src/cryptography/hazmat/primitives/poly1305.py b/src/cryptography/hazmat/primitives/poly1305.py index 6439686202de..7f5a77a576fd 100644 --- a/src/cryptography/hazmat/primitives/poly1305.py +++ b/src/cryptography/hazmat/primitives/poly1305.py @@ -2,57 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations +from cryptography.hazmat.bindings._rust import openssl as rust_openssl -from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - UnsupportedAlgorithm, - _Reasons, -) +__all__ = ["Poly1305"] - -class Poly1305(object): - def __init__(self, key): - from cryptography.hazmat.backends.openssl.backend import backend - - if not backend.poly1305_supported(): - raise UnsupportedAlgorithm( - "poly1305 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_MAC, - ) - self._ctx = backend.create_poly1305_ctx(key) - - def update(self, data): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - utils._check_byteslike("data", data) - self._ctx.update(data) - - def finalize(self): - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - mac = self._ctx.finalize() - self._ctx = None - return mac - - def verify(self, tag): - utils._check_bytes("tag", tag) - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - ctx, self._ctx = self._ctx, None - ctx.verify(tag) - - @classmethod - def generate_tag(cls, key, data): - p = Poly1305(key) - p.update(data) - return p.finalize() - - @classmethod - def verify_tag(cls, key, data, tag): - p = Poly1305(key) - p.update(data) - p.verify(tag) +Poly1305 = rust_openssl.poly1305.Poly1305 diff --git a/src/cryptography/hazmat/primitives/serialization/__init__.py b/src/cryptography/hazmat/primitives/serialization/__init__.py index c2f9b014a62d..b6c9a5cdc520 100644 --- a/src/cryptography/hazmat/primitives/serialization/__init__.py +++ b/src/cryptography/hazmat/primitives/serialization/__init__.py @@ -2,9 +2,9 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -from cryptography.hazmat.primitives.serialization.base import ( +from cryptography.hazmat.primitives._serialization import ( BestAvailableEncryption, Encoding, KeySerializationEncryption, @@ -12,6 +12,9 @@ ParameterFormat, PrivateFormat, PublicFormat, + _KeySerializationEncryption, +) +from cryptography.hazmat.primitives.serialization.base import ( load_der_parameters, load_der_private_key, load_der_public_key, @@ -20,11 +23,18 @@ load_pem_public_key, ) from cryptography.hazmat.primitives.serialization.ssh import ( + SSHCertificate, + SSHCertificateBuilder, + SSHCertificateType, + SSHCertPrivateKeyTypes, + SSHCertPublicKeyTypes, + SSHPrivateKeyTypes, + SSHPublicKeyTypes, load_ssh_private_key, + load_ssh_public_identity, load_ssh_public_key, ) - __all__ = [ "load_der_parameters", "load_der_private_key", @@ -33,6 +43,7 @@ "load_pem_private_key", "load_pem_public_key", "load_ssh_private_key", + "load_ssh_public_identity", "load_ssh_public_key", "Encoding", "PrivateFormat", @@ -41,4 +52,12 @@ "KeySerializationEncryption", "BestAvailableEncryption", "NoEncryption", + "_KeySerializationEncryption", + "SSHCertificateBuilder", + "SSHCertificate", + "SSHCertificateType", + "SSHCertPublicKeyTypes", + "SSHCertPrivateKeyTypes", + "SSHPrivateKeyTypes", + "SSHPublicKeyTypes", ] diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py index fc27235c5cf2..18a96ccfd5cd 100644 --- a/src/cryptography/hazmat/primitives/serialization/base.py +++ b/src/cryptography/hazmat/primitives/serialization/base.py @@ -2,90 +2,72 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import abc -from enum import Enum +import typing -import six +from cryptography.hazmat.primitives.asymmetric import dh +from cryptography.hazmat.primitives.asymmetric.types import ( + PrivateKeyTypes, + PublicKeyTypes, +) -from cryptography import utils -from cryptography.hazmat.backends import _get_backend +def load_pem_private_key( + data: bytes, + password: typing.Optional[bytes], + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: + from cryptography.hazmat.backends.openssl.backend import backend as ossl -def load_pem_private_key(data, password, backend=None): - backend = _get_backend(backend) - return backend.load_pem_private_key(data, password) + return ossl.load_pem_private_key( + data, password, unsafe_skip_rsa_key_validation + ) -def load_pem_public_key(data, backend=None): - backend = _get_backend(backend) - return backend.load_pem_public_key(data) +def load_pem_public_key( + data: bytes, backend: typing.Any = None +) -> PublicKeyTypes: + from cryptography.hazmat.backends.openssl.backend import backend as ossl + return ossl.load_pem_public_key(data) -def load_pem_parameters(data, backend=None): - backend = _get_backend(backend) - return backend.load_pem_parameters(data) +def load_pem_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: + from cryptography.hazmat.backends.openssl.backend import backend as ossl -def load_der_private_key(data, password, backend=None): - backend = _get_backend(backend) - return backend.load_der_private_key(data, password) + return ossl.load_pem_parameters(data) -def load_der_public_key(data, backend=None): - backend = _get_backend(backend) - return backend.load_der_public_key(data) +def load_der_private_key( + data: bytes, + password: typing.Optional[bytes], + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: + from cryptography.hazmat.backends.openssl.backend import backend as ossl + return ossl.load_der_private_key( + data, password, unsafe_skip_rsa_key_validation + ) -def load_der_parameters(data, backend=None): - backend = _get_backend(backend) - return backend.load_der_parameters(data) +def load_der_public_key( + data: bytes, backend: typing.Any = None +) -> PublicKeyTypes: + from cryptography.hazmat.backends.openssl.backend import backend as ossl -class Encoding(Enum): - PEM = "PEM" - DER = "DER" - OpenSSH = "OpenSSH" - Raw = "Raw" - X962 = "ANSI X9.62" - SMIME = "S/MIME" + return ossl.load_der_public_key(data) -class PrivateFormat(Enum): - PKCS8 = "PKCS8" - TraditionalOpenSSL = "TraditionalOpenSSL" - Raw = "Raw" - OpenSSH = "OpenSSH" +def load_der_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: + from cryptography.hazmat.backends.openssl.backend import backend as ossl - -class PublicFormat(Enum): - SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1" - PKCS1 = "Raw PKCS#1" - OpenSSH = "OpenSSH" - Raw = "Raw" - CompressedPoint = "X9.62 Compressed Point" - UncompressedPoint = "X9.62 Uncompressed Point" - - -class ParameterFormat(Enum): - PKCS3 = "PKCS3" - - -@six.add_metaclass(abc.ABCMeta) -class KeySerializationEncryption(object): - pass - - -@utils.register_interface(KeySerializationEncryption) -class BestAvailableEncryption(object): - def __init__(self, password): - if not isinstance(password, bytes) or len(password) == 0: - raise ValueError("Password must be 1 or more bytes.") - - self.password = password - - -@utils.register_interface(KeySerializationEncryption) -class NoEncryption(object): - pass + return ossl.load_der_parameters(data) diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py index 201f32941c1f..27133a3fa851 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -2,35 +2,213 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import typing from cryptography import x509 -from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +from cryptography.hazmat.primitives._serialization import PBES as PBES +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed448, + ed25519, + rsa, +) +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes + +__all__ = [ + "PBES", + "PKCS12PrivateKeyTypes", + "PKCS12Certificate", + "PKCS12KeyAndCertificates", + "load_key_and_certificates", + "load_pkcs12", + "serialize_key_and_certificates", +] + +PKCS12PrivateKeyTypes = typing.Union[ + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, +] + + +class PKCS12Certificate: + def __init__( + self, + cert: x509.Certificate, + friendly_name: typing.Optional[bytes], + ): + if not isinstance(cert, x509.Certificate): + raise TypeError("Expecting x509.Certificate object") + if friendly_name is not None and not isinstance(friendly_name, bytes): + raise TypeError("friendly_name must be bytes or None") + self._cert = cert + self._friendly_name = friendly_name + + @property + def friendly_name(self) -> typing.Optional[bytes]: + return self._friendly_name + + @property + def certificate(self) -> x509.Certificate: + return self._cert + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PKCS12Certificate): + return NotImplemented + + return ( + self.certificate == other.certificate + and self.friendly_name == other.friendly_name + ) + + def __hash__(self) -> int: + return hash((self.certificate, self.friendly_name)) + + def __repr__(self) -> str: + return "".format( + self.certificate, self.friendly_name + ) + + +class PKCS12KeyAndCertificates: + def __init__( + self, + key: typing.Optional[PrivateKeyTypes], + cert: typing.Optional[PKCS12Certificate], + additional_certs: typing.List[PKCS12Certificate], + ): + if key is not None and not isinstance( + key, + ( + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + ), + ): + raise TypeError( + "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" + " private key, or None." + ) + if cert is not None and not isinstance(cert, PKCS12Certificate): + raise TypeError("cert must be a PKCS12Certificate object or None") + if not all( + isinstance(add_cert, PKCS12Certificate) + for add_cert in additional_certs + ): + raise TypeError( + "all values in additional_certs must be PKCS12Certificate" + " objects" + ) + self._key = key + self._cert = cert + self._additional_certs = additional_certs + + @property + def key(self) -> typing.Optional[PrivateKeyTypes]: + return self._key + @property + def cert(self) -> typing.Optional[PKCS12Certificate]: + return self._cert -def load_key_and_certificates(data, password, backend=None): - backend = _get_backend(backend) - return backend.load_key_and_certificates_from_pkcs12(data, password) + @property + def additional_certs(self) -> typing.List[PKCS12Certificate]: + return self._additional_certs + def __eq__(self, other: object) -> bool: + if not isinstance(other, PKCS12KeyAndCertificates): + return NotImplemented -def serialize_key_and_certificates(name, key, cert, cas, encryption_algorithm): + return ( + self.key == other.key + and self.cert == other.cert + and self.additional_certs == other.additional_certs + ) + + def __hash__(self) -> int: + return hash((self.key, self.cert, tuple(self.additional_certs))) + + def __repr__(self) -> str: + fmt = ( + "" + ) + return fmt.format(self.key, self.cert, self.additional_certs) + + +def load_key_and_certificates( + data: bytes, + password: typing.Optional[bytes], + backend: typing.Any = None, +) -> typing.Tuple[ + typing.Optional[PrivateKeyTypes], + typing.Optional[x509.Certificate], + typing.List[x509.Certificate], +]: + from cryptography.hazmat.backends.openssl.backend import backend as ossl + + return ossl.load_key_and_certificates_from_pkcs12(data, password) + + +def load_pkcs12( + data: bytes, + password: typing.Optional[bytes], + backend: typing.Any = None, +) -> PKCS12KeyAndCertificates: + from cryptography.hazmat.backends.openssl.backend import backend as ossl + + return ossl.load_pkcs12(data, password) + + +_PKCS12CATypes = typing.Union[ + x509.Certificate, + PKCS12Certificate, +] + + +def serialize_key_and_certificates( + name: typing.Optional[bytes], + key: typing.Optional[PKCS12PrivateKeyTypes], + cert: typing.Optional[x509.Certificate], + cas: typing.Optional[typing.Iterable[_PKCS12CATypes]], + encryption_algorithm: serialization.KeySerializationEncryption, +) -> bytes: if key is not None and not isinstance( key, ( - rsa.RSAPrivateKeyWithSerialization, - dsa.DSAPrivateKeyWithSerialization, - ec.EllipticCurvePrivateKeyWithSerialization, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, ), ): - raise TypeError("Key must be RSA, DSA, or EllipticCurve private key.") + raise TypeError( + "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" + " private key, or None." + ) if cert is not None and not isinstance(cert, x509.Certificate): - raise TypeError("cert must be a certificate") + raise TypeError("cert must be a certificate or None") if cas is not None: cas = list(cas) - if not all(isinstance(val, x509.Certificate) for val in cas): + if not all( + isinstance( + val, + ( + x509.Certificate, + PKCS12Certificate, + ), + ) + for val in cas + ): raise TypeError("all values in cas must be certificates") if not isinstance( @@ -44,7 +222,8 @@ def serialize_key_and_certificates(name, key, cert, cas, encryption_algorithm): if key is None and cert is None and not cas: raise ValueError("You must supply at least one of key, cert, or cas") - backend = _get_backend(None) + from cryptography.hazmat.backends.openssl.backend import backend + return backend.serialize_key_and_certificates_to_pkcs12( name, key, cert, cas, encryption_algorithm ) diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py index 1e11e28ef5b3..9998bcaa1131 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -2,45 +2,95 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -from enum import Enum +import email.base64mime +import email.generator +import email.message +import email.policy +import io +import typing -from cryptography import x509 -from cryptography.hazmat.backends import _get_backend +from cryptography import utils, x509 +from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.utils import _check_byteslike -def load_pem_pkcs7_certificates(data): - backend = _get_backend(None) +def load_pem_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]: + from cryptography.hazmat.backends.openssl.backend import backend + return backend.load_pem_pkcs7_certificates(data) -def load_der_pkcs7_certificates(data): - backend = _get_backend(None) +def load_der_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]: + from cryptography.hazmat.backends.openssl.backend import backend + return backend.load_der_pkcs7_certificates(data) -class PKCS7SignatureBuilder(object): - def __init__(self, data=None, signers=[], additional_certs=[]): +def serialize_certificates( + certs: typing.List[x509.Certificate], + encoding: serialization.Encoding, +) -> bytes: + return rust_pkcs7.serialize_certificates(certs, encoding) + + +PKCS7HashTypes = typing.Union[ + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, +] + +PKCS7PrivateKeyTypes = typing.Union[ + rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey +] + + +class PKCS7Options(utils.Enum): + Text = "Add text/plain MIME type" + Binary = "Don't translate input data into canonical MIME format" + DetachedSignature = "Don't embed data in the PKCS7 structure" + NoCapabilities = "Don't embed SMIME capabilities" + NoAttributes = "Don't embed authenticatedAttributes" + NoCerts = "Don't embed signer certificate" + + +class PKCS7SignatureBuilder: + def __init__( + self, + data: typing.Optional[bytes] = None, + signers: typing.List[ + typing.Tuple[ + x509.Certificate, + PKCS7PrivateKeyTypes, + PKCS7HashTypes, + ] + ] = [], + additional_certs: typing.List[x509.Certificate] = [], + ): self._data = data self._signers = signers self._additional_certs = additional_certs - def set_data(self, data): + def set_data(self, data: bytes) -> PKCS7SignatureBuilder: _check_byteslike("data", data) if self._data is not None: raise ValueError("data may only be set once") return PKCS7SignatureBuilder(data, self._signers) - def add_signer(self, certificate, private_key, hash_algorithm): + def add_signer( + self, + certificate: x509.Certificate, + private_key: PKCS7PrivateKeyTypes, + hash_algorithm: PKCS7HashTypes, + ) -> PKCS7SignatureBuilder: if not isinstance( hash_algorithm, ( - hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, @@ -48,7 +98,7 @@ def add_signer(self, certificate, private_key, hash_algorithm): ), ): raise TypeError( - "hash_algorithm must be one of hashes.SHA1, SHA224, " + "hash_algorithm must be one of hashes.SHA224, " "SHA256, SHA384, or SHA512" ) if not isinstance(certificate, x509.Certificate): @@ -64,7 +114,9 @@ def add_signer(self, certificate, private_key, hash_algorithm): self._signers + [(certificate, private_key, hash_algorithm)], ) - def add_certificate(self, certificate): + def add_certificate( + self, certificate: x509.Certificate + ) -> PKCS7SignatureBuilder: if not isinstance(certificate, x509.Certificate): raise TypeError("certificate must be a x509.Certificate") @@ -72,7 +124,12 @@ def add_certificate(self, certificate): self._data, self._signers, self._additional_certs + [certificate] ) - def sign(self, encoding, options, backend=None): + def sign( + self, + encoding: serialization.Encoding, + options: typing.Iterable[PKCS7Options], + backend: typing.Any = None, + ) -> bytes: if len(self._signers) == 0: raise ValueError("Must have at least one signer") if self._data is None: @@ -119,14 +176,60 @@ def sign(self, encoding, options, backend=None): "both values." ) - backend = _get_backend(backend) - return backend.pkcs7_sign(self, encoding, options) - - -class PKCS7Options(Enum): - Text = "Add text/plain MIME type" - Binary = "Don't translate input data into canonical MIME format" - DetachedSignature = "Don't embed data in the PKCS7 structure" - NoCapabilities = "Don't embed SMIME capabilities" - NoAttributes = "Don't embed authenticatedAttributes" - NoCerts = "Don't embed signer certificate" + return rust_pkcs7.sign_and_serialize(self, encoding, options) + + +def _smime_encode( + data: bytes, signature: bytes, micalg: str, text_mode: bool +) -> bytes: + # This function works pretty hard to replicate what OpenSSL does + # precisely. For good and for ill. + + m = email.message.Message() + m.add_header("MIME-Version", "1.0") + m.add_header( + "Content-Type", + "multipart/signed", + protocol="application/x-pkcs7-signature", + micalg=micalg, + ) + + m.preamble = "This is an S/MIME signed message\n" + + msg_part = OpenSSLMimePart() + msg_part.set_payload(data) + if text_mode: + msg_part.add_header("Content-Type", "text/plain") + m.attach(msg_part) + + sig_part = email.message.MIMEPart() + sig_part.add_header( + "Content-Type", "application/x-pkcs7-signature", name="smime.p7s" + ) + sig_part.add_header("Content-Transfer-Encoding", "base64") + sig_part.add_header( + "Content-Disposition", "attachment", filename="smime.p7s" + ) + sig_part.set_payload( + email.base64mime.body_encode(signature, maxlinelen=65) + ) + del sig_part["MIME-Version"] + m.attach(sig_part) + + fp = io.BytesIO() + g = email.generator.BytesGenerator( + fp, + maxheaderlen=0, + mangle_from_=False, + policy=m.policy.clone(linesep="\r\n"), + ) + g.flatten(m) + return fp.getvalue() + + +class OpenSSLMimePart(email.message.MIMEPart): + # A MIMEPart subclass that replicates OpenSSL's behavior of not including + # a newline if there are no headers. + def _write_headers(self, generator) -> None: + if list(self.raw_items()): + generator._write_headers(self) diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index 5ecae59f8aa6..35e53c102875 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -2,25 +2,41 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import binascii +import enum import os import re -import struct - -import six +import typing +import warnings +from base64 import encodebytes as _base64_encode +from dataclasses import dataclass from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed25519, rsa -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed25519, + padding, + rsa, +) +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.hazmat.primitives.ciphers import ( + AEADDecryptionContext, + Cipher, + algorithms, + modes, +) from cryptography.hazmat.primitives.serialization import ( Encoding, + KeySerializationEncryption, NoEncryption, PrivateFormat, PublicFormat, + _KeySerializationEncryption, ) try: @@ -30,15 +46,16 @@ except ImportError: _bcrypt_supported = False - def _bcrypt_kdf(*args, **kwargs): + def _bcrypt_kdf( + password: bytes, + salt: bytes, + desired_key_bytes: int, + rounds: int, + ignore_few_rounds: bool = False, + ) -> bytes: raise UnsupportedAlgorithm("Need bcrypt module") -try: - from base64 import encodebytes as _base64_encode -except ImportError: - from base64 import encodestring as _base64_encode - _SSH_ED25519 = b"ssh-ed25519" _SSH_RSA = b"ssh-rsa" _SSH_DSA = b"ssh-dss" @@ -47,7 +64,12 @@ def _bcrypt_kdf(*args, **kwargs): _ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" _CERT_SUFFIX = b"-cert-v01@openssh.com" -_SSH_PUBKEY_RC = re.compile(br"\A(\S+)[ \t]+(\S+)") +# These are not key types, only algorithms, so they cannot appear +# as a public key type +_SSH_RSA_SHA256 = b"rsa-sha2-256" +_SSH_RSA_SHA512 = b"rsa-sha2-512" + +_SSH_PUBKEY_RC = re.compile(rb"\A(\S+)[ \t]+(\S+)") _SK_MAGIC = b"openssh-key-v1\0" _SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----" _SK_END = b"-----END OPENSSH PRIVATE KEY-----" @@ -55,7 +77,6 @@ def _bcrypt_kdf(*args, **kwargs): _NONE = b"none" _DEFAULT_CIPHER = b"aes256-ctr" _DEFAULT_ROUNDS = 16 -_MAX_PASSWORD = 72 # re is only way to work on bytes-like data _PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL) @@ -63,10 +84,51 @@ def _bcrypt_kdf(*args, **kwargs): # padding for max blocksize _PADDING = memoryview(bytearray(range(1, 1 + 16))) + +@dataclass +class _SSHCipher: + alg: typing.Type[algorithms.AES] + key_len: int + mode: typing.Union[ + typing.Type[modes.CTR], + typing.Type[modes.CBC], + typing.Type[modes.GCM], + ] + block_len: int + iv_len: int + tag_len: typing.Optional[int] + is_aead: bool + + # ciphers that are actually used in key wrapping -_SSH_CIPHERS = { - b"aes256-ctr": (algorithms.AES, 32, modes.CTR, 16), - b"aes256-cbc": (algorithms.AES, 32, modes.CBC, 16), +_SSH_CIPHERS: typing.Dict[bytes, _SSHCipher] = { + b"aes256-ctr": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.CTR, + block_len=16, + iv_len=16, + tag_len=None, + is_aead=False, + ), + b"aes256-cbc": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.CBC, + block_len=16, + iv_len=16, + tag_len=None, + is_aead=False, + ), + b"aes256-gcm@openssh.com": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.GCM, + block_len=16, + iv_len=12, + tag_len=16, + is_aead=True, + ), } # map local curve name to key type @@ -76,61 +138,93 @@ def _bcrypt_kdf(*args, **kwargs): "secp521r1": _ECDSA_NISTP521, } -_U32 = struct.Struct(b">I") -_U64 = struct.Struct(b">Q") +def _get_ssh_key_type( + key: typing.Union[SSHPrivateKeyTypes, SSHPublicKeyTypes] +) -> bytes: + if isinstance(key, ec.EllipticCurvePrivateKey): + key_type = _ecdsa_key_type(key.public_key()) + elif isinstance(key, ec.EllipticCurvePublicKey): + key_type = _ecdsa_key_type(key) + elif isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): + key_type = _SSH_RSA + elif isinstance(key, (dsa.DSAPrivateKey, dsa.DSAPublicKey)): + key_type = _SSH_DSA + elif isinstance( + key, (ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey) + ): + key_type = _SSH_ED25519 + else: + raise ValueError("Unsupported key type") -def _ecdsa_key_type(public_key): + return key_type + + +def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes: """Return SSH key_type and curve_name for private key.""" curve = public_key.curve if curve.name not in _ECDSA_KEY_TYPE: raise ValueError( - "Unsupported curve for ssh private key: %r" % curve.name + f"Unsupported curve for ssh private key: {curve.name!r}" ) return _ECDSA_KEY_TYPE[curve.name] -def _ssh_pem_encode(data, prefix=_SK_START + b"\n", suffix=_SK_END + b"\n"): +def _ssh_pem_encode( + data: bytes, + prefix: bytes = _SK_START + b"\n", + suffix: bytes = _SK_END + b"\n", +) -> bytes: return b"".join([prefix, _base64_encode(data), suffix]) -def _check_block_size(data, block_len): +def _check_block_size(data: bytes, block_len: int) -> None: """Require data to be full blocks""" if not data or len(data) % block_len != 0: raise ValueError("Corrupt data: missing padding") -def _check_empty(data): +def _check_empty(data: bytes) -> None: """All data should have been parsed.""" if data: raise ValueError("Corrupt data: unparsed data") -def _init_cipher(ciphername, password, salt, rounds, backend): +def _init_cipher( + ciphername: bytes, + password: typing.Optional[bytes], + salt: bytes, + rounds: int, +) -> Cipher[typing.Union[modes.CBC, modes.CTR, modes.GCM]]: """Generate key + iv and return cipher.""" if not password: raise ValueError("Key is password-protected.") - algo, key_len, mode, iv_len = _SSH_CIPHERS[ciphername] - seed = _bcrypt_kdf(password, salt, key_len + iv_len, rounds, True) - return Cipher(algo(seed[:key_len]), mode(seed[key_len:]), backend) + ciph = _SSH_CIPHERS[ciphername] + seed = _bcrypt_kdf( + password, salt, ciph.key_len + ciph.iv_len, rounds, True + ) + return Cipher( + ciph.alg(seed[: ciph.key_len]), + ciph.mode(seed[ciph.key_len :]), + ) -def _get_u32(data): +def _get_u32(data: memoryview) -> typing.Tuple[int, memoryview]: """Uint32""" if len(data) < 4: raise ValueError("Invalid data") - return _U32.unpack(data[:4])[0], data[4:] + return int.from_bytes(data[:4], byteorder="big"), data[4:] -def _get_u64(data): +def _get_u64(data: memoryview) -> typing.Tuple[int, memoryview]: """Uint64""" if len(data) < 8: raise ValueError("Invalid data") - return _U64.unpack(data[:8])[0], data[8:] + return int.from_bytes(data[:8], byteorder="big"), data[8:] -def _get_sshstr(data): +def _get_sshstr(data: memoryview) -> typing.Tuple[memoryview, memoryview]: """Bytes with u32 length prefix""" n, data = _get_u32(data) if n > len(data): @@ -138,15 +232,15 @@ def _get_sshstr(data): return data[:n], data[n:] -def _get_mpint(data): +def _get_mpint(data: memoryview) -> typing.Tuple[int, memoryview]: """Big integer.""" val, data = _get_sshstr(data) - if val and six.indexbytes(val, 0) > 0x7F: + if val and val[0] > 0x7F: raise ValueError("Invalid data") - return utils.int_from_bytes(val, "big"), data + return int.from_bytes(val, "big"), data -def _to_mpint(val): +def _to_mpint(val: int) -> bytes: """Storage format for signed bigint.""" if val < 0: raise ValueError("negative mpint not allowed") @@ -156,23 +250,31 @@ def _to_mpint(val): return utils.int_to_bytes(val, nbytes) -class _FragList(object): +class _FragList: """Build recursive structure without data copy.""" - def __init__(self, init=None): + flist: typing.List[bytes] + + def __init__( + self, init: typing.Optional[typing.List[bytes]] = None + ) -> None: self.flist = [] if init: self.flist.extend(init) - def put_raw(self, val): + def put_raw(self, val: bytes) -> None: """Add plain bytes""" self.flist.append(val) - def put_u32(self, val): + def put_u32(self, val: int) -> None: """Big-endian uint32""" - self.flist.append(_U32.pack(val)) + self.flist.append(val.to_bytes(length=4, byteorder="big")) - def put_sshstr(self, val): + def put_u64(self, val: int) -> None: + """Big-endian uint64""" + self.flist.append(val.to_bytes(length=8, byteorder="big")) + + def put_sshstr(self, val: typing.Union[bytes, _FragList]) -> None: """Bytes prefixed with u32 length""" if isinstance(val, (bytes, memoryview, bytearray)): self.put_u32(len(val)) @@ -181,15 +283,15 @@ def put_sshstr(self, val): self.put_u32(val.size()) self.flist.extend(val.flist) - def put_mpint(self, val): + def put_mpint(self, val: int) -> None: """Big-endian bigint prefixed with u32 length""" self.put_sshstr(_to_mpint(val)) - def size(self): + def size(self) -> int: """Current number of bytes""" return sum(map(len, self.flist)) - def render(self, dstbuf, pos=0): + def render(self, dstbuf: memoryview, pos: int = 0) -> int: """Write into bytearray""" for frag in self.flist: flen = len(frag) @@ -197,14 +299,14 @@ def render(self, dstbuf, pos=0): dstbuf[start:pos] = frag return pos - def tobytes(self): + def tobytes(self) -> bytes: """Return as bytes""" buf = memoryview(bytearray(self.size())) self.render(buf) return buf.tobytes() -class _SSHFormatRSA(object): +class _SSHFormatRSA: """Format for RSA keys. Public: @@ -213,20 +315,24 @@ class _SSHFormatRSA(object): mpint n, e, d, iqmp, p, q """ - def get_public(self, data): + def get_public(self, data: memoryview): """RSA public fields""" e, data = _get_mpint(data) n, data = _get_mpint(data) return (e, n), data - def load_public(self, key_type, data, backend): + def load_public( + self, data: memoryview + ) -> typing.Tuple[rsa.RSAPublicKey, memoryview]: """Make RSA public key from data.""" (e, n), data = self.get_public(data) public_numbers = rsa.RSAPublicNumbers(e, n) - public_key = public_numbers.public_key(backend) + public_key = public_numbers.public_key() return public_key, data - def load_private(self, data, pubfields, backend): + def load_private( + self, data: memoryview, pubfields + ) -> typing.Tuple[rsa.RSAPrivateKey, memoryview]: """Make RSA private key from data.""" n, data = _get_mpint(data) e, data = _get_mpint(data) @@ -243,16 +349,20 @@ def load_private(self, data, pubfields, backend): private_numbers = rsa.RSAPrivateNumbers( p, q, d, dmp1, dmq1, iqmp, public_numbers ) - private_key = private_numbers.private_key(backend) + private_key = private_numbers.private_key() return private_key, data - def encode_public(self, public_key, f_pub): + def encode_public( + self, public_key: rsa.RSAPublicKey, f_pub: _FragList + ) -> None: """Write RSA public key""" pubn = public_key.public_numbers() f_pub.put_mpint(pubn.e) f_pub.put_mpint(pubn.n) - def encode_private(self, private_key, f_priv): + def encode_private( + self, private_key: rsa.RSAPrivateKey, f_priv: _FragList + ) -> None: """Write RSA private key""" private_numbers = private_key.private_numbers() public_numbers = private_numbers.public_numbers @@ -266,7 +376,7 @@ def encode_private(self, private_key, f_priv): f_priv.put_mpint(private_numbers.q) -class _SSHFormatDSA(object): +class _SSHFormatDSA: """Format for DSA keys. Public: @@ -275,7 +385,9 @@ class _SSHFormatDSA(object): mpint p, q, g, y, x """ - def get_public(self, data): + def get_public( + self, data: memoryview + ) -> typing.Tuple[typing.Tuple, memoryview]: """DSA public fields""" p, data = _get_mpint(data) q, data = _get_mpint(data) @@ -283,16 +395,20 @@ def get_public(self, data): y, data = _get_mpint(data) return (p, q, g, y), data - def load_public(self, key_type, data, backend): + def load_public( + self, data: memoryview + ) -> typing.Tuple[dsa.DSAPublicKey, memoryview]: """Make DSA public key from data.""" (p, q, g, y), data = self.get_public(data) parameter_numbers = dsa.DSAParameterNumbers(p, q, g) public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) self._validate(public_numbers) - public_key = public_numbers.public_key(backend) + public_key = public_numbers.public_key() return public_key, data - def load_private(self, data, pubfields, backend): + def load_private( + self, data: memoryview, pubfields + ) -> typing.Tuple[dsa.DSAPrivateKey, memoryview]: """Make DSA private key from data.""" (p, q, g, y), data = self.get_public(data) x, data = _get_mpint(data) @@ -303,10 +419,12 @@ def load_private(self, data, pubfields, backend): public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) self._validate(public_numbers) private_numbers = dsa.DSAPrivateNumbers(x, public_numbers) - private_key = private_numbers.private_key(backend) + private_key = private_numbers.private_key() return private_key, data - def encode_public(self, public_key, f_pub): + def encode_public( + self, public_key: dsa.DSAPublicKey, f_pub: _FragList + ) -> None: """Write DSA public key""" public_numbers = public_key.public_numbers() parameter_numbers = public_numbers.parameter_numbers @@ -317,18 +435,20 @@ def encode_public(self, public_key, f_pub): f_pub.put_mpint(parameter_numbers.g) f_pub.put_mpint(public_numbers.y) - def encode_private(self, private_key, f_priv): + def encode_private( + self, private_key: dsa.DSAPrivateKey, f_priv: _FragList + ) -> None: """Write DSA private key""" self.encode_public(private_key.public_key(), f_priv) f_priv.put_mpint(private_key.private_numbers().x) - def _validate(self, public_numbers): + def _validate(self, public_numbers: dsa.DSAPublicNumbers) -> None: parameter_numbers = public_numbers.parameter_numbers if parameter_numbers.p.bit_length() != 1024: raise ValueError("SSH supports only 1024 bit DSA keys") -class _SSHFormatECDSA(object): +class _SSHFormatECDSA: """Format for ECDSA keys. Public: @@ -340,21 +460,25 @@ class _SSHFormatECDSA(object): mpint secret """ - def __init__(self, ssh_curve_name, curve): + def __init__(self, ssh_curve_name: bytes, curve: ec.EllipticCurve): self.ssh_curve_name = ssh_curve_name self.curve = curve - def get_public(self, data): + def get_public( + self, data: memoryview + ) -> typing.Tuple[typing.Tuple, memoryview]: """ECDSA public fields""" curve, data = _get_sshstr(data) point, data = _get_sshstr(data) if curve != self.ssh_curve_name: raise ValueError("Curve name mismatch") - if six.indexbytes(point, 0) != 4: + if point[0] != 4: raise NotImplementedError("Need uncompressed point") return (curve, point), data - def load_public(self, key_type, data, backend): + def load_public( + self, data: memoryview + ) -> typing.Tuple[ec.EllipticCurvePublicKey, memoryview]: """Make ECDSA public key from data.""" (curve_name, point), data = self.get_public(data) public_key = ec.EllipticCurvePublicKey.from_encoded_point( @@ -362,17 +486,21 @@ def load_public(self, key_type, data, backend): ) return public_key, data - def load_private(self, data, pubfields, backend): + def load_private( + self, data: memoryview, pubfields + ) -> typing.Tuple[ec.EllipticCurvePrivateKey, memoryview]: """Make ECDSA private key from data.""" (curve_name, point), data = self.get_public(data) secret, data = _get_mpint(data) if (curve_name, point) != pubfields: raise ValueError("Corrupt data: ecdsa field mismatch") - private_key = ec.derive_private_key(secret, self.curve, backend) + private_key = ec.derive_private_key(secret, self.curve) return private_key, data - def encode_public(self, public_key, f_pub): + def encode_public( + self, public_key: ec.EllipticCurvePublicKey, f_pub: _FragList + ) -> None: """Write ECDSA public key""" point = public_key.public_bytes( Encoding.X962, PublicFormat.UncompressedPoint @@ -380,7 +508,9 @@ def encode_public(self, public_key, f_pub): f_pub.put_sshstr(self.ssh_curve_name) f_pub.put_sshstr(point) - def encode_private(self, private_key, f_priv): + def encode_private( + self, private_key: ec.EllipticCurvePrivateKey, f_priv: _FragList + ) -> None: """Write ECDSA private key""" public_key = private_key.public_key() private_numbers = private_key.private_numbers() @@ -389,7 +519,7 @@ def encode_private(self, private_key, f_priv): f_priv.put_mpint(private_numbers.private_value) -class _SSHFormatEd25519(object): +class _SSHFormatEd25519: """Format for Ed25519 keys. Public: @@ -399,12 +529,16 @@ class _SSHFormatEd25519(object): bytes secret_and_point """ - def get_public(self, data): + def get_public( + self, data: memoryview + ) -> typing.Tuple[typing.Tuple, memoryview]: """Ed25519 public fields""" point, data = _get_sshstr(data) return (point,), data - def load_public(self, key_type, data, backend): + def load_public( + self, data: memoryview + ) -> typing.Tuple[ed25519.Ed25519PublicKey, memoryview]: """Make Ed25519 public key from data.""" (point,), data = self.get_public(data) public_key = ed25519.Ed25519PublicKey.from_public_bytes( @@ -412,7 +546,9 @@ def load_public(self, key_type, data, backend): ) return public_key, data - def load_private(self, data, pubfields, backend): + def load_private( + self, data: memoryview, pubfields + ) -> typing.Tuple[ed25519.Ed25519PrivateKey, memoryview]: """Make Ed25519 private key from data.""" (point,), data = self.get_public(data) keypair, data = _get_sshstr(data) @@ -424,14 +560,18 @@ def load_private(self, data, pubfields, backend): private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) return private_key, data - def encode_public(self, public_key, f_pub): + def encode_public( + self, public_key: ed25519.Ed25519PublicKey, f_pub: _FragList + ) -> None: """Write Ed25519 public key""" raw_public_key = public_key.public_bytes( Encoding.Raw, PublicFormat.Raw ) f_pub.put_sshstr(raw_public_key) - def encode_private(self, private_key, f_priv): + def encode_private( + self, private_key: ed25519.Ed25519PrivateKey, f_priv: _FragList + ) -> None: """Write Ed25519 private key""" public_key = private_key.public_key() raw_private_key = private_key.private_bytes( @@ -456,19 +596,30 @@ def encode_private(self, private_key, f_priv): } -def _lookup_kformat(key_type): +def _lookup_kformat(key_type: bytes): """Return valid format or throw error""" if not isinstance(key_type, bytes): key_type = memoryview(key_type).tobytes() if key_type in _KEY_FORMATS: return _KEY_FORMATS[key_type] - raise UnsupportedAlgorithm("Unsupported key type: %r" % key_type) + raise UnsupportedAlgorithm(f"Unsupported key type: {key_type!r}") + +SSHPrivateKeyTypes = typing.Union[ + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ed25519.Ed25519PrivateKey, +] -def load_ssh_private_key(data, password, backend=None): + +def load_ssh_private_key( + data: bytes, + password: typing.Optional[bytes], + backend: typing.Any = None, +) -> SSHPrivateKeyTypes: """Load private key from OpenSSH custom encoding.""" utils._check_byteslike("data", data) - backend = _get_backend(backend) if password is not None: utils._check_bytes("password", password) @@ -497,26 +648,44 @@ def load_ssh_private_key(data, password, backend=None): pubfields, pubdata = kformat.get_public(pubdata) _check_empty(pubdata) - # load secret data - edata, data = _get_sshstr(data) - _check_empty(data) - if (ciphername, kdfname) != (_NONE, _NONE): - ciphername = ciphername.tobytes() - if ciphername not in _SSH_CIPHERS: - raise UnsupportedAlgorithm("Unsupported cipher: %r" % ciphername) + ciphername_bytes = ciphername.tobytes() + if ciphername_bytes not in _SSH_CIPHERS: + raise UnsupportedAlgorithm( + f"Unsupported cipher: {ciphername_bytes!r}" + ) if kdfname != _BCRYPT: - raise UnsupportedAlgorithm("Unsupported KDF: %r" % kdfname) - blklen = _SSH_CIPHERS[ciphername][3] + raise UnsupportedAlgorithm(f"Unsupported KDF: {kdfname!r}") + blklen = _SSH_CIPHERS[ciphername_bytes].block_len + tag_len = _SSH_CIPHERS[ciphername_bytes].tag_len + # load secret data + edata, data = _get_sshstr(data) + # see https://bugzilla.mindrot.org/show_bug.cgi?id=3553 for + # information about how OpenSSH handles AEAD tags + if _SSH_CIPHERS[ciphername_bytes].is_aead: + tag = bytes(data) + if len(tag) != tag_len: + raise ValueError("Corrupt data: invalid tag length for cipher") + else: + _check_empty(data) _check_block_size(edata, blklen) salt, kbuf = _get_sshstr(kdfoptions) rounds, kbuf = _get_u32(kbuf) _check_empty(kbuf) - ciph = _init_cipher( - ciphername, password, salt.tobytes(), rounds, backend - ) - edata = memoryview(ciph.decryptor().update(edata)) + ciph = _init_cipher(ciphername_bytes, password, salt.tobytes(), rounds) + dec = ciph.decryptor() + edata = memoryview(dec.update(edata)) + if _SSH_CIPHERS[ciphername_bytes].is_aead: + assert isinstance(dec, AEADDecryptionContext) + _check_empty(dec.finalize_with_tag(tag)) + else: + # _check_block_size requires data to be a full block so there + # should be no output from finalize + _check_empty(dec.finalize()) else: + # load secret data + edata, data = _get_sshstr(data) + _check_empty(data) blklen = 8 _check_block_size(edata, blklen) ck1, edata = _get_u32(edata) @@ -528,7 +697,7 @@ def load_ssh_private_key(data, password, backend=None): key_type, edata = _get_sshstr(edata) if key_type != pub_key_type: raise ValueError("Corrupt data: key type mismatch") - private_key, edata = kformat.load_private(edata, pubfields, backend) + private_key, edata = kformat.load_private(edata, pubfields) comment, edata = _get_sshstr(edata) # yes, SSH does padding check *after* all other parsing is done. @@ -536,43 +705,51 @@ def load_ssh_private_key(data, password, backend=None): if edata != _PADDING[: len(edata)]: raise ValueError("Corrupt data: invalid padding") + if isinstance(private_key, dsa.DSAPrivateKey): + warnings.warn( + "SSH DSA keys are deprecated and will be removed in a future " + "release.", + utils.DeprecatedIn40, + stacklevel=2, + ) + return private_key -def serialize_ssh_private_key(private_key, password=None): +def _serialize_ssh_private_key( + private_key: SSHPrivateKeyTypes, + password: bytes, + encryption_algorithm: KeySerializationEncryption, +) -> bytes: """Serialize private key with OpenSSH custom encoding.""" - if password is not None: - utils._check_bytes("password", password) - if password and len(password) > _MAX_PASSWORD: - raise ValueError( - "Passwords longer than 72 bytes are not supported by " - "OpenSSH private key format" + utils._check_bytes("password", password) + if isinstance(private_key, dsa.DSAPrivateKey): + warnings.warn( + "SSH DSA key support is deprecated and will be " + "removed in a future release", + utils.DeprecatedIn40, + stacklevel=4, ) - if isinstance(private_key, ec.EllipticCurvePrivateKey): - key_type = _ecdsa_key_type(private_key.public_key()) - elif isinstance(private_key, rsa.RSAPrivateKey): - key_type = _SSH_RSA - elif isinstance(private_key, dsa.DSAPrivateKey): - key_type = _SSH_DSA - elif isinstance(private_key, ed25519.Ed25519PrivateKey): - key_type = _SSH_ED25519 - else: - raise ValueError("Unsupported key type") + key_type = _get_ssh_key_type(private_key) kformat = _lookup_kformat(key_type) # setup parameters f_kdfoptions = _FragList() if password: ciphername = _DEFAULT_CIPHER - blklen = _SSH_CIPHERS[ciphername][3] + blklen = _SSH_CIPHERS[ciphername].block_len kdfname = _BCRYPT rounds = _DEFAULT_ROUNDS + if ( + isinstance(encryption_algorithm, _KeySerializationEncryption) + and encryption_algorithm._kdf_rounds is not None + ): + rounds = encryption_algorithm._kdf_rounds salt = os.urandom(16) f_kdfoptions.put_sshstr(salt) f_kdfoptions.put_u32(rounds) - backend = _get_backend(None) - ciph = _init_cipher(ciphername, password, salt, rounds, backend) + ciph = _init_cipher(ciphername, password, salt, rounds) else: ciphername = kdfname = _NONE blklen = 8 @@ -613,14 +790,171 @@ def serialize_ssh_private_key(private_key, password=None): if ciph is not None: ciph.encryptor().update_into(buf[ofs:mlen], buf[ofs:]) - txt = _ssh_pem_encode(buf[:mlen]) - buf[ofs:mlen] = bytearray(slen) - return txt + return _ssh_pem_encode(buf[:mlen]) + + +SSHPublicKeyTypes = typing.Union[ + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + dsa.DSAPublicKey, + ed25519.Ed25519PublicKey, +] + +SSHCertPublicKeyTypes = typing.Union[ + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + ed25519.Ed25519PublicKey, +] + + +class SSHCertificateType(enum.Enum): + USER = 1 + HOST = 2 + + +class SSHCertificate: + def __init__( + self, + _nonce: memoryview, + _public_key: SSHPublicKeyTypes, + _serial: int, + _cctype: int, + _key_id: memoryview, + _valid_principals: typing.List[bytes], + _valid_after: int, + _valid_before: int, + _critical_options: typing.Dict[bytes, bytes], + _extensions: typing.Dict[bytes, bytes], + _sig_type: memoryview, + _sig_key: memoryview, + _inner_sig_type: memoryview, + _signature: memoryview, + _tbs_cert_body: memoryview, + _cert_key_type: bytes, + _cert_body: memoryview, + ): + self._nonce = _nonce + self._public_key = _public_key + self._serial = _serial + try: + self._type = SSHCertificateType(_cctype) + except ValueError: + raise ValueError("Invalid certificate type") + self._key_id = _key_id + self._valid_principals = _valid_principals + self._valid_after = _valid_after + self._valid_before = _valid_before + self._critical_options = _critical_options + self._extensions = _extensions + self._sig_type = _sig_type + self._sig_key = _sig_key + self._inner_sig_type = _inner_sig_type + self._signature = _signature + self._cert_key_type = _cert_key_type + self._cert_body = _cert_body + self._tbs_cert_body = _tbs_cert_body + + @property + def nonce(self) -> bytes: + return bytes(self._nonce) + + def public_key(self) -> SSHCertPublicKeyTypes: + # make mypy happy until we remove DSA support entirely and + # the underlying union won't have a disallowed type + return typing.cast(SSHCertPublicKeyTypes, self._public_key) + + @property + def serial(self) -> int: + return self._serial + + @property + def type(self) -> SSHCertificateType: + return self._type + + @property + def key_id(self) -> bytes: + return bytes(self._key_id) + + @property + def valid_principals(self) -> typing.List[bytes]: + return self._valid_principals + + @property + def valid_before(self) -> int: + return self._valid_before + + @property + def valid_after(self) -> int: + return self._valid_after + + @property + def critical_options(self) -> typing.Dict[bytes, bytes]: + return self._critical_options + + @property + def extensions(self) -> typing.Dict[bytes, bytes]: + return self._extensions + + def signature_key(self) -> SSHCertPublicKeyTypes: + sigformat = _lookup_kformat(self._sig_type) + signature_key, sigkey_rest = sigformat.load_public(self._sig_key) + _check_empty(sigkey_rest) + return signature_key + + def public_bytes(self) -> bytes: + return ( + bytes(self._cert_key_type) + + b" " + + binascii.b2a_base64(bytes(self._cert_body), newline=False) + ) + + def verify_cert_signature(self) -> None: + signature_key = self.signature_key() + if isinstance(signature_key, ed25519.Ed25519PublicKey): + signature_key.verify( + bytes(self._signature), bytes(self._tbs_cert_body) + ) + elif isinstance(signature_key, ec.EllipticCurvePublicKey): + # The signature is encoded as a pair of big-endian integers + r, data = _get_mpint(self._signature) + s, data = _get_mpint(data) + _check_empty(data) + computed_sig = asym_utils.encode_dss_signature(r, s) + hash_alg = _get_ec_hash_alg(signature_key.curve) + signature_key.verify( + computed_sig, bytes(self._tbs_cert_body), ec.ECDSA(hash_alg) + ) + else: + assert isinstance(signature_key, rsa.RSAPublicKey) + if self._inner_sig_type == _SSH_RSA: + hash_alg = hashes.SHA1() + elif self._inner_sig_type == _SSH_RSA_SHA256: + hash_alg = hashes.SHA256() + else: + assert self._inner_sig_type == _SSH_RSA_SHA512 + hash_alg = hashes.SHA512() + signature_key.verify( + bytes(self._signature), + bytes(self._tbs_cert_body), + padding.PKCS1v15(), + hash_alg, + ) + + +def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm: + if isinstance(curve, ec.SECP256R1): + return hashes.SHA256() + elif isinstance(curve, ec.SECP384R1): + return hashes.SHA384() + else: + assert isinstance(curve, ec.SECP521R1) + return hashes.SHA512() -def load_ssh_public_key(data, backend=None): - """Load public key from OpenSSH one-line format.""" - backend = _get_backend(backend) +def _load_ssh_public_identity( + data: bytes, + _legacy_dsa_allowed=False, +) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: utils._check_byteslike("data", data) m = _SSH_PUBKEY_RC.match(data) @@ -629,50 +963,155 @@ def load_ssh_public_key(data, backend=None): key_type = orig_key_type = m.group(1) key_body = m.group(2) with_cert = False - if _CERT_SUFFIX == key_type[-len(_CERT_SUFFIX) :]: + if key_type.endswith(_CERT_SUFFIX): with_cert = True key_type = key_type[: -len(_CERT_SUFFIX)] + if key_type == _SSH_DSA and not _legacy_dsa_allowed: + raise UnsupportedAlgorithm( + "DSA keys aren't supported in SSH certificates" + ) kformat = _lookup_kformat(key_type) try: - data = memoryview(binascii.a2b_base64(key_body)) + rest = memoryview(binascii.a2b_base64(key_body)) except (TypeError, binascii.Error): - raise ValueError("Invalid key format") + raise ValueError("Invalid format") - inner_key_type, data = _get_sshstr(data) + if with_cert: + cert_body = rest + inner_key_type, rest = _get_sshstr(rest) if inner_key_type != orig_key_type: raise ValueError("Invalid key format") if with_cert: - nonce, data = _get_sshstr(data) - public_key, data = kformat.load_public(key_type, data, backend) + nonce, rest = _get_sshstr(rest) + public_key, rest = kformat.load_public(rest) if with_cert: - serial, data = _get_u64(data) - cctype, data = _get_u32(data) - key_id, data = _get_sshstr(data) - principals, data = _get_sshstr(data) - valid_after, data = _get_u64(data) - valid_before, data = _get_u64(data) - crit_options, data = _get_sshstr(data) - extensions, data = _get_sshstr(data) - reserved, data = _get_sshstr(data) - sig_key, data = _get_sshstr(data) - signature, data = _get_sshstr(data) - _check_empty(data) + serial, rest = _get_u64(rest) + cctype, rest = _get_u32(rest) + key_id, rest = _get_sshstr(rest) + principals, rest = _get_sshstr(rest) + valid_principals = [] + while principals: + principal, principals = _get_sshstr(principals) + valid_principals.append(bytes(principal)) + valid_after, rest = _get_u64(rest) + valid_before, rest = _get_u64(rest) + crit_options, rest = _get_sshstr(rest) + critical_options = _parse_exts_opts(crit_options) + exts, rest = _get_sshstr(rest) + extensions = _parse_exts_opts(exts) + # Get the reserved field, which is unused. + _, rest = _get_sshstr(rest) + sig_key_raw, rest = _get_sshstr(rest) + sig_type, sig_key = _get_sshstr(sig_key_raw) + if sig_type == _SSH_DSA and not _legacy_dsa_allowed: + raise UnsupportedAlgorithm( + "DSA signatures aren't supported in SSH certificates" + ) + # Get the entire cert body and subtract the signature + tbs_cert_body = cert_body[: -len(rest)] + signature_raw, rest = _get_sshstr(rest) + _check_empty(rest) + inner_sig_type, sig_rest = _get_sshstr(signature_raw) + # RSA certs can have multiple algorithm types + if ( + sig_type == _SSH_RSA + and inner_sig_type + not in [_SSH_RSA_SHA256, _SSH_RSA_SHA512, _SSH_RSA] + ) or (sig_type != _SSH_RSA and inner_sig_type != sig_type): + raise ValueError("Signature key type does not match") + signature, sig_rest = _get_sshstr(sig_rest) + _check_empty(sig_rest) + return SSHCertificate( + nonce, + public_key, + serial, + cctype, + key_id, + valid_principals, + valid_after, + valid_before, + critical_options, + extensions, + sig_type, + sig_key, + inner_sig_type, + signature, + tbs_cert_body, + orig_key_type, + cert_body, + ) + else: + _check_empty(rest) + return public_key + + +def load_ssh_public_identity( + data: bytes, +) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: + return _load_ssh_public_identity(data) + + +def _parse_exts_opts(exts_opts: memoryview) -> typing.Dict[bytes, bytes]: + result: typing.Dict[bytes, bytes] = {} + last_name = None + while exts_opts: + name, exts_opts = _get_sshstr(exts_opts) + bname: bytes = bytes(name) + if bname in result: + raise ValueError("Duplicate name") + if last_name is not None and bname < last_name: + raise ValueError("Fields not lexically sorted") + value, exts_opts = _get_sshstr(exts_opts) + if len(value) > 0: + try: + value, extra = _get_sshstr(value) + except ValueError: + warnings.warn( + "This certificate has an incorrect encoding for critical " + "options or extensions. This will be an exception in " + "cryptography 42", + utils.DeprecatedIn41, + stacklevel=4, + ) + else: + if len(extra) > 0: + raise ValueError("Unexpected extra data after value") + result[bname] = bytes(value) + last_name = bname + return result + + +def load_ssh_public_key( + data: bytes, backend: typing.Any = None +) -> SSHPublicKeyTypes: + cert_or_key = _load_ssh_public_identity(data, _legacy_dsa_allowed=True) + public_key: SSHPublicKeyTypes + if isinstance(cert_or_key, SSHCertificate): + public_key = cert_or_key.public_key() + else: + public_key = cert_or_key + + if isinstance(public_key, dsa.DSAPublicKey): + warnings.warn( + "SSH DSA keys are deprecated and will be removed in a future " + "release.", + utils.DeprecatedIn40, + stacklevel=2, + ) return public_key -def serialize_ssh_public_key(public_key): +def serialize_ssh_public_key(public_key: SSHPublicKeyTypes) -> bytes: """One-line public key format for OpenSSH""" - if isinstance(public_key, ec.EllipticCurvePublicKey): - key_type = _ecdsa_key_type(public_key) - elif isinstance(public_key, rsa.RSAPublicKey): - key_type = _SSH_RSA - elif isinstance(public_key, dsa.DSAPublicKey): - key_type = _SSH_DSA - elif isinstance(public_key, ed25519.Ed25519PublicKey): - key_type = _SSH_ED25519 - else: - raise ValueError("Unsupported key type") + if isinstance(public_key, dsa.DSAPublicKey): + warnings.warn( + "SSH DSA key support is deprecated and will be " + "removed in a future release", + utils.DeprecatedIn40, + stacklevel=4, + ) + key_type = _get_ssh_key_type(public_key) kformat = _lookup_kformat(key_type) f_pub = _FragList() @@ -681,3 +1120,415 @@ def serialize_ssh_public_key(public_key): pub = binascii.b2a_base64(f_pub.tobytes()).strip() return b"".join([key_type, b" ", pub]) + + +SSHCertPrivateKeyTypes = typing.Union[ + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + ed25519.Ed25519PrivateKey, +] + + +# This is an undocumented limit enforced in the openssh codebase for sshd and +# ssh-keygen, but it is undefined in the ssh certificates spec. +_SSHKEY_CERT_MAX_PRINCIPALS = 256 + + +class SSHCertificateBuilder: + def __init__( + self, + _public_key: typing.Optional[SSHCertPublicKeyTypes] = None, + _serial: typing.Optional[int] = None, + _type: typing.Optional[SSHCertificateType] = None, + _key_id: typing.Optional[bytes] = None, + _valid_principals: typing.List[bytes] = [], + _valid_for_all_principals: bool = False, + _valid_before: typing.Optional[int] = None, + _valid_after: typing.Optional[int] = None, + _critical_options: typing.List[typing.Tuple[bytes, bytes]] = [], + _extensions: typing.List[typing.Tuple[bytes, bytes]] = [], + ): + self._public_key = _public_key + self._serial = _serial + self._type = _type + self._key_id = _key_id + self._valid_principals = _valid_principals + self._valid_for_all_principals = _valid_for_all_principals + self._valid_before = _valid_before + self._valid_after = _valid_after + self._critical_options = _critical_options + self._extensions = _extensions + + def public_key( + self, public_key: SSHCertPublicKeyTypes + ) -> SSHCertificateBuilder: + if not isinstance( + public_key, + ( + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + ed25519.Ed25519PublicKey, + ), + ): + raise TypeError("Unsupported key type") + if self._public_key is not None: + raise ValueError("public_key already set") + + return SSHCertificateBuilder( + _public_key=public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def serial(self, serial: int) -> SSHCertificateBuilder: + if not isinstance(serial, int): + raise TypeError("serial must be an integer") + if not 0 <= serial < 2**64: + raise ValueError("serial must be between 0 and 2**64") + if self._serial is not None: + raise ValueError("serial already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def type(self, type: SSHCertificateType) -> SSHCertificateBuilder: + if not isinstance(type, SSHCertificateType): + raise TypeError("type must be an SSHCertificateType") + if self._type is not None: + raise ValueError("type already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def key_id(self, key_id: bytes) -> SSHCertificateBuilder: + if not isinstance(key_id, bytes): + raise TypeError("key_id must be bytes") + if self._key_id is not None: + raise ValueError("key_id already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_principals( + self, valid_principals: typing.List[bytes] + ) -> SSHCertificateBuilder: + if self._valid_for_all_principals: + raise ValueError( + "Principals can't be set because the cert is valid " + "for all principals" + ) + if ( + not all(isinstance(x, bytes) for x in valid_principals) + or not valid_principals + ): + raise TypeError( + "principals must be a list of bytes and can't be empty" + ) + if self._valid_principals: + raise ValueError("valid_principals already set") + + if len(valid_principals) > _SSHKEY_CERT_MAX_PRINCIPALS: + raise ValueError( + "Reached or exceeded the maximum number of valid_principals" + ) + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_for_all_principals(self): + if self._valid_principals: + raise ValueError( + "valid_principals already set, can't set " + "valid_for_all_principals" + ) + if self._valid_for_all_principals: + raise ValueError("valid_for_all_principals already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=True, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_before( + self, valid_before: typing.Union[int, float] + ) -> SSHCertificateBuilder: + if not isinstance(valid_before, (int, float)): + raise TypeError("valid_before must be an int or float") + valid_before = int(valid_before) + if valid_before < 0 or valid_before >= 2**64: + raise ValueError("valid_before must [0, 2**64)") + if self._valid_before is not None: + raise ValueError("valid_before already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_after( + self, valid_after: typing.Union[int, float] + ) -> SSHCertificateBuilder: + if not isinstance(valid_after, (int, float)): + raise TypeError("valid_after must be an int or float") + valid_after = int(valid_after) + if valid_after < 0 or valid_after >= 2**64: + raise ValueError("valid_after must [0, 2**64)") + if self._valid_after is not None: + raise ValueError("valid_after already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def add_critical_option( + self, name: bytes, value: bytes + ) -> SSHCertificateBuilder: + if not isinstance(name, bytes) or not isinstance(value, bytes): + raise TypeError("name and value must be bytes") + # This is O(n**2) + if name in [name for name, _ in self._critical_options]: + raise ValueError("Duplicate critical option name") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options + [(name, value)], + _extensions=self._extensions, + ) + + def add_extension( + self, name: bytes, value: bytes + ) -> SSHCertificateBuilder: + if not isinstance(name, bytes) or not isinstance(value, bytes): + raise TypeError("name and value must be bytes") + # This is O(n**2) + if name in [name for name, _ in self._extensions]: + raise ValueError("Duplicate extension name") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions + [(name, value)], + ) + + def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate: + if not isinstance( + private_key, + ( + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + ed25519.Ed25519PrivateKey, + ), + ): + raise TypeError("Unsupported private key type") + + if self._public_key is None: + raise ValueError("public_key must be set") + + # Not required + serial = 0 if self._serial is None else self._serial + + if self._type is None: + raise ValueError("type must be set") + + # Not required + key_id = b"" if self._key_id is None else self._key_id + + # A zero length list is valid, but means the certificate + # is valid for any principal of the specified type. We require + # the user to explicitly set valid_for_all_principals to get + # that behavior. + if not self._valid_principals and not self._valid_for_all_principals: + raise ValueError( + "valid_principals must be set if valid_for_all_principals " + "is False" + ) + + if self._valid_before is None: + raise ValueError("valid_before must be set") + + if self._valid_after is None: + raise ValueError("valid_after must be set") + + if self._valid_after > self._valid_before: + raise ValueError("valid_after must be earlier than valid_before") + + # lexically sort our byte strings + self._critical_options.sort(key=lambda x: x[0]) + self._extensions.sort(key=lambda x: x[0]) + + key_type = _get_ssh_key_type(self._public_key) + cert_prefix = key_type + _CERT_SUFFIX + + # Marshal the bytes to be signed + nonce = os.urandom(32) + kformat = _lookup_kformat(key_type) + f = _FragList() + f.put_sshstr(cert_prefix) + f.put_sshstr(nonce) + kformat.encode_public(self._public_key, f) + f.put_u64(serial) + f.put_u32(self._type.value) + f.put_sshstr(key_id) + fprincipals = _FragList() + for p in self._valid_principals: + fprincipals.put_sshstr(p) + f.put_sshstr(fprincipals.tobytes()) + f.put_u64(self._valid_after) + f.put_u64(self._valid_before) + fcrit = _FragList() + for name, value in self._critical_options: + fcrit.put_sshstr(name) + if len(value) > 0: + foptval = _FragList() + foptval.put_sshstr(value) + fcrit.put_sshstr(foptval.tobytes()) + else: + fcrit.put_sshstr(value) + f.put_sshstr(fcrit.tobytes()) + fext = _FragList() + for name, value in self._extensions: + fext.put_sshstr(name) + if len(value) > 0: + fextval = _FragList() + fextval.put_sshstr(value) + fext.put_sshstr(fextval.tobytes()) + else: + fext.put_sshstr(value) + f.put_sshstr(fext.tobytes()) + f.put_sshstr(b"") # RESERVED FIELD + # encode CA public key + ca_type = _get_ssh_key_type(private_key) + caformat = _lookup_kformat(ca_type) + caf = _FragList() + caf.put_sshstr(ca_type) + caformat.encode_public(private_key.public_key(), caf) + f.put_sshstr(caf.tobytes()) + # Sigs according to the rules defined for the CA's public key + # (RFC4253 section 6.6 for ssh-rsa, RFC5656 for ECDSA, + # and RFC8032 for Ed25519). + if isinstance(private_key, ed25519.Ed25519PrivateKey): + signature = private_key.sign(f.tobytes()) + fsig = _FragList() + fsig.put_sshstr(ca_type) + fsig.put_sshstr(signature) + f.put_sshstr(fsig.tobytes()) + elif isinstance(private_key, ec.EllipticCurvePrivateKey): + hash_alg = _get_ec_hash_alg(private_key.curve) + signature = private_key.sign(f.tobytes(), ec.ECDSA(hash_alg)) + r, s = asym_utils.decode_dss_signature(signature) + fsig = _FragList() + fsig.put_sshstr(ca_type) + fsigblob = _FragList() + fsigblob.put_mpint(r) + fsigblob.put_mpint(s) + fsig.put_sshstr(fsigblob.tobytes()) + f.put_sshstr(fsig.tobytes()) + + else: + assert isinstance(private_key, rsa.RSAPrivateKey) + # Just like Golang, we're going to use SHA512 for RSA + # https://cs.opensource.google/go/x/crypto/+/refs/tags/ + # v0.4.0:ssh/certs.go;l=445 + # RFC 8332 defines SHA256 and 512 as options + fsig = _FragList() + fsig.put_sshstr(_SSH_RSA_SHA512) + signature = private_key.sign( + f.tobytes(), padding.PKCS1v15(), hashes.SHA512() + ) + fsig.put_sshstr(signature) + f.put_sshstr(fsig.tobytes()) + + cert_data = binascii.b2a_base64(f.tobytes()).strip() + # load_ssh_public_identity returns a union, but this is + # guaranteed to be an SSHCertificate, so we cast to make + # mypy happy. + return typing.cast( + SSHCertificate, + load_ssh_public_identity(b"".join([cert_prefix, b" ", cert_data])), + ) diff --git a/src/cryptography/hazmat/primitives/twofactor/__init__.py b/src/cryptography/hazmat/primitives/twofactor/__init__.py index e71f9e67a334..c1af42300486 100644 --- a/src/cryptography/hazmat/primitives/twofactor/__init__.py +++ b/src/cryptography/hazmat/primitives/twofactor/__init__.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations class InvalidToken(Exception): diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py index c00eec0e548b..2067108a63d6 100644 --- a/src/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -2,40 +2,62 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import struct +import base64 +import typing +from urllib.parse import quote, urlencode -import six - -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time, hmac from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 from cryptography.hazmat.primitives.twofactor import InvalidToken -from cryptography.hazmat.primitives.twofactor.utils import _generate_uri +HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512] -class HOTP(object): - def __init__( - self, key, length, algorithm, backend=None, enforce_key_length=True - ): - backend = _get_backend(backend) - if not isinstance(backend, HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) +def _generate_uri( + hotp: HOTP, + type_name: str, + account_name: str, + issuer: typing.Optional[str], + extra_parameters: typing.List[typing.Tuple[str, int]], +) -> str: + parameters = [ + ("digits", hotp._length), + ("secret", base64.b32encode(hotp._key)), + ("algorithm", hotp._algorithm.name.upper()), + ] + + if issuer is not None: + parameters.append(("issuer", issuer)) + + parameters.extend(extra_parameters) + + label = ( + f"{quote(issuer)}:{quote(account_name)}" + if issuer + else quote(account_name) + ) + return f"otpauth://{type_name}/{label}?{urlencode(parameters)}" + + +class HOTP: + def __init__( + self, + key: bytes, + length: int, + algorithm: HOTPHashTypes, + backend: typing.Any = None, + enforce_key_length: bool = True, + ) -> None: if len(key) < 16 and enforce_key_length is True: raise ValueError("Key length has to be at least 128 bits.") - if not isinstance(length, six.integer_types): + if not isinstance(length, int): raise TypeError("Length parameter must be an integer type.") if length < 6 or length > 8: - raise ValueError("Length of HOTP has to be between 6 to 8.") + raise ValueError("Length of HOTP has to be between 6 and 8.") if not isinstance(algorithm, (SHA1, SHA256, SHA512)): raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.") @@ -43,27 +65,28 @@ def __init__( self._key = key self._length = length self._algorithm = algorithm - self._backend = backend - def generate(self, counter): + def generate(self, counter: int) -> bytes: truncated_value = self._dynamic_truncate(counter) - hotp = truncated_value % (10 ** self._length) + hotp = truncated_value % (10**self._length) return "{0:0{1}}".format(hotp, self._length).encode() - def verify(self, hotp, counter): + def verify(self, hotp: bytes, counter: int) -> None: if not constant_time.bytes_eq(self.generate(counter), hotp): raise InvalidToken("Supplied HOTP value does not match.") - def _dynamic_truncate(self, counter): - ctx = hmac.HMAC(self._key, self._algorithm, self._backend) - ctx.update(struct.pack(">Q", counter)) + def _dynamic_truncate(self, counter: int) -> int: + ctx = hmac.HMAC(self._key, self._algorithm) + ctx.update(counter.to_bytes(length=8, byteorder="big")) hmac_value = ctx.finalize() - offset = six.indexbytes(hmac_value, len(hmac_value) - 1) & 0b1111 + offset = hmac_value[len(hmac_value) - 1] & 0b1111 p = hmac_value[offset : offset + 4] - return struct.unpack(">I", p)[0] & 0x7FFFFFFF + return int.from_bytes(p, byteorder="big") & 0x7FFFFFFF - def get_provisioning_uri(self, account_name, counter, issuer): + def get_provisioning_uri( + self, account_name: str, counter: int, issuer: typing.Optional[str] + ) -> str: return _generate_uri( self, "hotp", account_name, issuer, [("counter", int(counter))] ) diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py index d59539b3f9db..daddcea2f77e 100644 --- a/src/cryptography/hazmat/primitives/twofactor/totp.py +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -2,46 +2,45 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +import typing -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.backends import _get_backend -from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.twofactor import InvalidToken -from cryptography.hazmat.primitives.twofactor.hotp import HOTP -from cryptography.hazmat.primitives.twofactor.utils import _generate_uri +from cryptography.hazmat.primitives.twofactor.hotp import ( + HOTP, + HOTPHashTypes, + _generate_uri, +) -class TOTP(object): +class TOTP: def __init__( self, - key, - length, - algorithm, - time_step, - backend=None, - enforce_key_length=True, + key: bytes, + length: int, + algorithm: HOTPHashTypes, + time_step: int, + backend: typing.Any = None, + enforce_key_length: bool = True, ): - backend = _get_backend(backend) - if not isinstance(backend, HMACBackend): - raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE, - ) - self._time_step = time_step - self._hotp = HOTP(key, length, algorithm, backend, enforce_key_length) + self._hotp = HOTP( + key, length, algorithm, enforce_key_length=enforce_key_length + ) - def generate(self, time): + def generate(self, time: typing.Union[int, float]) -> bytes: counter = int(time / self._time_step) return self._hotp.generate(counter) - def verify(self, totp, time): + def verify(self, totp: bytes, time: int) -> None: if not constant_time.bytes_eq(self.generate(time), totp): raise InvalidToken("Supplied TOTP value does not match.") - def get_provisioning_uri(self, account_name, issuer): + def get_provisioning_uri( + self, account_name: str, issuer: typing.Optional[str] + ) -> str: return _generate_uri( self._hotp, "totp", diff --git a/src/cryptography/hazmat/primitives/twofactor/utils.py b/src/cryptography/hazmat/primitives/twofactor/utils.py deleted file mode 100644 index 0afa1ccc04b0..000000000000 --- a/src/cryptography/hazmat/primitives/twofactor/utils.py +++ /dev/null @@ -1,33 +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 absolute_import, division, print_function - -import base64 - -from six.moves.urllib.parse import quote, urlencode - - -def _generate_uri(hotp, type_name, account_name, issuer, extra_parameters): - parameters = [ - ("digits", hotp._length), - ("secret", base64.b32encode(hotp._key)), - ("algorithm", hotp._algorithm.name.upper()), - ] - - if issuer is not None: - parameters.append(("issuer", issuer)) - - parameters.extend(extra_parameters) - - uriparts = { - "type": type_name, - "label": ( - "%s:%s" % (quote(issuer), quote(account_name)) - if issuer - else quote(account_name) - ), - "parameters": urlencode(parameters), - } - return "otpauth://{type}/{label}?{parameters}".format(**uriparts) diff --git a/src/cryptography/py.typed b/src/cryptography/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index bdb3dbf4776f..719168168440 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -2,12 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -import abc -import binascii -import inspect +import enum import sys +import types +import typing import warnings @@ -20,147 +20,96 @@ class CryptographyDeprecationWarning(UserWarning): # Several APIs were deprecated with no specific end-of-life date because of the # ubiquity of their use. They should not be removed until we agree on when that # cycle ends. -PersistentlyDeprecated2017 = CryptographyDeprecationWarning -PersistentlyDeprecated2019 = CryptographyDeprecationWarning +DeprecatedIn36 = CryptographyDeprecationWarning +DeprecatedIn37 = CryptographyDeprecationWarning +DeprecatedIn40 = CryptographyDeprecationWarning +DeprecatedIn41 = CryptographyDeprecationWarning -def _check_bytes(name, value): +def _check_bytes(name: str, value: bytes) -> None: if not isinstance(value, bytes): - raise TypeError("{} must be bytes".format(name)) + raise TypeError(f"{name} must be bytes") -def _check_byteslike(name, value): +def _check_byteslike(name: str, value: bytes) -> None: try: memoryview(value) except TypeError: - raise TypeError("{} must be bytes-like".format(name)) + raise TypeError(f"{name} must be bytes-like") -def read_only_property(name): - return property(lambda self: getattr(self, name)) +def int_to_bytes(integer: int, length: typing.Optional[int] = None) -> bytes: + return integer.to_bytes( + length or (integer.bit_length() + 7) // 8 or 1, "big" + ) -def register_interface(iface): - def register_decorator(klass): - verify_interface(iface, klass) - iface.register(klass) - return klass +def _extract_buffer_length(obj: typing.Any) -> typing.Tuple[typing.Any, int]: + from cryptography.hazmat.bindings._rust import _openssl - return register_decorator - - -def register_interface_if(predicate, iface): - def register_decorator(klass): - if predicate: - verify_interface(iface, klass) - iface.register(klass) - return klass - - return register_decorator - - -if hasattr(int, "from_bytes"): - int_from_bytes = int.from_bytes -else: - - def int_from_bytes(data, byteorder, signed=False): - assert byteorder == "big" - assert not signed - - return int(binascii.hexlify(data), 16) - - -if hasattr(int, "to_bytes"): - - def int_to_bytes(integer, length=None): - return integer.to_bytes( - length or (integer.bit_length() + 7) // 8 or 1, "big" - ) - - -else: - - def int_to_bytes(integer, length=None): - hex_string = "%x" % integer - if length is None: - n = len(hex_string) - else: - n = length * 2 - return binascii.unhexlify(hex_string.zfill(n + (n & 1))) + buf = _openssl.ffi.from_buffer(obj) + return buf, int(_openssl.ffi.cast("uintptr_t", buf)) class InterfaceNotImplemented(Exception): pass -if hasattr(inspect, "signature"): - signature = inspect.signature -else: - signature = inspect.getargspec - - -def verify_interface(iface, klass): - for method in iface.__abstractmethods__: - if not hasattr(klass, method): - raise InterfaceNotImplemented( - "{} is missing a {!r} method".format(klass, method) - ) - if isinstance(getattr(iface, method), abc.abstractproperty): - # Can't properly verify these yet. - continue - sig = signature(getattr(iface, method)) - actual = signature(getattr(klass, method)) - if sig != actual: - raise InterfaceNotImplemented( - "{}.{}'s signature differs from the expected. Expected: " - "{!r}. Received: {!r}".format(klass, method, sig, actual) - ) - - -class _DeprecatedValue(object): - def __init__(self, value, message, warning_class): +class _DeprecatedValue: + def __init__(self, value: object, message: str, warning_class): self.value = value self.message = message self.warning_class = warning_class -class _ModuleWithDeprecations(object): - def __init__(self, module): +class _ModuleWithDeprecations(types.ModuleType): + def __init__(self, module: types.ModuleType): + super().__init__(module.__name__) self.__dict__["_module"] = module - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> object: obj = getattr(self._module, attr) if isinstance(obj, _DeprecatedValue): warnings.warn(obj.message, obj.warning_class, stacklevel=2) obj = obj.value return obj - def __setattr__(self, attr, value): + def __setattr__(self, attr: str, value: object) -> None: setattr(self._module, attr, value) - def __delattr__(self, attr): + def __delattr__(self, attr: str) -> None: obj = getattr(self._module, attr) if isinstance(obj, _DeprecatedValue): warnings.warn(obj.message, obj.warning_class, stacklevel=2) delattr(self._module, attr) - def __dir__(self): + def __dir__(self) -> typing.Sequence[str]: return ["_module"] + dir(self._module) -def deprecated(value, module_name, message, warning_class): +def deprecated( + value: object, + module_name: str, + message: str, + warning_class: typing.Type[Warning], + name: typing.Optional[str] = None, +) -> _DeprecatedValue: module = sys.modules[module_name] if not isinstance(module, _ModuleWithDeprecations): - sys.modules[module_name] = _ModuleWithDeprecations(module) - return _DeprecatedValue(value, message, warning_class) + sys.modules[module_name] = module = _ModuleWithDeprecations(module) + dv = _DeprecatedValue(value, message, warning_class) + # Maintain backwards compatibility with `name is None` for pyOpenSSL. + if name is not None: + setattr(module, name, dv) + return dv -def cached_property(func): - cached_name = "_cached_{}".format(func) +def cached_property(func: typing.Callable) -> property: + cached_name = f"_cached_{func}" sentinel = object() - def inner(instance): + def inner(instance: object): cache = getattr(instance, cached_name, sentinel) if cache is not sentinel: return cache @@ -169,3 +118,13 @@ def inner(instance): return result return property(inner) + + +# Python 3.10 changed representation of enums. We use well-defined object +# representation and string representation from Python 3.9. +class Enum(enum.Enum): + def __repr__(self) -> str: + return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>" + + def __str__(self) -> str: + return f"{self.__class__.__name__}.{self._name_}" diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 69630e4cba8b..d77694a29906 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -2,11 +2,13 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations from cryptography.x509 import certificate_transparency from cryptography.x509.base import ( + Attribute, AttributeNotFound, + Attributes, Certificate, CertificateBuilder, CertificateRevocationList, @@ -21,6 +23,7 @@ load_der_x509_crl, load_der_x509_csr, load_pem_x509_certificate, + load_pem_x509_certificates, load_pem_x509_crl, load_pem_x509_csr, random_serial_number, @@ -30,19 +33,19 @@ AuthorityInformationAccess, AuthorityKeyIdentifier, BasicConstraints, + CertificateIssuer, + CertificatePolicies, CRLDistributionPoints, CRLNumber, CRLReason, - CertificateIssuer, - CertificatePolicies, DeltaCRLIndicator, DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension, ExtensionNotFound, - ExtensionType, Extensions, + ExtensionType, FreshestCRL, GeneralNames, InhibitAnyPolicy, @@ -50,14 +53,16 @@ IssuerAlternativeName, IssuingDistributionPoint, KeyUsage, + MSCertificateTemplate, NameConstraints, NoticeReference, + OCSPAcceptableResponses, OCSPNoCheck, OCSPNonce, PolicyConstraints, PolicyInformation, - PrecertPoison, PrecertificateSignedCertificateTimestamps, + PrecertPoison, ReasonFlags, SignedCertificateTimestamps, SubjectAlternativeName, @@ -69,16 +74,15 @@ UserNotice, ) from cryptography.x509.general_name import ( - DNSName, DirectoryName, + DNSName, GeneralName, IPAddress, OtherName, - RFC822Name, RegisteredID, + RFC822Name, UniformResourceIdentifier, UnsupportedGeneralNameType, - _GENERAL_NAMES, ) from cryptography.x509.name import ( Name, @@ -87,17 +91,15 @@ ) from cryptography.x509.oid import ( AuthorityInformationAccessOID, - CRLEntryExtensionOID, CertificatePoliciesOID, + CRLEntryExtensionOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, SignatureAlgorithmOID, - _SIG_OIDS_TO_HASH, ) - OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS OID_AUTHORITY_KEY_IDENTIFIER = ExtensionOID.AUTHORITY_KEY_IDENTIFIER OID_BASIC_CONSTRAINTS = ExtensionOID.BASIC_CONSTRAINTS @@ -170,13 +172,16 @@ __all__ = [ "certificate_transparency", "load_pem_x509_certificate", + "load_pem_x509_certificates", "load_der_x509_certificate", "load_pem_x509_csr", "load_der_x509_csr", "load_pem_x509_crl", "load_der_x509_crl", "random_serial_number", + "Attribute", "AttributeNotFound", + "Attributes", "InvalidVersion", "DeltaCRLIndicator", "DuplicateExtension", @@ -194,6 +199,7 @@ "IssuingDistributionPoint", "TLSFeature", "TLSFeatureType", + "OCSPAcceptableResponses", "OCSPNoCheck", "BasicConstraints", "CRLNumber", @@ -232,10 +238,8 @@ "CertificateSigningRequestBuilder", "CertificateBuilder", "Version", - "_SIG_OIDS_TO_HASH", "OID_CA_ISSUERS", "OID_OCSP", - "_GENERAL_NAMES", "CertificateIssuer", "CRLReason", "InvalidityDate", @@ -245,4 +249,7 @@ "PrecertPoison", "OCSPNonce", "SignedCertificateTimestamps", + "SignatureAlgorithmOID", + "NameOID", + "MSCertificateTemplate", ] diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index f3bc872b9456..576385e088d8 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -2,53 +2,85 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc import datetime import os -from enum import Enum - -import six +import typing from cryptography import utils -from cryptography.hazmat.backends import _get_backend +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, - ed25519, ed448, + ed25519, + padding, rsa, + x448, + x25519, +) +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPrivateKeyTypes, + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, +) +from cryptography.x509.extensions import ( + Extension, + Extensions, + ExtensionType, + _make_sequence_methods, ) -from cryptography.x509.extensions import Extension, ExtensionType -from cryptography.x509.name import Name +from cryptography.x509.name import Name, _ASN1Type from cryptography.x509.oid import ObjectIdentifier - _EARLIEST_UTC_TIME = datetime.datetime(1950, 1, 1) +# This must be kept in sync with sign.rs's list of allowable types in +# identify_hash_type +_AllowedHashTypes = typing.Union[ + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA3_224, + hashes.SHA3_256, + hashes.SHA3_384, + hashes.SHA3_512, +] + class AttributeNotFound(Exception): - def __init__(self, msg, oid): - super(AttributeNotFound, self).__init__(msg) + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) self.oid = oid -def _reject_duplicate_extension(extension, extensions): +def _reject_duplicate_extension( + extension: Extension[ExtensionType], + extensions: typing.List[Extension[ExtensionType]], +) -> None: # This is quadratic in the number of extensions for e in extensions: if e.oid == extension.oid: raise ValueError("This extension has already been set.") -def _reject_duplicate_attribute(oid, attributes): +def _reject_duplicate_attribute( + oid: ObjectIdentifier, + attributes: typing.List[ + typing.Tuple[ObjectIdentifier, bytes, typing.Optional[int]] + ], +) -> None: # This is quadratic in the number of attributes - for attr_oid, _ in attributes: + for attr_oid, _, _ in attributes: if attr_oid == oid: raise ValueError("This attribute has already been set.") -def _convert_to_naive_utc_time(time): +def _convert_to_naive_utc_time(time: datetime.datetime) -> datetime.datetime: """Normalizes a datetime to a naive datetime in UTC. time -- datetime to normalize. Assumed to be in UTC if not timezone @@ -62,366 +94,543 @@ def _convert_to_naive_utc_time(time): return time -class Version(Enum): - v1 = 0 - v3 = 2 - - -def load_pem_x509_certificate(data, backend=None): - backend = _get_backend(backend) - return backend.load_pem_x509_certificate(data) - +class Attribute: + def __init__( + self, + oid: ObjectIdentifier, + value: bytes, + _type: int = _ASN1Type.UTF8String.value, + ) -> None: + self._oid = oid + self._value = value + self._type = _type + + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def value(self) -> bytes: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Attribute): + return NotImplemented + + return ( + self.oid == other.oid + and self.value == other.value + and self._type == other._type + ) -def load_der_x509_certificate(data, backend=None): - backend = _get_backend(backend) - return backend.load_der_x509_certificate(data) + def __hash__(self) -> int: + return hash((self.oid, self.value, self._type)) -def load_pem_x509_csr(data, backend=None): - backend = _get_backend(backend) - return backend.load_pem_x509_csr(data) +class Attributes: + def __init__( + self, + attributes: typing.Iterable[Attribute], + ) -> None: + self._attributes = list(attributes) + __len__, __iter__, __getitem__ = _make_sequence_methods("_attributes") -def load_der_x509_csr(data, backend=None): - backend = _get_backend(backend) - return backend.load_der_x509_csr(data) + def __repr__(self) -> str: + return f"" + def get_attribute_for_oid(self, oid: ObjectIdentifier) -> Attribute: + for attr in self: + if attr.oid == oid: + return attr -def load_pem_x509_crl(data, backend=None): - backend = _get_backend(backend) - return backend.load_pem_x509_crl(data) + raise AttributeNotFound(f"No {oid} attribute was found", oid) -def load_der_x509_crl(data, backend=None): - backend = _get_backend(backend) - return backend.load_der_x509_crl(data) +class Version(utils.Enum): + v1 = 0 + v3 = 2 class InvalidVersion(Exception): - def __init__(self, msg, parsed_version): - super(InvalidVersion, self).__init__(msg) + def __init__(self, msg: str, parsed_version: int) -> None: + super().__init__(msg) self.parsed_version = parsed_version -@six.add_metaclass(abc.ABCMeta) -class Certificate(object): +class Certificate(metaclass=abc.ABCMeta): @abc.abstractmethod - def fingerprint(self, algorithm): + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: """ Returns bytes using digest passed. """ - @abc.abstractproperty - def serial_number(self): + @property + @abc.abstractmethod + def serial_number(self) -> int: """ Returns certificate serial number """ - @abc.abstractproperty - def version(self): + @property + @abc.abstractmethod + def version(self) -> Version: """ Returns the certificate version """ @abc.abstractmethod - def public_key(self): + def public_key(self) -> CertificatePublicKeyTypes: """ Returns the public key """ - @abc.abstractproperty - def not_valid_before(self): + @property + @abc.abstractmethod + def not_valid_before(self) -> datetime.datetime: """ Not before time (represented as UTC datetime) """ - @abc.abstractproperty - def not_valid_after(self): + @property + @abc.abstractmethod + def not_valid_after(self) -> datetime.datetime: """ Not after time (represented as UTC datetime) """ - @abc.abstractproperty - def issuer(self): + @property + @abc.abstractmethod + def issuer(self) -> Name: """ Returns the issuer name object. """ - @abc.abstractproperty - def subject(self): + @property + @abc.abstractmethod + def subject(self) -> Name: """ Returns the subject name object. """ - @abc.abstractproperty - def signature_hash_algorithm(self): + @property + @abc.abstractmethod + def signature_hash_algorithm( + self, + ) -> typing.Optional[hashes.HashAlgorithm]: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. """ - @abc.abstractproperty - def signature_algorithm_oid(self): + @property + @abc.abstractmethod + def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ - @abc.abstractproperty - def extensions(self): + @property + @abc.abstractmethod + def signature_algorithm_parameters( + self, + ) -> typing.Union[None, padding.PSS, padding.PKCS1v15, ec.ECDSA]: + """ + Returns the signature algorithm parameters. + """ + + @property + @abc.abstractmethod + def extensions(self) -> Extensions: """ Returns an Extensions object. """ - @abc.abstractproperty - def signature(self): + @property + @abc.abstractmethod + def signature(self) -> bytes: """ Returns the signature bytes. """ - @abc.abstractproperty - def tbs_certificate_bytes(self): + @property + @abc.abstractmethod + def tbs_certificate_bytes(self) -> bytes: """ Returns the tbsCertificate payload bytes as defined in RFC 5280. """ + @property @abc.abstractmethod - def __eq__(self, other): + def tbs_precertificate_bytes(self) -> bytes: """ - Checks equality. + Returns the tbsCertificate payload bytes with the SCT list extension + stripped. """ @abc.abstractmethod - def __ne__(self, other): + def __eq__(self, other: object) -> bool: """ - Checks not equal. + Checks equality. """ @abc.abstractmethod - def __hash__(self): + def __hash__(self) -> int: """ Computes a hash. """ @abc.abstractmethod - def public_bytes(self, encoding): + def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Serializes the certificate to PEM or DER format. """ + @abc.abstractmethod + def verify_directly_issued_by(self, issuer: Certificate) -> None: + """ + This method verifies that certificate issuer name matches the + issuer subject name and that the certificate is signed by the + issuer's private key. No other validation is performed. + """ + -@six.add_metaclass(abc.ABCMeta) -class CertificateRevocationList(object): +# Runtime isinstance checks need this since the rust class is not a subclass. +Certificate.register(rust_x509.Certificate) + + +class RevokedCertificate(metaclass=abc.ABCMeta): + @property @abc.abstractmethod - def public_bytes(self, encoding): + def serial_number(self) -> int: + """ + Returns the serial number of the revoked certificate. + """ + + @property + @abc.abstractmethod + def revocation_date(self) -> datetime.datetime: + """ + Returns the date of when this certificate was revoked. + """ + + @property + @abc.abstractmethod + def extensions(self) -> Extensions: + """ + Returns an Extensions object containing a list of Revoked extensions. + """ + + +# Runtime isinstance checks need this since the rust class is not a subclass. +RevokedCertificate.register(rust_x509.RevokedCertificate) + + +class _RawRevokedCertificate(RevokedCertificate): + def __init__( + self, + serial_number: int, + revocation_date: datetime.datetime, + extensions: Extensions, + ): + self._serial_number = serial_number + self._revocation_date = revocation_date + self._extensions = extensions + + @property + def serial_number(self) -> int: + return self._serial_number + + @property + def revocation_date(self) -> datetime.datetime: + return self._revocation_date + + @property + def extensions(self) -> Extensions: + return self._extensions + + +class CertificateRevocationList(metaclass=abc.ABCMeta): + @abc.abstractmethod + def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Serializes the CRL to PEM or DER format. """ @abc.abstractmethod - def fingerprint(self, algorithm): + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: """ Returns bytes using digest passed. """ @abc.abstractmethod - def get_revoked_certificate_by_serial_number(self, serial_number): + def get_revoked_certificate_by_serial_number( + self, serial_number: int + ) -> typing.Optional[RevokedCertificate]: """ Returns an instance of RevokedCertificate or None if the serial_number is not in the CRL. """ - @abc.abstractproperty - def signature_hash_algorithm(self): + @property + @abc.abstractmethod + def signature_hash_algorithm( + self, + ) -> typing.Optional[hashes.HashAlgorithm]: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. """ - @abc.abstractproperty - def signature_algorithm_oid(self): + @property + @abc.abstractmethod + def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ - @abc.abstractproperty - def issuer(self): + @property + @abc.abstractmethod + def issuer(self) -> Name: """ Returns the X509Name with the issuer of this CRL. """ - @abc.abstractproperty - def next_update(self): + @property + @abc.abstractmethod + def next_update(self) -> typing.Optional[datetime.datetime]: """ Returns the date of next update for this CRL. """ - @abc.abstractproperty - def last_update(self): + @property + @abc.abstractmethod + def last_update(self) -> datetime.datetime: """ Returns the date of last update for this CRL. """ - @abc.abstractproperty - def extensions(self): + @property + @abc.abstractmethod + def extensions(self) -> Extensions: """ Returns an Extensions object containing a list of CRL extensions. """ - @abc.abstractproperty - def signature(self): + @property + @abc.abstractmethod + def signature(self) -> bytes: """ Returns the signature bytes. """ - @abc.abstractproperty - def tbs_certlist_bytes(self): + @property + @abc.abstractmethod + def tbs_certlist_bytes(self) -> bytes: """ Returns the tbsCertList payload bytes as defined in RFC 5280. """ @abc.abstractmethod - def __eq__(self, other): + def __eq__(self, other: object) -> bool: """ Checks equality. """ @abc.abstractmethod - def __ne__(self, other): - """ - Checks not equal. - """ - - @abc.abstractmethod - def __len__(self): + def __len__(self) -> int: """ Number of revoked certificates in the CRL. """ + @typing.overload + def __getitem__(self, idx: int) -> RevokedCertificate: + ... + + @typing.overload + def __getitem__(self, idx: slice) -> typing.List[RevokedCertificate]: + ... + @abc.abstractmethod - def __getitem__(self, idx): + def __getitem__( + self, idx: typing.Union[int, slice] + ) -> typing.Union[RevokedCertificate, typing.List[RevokedCertificate]]: """ Returns a revoked certificate (or slice of revoked certificates). """ @abc.abstractmethod - def __iter__(self): + def __iter__(self) -> typing.Iterator[RevokedCertificate]: """ Iterator over the revoked certificates """ @abc.abstractmethod - def is_signature_valid(self, public_key): + def is_signature_valid( + self, public_key: CertificateIssuerPublicKeyTypes + ) -> bool: """ Verifies signature of revocation list against given public key. """ -@six.add_metaclass(abc.ABCMeta) -class CertificateSigningRequest(object): - @abc.abstractmethod - def __eq__(self, other): - """ - Checks equality. - """ +CertificateRevocationList.register(rust_x509.CertificateRevocationList) + +class CertificateSigningRequest(metaclass=abc.ABCMeta): @abc.abstractmethod - def __ne__(self, other): + def __eq__(self, other: object) -> bool: """ - Checks not equal. + Checks equality. """ @abc.abstractmethod - def __hash__(self): + def __hash__(self) -> int: """ Computes a hash. """ @abc.abstractmethod - def public_key(self): + def public_key(self) -> CertificatePublicKeyTypes: """ Returns the public key """ - @abc.abstractproperty - def subject(self): + @property + @abc.abstractmethod + def subject(self) -> Name: """ Returns the subject name object. """ - @abc.abstractproperty - def signature_hash_algorithm(self): + @property + @abc.abstractmethod + def signature_hash_algorithm( + self, + ) -> typing.Optional[hashes.HashAlgorithm]: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. """ - @abc.abstractproperty - def signature_algorithm_oid(self): + @property + @abc.abstractmethod + def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ - @abc.abstractproperty - def extensions(self): + @property + @abc.abstractmethod + def extensions(self) -> Extensions: """ Returns the extensions in the signing request. """ + @property + @abc.abstractmethod + def attributes(self) -> Attributes: + """ + Returns an Attributes object. + """ + @abc.abstractmethod - def public_bytes(self, encoding): + def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Encodes the request to PEM or DER format. """ - @abc.abstractproperty - def signature(self): + @property + @abc.abstractmethod + def signature(self) -> bytes: """ Returns the signature bytes. """ - @abc.abstractproperty - def tbs_certrequest_bytes(self): + @property + @abc.abstractmethod + def tbs_certrequest_bytes(self) -> bytes: """ Returns the PKCS#10 CertificationRequestInfo bytes as defined in RFC 2986. """ - @abc.abstractproperty - def is_signature_valid(self): + @property + @abc.abstractmethod + def is_signature_valid(self) -> bool: """ Verifies signature of signing request. """ - @abc.abstractproperty - def get_attribute_for_oid(self): + @abc.abstractmethod + def get_attribute_for_oid(self, oid: ObjectIdentifier) -> bytes: """ Get the attribute value for a given OID. """ -@six.add_metaclass(abc.ABCMeta) -class RevokedCertificate(object): - @abc.abstractproperty - def serial_number(self): - """ - Returns the serial number of the revoked certificate. - """ +# Runtime isinstance checks need this since the rust class is not a subclass. +CertificateSigningRequest.register(rust_x509.CertificateSigningRequest) - @abc.abstractproperty - def revocation_date(self): - """ - Returns the date of when this certificate was revoked. - """ - @abc.abstractproperty - def extensions(self): - """ - Returns an Extensions object containing a list of Revoked extensions. - """ +# Backend argument preserved for API compatibility, but ignored. +def load_pem_x509_certificate( + data: bytes, backend: typing.Any = None +) -> Certificate: + return rust_x509.load_pem_x509_certificate(data) + + +def load_pem_x509_certificates(data: bytes) -> typing.List[Certificate]: + return rust_x509.load_pem_x509_certificates(data) + + +# Backend argument preserved for API compatibility, but ignored. +def load_der_x509_certificate( + data: bytes, backend: typing.Any = None +) -> Certificate: + return rust_x509.load_der_x509_certificate(data) + +# Backend argument preserved for API compatibility, but ignored. +def load_pem_x509_csr( + data: bytes, backend: typing.Any = None +) -> CertificateSigningRequest: + return rust_x509.load_pem_x509_csr(data) -class CertificateSigningRequestBuilder(object): - def __init__(self, subject_name=None, extensions=[], attributes=[]): + +# Backend argument preserved for API compatibility, but ignored. +def load_der_x509_csr( + data: bytes, backend: typing.Any = None +) -> CertificateSigningRequest: + return rust_x509.load_der_x509_csr(data) + + +# Backend argument preserved for API compatibility, but ignored. +def load_pem_x509_crl( + data: bytes, backend: typing.Any = None +) -> CertificateRevocationList: + return rust_x509.load_pem_x509_crl(data) + + +# Backend argument preserved for API compatibility, but ignored. +def load_der_x509_crl( + data: bytes, backend: typing.Any = None +) -> CertificateRevocationList: + return rust_x509.load_der_x509_crl(data) + + +class CertificateSigningRequestBuilder: + def __init__( + self, + subject_name: typing.Optional[Name] = None, + extensions: typing.List[Extension[ExtensionType]] = [], + attributes: typing.List[ + typing.Tuple[ObjectIdentifier, bytes, typing.Optional[int]] + ] = [], + ): """ Creates an empty X.509 certificate request (v1). """ @@ -429,7 +638,7 @@ def __init__(self, subject_name=None, extensions=[], attributes=[]): self._extensions = extensions self._attributes = attributes - def subject_name(self, name): + def subject_name(self, name: Name) -> CertificateSigningRequestBuilder: """ Sets the certificate requestor's distinguished name. """ @@ -441,14 +650,16 @@ def subject_name(self, name): name, self._extensions, self._attributes ) - def add_extension(self, extension, critical): + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateSigningRequestBuilder: """ Adds an X.509 extension to the certificate request. """ - if not isinstance(extension, ExtensionType): + if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") - extension = Extension(extension.oid, critical, extension) + extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return CertificateSigningRequestBuilder( @@ -457,7 +668,13 @@ def add_extension(self, extension, critical): self._attributes, ) - def add_attribute(self, oid, value): + def add_attribute( + self, + oid: ObjectIdentifier, + value: bytes, + *, + _tag: typing.Optional[_ASN1Type] = None, + ) -> CertificateSigningRequestBuilder: """ Adds an X.509 attribute with an OID and associated value. """ @@ -467,35 +684,49 @@ def add_attribute(self, oid, value): if not isinstance(value, bytes): raise TypeError("value must be bytes") + if _tag is not None and not isinstance(_tag, _ASN1Type): + raise TypeError("tag must be _ASN1Type") + _reject_duplicate_attribute(oid, self._attributes) + if _tag is not None: + tag = _tag.value + else: + tag = None + return CertificateSigningRequestBuilder( self._subject_name, self._extensions, - self._attributes + [(oid, value)], + self._attributes + [(oid, value, tag)], ) - def sign(self, private_key, algorithm, backend=None): + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: typing.Optional[_AllowedHashTypes], + backend: typing.Any = None, + ) -> CertificateSigningRequest: """ Signs the request using the requestor's private key. """ - backend = _get_backend(backend) if self._subject_name is None: raise ValueError("A CertificateSigningRequest must have a subject") - return backend.create_x509_csr(self, private_key, algorithm) + return rust_x509.create_x509_csr(self, private_key, algorithm) -class CertificateBuilder(object): +class CertificateBuilder: + _extensions: typing.List[Extension[ExtensionType]] + def __init__( self, - issuer_name=None, - subject_name=None, - public_key=None, - serial_number=None, - not_valid_before=None, - not_valid_after=None, - extensions=[], - ): + issuer_name: typing.Optional[Name] = None, + subject_name: typing.Optional[Name] = None, + public_key: typing.Optional[CertificatePublicKeyTypes] = None, + serial_number: typing.Optional[int] = None, + not_valid_before: typing.Optional[datetime.datetime] = None, + not_valid_after: typing.Optional[datetime.datetime] = None, + extensions: typing.List[Extension[ExtensionType]] = [], + ) -> None: self._version = Version.v3 self._issuer_name = issuer_name self._subject_name = subject_name @@ -505,7 +736,7 @@ def __init__( self._not_valid_after = not_valid_after self._extensions = extensions - def issuer_name(self, name): + def issuer_name(self, name: Name) -> CertificateBuilder: """ Sets the CA's distinguished name. """ @@ -523,7 +754,7 @@ def issuer_name(self, name): self._extensions, ) - def subject_name(self, name): + def subject_name(self, name: Name) -> CertificateBuilder: """ Sets the requestor's distinguished name. """ @@ -541,7 +772,10 @@ def subject_name(self, name): self._extensions, ) - def public_key(self, key): + def public_key( + self, + key: CertificatePublicKeyTypes, + ) -> CertificateBuilder: """ Sets the requestor's public key (as found in the signing request). """ @@ -553,12 +787,15 @@ def public_key(self, key): ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey, ed448.Ed448PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, ), ): raise TypeError( "Expecting one of DSAPublicKey, RSAPublicKey," - " EllipticCurvePublicKey, Ed25519PublicKey or" - " Ed448PublicKey." + " EllipticCurvePublicKey, Ed25519PublicKey," + " Ed448PublicKey, X25519PublicKey, or " + "X448PublicKey." ) if self._public_key is not None: raise ValueError("The public key may only be set once.") @@ -572,11 +809,11 @@ def public_key(self, key): self._extensions, ) - def serial_number(self, number): + def serial_number(self, number: int) -> CertificateBuilder: """ Sets the certificate serial number. """ - if not isinstance(number, six.integer_types): + if not isinstance(number, int): raise TypeError("Serial number must be of integral type.") if self._serial_number is not None: raise ValueError("The serial number may only be set once.") @@ -599,7 +836,7 @@ def serial_number(self, number): self._extensions, ) - def not_valid_before(self, time): + def not_valid_before(self, time: datetime.datetime) -> CertificateBuilder: """ Sets the certificate activation time. """ @@ -628,7 +865,7 @@ def not_valid_before(self, time): self._extensions, ) - def not_valid_after(self, time): + def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder: """ Sets the certificate expiration time. """ @@ -660,14 +897,16 @@ def not_valid_after(self, time): self._extensions, ) - def add_extension(self, extension, critical): + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateBuilder: """ Adds an X.509 extension to the certificate. """ - if not isinstance(extension, ExtensionType): + if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") - extension = Extension(extension.oid, critical, extension) + extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return CertificateBuilder( @@ -680,11 +919,19 @@ def add_extension(self, extension, critical): self._extensions + [extension], ) - def sign(self, private_key, algorithm, backend=None): + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: typing.Optional[_AllowedHashTypes], + backend: typing.Any = None, + *, + rsa_padding: typing.Optional[ + typing.Union[padding.PSS, padding.PKCS1v15] + ] = None, + ) -> Certificate: """ Signs the certificate using the CA's private key. """ - backend = _get_backend(backend) if self._subject_name is None: raise ValueError("A certificate must have a subject name") @@ -703,17 +950,28 @@ def sign(self, private_key, algorithm, backend=None): if self._public_key is None: raise ValueError("A certificate must have a public key") - return backend.create_x509_certificate(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_certificate( + self, private_key, algorithm, rsa_padding + ) + + +class CertificateRevocationListBuilder: + _extensions: typing.List[Extension[ExtensionType]] + _revoked_certificates: typing.List[RevokedCertificate] -class CertificateRevocationListBuilder(object): def __init__( self, - issuer_name=None, - last_update=None, - next_update=None, - extensions=[], - revoked_certificates=[], + issuer_name: typing.Optional[Name] = None, + last_update: typing.Optional[datetime.datetime] = None, + next_update: typing.Optional[datetime.datetime] = None, + extensions: typing.List[Extension[ExtensionType]] = [], + revoked_certificates: typing.List[RevokedCertificate] = [], ): self._issuer_name = issuer_name self._last_update = last_update @@ -721,7 +979,9 @@ def __init__( self._extensions = extensions self._revoked_certificates = revoked_certificates - def issuer_name(self, issuer_name): + def issuer_name( + self, issuer_name: Name + ) -> CertificateRevocationListBuilder: if not isinstance(issuer_name, Name): raise TypeError("Expecting x509.Name object.") if self._issuer_name is not None: @@ -734,7 +994,9 @@ def issuer_name(self, issuer_name): self._revoked_certificates, ) - def last_update(self, last_update): + def last_update( + self, last_update: datetime.datetime + ) -> CertificateRevocationListBuilder: if not isinstance(last_update, datetime.datetime): raise TypeError("Expecting datetime object.") if self._last_update is not None: @@ -756,7 +1018,9 @@ def last_update(self, last_update): self._revoked_certificates, ) - def next_update(self, next_update): + def next_update( + self, next_update: datetime.datetime + ) -> CertificateRevocationListBuilder: if not isinstance(next_update, datetime.datetime): raise TypeError("Expecting datetime object.") if self._next_update is not None: @@ -778,14 +1042,16 @@ def next_update(self, next_update): self._revoked_certificates, ) - def add_extension(self, extension, critical): + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateRevocationListBuilder: """ Adds an X.509 extension to the certificate revocation list. """ - if not isinstance(extension, ExtensionType): + if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") - extension = Extension(extension.oid, critical, extension) + extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return CertificateRevocationListBuilder( self._issuer_name, @@ -795,7 +1061,9 @@ def add_extension(self, extension, critical): self._revoked_certificates, ) - def add_revoked_certificate(self, revoked_certificate): + def add_revoked_certificate( + self, revoked_certificate: RevokedCertificate + ) -> CertificateRevocationListBuilder: """ Adds a revoked certificate to the CRL. """ @@ -810,8 +1078,12 @@ def add_revoked_certificate(self, revoked_certificate): self._revoked_certificates + [revoked_certificate], ) - def sign(self, private_key, algorithm, backend=None): - backend = _get_backend(backend) + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: typing.Optional[_AllowedHashTypes], + backend: typing.Any = None, + ) -> CertificateRevocationList: if self._issuer_name is None: raise ValueError("A CRL must have an issuer name") @@ -821,19 +1093,22 @@ def sign(self, private_key, algorithm, backend=None): if self._next_update is None: raise ValueError("A CRL must have a next update time") - return backend.create_x509_crl(self, private_key, algorithm) + return rust_x509.create_x509_crl(self, private_key, algorithm) -class RevokedCertificateBuilder(object): +class RevokedCertificateBuilder: def __init__( - self, serial_number=None, revocation_date=None, extensions=[] + self, + serial_number: typing.Optional[int] = None, + revocation_date: typing.Optional[datetime.datetime] = None, + extensions: typing.List[Extension[ExtensionType]] = [], ): self._serial_number = serial_number self._revocation_date = revocation_date self._extensions = extensions - def serial_number(self, number): - if not isinstance(number, six.integer_types): + def serial_number(self, number: int) -> RevokedCertificateBuilder: + if not isinstance(number, int): raise TypeError("Serial number must be of integral type.") if self._serial_number is not None: raise ValueError("The serial number may only be set once.") @@ -850,7 +1125,9 @@ def serial_number(self, number): number, self._revocation_date, self._extensions ) - def revocation_date(self, time): + def revocation_date( + self, time: datetime.datetime + ) -> RevokedCertificateBuilder: if not isinstance(time, datetime.datetime): raise TypeError("Expecting datetime object.") if self._revocation_date is not None: @@ -864,11 +1141,13 @@ def revocation_date(self, time): self._serial_number, time, self._extensions ) - def add_extension(self, extension, critical): - if not isinstance(extension, ExtensionType): + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> RevokedCertificateBuilder: + if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") - extension = Extension(extension.oid, critical, extension) + extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return RevokedCertificateBuilder( self._serial_number, @@ -876,17 +1155,19 @@ def add_extension(self, extension, critical): self._extensions + [extension], ) - def build(self, backend=None): - backend = _get_backend(backend) + def build(self, backend: typing.Any = None) -> RevokedCertificate: if self._serial_number is None: raise ValueError("A revoked certificate must have a serial number") if self._revocation_date is None: raise ValueError( "A revoked certificate must have a revocation date" ) - - return backend.create_x509_revoked_certificate(self) + return _RawRevokedCertificate( + self._serial_number, + self._revocation_date, + Extensions(self._extensions), + ) -def random_serial_number(): - return utils.int_from_bytes(os.urandom(20), "big") >> 1 +def random_serial_number() -> int: + return int.from_bytes(os.urandom(20), "big") >> 1 diff --git a/src/cryptography/x509/certificate_transparency.py b/src/cryptography/x509/certificate_transparency.py index d00fe8126925..73647ee716fc 100644 --- a/src/cryptography/x509/certificate_transparency.py +++ b/src/cryptography/x509/certificate_transparency.py @@ -2,45 +2,96 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc -from enum import Enum +import datetime -import six +from cryptography import utils +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.hazmat.primitives.hashes import HashAlgorithm -class LogEntryType(Enum): +class LogEntryType(utils.Enum): X509_CERTIFICATE = 0 PRE_CERTIFICATE = 1 -class Version(Enum): +class Version(utils.Enum): v1 = 0 -@six.add_metaclass(abc.ABCMeta) -class SignedCertificateTimestamp(object): - @abc.abstractproperty - def version(self): +class SignatureAlgorithm(utils.Enum): + """ + Signature algorithms that are valid for SCTs. + + These are exactly the same as SignatureAlgorithm in RFC 5246 (TLS 1.2). + + See: + """ + + ANONYMOUS = 0 + RSA = 1 + DSA = 2 + ECDSA = 3 + + +class SignedCertificateTimestamp(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def version(self) -> Version: """ Returns the SCT version. """ - @abc.abstractproperty - def log_id(self): + @property + @abc.abstractmethod + def log_id(self) -> bytes: """ Returns an identifier indicating which log this SCT is for. """ - @abc.abstractproperty - def timestamp(self): + @property + @abc.abstractmethod + def timestamp(self) -> datetime.datetime: """ Returns the timestamp for this SCT. """ - @abc.abstractproperty - def entry_type(self): + @property + @abc.abstractmethod + def entry_type(self) -> LogEntryType: """ Returns whether this is an SCT for a certificate or pre-certificate. """ + + @property + @abc.abstractmethod + def signature_hash_algorithm(self) -> HashAlgorithm: + """ + Returns the hash algorithm used for the SCT's signature. + """ + + @property + @abc.abstractmethod + def signature_algorithm(self) -> SignatureAlgorithm: + """ + Returns the signing algorithm used for the SCT's signature. + """ + + @property + @abc.abstractmethod + def signature(self) -> bytes: + """ + Returns the signature for this SCT. + """ + + @property + @abc.abstractmethod + def extension_bytes(self) -> bytes: + """ + Returns the raw bytes of any extensions for this SCT. + """ + + +SignedCertificateTimestamp.register(rust_x509.Sct) diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index 130ba69b8769..ac99592f55a7 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -2,40 +2,54 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc import datetime import hashlib import ipaddress -from enum import Enum - -import six +import typing from cryptography import utils -from cryptography.hazmat._der import ( - BIT_STRING, - DERReader, - OBJECT_IDENTIFIER, - SEQUENCE, -) +from cryptography.hazmat.bindings._rust import asn1 +from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.hazmat.primitives import constant_time, serialization from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, +) from cryptography.x509.certificate_transparency import ( SignedCertificateTimestamp, ) -from cryptography.x509.general_name import GeneralName, IPAddress, OtherName -from cryptography.x509.name import RelativeDistinguishedName +from cryptography.x509.general_name import ( + DirectoryName, + DNSName, + GeneralName, + IPAddress, + OtherName, + RegisteredID, + RFC822Name, + UniformResourceIdentifier, + _IPAddressTypes, +) +from cryptography.x509.name import Name, RelativeDistinguishedName from cryptography.x509.oid import ( CRLEntryExtensionOID, ExtensionOID, - OCSPExtensionOID, ObjectIdentifier, + OCSPExtensionOID, +) + +ExtensionTypeVar = typing.TypeVar( + "ExtensionTypeVar", bound="ExtensionType", covariant=True ) -def _key_identifier_from_public_key(public_key): +def _key_identifier_from_public_key( + public_key: CertificatePublicKeyTypes, +) -> bytes: if isinstance(public_key, RSAPublicKey): data = public_key.public_bytes( serialization.Encoding.DER, @@ -52,31 +66,13 @@ def _key_identifier_from_public_key(public_key): serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo, ) - - reader = DERReader(serialized) - with reader.read_single_element(SEQUENCE) as public_key_info: - algorithm = public_key_info.read_element(SEQUENCE) - public_key = public_key_info.read_element(BIT_STRING) - - # Double-check the algorithm structure. - with algorithm: - algorithm.read_element(OBJECT_IDENTIFIER) - if not algorithm.is_empty(): - # Skip the optional parameters field. - algorithm.read_any_element() - - # BIT STRING contents begin with the number of padding bytes added. It - # must be zero for SubjectPublicKeyInfo structures. - if public_key.read_byte() != 0: - raise ValueError("Invalid public key encoding") - - data = public_key.data + data = asn1.parse_spki_for_data(serialized) return hashlib.sha1(data).digest() -def _make_sequence_methods(field_name): - def len_method(self): +def _make_sequence_methods(field_name: str): + def len_method(self) -> int: return len(getattr(self, field_name)) def iter_method(self): @@ -89,38 +85,49 @@ def getitem_method(self, idx): class DuplicateExtension(Exception): - def __init__(self, msg, oid): - super(DuplicateExtension, self).__init__(msg) + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) self.oid = oid class ExtensionNotFound(Exception): - def __init__(self, msg, oid): - super(ExtensionNotFound, self).__init__(msg) + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) self.oid = oid -@six.add_metaclass(abc.ABCMeta) -class ExtensionType(object): - @abc.abstractproperty - def oid(self): +class ExtensionType(metaclass=abc.ABCMeta): + oid: typing.ClassVar[ObjectIdentifier] + + def public_bytes(self) -> bytes: """ - Returns the oid associated with the given extension type. + Serializes the extension type to DER. """ + raise NotImplementedError( + "public_bytes is not implemented for extension type {!r}".format( + self + ) + ) -class Extensions(object): - def __init__(self, extensions): - self._extensions = extensions +class Extensions: + def __init__( + self, extensions: typing.Iterable[Extension[ExtensionType]] + ) -> None: + self._extensions = list(extensions) - def get_extension_for_oid(self, oid): + def get_extension_for_oid( + self, oid: ObjectIdentifier + ) -> Extension[ExtensionType]: for ext in self: if ext.oid == oid: return ext - raise ExtensionNotFound("No {} extension was found".format(oid), oid) + raise ExtensionNotFound(f"No {oid} extension was found", oid) - def get_extension_for_class(self, extclass): + def get_extension_for_class( + self, extclass: typing.Type[ExtensionTypeVar] + ) -> Extension[ExtensionTypeVar]: if extclass is UnrecognizedExtension: raise TypeError( "UnrecognizedExtension can't be used with " @@ -133,53 +140,53 @@ def get_extension_for_class(self, extclass): return ext raise ExtensionNotFound( - "No {} extension was found".format(extclass), extclass.oid + f"No {extclass} extension was found", extclass.oid ) __len__, __iter__, __getitem__ = _make_sequence_methods("_extensions") - def __repr__(self): - return "".format(self._extensions) + def __repr__(self) -> str: + return f"" -@utils.register_interface(ExtensionType) -class CRLNumber(object): +class CRLNumber(ExtensionType): oid = ExtensionOID.CRL_NUMBER - def __init__(self, crl_number): - if not isinstance(crl_number, six.integer_types): + def __init__(self, crl_number: int) -> None: + if not isinstance(crl_number, int): raise TypeError("crl_number must be an integer") self._crl_number = crl_number - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, CRLNumber): return NotImplemented return self.crl_number == other.crl_number - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.crl_number) - def __repr__(self): - return "".format(self.crl_number) + def __repr__(self) -> str: + return f"" - crl_number = utils.read_only_property("_crl_number") + @property + def crl_number(self) -> int: + return self._crl_number + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class AuthorityKeyIdentifier(object): +class AuthorityKeyIdentifier(ExtensionType): oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER def __init__( self, - key_identifier, - authority_cert_issuer, - authority_cert_serial_number, - ): + key_identifier: typing.Optional[bytes], + authority_cert_issuer: typing.Optional[typing.Iterable[GeneralName]], + authority_cert_serial_number: typing.Optional[int], + ) -> None: if (authority_cert_issuer is None) != ( authority_cert_serial_number is None ): @@ -199,7 +206,7 @@ def __init__( ) if authority_cert_serial_number is not None and not isinstance( - authority_cert_serial_number, six.integer_types + authority_cert_serial_number, int ): raise TypeError("authority_cert_serial_number must be an integer") @@ -207,8 +214,15 @@ def __init__( self._authority_cert_issuer = authority_cert_issuer self._authority_cert_serial_number = authority_cert_serial_number + # This takes a subset of CertificatePublicKeyTypes because an issuer + # cannot have an X25519/X448 key. This introduces some unfortunate + # asymmetry that requires typing users to explicitly + # narrow their type, but we should make this accurate and not just + # convenient. @classmethod - def from_issuer_public_key(cls, public_key): + def from_issuer_public_key( + cls, public_key: CertificateIssuerPublicKeyTypes + ) -> AuthorityKeyIdentifier: digest = _key_identifier_from_public_key(public_key) return cls( key_identifier=digest, @@ -217,14 +231,16 @@ def from_issuer_public_key(cls, public_key): ) @classmethod - def from_issuer_subject_key_identifier(cls, ski): + def from_issuer_subject_key_identifier( + cls, ski: SubjectKeyIdentifier + ) -> AuthorityKeyIdentifier: return cls( key_identifier=ski.digest, authority_cert_issuer=None, authority_cert_serial_number=None, ) - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, AuthorityKeyIdentifier): return NotImplemented @@ -243,10 +259,7 @@ def __eq__(self, other): == other.authority_cert_serial_number ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: if self.authority_cert_issuer is None: aci = None else: @@ -255,47 +268,66 @@ def __hash__(self): (self.key_identifier, aci, self.authority_cert_serial_number) ) - key_identifier = utils.read_only_property("_key_identifier") - authority_cert_issuer = utils.read_only_property("_authority_cert_issuer") - authority_cert_serial_number = utils.read_only_property( - "_authority_cert_serial_number" - ) + @property + def key_identifier(self) -> typing.Optional[bytes]: + return self._key_identifier + + @property + def authority_cert_issuer( + self, + ) -> typing.Optional[typing.List[GeneralName]]: + return self._authority_cert_issuer + @property + def authority_cert_serial_number(self) -> typing.Optional[int]: + return self._authority_cert_serial_number + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class SubjectKeyIdentifier(object): + +class SubjectKeyIdentifier(ExtensionType): oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER - def __init__(self, digest): + def __init__(self, digest: bytes) -> None: self._digest = digest @classmethod - def from_public_key(cls, public_key): + def from_public_key( + cls, public_key: CertificatePublicKeyTypes + ) -> SubjectKeyIdentifier: return cls(_key_identifier_from_public_key(public_key)) - digest = utils.read_only_property("_digest") + @property + def digest(self) -> bytes: + return self._digest + + @property + def key_identifier(self) -> bytes: + return self._digest - def __repr__(self): - return "".format(self.digest) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectKeyIdentifier): return NotImplemented return constant_time.bytes_eq(self.digest, other.digest) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.digest) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + -@utils.register_interface(ExtensionType) -class AuthorityInformationAccess(object): +class AuthorityInformationAccess(ExtensionType): oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS - def __init__(self, descriptions): + def __init__( + self, descriptions: typing.Iterable[AccessDescription] + ) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( @@ -307,27 +339,28 @@ def __init__(self, descriptions): __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") - def __repr__(self): - return "".format(self._descriptions) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, AuthorityInformationAccess): return NotImplemented return self._descriptions == other._descriptions - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._descriptions)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class SubjectInformationAccess(object): + +class SubjectInformationAccess(ExtensionType): oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS - def __init__(self, descriptions): + def __init__( + self, descriptions: typing.Iterable[AccessDescription] + ) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( @@ -339,24 +372,26 @@ def __init__(self, descriptions): __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") - def __repr__(self): - return "".format(self._descriptions) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectInformationAccess): return NotImplemented return self._descriptions == other._descriptions - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._descriptions)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + -class AccessDescription(object): - def __init__(self, access_method, access_location): +class AccessDescription: + def __init__( + self, access_method: ObjectIdentifier, access_location: GeneralName + ) -> None: if not isinstance(access_method, ObjectIdentifier): raise TypeError("access_method must be an ObjectIdentifier") @@ -366,13 +401,13 @@ def __init__(self, access_method, access_location): self._access_method = access_method self._access_location = access_location - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, AccessDescription): return NotImplemented @@ -381,21 +416,22 @@ def __eq__(self, other): and self.access_location == other.access_location ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.access_method, self.access_location)) - access_method = utils.read_only_property("_access_method") - access_location = utils.read_only_property("_access_location") + @property + def access_method(self) -> ObjectIdentifier: + return self._access_method + + @property + def access_location(self) -> GeneralName: + return self._access_location -@utils.register_interface(ExtensionType) -class BasicConstraints(object): +class BasicConstraints(ExtensionType): oid = ExtensionOID.BASIC_CONSTRAINTS - def __init__(self, ca, path_length): + def __init__(self, ca: bool, path_length: typing.Optional[int]) -> None: if not isinstance(ca, bool): raise TypeError("ca must be a boolean value") @@ -403,7 +439,7 @@ def __init__(self, ca, path_length): raise ValueError("path_length must be None when ca is False") if path_length is not None and ( - not isinstance(path_length, six.integer_types) or path_length < 0 + not isinstance(path_length, int) or path_length < 0 ): raise TypeError( "path_length must be a non-negative integer or None" @@ -412,60 +448,67 @@ def __init__(self, ca, path_length): self._ca = ca self._path_length = path_length - ca = utils.read_only_property("_ca") - path_length = utils.read_only_property("_path_length") + @property + def ca(self) -> bool: + return self._ca + + @property + def path_length(self) -> typing.Optional[int]: + return self._path_length - def __repr__(self): + def __repr__(self) -> str: return ( "" ).format(self) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, BasicConstraints): return NotImplemented return self.ca == other.ca and self.path_length == other.path_length - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.ca, self.path_length)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + -@utils.register_interface(ExtensionType) -class DeltaCRLIndicator(object): +class DeltaCRLIndicator(ExtensionType): oid = ExtensionOID.DELTA_CRL_INDICATOR - def __init__(self, crl_number): - if not isinstance(crl_number, six.integer_types): + def __init__(self, crl_number: int) -> None: + if not isinstance(crl_number, int): raise TypeError("crl_number must be an integer") self._crl_number = crl_number - crl_number = utils.read_only_property("_crl_number") + @property + def crl_number(self) -> int: + return self._crl_number - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DeltaCRLIndicator): return NotImplemented return self.crl_number == other.crl_number - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.crl_number) - def __repr__(self): - return "".format(self) + def __repr__(self) -> str: + return f"" + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class CRLDistributionPoints(object): + +class CRLDistributionPoints(ExtensionType): oid = ExtensionOID.CRL_DISTRIBUTION_POINTS - def __init__(self, distribution_points): + def __init__( + self, distribution_points: typing.Iterable[DistributionPoint] + ) -> None: distribution_points = list(distribution_points) if not all( isinstance(x, DistributionPoint) for x in distribution_points @@ -481,27 +524,28 @@ def __init__(self, distribution_points): "_distribution_points" ) - def __repr__(self): - return "".format(self._distribution_points) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, CRLDistributionPoints): return NotImplemented return self._distribution_points == other._distribution_points - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._distribution_points)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + -@utils.register_interface(ExtensionType) -class FreshestCRL(object): +class FreshestCRL(ExtensionType): oid = ExtensionOID.FRESHEST_CRL - def __init__(self, distribution_points): + def __init__( + self, distribution_points: typing.Iterable[DistributionPoint] + ) -> None: distribution_points = list(distribution_points) if not all( isinstance(x, DistributionPoint) for x in distribution_points @@ -517,31 +561,42 @@ def __init__(self, distribution_points): "_distribution_points" ) - def __repr__(self): - return "".format(self._distribution_points) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, FreshestCRL): return NotImplemented return self._distribution_points == other._distribution_points - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._distribution_points)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -class DistributionPoint(object): - def __init__(self, full_name, relative_name, reasons, crl_issuer): + +class DistributionPoint: + def __init__( + self, + full_name: typing.Optional[typing.Iterable[GeneralName]], + relative_name: typing.Optional[RelativeDistinguishedName], + reasons: typing.Optional[typing.FrozenSet[ReasonFlags]], + crl_issuer: typing.Optional[typing.Iterable[GeneralName]], + ) -> None: if full_name and relative_name: raise ValueError( "You cannot provide both full_name and relative_name, at " "least one must be None." ) + if not full_name and not relative_name and not crl_issuer: + raise ValueError( + "Either full_name, relative_name or crl_issuer must be " + "provided." + ) - if full_name: + if full_name is not None: full_name = list(full_name) if not all(isinstance(x, GeneralName) for x in full_name): raise TypeError( @@ -554,7 +609,7 @@ def __init__(self, full_name, relative_name, reasons, crl_issuer): "relative_name must be a RelativeDistinguishedName" ) - if crl_issuer: + if crl_issuer is not None: crl_issuer = list(crl_issuer) if not all(isinstance(x, GeneralName) for x in crl_issuer): raise TypeError( @@ -576,25 +631,19 @@ def __init__(self, full_name, relative_name, reasons, crl_issuer): "DistributionPoint" ) - if reasons and not crl_issuer and not (full_name or relative_name): - raise ValueError( - "You must supply crl_issuer, full_name, or relative_name when " - "reasons is not None" - ) - self._full_name = full_name self._relative_name = relative_name self._reasons = reasons self._crl_issuer = crl_issuer - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DistributionPoint): return NotImplemented @@ -605,29 +654,41 @@ def __eq__(self, other): and self.crl_issuer == other.crl_issuer ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: if self.full_name is not None: - fn = tuple(self.full_name) + fn: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( + self.full_name + ) else: fn = None if self.crl_issuer is not None: - crl_issuer = tuple(self.crl_issuer) + crl_issuer: typing.Optional[ + typing.Tuple[GeneralName, ...] + ] = tuple(self.crl_issuer) else: crl_issuer = None return hash((fn, self.relative_name, self.reasons, crl_issuer)) - full_name = utils.read_only_property("_full_name") - relative_name = utils.read_only_property("_relative_name") - reasons = utils.read_only_property("_reasons") - crl_issuer = utils.read_only_property("_crl_issuer") + @property + def full_name(self) -> typing.Optional[typing.List[GeneralName]]: + return self._full_name + + @property + def relative_name(self) -> typing.Optional[RelativeDistinguishedName]: + return self._relative_name + + @property + def reasons(self) -> typing.Optional[typing.FrozenSet[ReasonFlags]]: + return self._reasons + + @property + def crl_issuer(self) -> typing.Optional[typing.List[GeneralName]]: + return self._crl_issuer -class ReasonFlags(Enum): +class ReasonFlags(utils.Enum): unspecified = "unspecified" key_compromise = "keyCompromise" ca_compromise = "cACompromise" @@ -640,13 +701,51 @@ class ReasonFlags(Enum): remove_from_crl = "removeFromCRL" -@utils.register_interface(ExtensionType) -class PolicyConstraints(object): +# These are distribution point bit string mappings. Not to be confused with +# CRLReason reason flags bit string mappings. +# ReasonFlags ::= BIT STRING { +# unused (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# privilegeWithdrawn (7), +# aACompromise (8) } +_REASON_BIT_MAPPING = { + 1: ReasonFlags.key_compromise, + 2: ReasonFlags.ca_compromise, + 3: ReasonFlags.affiliation_changed, + 4: ReasonFlags.superseded, + 5: ReasonFlags.cessation_of_operation, + 6: ReasonFlags.certificate_hold, + 7: ReasonFlags.privilege_withdrawn, + 8: ReasonFlags.aa_compromise, +} + +_CRLREASONFLAGS = { + ReasonFlags.key_compromise: 1, + ReasonFlags.ca_compromise: 2, + ReasonFlags.affiliation_changed: 3, + ReasonFlags.superseded: 4, + ReasonFlags.cessation_of_operation: 5, + ReasonFlags.certificate_hold: 6, + ReasonFlags.privilege_withdrawn: 7, + ReasonFlags.aa_compromise: 8, +} + + +class PolicyConstraints(ExtensionType): oid = ExtensionOID.POLICY_CONSTRAINTS - def __init__(self, require_explicit_policy, inhibit_policy_mapping): + def __init__( + self, + require_explicit_policy: typing.Optional[int], + inhibit_policy_mapping: typing.Optional[int], + ) -> None: if require_explicit_policy is not None and not isinstance( - require_explicit_policy, six.integer_types + require_explicit_policy, int ): raise TypeError( "require_explicit_policy must be a non-negative integer or " @@ -654,7 +753,7 @@ def __init__(self, require_explicit_policy, inhibit_policy_mapping): ) if inhibit_policy_mapping is not None and not isinstance( - inhibit_policy_mapping, six.integer_types + inhibit_policy_mapping, int ): raise TypeError( "inhibit_policy_mapping must be a non-negative integer or None" @@ -669,14 +768,14 @@ def __init__(self, require_explicit_policy, inhibit_policy_mapping): self._require_explicit_policy = require_explicit_policy self._inhibit_policy_mapping = inhibit_policy_mapping - def __repr__(self): + def __repr__(self) -> str: return ( - u"".format(self) + "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, PolicyConstraints): return NotImplemented @@ -685,27 +784,27 @@ def __eq__(self, other): and self.inhibit_policy_mapping == other.inhibit_policy_mapping ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash( (self.require_explicit_policy, self.inhibit_policy_mapping) ) - require_explicit_policy = utils.read_only_property( - "_require_explicit_policy" - ) - inhibit_policy_mapping = utils.read_only_property( - "_inhibit_policy_mapping" - ) + @property + def require_explicit_policy(self) -> typing.Optional[int]: + return self._require_explicit_policy + + @property + def inhibit_policy_mapping(self) -> typing.Optional[int]: + return self._inhibit_policy_mapping + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class CertificatePolicies(object): +class CertificatePolicies(ExtensionType): oid = ExtensionOID.CERTIFICATE_POLICIES - def __init__(self, policies): + def __init__(self, policies: typing.Iterable[PolicyInformation]) -> None: policies = list(policies) if not all(isinstance(x, PolicyInformation) for x in policies): raise TypeError( @@ -717,34 +816,39 @@ def __init__(self, policies): __len__, __iter__, __getitem__ = _make_sequence_methods("_policies") - def __repr__(self): - return "".format(self._policies) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, CertificatePolicies): return NotImplemented return self._policies == other._policies - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._policies)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + -class PolicyInformation(object): - def __init__(self, policy_identifier, policy_qualifiers): +class PolicyInformation: + def __init__( + self, + policy_identifier: ObjectIdentifier, + policy_qualifiers: typing.Optional[ + typing.Iterable[typing.Union[str, UserNotice]] + ], + ) -> None: if not isinstance(policy_identifier, ObjectIdentifier): raise TypeError("policy_identifier must be an ObjectIdentifier") self._policy_identifier = policy_identifier - if policy_qualifiers: + if policy_qualifiers is not None: policy_qualifiers = list(policy_qualifiers) if not all( - isinstance(x, (six.text_type, UserNotice)) - for x in policy_qualifiers + isinstance(x, (str, UserNotice)) for x in policy_qualifiers ): raise TypeError( "policy_qualifiers must be a list of strings and/or " @@ -753,13 +857,13 @@ def __init__(self, policy_identifier, policy_qualifiers): self._policy_qualifiers = policy_qualifiers - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, PolicyInformation): return NotImplemented @@ -768,23 +872,33 @@ def __eq__(self, other): and self.policy_qualifiers == other.policy_qualifiers ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: if self.policy_qualifiers is not None: - pq = tuple(self.policy_qualifiers) + pq: typing.Optional[ + typing.Tuple[typing.Union[str, UserNotice], ...] + ] = tuple(self.policy_qualifiers) else: pq = None return hash((self.policy_identifier, pq)) - policy_identifier = utils.read_only_property("_policy_identifier") - policy_qualifiers = utils.read_only_property("_policy_qualifiers") + @property + def policy_identifier(self) -> ObjectIdentifier: + return self._policy_identifier + + @property + def policy_qualifiers( + self, + ) -> typing.Optional[typing.List[typing.Union[str, UserNotice]]]: + return self._policy_qualifiers -class UserNotice(object): - def __init__(self, notice_reference, explicit_text): +class UserNotice: + def __init__( + self, + notice_reference: typing.Optional[NoticeReference], + explicit_text: typing.Optional[str], + ) -> None: if notice_reference and not isinstance( notice_reference, NoticeReference ): @@ -795,13 +909,13 @@ def __init__(self, notice_reference, explicit_text): self._notice_reference = notice_reference self._explicit_text = explicit_text - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, UserNotice): return NotImplemented @@ -810,18 +924,24 @@ def __eq__(self, other): and self.explicit_text == other.explicit_text ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.notice_reference, self.explicit_text)) - notice_reference = utils.read_only_property("_notice_reference") - explicit_text = utils.read_only_property("_explicit_text") + @property + def notice_reference(self) -> typing.Optional[NoticeReference]: + return self._notice_reference + + @property + def explicit_text(self) -> typing.Optional[str]: + return self._explicit_text -class NoticeReference(object): - def __init__(self, organization, notice_numbers): +class NoticeReference: + def __init__( + self, + organization: typing.Optional[str], + notice_numbers: typing.Iterable[int], + ) -> None: self._organization = organization notice_numbers = list(notice_numbers) if not all(isinstance(x, int) for x in notice_numbers): @@ -829,13 +949,13 @@ def __init__(self, organization, notice_numbers): self._notice_numbers = notice_numbers - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, NoticeReference): return NotImplemented @@ -844,21 +964,22 @@ def __eq__(self, other): and self.notice_numbers == other.notice_numbers ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.organization, tuple(self.notice_numbers))) - organization = utils.read_only_property("_organization") - notice_numbers = utils.read_only_property("_notice_numbers") + @property + def organization(self) -> typing.Optional[str]: + return self._organization + + @property + def notice_numbers(self) -> typing.List[int]: + return self._notice_numbers -@utils.register_interface(ExtensionType) -class ExtendedKeyUsage(object): +class ExtendedKeyUsage(ExtensionType): oid = ExtensionOID.EXTENDED_KEY_USAGE - def __init__(self, usages): + def __init__(self, usages: typing.Iterable[ObjectIdentifier]) -> None: usages = list(usages) if not all(isinstance(x, ObjectIdentifier) for x in usages): raise TypeError( @@ -869,67 +990,64 @@ def __init__(self, usages): __len__, __iter__, __getitem__ = _make_sequence_methods("_usages") - def __repr__(self): - return "".format(self._usages) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, ExtendedKeyUsage): return NotImplemented return self._usages == other._usages - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._usages)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class OCSPNoCheck(object): + +class OCSPNoCheck(ExtensionType): oid = ExtensionOID.OCSP_NO_CHECK - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, OCSPNoCheck): return NotImplemented return True - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(OCSPNoCheck) - def __repr__(self): + def __repr__(self) -> str: return "" + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class PrecertPoison(object): + +class PrecertPoison(ExtensionType): oid = ExtensionOID.PRECERT_POISON - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, PrecertPoison): return NotImplemented return True - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(PrecertPoison) - def __repr__(self): + def __repr__(self) -> str: return "" + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class TLSFeature(object): + +class TLSFeature(ExtensionType): oid = ExtensionOID.TLS_FEATURE - def __init__(self, features): + def __init__(self, features: typing.Iterable[TLSFeatureType]) -> None: features = list(features) if ( not all(isinstance(x, TLSFeatureType) for x in features) @@ -944,23 +1062,23 @@ def __init__(self, features): __len__, __iter__, __getitem__ = _make_sequence_methods("_features") - def __repr__(self): - return "".format(self) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, TLSFeature): return NotImplemented return self._features == other._features - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._features)) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -class TLSFeatureType(Enum): + +class TLSFeatureType(utils.Enum): # status_request is defined in RFC 6066 and is used for what is commonly # called OCSP Must-Staple when present in the TLS Feature extension in an # X.509 certificate. @@ -974,12 +1092,11 @@ class TLSFeatureType(Enum): _TLS_FEATURE_TYPE_TO_ENUM = {x.value: x for x in TLSFeatureType} -@utils.register_interface(ExtensionType) -class InhibitAnyPolicy(object): +class InhibitAnyPolicy(ExtensionType): oid = ExtensionOID.INHIBIT_ANY_POLICY - def __init__(self, skip_certs): - if not isinstance(skip_certs, six.integer_types): + def __init__(self, skip_certs: int) -> None: + if not isinstance(skip_certs, int): raise TypeError("skip_certs must be an integer") if skip_certs < 0: @@ -987,40 +1104,41 @@ def __init__(self, skip_certs): self._skip_certs = skip_certs - def __repr__(self): - return "".format(self) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, InhibitAnyPolicy): return NotImplemented return self.skip_certs == other.skip_certs - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.skip_certs) - skip_certs = utils.read_only_property("_skip_certs") + @property + def skip_certs(self) -> int: + return self._skip_certs + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class KeyUsage(object): +class KeyUsage(ExtensionType): oid = ExtensionOID.KEY_USAGE def __init__( self, - digital_signature, - content_commitment, - key_encipherment, - data_encipherment, - key_agreement, - key_cert_sign, - crl_sign, - encipher_only, - decipher_only, - ): + digital_signature: bool, + content_commitment: bool, + key_encipherment: bool, + data_encipherment: bool, + key_agreement: bool, + key_cert_sign: bool, + crl_sign: bool, + encipher_only: bool, + decipher_only: bool, + ) -> None: if not key_agreement and (encipher_only or decipher_only): raise ValueError( "encipher_only and decipher_only can only be true when " @@ -1037,16 +1155,36 @@ def __init__( self._encipher_only = encipher_only self._decipher_only = decipher_only - digital_signature = utils.read_only_property("_digital_signature") - content_commitment = utils.read_only_property("_content_commitment") - key_encipherment = utils.read_only_property("_key_encipherment") - data_encipherment = utils.read_only_property("_data_encipherment") - key_agreement = utils.read_only_property("_key_agreement") - key_cert_sign = utils.read_only_property("_key_cert_sign") - crl_sign = utils.read_only_property("_crl_sign") + @property + def digital_signature(self) -> bool: + return self._digital_signature + + @property + def content_commitment(self) -> bool: + return self._content_commitment + + @property + def key_encipherment(self) -> bool: + return self._key_encipherment + + @property + def data_encipherment(self) -> bool: + return self._data_encipherment + + @property + def key_agreement(self) -> bool: + return self._key_agreement + + @property + def key_cert_sign(self) -> bool: + return self._key_cert_sign + + @property + def crl_sign(self) -> bool: + return self._crl_sign @property - def encipher_only(self): + def encipher_only(self) -> bool: if not self.key_agreement: raise ValueError( "encipher_only is undefined unless key_agreement is true" @@ -1055,7 +1193,7 @@ def encipher_only(self): return self._encipher_only @property - def decipher_only(self): + def decipher_only(self) -> bool: if not self.key_agreement: raise ValueError( "decipher_only is undefined unless key_agreement is true" @@ -1063,7 +1201,7 @@ def decipher_only(self): else: return self._decipher_only - def __repr__(self): + def __repr__(self) -> str: try: encipher_only = self.encipher_only decipher_only = self.decipher_only @@ -1084,7 +1222,7 @@ def __repr__(self): "encipher_only={1}, decipher_only={2})>" ).format(self, encipher_only, decipher_only) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, KeyUsage): return NotImplemented @@ -1100,10 +1238,7 @@ def __eq__(self, other): and self._decipher_only == other._decipher_only ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash( ( self.digital_signature, @@ -1118,31 +1253,45 @@ def __hash__(self): ) ) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class NameConstraints(object): + +class NameConstraints(ExtensionType): oid = ExtensionOID.NAME_CONSTRAINTS - def __init__(self, permitted_subtrees, excluded_subtrees): + def __init__( + self, + permitted_subtrees: typing.Optional[typing.Iterable[GeneralName]], + excluded_subtrees: typing.Optional[typing.Iterable[GeneralName]], + ) -> None: if permitted_subtrees is not None: permitted_subtrees = list(permitted_subtrees) + if not permitted_subtrees: + raise ValueError( + "permitted_subtrees must be a non-empty list or None" + ) if not all(isinstance(x, GeneralName) for x in permitted_subtrees): raise TypeError( "permitted_subtrees must be a list of GeneralName objects " "or None" ) - self._validate_ip_name(permitted_subtrees) + self._validate_tree(permitted_subtrees) if excluded_subtrees is not None: excluded_subtrees = list(excluded_subtrees) + if not excluded_subtrees: + raise ValueError( + "excluded_subtrees must be a non-empty list or None" + ) if not all(isinstance(x, GeneralName) for x in excluded_subtrees): raise TypeError( "excluded_subtrees must be a list of GeneralName objects " "or None" ) - self._validate_ip_name(excluded_subtrees) + self._validate_tree(excluded_subtrees) if permitted_subtrees is None and excluded_subtrees is None: raise ValueError( @@ -1153,7 +1302,7 @@ def __init__(self, permitted_subtrees, excluded_subtrees): self._permitted_subtrees = permitted_subtrees self._excluded_subtrees = excluded_subtrees - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, NameConstraints): return NotImplemented @@ -1162,10 +1311,11 @@ def __eq__(self, other): and self.permitted_subtrees == other.permitted_subtrees ) - def __ne__(self, other): - return not self == other + def _validate_tree(self, tree: typing.Iterable[GeneralName]) -> None: + self._validate_ip_name(tree) + self._validate_dns_name(tree) - def _validate_ip_name(self, tree): + def _validate_ip_name(self, tree: typing.Iterable[GeneralName]) -> None: if any( isinstance(name, IPAddress) and not isinstance( @@ -1178,31 +1328,58 @@ def _validate_ip_name(self, tree): " IPv6Network object" ) - def __repr__(self): + def _validate_dns_name(self, tree: typing.Iterable[GeneralName]) -> None: + if any( + isinstance(name, DNSName) and "*" in name.value for name in tree + ): + raise ValueError( + "DNSName name constraints must not contain the '*' wildcard" + " character" + ) + + def __repr__(self) -> str: return ( - u"".format(self) + "".format(self) ) - def __hash__(self): + def __hash__(self) -> int: if self.permitted_subtrees is not None: - ps = tuple(self.permitted_subtrees) + ps: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( + self.permitted_subtrees + ) else: ps = None if self.excluded_subtrees is not None: - es = tuple(self.excluded_subtrees) + es: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( + self.excluded_subtrees + ) else: es = None return hash((ps, es)) - permitted_subtrees = utils.read_only_property("_permitted_subtrees") - excluded_subtrees = utils.read_only_property("_excluded_subtrees") + @property + def permitted_subtrees( + self, + ) -> typing.Optional[typing.List[GeneralName]]: + return self._permitted_subtrees + + @property + def excluded_subtrees( + self, + ) -> typing.Optional[typing.List[GeneralName]]: + return self._excluded_subtrees + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -class Extension(object): - def __init__(self, oid, critical, value): +class Extension(typing.Generic[ExtensionTypeVar]): + def __init__( + self, oid: ObjectIdentifier, critical: bool, value: ExtensionTypeVar + ) -> None: if not isinstance(oid, ObjectIdentifier): raise TypeError( "oid argument must be an ObjectIdentifier instance." @@ -1215,17 +1392,25 @@ def __init__(self, oid, critical, value): self._critical = critical self._value = value - oid = utils.read_only_property("_oid") - critical = utils.read_only_property("_critical") - value = utils.read_only_property("_value") + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def critical(self) -> bool: + return self._critical + + @property + def value(self) -> ExtensionTypeVar: + return self._value - def __repr__(self): + def __repr__(self) -> str: return ( "" ).format(self) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, Extension): return NotImplemented @@ -1235,15 +1420,12 @@ def __eq__(self, other): and self.value == other.value ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.oid, self.critical, self.value)) -class GeneralNames(object): - def __init__(self, general_names): +class GeneralNames: + def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: general_names = list(general_names) if not all(isinstance(x, GeneralName) for x in general_names): raise TypeError( @@ -1255,178 +1437,394 @@ def __init__(self, general_names): __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") - def get_values_for_type(self, type): + @typing.overload + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[UniformResourceIdentifier], + typing.Type[RFC822Name], + ], + ) -> typing.List[str]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[DirectoryName], + ) -> typing.List[Name]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[RegisteredID], + ) -> typing.List[ObjectIdentifier]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[IPAddress] + ) -> typing.List[_IPAddressTypes]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[OtherName] + ) -> typing.List[OtherName]: + ... + + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[DirectoryName], + typing.Type[IPAddress], + typing.Type[OtherName], + typing.Type[RFC822Name], + typing.Type[RegisteredID], + typing.Type[UniformResourceIdentifier], + ], + ) -> typing.Union[ + typing.List[_IPAddressTypes], + typing.List[str], + typing.List[OtherName], + typing.List[Name], + typing.List[ObjectIdentifier], + ]: # Return the value of each GeneralName, except for OtherName instances # which we return directly because it has two important properties not # just one value. objs = (i for i in self if isinstance(i, type)) if type != OtherName: - objs = (i.value for i in objs) + return [i.value for i in objs] return list(objs) - def __repr__(self): - return "".format(self._general_names) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, GeneralNames): return NotImplemented return self._general_names == other._general_names - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._general_names)) -@utils.register_interface(ExtensionType) -class SubjectAlternativeName(object): +class SubjectAlternativeName(ExtensionType): oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME - def __init__(self, general_names): + def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") - def get_values_for_type(self, type): + @typing.overload + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[UniformResourceIdentifier], + typing.Type[RFC822Name], + ], + ) -> typing.List[str]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[DirectoryName], + ) -> typing.List[Name]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[RegisteredID], + ) -> typing.List[ObjectIdentifier]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[IPAddress] + ) -> typing.List[_IPAddressTypes]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[OtherName] + ) -> typing.List[OtherName]: + ... + + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[DirectoryName], + typing.Type[IPAddress], + typing.Type[OtherName], + typing.Type[RFC822Name], + typing.Type[RegisteredID], + typing.Type[UniformResourceIdentifier], + ], + ) -> typing.Union[ + typing.List[_IPAddressTypes], + typing.List[str], + typing.List[OtherName], + typing.List[Name], + typing.List[ObjectIdentifier], + ]: return self._general_names.get_values_for_type(type) - def __repr__(self): - return "".format(self._general_names) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectAlternativeName): return NotImplemented return self._general_names == other._general_names - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self._general_names) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + -@utils.register_interface(ExtensionType) -class IssuerAlternativeName(object): +class IssuerAlternativeName(ExtensionType): oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME - def __init__(self, general_names): + def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") - def get_values_for_type(self, type): + @typing.overload + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[UniformResourceIdentifier], + typing.Type[RFC822Name], + ], + ) -> typing.List[str]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[DirectoryName], + ) -> typing.List[Name]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[RegisteredID], + ) -> typing.List[ObjectIdentifier]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[IPAddress] + ) -> typing.List[_IPAddressTypes]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[OtherName] + ) -> typing.List[OtherName]: + ... + + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[DirectoryName], + typing.Type[IPAddress], + typing.Type[OtherName], + typing.Type[RFC822Name], + typing.Type[RegisteredID], + typing.Type[UniformResourceIdentifier], + ], + ) -> typing.Union[ + typing.List[_IPAddressTypes], + typing.List[str], + typing.List[OtherName], + typing.List[Name], + typing.List[ObjectIdentifier], + ]: return self._general_names.get_values_for_type(type) - def __repr__(self): - return "".format(self._general_names) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, IssuerAlternativeName): return NotImplemented return self._general_names == other._general_names - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self._general_names) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class CertificateIssuer(object): + +class CertificateIssuer(ExtensionType): oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER - def __init__(self, general_names): + def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") - def get_values_for_type(self, type): + @typing.overload + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[UniformResourceIdentifier], + typing.Type[RFC822Name], + ], + ) -> typing.List[str]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[DirectoryName], + ) -> typing.List[Name]: + ... + + @typing.overload + def get_values_for_type( + self, + type: typing.Type[RegisteredID], + ) -> typing.List[ObjectIdentifier]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[IPAddress] + ) -> typing.List[_IPAddressTypes]: + ... + + @typing.overload + def get_values_for_type( + self, type: typing.Type[OtherName] + ) -> typing.List[OtherName]: + ... + + def get_values_for_type( + self, + type: typing.Union[ + typing.Type[DNSName], + typing.Type[DirectoryName], + typing.Type[IPAddress], + typing.Type[OtherName], + typing.Type[RFC822Name], + typing.Type[RegisteredID], + typing.Type[UniformResourceIdentifier], + ], + ) -> typing.Union[ + typing.List[_IPAddressTypes], + typing.List[str], + typing.List[OtherName], + typing.List[Name], + typing.List[ObjectIdentifier], + ]: return self._general_names.get_values_for_type(type) - def __repr__(self): - return "".format(self._general_names) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, CertificateIssuer): return NotImplemented return self._general_names == other._general_names - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self._general_names) + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + -@utils.register_interface(ExtensionType) -class CRLReason(object): +class CRLReason(ExtensionType): oid = CRLEntryExtensionOID.CRL_REASON - def __init__(self, reason): + def __init__(self, reason: ReasonFlags) -> None: if not isinstance(reason, ReasonFlags): raise TypeError("reason must be an element from ReasonFlags") self._reason = reason - def __repr__(self): - return "".format(self._reason) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, CRLReason): return NotImplemented return self.reason == other.reason - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.reason) - reason = utils.read_only_property("_reason") + @property + def reason(self) -> ReasonFlags: + return self._reason + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class InvalidityDate(object): +class InvalidityDate(ExtensionType): oid = CRLEntryExtensionOID.INVALIDITY_DATE - def __init__(self, invalidity_date): + def __init__(self, invalidity_date: datetime.datetime) -> None: if not isinstance(invalidity_date, datetime.datetime): raise TypeError("invalidity_date must be a datetime.datetime") self._invalidity_date = invalidity_date - def __repr__(self): + def __repr__(self) -> str: return "".format( self._invalidity_date ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, InvalidityDate): return NotImplemented return self.invalidity_date == other.invalidity_date - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.invalidity_date) - invalidity_date = utils.read_only_property("_invalidity_date") + @property + def invalidity_date(self) -> datetime.datetime: + return self._invalidity_date + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class PrecertificateSignedCertificateTimestamps(object): +class PrecertificateSignedCertificateTimestamps(ExtensionType): oid = ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS - def __init__(self, signed_certificate_timestamps): + def __init__( + self, + signed_certificate_timestamps: typing.Iterable[ + SignedCertificateTimestamp + ], + ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( isinstance(sct, SignedCertificateTimestamp) @@ -1442,15 +1840,15 @@ def __init__(self, signed_certificate_timestamps): "_signed_certificate_timestamps" ) - def __repr__(self): + def __repr__(self) -> str: return "".format( list(self) ) - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._signed_certificate_timestamps)) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, PrecertificateSignedCertificateTimestamps): return NotImplemented @@ -1459,15 +1857,19 @@ def __eq__(self, other): == other._signed_certificate_timestamps ) - def __ne__(self, other): - return not self == other + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class SignedCertificateTimestamps(object): +class SignedCertificateTimestamps(ExtensionType): oid = ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS - def __init__(self, signed_certificate_timestamps): + def __init__( + self, + signed_certificate_timestamps: typing.Iterable[ + SignedCertificateTimestamp + ], + ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( isinstance(sct, SignedCertificateTimestamp) @@ -1483,13 +1885,13 @@ def __init__(self, signed_certificate_timestamps): "_signed_certificate_timestamps" ) - def __repr__(self): - return "".format(list(self)) + def __repr__(self) -> str: + return f"" - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self._signed_certificate_timestamps)) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, SignedCertificateTimestamps): return NotImplemented @@ -1498,52 +1900,84 @@ def __eq__(self, other): == other._signed_certificate_timestamps ) - def __ne__(self, other): - return not self == other + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class OCSPNonce(object): +class OCSPNonce(ExtensionType): oid = OCSPExtensionOID.NONCE - def __init__(self, nonce): + def __init__(self, nonce: bytes) -> None: if not isinstance(nonce, bytes): raise TypeError("nonce must be bytes") self._nonce = nonce - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, OCSPNonce): return NotImplemented return self.nonce == other.nonce - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.nonce) - def __repr__(self): - return "".format(self) + def __repr__(self) -> str: + return f"" + + @property + def nonce(self) -> bytes: + return self._nonce + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class OCSPAcceptableResponses(ExtensionType): + oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES + + def __init__(self, responses: typing.Iterable[ObjectIdentifier]) -> None: + responses = list(responses) + if any(not isinstance(r, ObjectIdentifier) for r in responses): + raise TypeError("All responses must be ObjectIdentifiers") + + self._responses = responses + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OCSPAcceptableResponses): + return NotImplemented - nonce = utils.read_only_property("_nonce") + return self._responses == other._responses + def __hash__(self) -> int: + return hash(tuple(self._responses)) -@utils.register_interface(ExtensionType) -class IssuingDistributionPoint(object): + def __repr__(self) -> str: + return f"" + + def __iter__(self) -> typing.Iterator[ObjectIdentifier]: + return iter(self._responses) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class IssuingDistributionPoint(ExtensionType): oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT def __init__( self, - full_name, - relative_name, - only_contains_user_certs, - only_contains_ca_certs, - only_some_reasons, - indirect_crl, - only_contains_attribute_certs, - ): + full_name: typing.Optional[typing.Iterable[GeneralName]], + relative_name: typing.Optional[RelativeDistinguishedName], + only_contains_user_certs: bool, + only_contains_ca_certs: bool, + only_some_reasons: typing.Optional[typing.FrozenSet[ReasonFlags]], + indirect_crl: bool, + only_contains_attribute_certs: bool, + ) -> None: + if full_name is not None: + full_name = list(full_name) + if only_some_reasons and ( not isinstance(only_some_reasons, frozenset) or not all(isinstance(x, ReasonFlags) for x in only_some_reasons) @@ -1614,7 +2048,7 @@ def __init__( self._full_name = full_name self._relative_name = relative_name - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, IssuingDistributionPoint): return NotImplemented @@ -1641,10 +2075,7 @@ def __eq__(self, other): == other.only_contains_attribute_certs ) - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash( ( self.full_name, @@ -1657,46 +2088,128 @@ def __hash__(self): ) ) - full_name = utils.read_only_property("_full_name") - relative_name = utils.read_only_property("_relative_name") - only_contains_user_certs = utils.read_only_property( - "_only_contains_user_certs" - ) - only_contains_ca_certs = utils.read_only_property( - "_only_contains_ca_certs" - ) - only_some_reasons = utils.read_only_property("_only_some_reasons") - indirect_crl = utils.read_only_property("_indirect_crl") - only_contains_attribute_certs = utils.read_only_property( - "_only_contains_attribute_certs" - ) + @property + def full_name(self) -> typing.Optional[typing.List[GeneralName]]: + return self._full_name + + @property + def relative_name(self) -> typing.Optional[RelativeDistinguishedName]: + return self._relative_name + + @property + def only_contains_user_certs(self) -> bool: + return self._only_contains_user_certs + + @property + def only_contains_ca_certs(self) -> bool: + return self._only_contains_ca_certs + + @property + def only_some_reasons( + self, + ) -> typing.Optional[typing.FrozenSet[ReasonFlags]]: + return self._only_some_reasons + + @property + def indirect_crl(self) -> bool: + return self._indirect_crl + + @property + def only_contains_attribute_certs(self) -> bool: + return self._only_contains_attribute_certs + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) -@utils.register_interface(ExtensionType) -class UnrecognizedExtension(object): - def __init__(self, oid, value): + +class MSCertificateTemplate(ExtensionType): + oid = ExtensionOID.MS_CERTIFICATE_TEMPLATE + + def __init__( + self, + template_id: ObjectIdentifier, + major_version: typing.Optional[int], + minor_version: typing.Optional[int], + ) -> None: + if not isinstance(template_id, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + self._template_id = template_id + if ( + major_version is not None and not isinstance(major_version, int) + ) or ( + minor_version is not None and not isinstance(minor_version, int) + ): + raise TypeError( + "major_version and minor_version must be integers or None" + ) + self._major_version = major_version + self._minor_version = minor_version + + @property + def template_id(self) -> ObjectIdentifier: + return self._template_id + + @property + def major_version(self) -> typing.Optional[int]: + return self._major_version + + @property + def minor_version(self) -> typing.Optional[int]: + return self._minor_version + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, MSCertificateTemplate): + return NotImplemented + + return ( + self.template_id == other.template_id + and self.major_version == other.major_version + and self.minor_version == other.minor_version + ) + + def __hash__(self) -> int: + return hash((self.template_id, self.major_version, self.minor_version)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class UnrecognizedExtension(ExtensionType): + def __init__(self, oid: ObjectIdentifier, value: bytes) -> None: if not isinstance(oid, ObjectIdentifier): raise TypeError("oid must be an ObjectIdentifier") self._oid = oid self._value = value - oid = utils.read_only_property("_oid") - value = utils.read_only_property("_value") + @property + def oid(self) -> ObjectIdentifier: # type: ignore[override] + return self._oid + + @property + def value(self) -> bytes: + return self._value - def __repr__(self): + def __repr__(self) -> str: return ( "".format(self) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, UnrecognizedExtension): return NotImplemented return self.oid == other.oid and self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.oid, self.value)) + + def public_bytes(self) -> bytes: + return self.value diff --git a/src/cryptography/x509/general_name.py b/src/cryptography/x509/general_name.py index 9be9d8c991e1..79271afbf91e 100644 --- a/src/cryptography/x509/general_name.py +++ b/src/cryptography/x509/general_name.py @@ -2,51 +2,40 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc import ipaddress +import typing from email.utils import parseaddr -import six - -from cryptography import utils from cryptography.x509.name import Name from cryptography.x509.oid import ObjectIdentifier - -_GENERAL_NAMES = { - 0: "otherName", - 1: "rfc822Name", - 2: "dNSName", - 3: "x400Address", - 4: "directoryName", - 5: "ediPartyName", - 6: "uniformResourceIdentifier", - 7: "iPAddress", - 8: "registeredID", -} +_IPAddressTypes = typing.Union[ + ipaddress.IPv4Address, + ipaddress.IPv6Address, + ipaddress.IPv4Network, + ipaddress.IPv6Network, +] class UnsupportedGeneralNameType(Exception): - def __init__(self, msg, type): - super(UnsupportedGeneralNameType, self).__init__(msg) - self.type = type + pass -@six.add_metaclass(abc.ABCMeta) -class GeneralName(object): - @abc.abstractproperty - def value(self): +class GeneralName(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def value(self) -> typing.Any: """ Return the value of the object """ -@utils.register_interface(GeneralName) -class RFC822Name(object): - def __init__(self, value): - if isinstance(value, six.text_type): +class RFC822Name(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): try: value.encode("ascii") except UnicodeEncodeError: @@ -66,34 +55,32 @@ def __init__(self, value): self._value = value - value = utils.read_only_property("_value") + @property + def value(self) -> str: + return self._value @classmethod - def _init_without_validation(cls, value): + def _init_without_validation(cls, value: str) -> RFC822Name: instance = cls.__new__(cls) instance._value = value return instance - def __repr__(self): - return "".format(self.value) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, RFC822Name): return NotImplemented return self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.value) -@utils.register_interface(GeneralName) -class DNSName(object): - def __init__(self, value): - if isinstance(value, six.text_type): +class DNSName(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): try: value.encode("ascii") except UnicodeEncodeError: @@ -107,34 +94,32 @@ def __init__(self, value): self._value = value - value = utils.read_only_property("_value") + @property + def value(self) -> str: + return self._value @classmethod - def _init_without_validation(cls, value): + def _init_without_validation(cls, value: str) -> DNSName: instance = cls.__new__(cls) instance._value = value return instance - def __repr__(self): - return "".format(self.value) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DNSName): return NotImplemented return self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.value) -@utils.register_interface(GeneralName) -class UniformResourceIdentifier(object): - def __init__(self, value): - if isinstance(value, six.text_type): +class UniformResourceIdentifier(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): try: value.encode("ascii") except UnicodeEncodeError: @@ -148,85 +133,79 @@ def __init__(self, value): self._value = value - value = utils.read_only_property("_value") + @property + def value(self) -> str: + return self._value @classmethod - def _init_without_validation(cls, value): + def _init_without_validation(cls, value: str) -> UniformResourceIdentifier: instance = cls.__new__(cls) instance._value = value return instance - def __repr__(self): - return "".format(self.value) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, UniformResourceIdentifier): return NotImplemented return self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.value) -@utils.register_interface(GeneralName) -class DirectoryName(object): - def __init__(self, value): +class DirectoryName(GeneralName): + def __init__(self, value: Name) -> None: if not isinstance(value, Name): raise TypeError("value must be a Name") self._value = value - value = utils.read_only_property("_value") + @property + def value(self) -> Name: + return self._value - def __repr__(self): - return "".format(self.value) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, DirectoryName): return NotImplemented return self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.value) -@utils.register_interface(GeneralName) -class RegisteredID(object): - def __init__(self, value): +class RegisteredID(GeneralName): + def __init__(self, value: ObjectIdentifier) -> None: if not isinstance(value, ObjectIdentifier): raise TypeError("value must be an ObjectIdentifier") self._value = value - value = utils.read_only_property("_value") + @property + def value(self) -> ObjectIdentifier: + return self._value - def __repr__(self): - return "".format(self.value) + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, RegisteredID): return NotImplemented return self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.value) -@utils.register_interface(GeneralName) -class IPAddress(object): - def __init__(self, value): +class IPAddress(GeneralName): + def __init__(self, value: _IPAddressTypes) -> None: if not isinstance( value, ( @@ -244,27 +223,35 @@ def __init__(self, value): self._value = value - value = utils.read_only_property("_value") + @property + def value(self) -> _IPAddressTypes: + return self._value - def __repr__(self): - return "".format(self.value) + def _packed(self) -> bytes: + if isinstance( + self.value, (ipaddress.IPv4Address, ipaddress.IPv6Address) + ): + return self.value.packed + else: + return ( + self.value.network_address.packed + self.value.netmask.packed + ) + + def __repr__(self) -> str: + return f"" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, IPAddress): return NotImplemented return self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self.value) -@utils.register_interface(GeneralName) -class OtherName(object): - def __init__(self, type_id, value): +class OtherName(GeneralName): + def __init__(self, type_id: ObjectIdentifier, value: bytes) -> None: if not isinstance(type_id, ObjectIdentifier): raise TypeError("type_id must be an ObjectIdentifier") if not isinstance(value, bytes): @@ -273,22 +260,24 @@ def __init__(self, type_id, value): self._type_id = type_id self._value = value - type_id = utils.read_only_property("_type_id") - value = utils.read_only_property("_value") + @property + def type_id(self) -> ObjectIdentifier: + return self._type_id + + @property + def value(self) -> bytes: + return self._value - def __repr__(self): + def __repr__(self) -> str: return "".format( self.type_id, self.value ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, OtherName): return NotImplemented return self.type_id == other.type_id and self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.type_id, self.value)) diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index 0be876a0ed6c..ff98e8724af1 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -2,18 +2,22 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations -from enum import Enum - -import six +import binascii +import re +import sys +import typing +import warnings from cryptography import utils -from cryptography.hazmat.backends import _get_backend +from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.x509.oid import NameOID, ObjectIdentifier -class _ASN1Type(Enum): +class _ASN1Type(utils.Enum): + BitString = 3 + OctetString = 4 UTF8String = 12 NumericString = 18 PrintableString = 19 @@ -27,8 +31,7 @@ class _ASN1Type(Enum): _ASN1_TYPE_TO_ENUM = {i.value: i for i in _ASN1Type} -_SENTINEL = object() -_NAMEOID_DEFAULT_TYPE = { +_NAMEOID_DEFAULT_TYPE: typing.Dict[ObjectIdentifier, _ASN1Type] = { NameOID.COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString, @@ -37,9 +40,13 @@ class _ASN1Type(Enum): NameOID.DOMAIN_COMPONENT: _ASN1Type.IA5String, } +# Type alias +_OidNameMap = typing.Mapping[ObjectIdentifier, str] +_NameOidMap = typing.Mapping[str, ObjectIdentifier] + #: Short attribute names from RFC 4514: #: https://tools.ietf.org/html/rfc4514#page-7 -_NAMEOID_TO_NAME = { +_NAMEOID_TO_NAME: _OidNameMap = { NameOID.COMMON_NAME: "CN", NameOID.LOCALITY_NAME: "L", NameOID.STATE_OR_PROVINCE_NAME: "ST", @@ -50,14 +57,20 @@ class _ASN1Type(Enum): NameOID.DOMAIN_COMPONENT: "DC", NameOID.USER_ID: "UID", } +_NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()} -def _escape_dn_value(val): +def _escape_dn_value(val: typing.Union[str, bytes]) -> str: """Escape special characters in RFC4514 Distinguished Name value.""" if not val: return "" + # RFC 4514 Section 2.4 defines the value as being the # (U+0023) character + # followed by the hexadecimal encoding of the octets. + if isinstance(val, bytes): + return "#" + binascii.hexlify(val).decode("utf8") + # See https://tools.ietf.org/html/rfc4514#section-2.4 val = val.replace("\\", "\\\\") val = val.replace('"', '\\"') @@ -76,24 +89,65 @@ def _escape_dn_value(val): return val -class NameAttribute(object): - def __init__(self, oid, value, _type=_SENTINEL): +def _unescape_dn_value(val: str) -> str: + if not val: + return "" + + # See https://tools.ietf.org/html/rfc4514#section-3 + + # special = escaped / SPACE / SHARP / EQUALS + # escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE + def sub(m): + val = m.group(1) + # Regular escape + if len(val) == 1: + return val + # Hex-value scape + return chr(int(val, 16)) + + return _RFC4514NameParser._PAIR_RE.sub(sub, val) + + +class NameAttribute: + def __init__( + self, + oid: ObjectIdentifier, + value: typing.Union[str, bytes], + _type: typing.Optional[_ASN1Type] = None, + *, + _validate: bool = True, + ) -> None: if not isinstance(oid, ObjectIdentifier): raise TypeError( "oid argument must be an ObjectIdentifier instance." ) - - if not isinstance(value, six.text_type): - raise TypeError("value argument must be a text type.") + if _type == _ASN1Type.BitString: + if oid != NameOID.X500_UNIQUE_IDENTIFIER: + raise TypeError( + "oid must be X500_UNIQUE_IDENTIFIER for BitString type." + ) + if not isinstance(value, bytes): + raise TypeError("value must be bytes for BitString") + else: + if not isinstance(value, str): + raise TypeError("value argument must be a str") if ( oid == NameOID.COUNTRY_NAME or oid == NameOID.JURISDICTION_COUNTRY_NAME ): - if len(value.encode("utf8")) != 2: + assert isinstance(value, str) + c_len = len(value.encode("utf8")) + if c_len != 2 and _validate is True: raise ValueError( "Country name must be a 2 character country code" ) + elif c_len != 2: + warnings.warn( + "Country names should be two characters, but the " + "attribute is {} characters in length.".format(c_len), + stacklevel=2, + ) # The appropriate ASN1 string type varies by OID and is defined across # multiple RFCs including 2459, 3280, and 5280. In general UTF8String @@ -101,7 +155,7 @@ def __init__(self, oid, value, _type=_SENTINEL): # alternate types. This means when we see the sentinel value we need # to look up whether the OID has a non-UTF8 type. If it does, set it # to that. Otherwise, UTF8! - if _type == _SENTINEL: + if _type is None: _type = _NAMEOID_DEFAULT_TYPE.get(oid, _ASN1Type.UTF8String) if not isinstance(_type, _ASN1Type): @@ -111,37 +165,54 @@ def __init__(self, oid, value, _type=_SENTINEL): self._value = value self._type = _type - oid = utils.read_only_property("_oid") - value = utils.read_only_property("_value") + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def value(self) -> typing.Union[str, bytes]: + return self._value - def rfc4514_string(self): + @property + def rfc4514_attribute_name(self) -> str: + """ + The short attribute name (for example "CN") if available, + otherwise the OID dotted string. + """ + return _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string) + + def rfc4514_string( + self, attr_name_overrides: typing.Optional[_OidNameMap] = None + ) -> str: """ Format as RFC4514 Distinguished Name string. Use short attribute name if available, otherwise fall back to OID dotted string. """ - key = _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string) - return "%s=%s" % (key, _escape_dn_value(self.value)) + attr_name = ( + attr_name_overrides.get(self.oid) if attr_name_overrides else None + ) + if attr_name is None: + attr_name = self.rfc4514_attribute_name + + return f"{attr_name}={_escape_dn_value(self.value)}" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, NameAttribute): return NotImplemented return self.oid == other.oid and self.value == other.value - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash((self.oid, self.value)) - def __repr__(self): + def __repr__(self) -> str: return "".format(self) -class RelativeDistinguishedName(object): - def __init__(self, attributes): +class RelativeDistinguishedName: + def __init__(self, attributes: typing.Iterable[NameAttribute]): attributes = list(attributes) if not attributes: raise ValueError("a relative distinguished name cannot be empty") @@ -155,56 +226,88 @@ def __init__(self, attributes): if len(self._attribute_set) != len(attributes): raise ValueError("duplicate attributes are not allowed") - def get_attributes_for_oid(self, oid): + def get_attributes_for_oid( + self, oid: ObjectIdentifier + ) -> typing.List[NameAttribute]: return [i for i in self if i.oid == oid] - def rfc4514_string(self): + def rfc4514_string( + self, attr_name_overrides: typing.Optional[_OidNameMap] = None + ) -> str: """ Format as RFC4514 Distinguished Name string. Within each RDN, attributes are joined by '+', although that is rarely used in certificates. """ - return "+".join(attr.rfc4514_string() for attr in self._attributes) + return "+".join( + attr.rfc4514_string(attr_name_overrides) + for attr in self._attributes + ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, RelativeDistinguishedName): return NotImplemented return self._attribute_set == other._attribute_set - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: return hash(self._attribute_set) - def __iter__(self): + def __iter__(self) -> typing.Iterator[NameAttribute]: return iter(self._attributes) - def __len__(self): + def __len__(self) -> int: return len(self._attributes) - def __repr__(self): - return "".format(self.rfc4514_string()) + def __repr__(self) -> str: + return f"" + + +class Name: + @typing.overload + def __init__(self, attributes: typing.Iterable[NameAttribute]) -> None: + ... + @typing.overload + def __init__( + self, attributes: typing.Iterable[RelativeDistinguishedName] + ) -> None: + ... -class Name(object): - def __init__(self, attributes): + def __init__( + self, + attributes: typing.Iterable[ + typing.Union[NameAttribute, RelativeDistinguishedName] + ], + ) -> None: attributes = list(attributes) if all(isinstance(x, NameAttribute) for x in attributes): self._attributes = [ - RelativeDistinguishedName([x]) for x in attributes + RelativeDistinguishedName([typing.cast(NameAttribute, x)]) + for x in attributes ] elif all(isinstance(x, RelativeDistinguishedName) for x in attributes): - self._attributes = attributes + self._attributes = typing.cast( + typing.List[RelativeDistinguishedName], attributes + ) else: raise TypeError( "attributes must be a list of NameAttribute" " or a list RelativeDistinguishedName" ) - def rfc4514_string(self): + @classmethod + def from_rfc4514_string( + cls, + data: str, + attr_name_overrides: typing.Optional[_NameOidMap] = None, + ) -> Name: + return _RFC4514NameParser(data, attr_name_overrides or {}).parse() + + def rfc4514_string( + self, attr_name_overrides: typing.Optional[_OidNameMap] = None + ) -> str: """ Format as RFC4514 Distinguished Name string. For example 'CN=foobar.com,O=Foo Corp,C=US' @@ -216,46 +319,144 @@ def rfc4514_string(self): RDNSequence must be reversed when converting to string representation. """ return ",".join( - attr.rfc4514_string() for attr in reversed(self._attributes) + attr.rfc4514_string(attr_name_overrides) + for attr in reversed(self._attributes) ) - def get_attributes_for_oid(self, oid): + def get_attributes_for_oid( + self, oid: ObjectIdentifier + ) -> typing.List[NameAttribute]: return [i for i in self if i.oid == oid] @property - def rdns(self): + def rdns(self) -> typing.List[RelativeDistinguishedName]: return self._attributes - def public_bytes(self, backend=None): - backend = _get_backend(backend) - return backend.x509_name_bytes(self) + def public_bytes(self, backend: typing.Any = None) -> bytes: + return rust_x509.encode_name_bytes(self) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, Name): return NotImplemented return self._attributes == other._attributes - def __ne__(self, other): - return not self == other - - def __hash__(self): + def __hash__(self) -> int: # TODO: this is relatively expensive, if this looks like a bottleneck # for you, consider optimizing! return hash(tuple(self._attributes)) - def __iter__(self): + def __iter__(self) -> typing.Iterator[NameAttribute]: for rdn in self._attributes: for ava in rdn: yield ava - def __len__(self): + def __len__(self) -> int: return sum(len(rdn) for rdn in self._attributes) - def __repr__(self): + def __repr__(self) -> str: rdns = ",".join(attr.rfc4514_string() for attr in self._attributes) + return f"" + + +class _RFC4514NameParser: + _OID_RE = re.compile(r"(0|([1-9]\d*))(\.(0|([1-9]\d*)))+") + _DESCR_RE = re.compile(r"[a-zA-Z][a-zA-Z\d-]*") + + _PAIR = r"\\([\\ #=\"\+,;<>]|[\da-zA-Z]{2})" + _PAIR_RE = re.compile(_PAIR) + _LUTF1 = r"[\x01-\x1f\x21\x24-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _SUTF1 = r"[\x01-\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _TUTF1 = r"[\x01-\x1F\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _UTFMB = rf"[\x80-{chr(sys.maxunicode)}]" + _LEADCHAR = rf"{_LUTF1}|{_UTFMB}" + _STRINGCHAR = rf"{_SUTF1}|{_UTFMB}" + _TRAILCHAR = rf"{_TUTF1}|{_UTFMB}" + _STRING_RE = re.compile( + rf""" + ( + ({_LEADCHAR}|{_PAIR}) + ( + ({_STRINGCHAR}|{_PAIR})* + ({_TRAILCHAR}|{_PAIR}) + )? + )? + """, + re.VERBOSE, + ) + _HEXSTRING_RE = re.compile(r"#([\da-zA-Z]{2})+") + + def __init__(self, data: str, attr_name_overrides: _NameOidMap) -> None: + self._data = data + self._idx = 0 + + self._attr_name_overrides = attr_name_overrides + + def _has_data(self) -> bool: + return self._idx < len(self._data) + + def _peek(self) -> typing.Optional[str]: + if self._has_data(): + return self._data[self._idx] + return None + + def _read_char(self, ch: str) -> None: + if self._peek() != ch: + raise ValueError + self._idx += 1 + + def _read_re(self, pat) -> str: + match = pat.match(self._data, pos=self._idx) + if match is None: + raise ValueError + val = match.group() + self._idx += len(val) + return val + + def parse(self) -> Name: + """ + Parses the `data` string and converts it to a Name. + + According to RFC4514 section 2.1 the RDNSequence must be + reversed when converting to string representation. So, when + we parse it, we need to reverse again to get the RDNs on the + correct order. + """ + rdns = [self._parse_rdn()] + + while self._has_data(): + self._read_char(",") + rdns.append(self._parse_rdn()) + + return Name(reversed(rdns)) + + def _parse_rdn(self) -> RelativeDistinguishedName: + nas = [self._parse_na()] + while self._peek() == "+": + self._read_char("+") + nas.append(self._parse_na()) + + return RelativeDistinguishedName(nas) - if six.PY2: - return "".format(rdns.encode("utf8")) + def _parse_na(self) -> NameAttribute: + try: + oid_value = self._read_re(self._OID_RE) + except ValueError: + name = self._read_re(self._DESCR_RE) + oid = self._attr_name_overrides.get( + name, _NAME_TO_NAMEOID.get(name) + ) + if oid is None: + raise ValueError + else: + oid = ObjectIdentifier(oid_value) + + self._read_char("=") + if self._peek() == "#": + value = self._read_re(self._HEXSTRING_RE) + value = binascii.unhexlify(value[1:]).decode() else: - return "".format(rdns) + raw_value = self._read_re(self._STRING_RE) + value = _unescape_dn_value(raw_value) + + return NameAttribute(oid, value) diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py index f8e27224ecaf..7054795fcda8 100644 --- a/src/cryptography/x509/ocsp.py +++ b/src/cryptography/x509/ocsp.py @@ -2,16 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +from __future__ import annotations import abc import datetime -from enum import Enum +import typing -import six - -from cryptography import x509 -from cryptography.hazmat.primitives import hashes +from cryptography import utils, x509 +from cryptography.hazmat.bindings._rust import ocsp +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPrivateKeyTypes, +) from cryptography.x509.base import ( _EARLIEST_UTC_TIME, _convert_to_naive_utc_time, @@ -19,21 +21,12 @@ ) -_OIDS_TO_HASH = { - "1.3.14.3.2.26": hashes.SHA1(), - "2.16.840.1.101.3.4.2.4": hashes.SHA224(), - "2.16.840.1.101.3.4.2.1": hashes.SHA256(), - "2.16.840.1.101.3.4.2.2": hashes.SHA384(), - "2.16.840.1.101.3.4.2.3": hashes.SHA512(), -} - - -class OCSPResponderEncoding(Enum): +class OCSPResponderEncoding(utils.Enum): HASH = "By Hash" NAME = "By Name" -class OCSPResponseStatus(Enum): +class OCSPResponseStatus(utils.Enum): SUCCESSFUL = 0 MALFORMED_REQUEST = 1 INTERNAL_ERROR = 2 @@ -42,7 +35,6 @@ class OCSPResponseStatus(Enum): UNAUTHORIZED = 6 -_RESPONSE_STATUS_TO_ENUM = {x.value: x for x in OCSPResponseStatus} _ALLOWED_HASHES = ( hashes.SHA1, hashes.SHA224, @@ -52,82 +44,30 @@ class OCSPResponseStatus(Enum): ) -def _verify_algorithm(algorithm): +def _verify_algorithm(algorithm: hashes.HashAlgorithm) -> None: if not isinstance(algorithm, _ALLOWED_HASHES): raise ValueError( "Algorithm must be SHA1, SHA224, SHA256, SHA384, or SHA512" ) -class OCSPCertStatus(Enum): +class OCSPCertStatus(utils.Enum): GOOD = 0 REVOKED = 1 UNKNOWN = 2 -_CERT_STATUS_TO_ENUM = {x.value: x for x in OCSPCertStatus} - - -def load_der_ocsp_request(data): - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_der_ocsp_request(data) - - -def load_der_ocsp_response(data): - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_der_ocsp_response(data) - - -class OCSPRequestBuilder(object): - def __init__(self, request=None, extensions=[]): - self._request = request - self._extensions = extensions - - def add_certificate(self, cert, issuer, algorithm): - if self._request is not None: - raise ValueError("Only one certificate can be added to a request") - - _verify_algorithm(algorithm) - if not isinstance(cert, x509.Certificate) or not isinstance( - issuer, x509.Certificate - ): - raise TypeError("cert and issuer must be a Certificate") - - return OCSPRequestBuilder((cert, issuer, algorithm), self._extensions) - - def add_extension(self, extension, critical): - if not isinstance(extension, x509.ExtensionType): - raise TypeError("extension must be an ExtensionType") - - extension = x509.Extension(extension.oid, critical, extension) - _reject_duplicate_extension(extension, self._extensions) - - return OCSPRequestBuilder( - self._request, self._extensions + [extension] - ) - - def build(self): - from cryptography.hazmat.backends.openssl.backend import backend - - if self._request is None: - raise ValueError("You must add a certificate before building") - - return backend.create_ocsp_request(self) - - -class _SingleResponse(object): +class _SingleResponse: def __init__( self, - cert, - issuer, - algorithm, - cert_status, - this_update, - next_update, - revocation_time, - revocation_reason, + cert: x509.Certificate, + issuer: x509.Certificate, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: typing.Optional[datetime.datetime], + revocation_time: typing.Optional[datetime.datetime], + revocation_reason: typing.Optional[x509.ReasonFlags], ): if not isinstance(cert, x509.Certificate) or not isinstance( issuer, x509.Certificate @@ -187,281 +127,496 @@ def __init__( self._revocation_reason = revocation_reason -class OCSPResponseBuilder(object): - def __init__( - self, response=None, responder_id=None, certs=None, extensions=[] - ): - self._response = response - self._responder_id = responder_id - self._certs = certs - self._extensions = extensions - - def add_response( - self, - cert, - issuer, - algorithm, - cert_status, - this_update, - next_update, - revocation_time, - revocation_reason, - ): - if self._response is not None: - raise ValueError("Only one response per OCSPResponse.") - - singleresp = _SingleResponse( - cert, - issuer, - algorithm, - cert_status, - this_update, - next_update, - revocation_time, - revocation_reason, - ) - return OCSPResponseBuilder( - singleresp, - self._responder_id, - self._certs, - self._extensions, - ) - - def responder_id(self, encoding, responder_cert): - if self._responder_id is not None: - raise ValueError("responder_id can only be set once") - if not isinstance(responder_cert, x509.Certificate): - raise TypeError("responder_cert must be a Certificate") - if not isinstance(encoding, OCSPResponderEncoding): - raise TypeError( - "encoding must be an element from OCSPResponderEncoding" - ) - - return OCSPResponseBuilder( - self._response, - (responder_cert, encoding), - self._certs, - self._extensions, - ) +class OCSPRequest(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def issuer_key_hash(self) -> bytes: + """ + The hash of the issuer public key + """ - def certificates(self, certs): - if self._certs is not None: - raise ValueError("certificates may only be set once") - certs = list(certs) - if len(certs) == 0: - raise ValueError("certs must not be an empty list") - if not all(isinstance(x, x509.Certificate) for x in certs): - raise TypeError("certs must be a list of Certificates") - return OCSPResponseBuilder( - self._response, - self._responder_id, - certs, - self._extensions, - ) + @property + @abc.abstractmethod + def issuer_name_hash(self) -> bytes: + """ + The hash of the issuer name + """ - def add_extension(self, extension, critical): - if not isinstance(extension, x509.ExtensionType): - raise TypeError("extension must be an ExtensionType") + @property + @abc.abstractmethod + def hash_algorithm(self) -> hashes.HashAlgorithm: + """ + The hash algorithm used in the issuer name and key hashes + """ - extension = x509.Extension(extension.oid, critical, extension) - _reject_duplicate_extension(extension, self._extensions) + @property + @abc.abstractmethod + def serial_number(self) -> int: + """ + The serial number of the cert whose status is being checked + """ - return OCSPResponseBuilder( - self._response, - self._responder_id, - self._certs, - self._extensions + [extension], - ) + @abc.abstractmethod + def public_bytes(self, encoding: serialization.Encoding) -> bytes: + """ + Serializes the request to DER + """ - def sign(self, private_key, algorithm): - from cryptography.hazmat.backends.openssl.backend import backend + @property + @abc.abstractmethod + def extensions(self) -> x509.Extensions: + """ + The list of request extensions. Not single request extensions. + """ - if self._response is None: - raise ValueError("You must add a response before signing") - if self._responder_id is None: - raise ValueError("You must add a responder_id before signing") - return backend.create_ocsp_response( - OCSPResponseStatus.SUCCESSFUL, self, private_key, algorithm - ) +class OCSPSingleResponse(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def certificate_status(self) -> OCSPCertStatus: + """ + The status of the certificate (an element from the OCSPCertStatus enum) + """ - @classmethod - def build_unsuccessful(cls, response_status): - from cryptography.hazmat.backends.openssl.backend import backend + @property + @abc.abstractmethod + def revocation_time(self) -> typing.Optional[datetime.datetime]: + """ + The date of when the certificate was revoked or None if not + revoked. + """ - if not isinstance(response_status, OCSPResponseStatus): - raise TypeError( - "response_status must be an item from OCSPResponseStatus" - ) - if response_status is OCSPResponseStatus.SUCCESSFUL: - raise ValueError("response_status cannot be SUCCESSFUL") + @property + @abc.abstractmethod + def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: + """ + The reason the certificate was revoked or None if not specified or + not revoked. + """ - return backend.create_ocsp_response(response_status, None, None, None) + @property + @abc.abstractmethod + def this_update(self) -> datetime.datetime: + """ + The most recent time at which the status being indicated is known by + the responder to have been correct + """ + @property + @abc.abstractmethod + def next_update(self) -> typing.Optional[datetime.datetime]: + """ + The time when newer information will be available + """ -@six.add_metaclass(abc.ABCMeta) -class OCSPRequest(object): - @abc.abstractproperty - def issuer_key_hash(self): + @property + @abc.abstractmethod + def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ - @abc.abstractproperty - def issuer_name_hash(self): + @property + @abc.abstractmethod + def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ - @abc.abstractproperty - def hash_algorithm(self): + @property + @abc.abstractmethod + def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ - @abc.abstractproperty - def serial_number(self): + @property + @abc.abstractmethod + def serial_number(self) -> int: """ The serial number of the cert whose status is being checked """ - @abc.abstractmethod - def public_bytes(self, encoding): - """ - Serializes the request to DER - """ - @abc.abstractproperty - def extensions(self): +class OCSPResponse(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def responses(self) -> typing.Iterator[OCSPSingleResponse]: """ - The list of request extensions. Not single request extensions. + An iterator over the individual SINGLERESP structures in the + response """ - -@six.add_metaclass(abc.ABCMeta) -class OCSPResponse(object): - @abc.abstractproperty - def response_status(self): + @property + @abc.abstractmethod + def response_status(self) -> OCSPResponseStatus: """ The status of the response. This is a value from the OCSPResponseStatus enumeration """ - @abc.abstractproperty - def signature_algorithm_oid(self): + @property + @abc.abstractmethod + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: """ The ObjectIdentifier of the signature algorithm """ - @abc.abstractproperty - def signature_hash_algorithm(self): + @property + @abc.abstractmethod + def signature_hash_algorithm( + self, + ) -> typing.Optional[hashes.HashAlgorithm]: """ Returns a HashAlgorithm corresponding to the type of the digest signed """ - @abc.abstractproperty - def signature(self): + @property + @abc.abstractmethod + def signature(self) -> bytes: """ The signature bytes """ - @abc.abstractproperty - def tbs_response_bytes(self): + @property + @abc.abstractmethod + def tbs_response_bytes(self) -> bytes: """ The tbsResponseData bytes """ - @abc.abstractproperty - def certificates(self): + @property + @abc.abstractmethod + def certificates(self) -> typing.List[x509.Certificate]: """ A list of certificates used to help build a chain to verify the OCSP response. This situation occurs when the OCSP responder uses a delegate certificate. """ - @abc.abstractproperty - def responder_key_hash(self): + @property + @abc.abstractmethod + def responder_key_hash(self) -> typing.Optional[bytes]: """ The responder's key hash or None """ - @abc.abstractproperty - def responder_name(self): + @property + @abc.abstractmethod + def responder_name(self) -> typing.Optional[x509.Name]: """ The responder's Name or None """ - @abc.abstractproperty - def produced_at(self): + @property + @abc.abstractmethod + def produced_at(self) -> datetime.datetime: """ The time the response was produced """ - @abc.abstractproperty - def certificate_status(self): + @property + @abc.abstractmethod + def certificate_status(self) -> OCSPCertStatus: """ The status of the certificate (an element from the OCSPCertStatus enum) """ - @abc.abstractproperty - def revocation_time(self): + @property + @abc.abstractmethod + def revocation_time(self) -> typing.Optional[datetime.datetime]: """ The date of when the certificate was revoked or None if not revoked. """ - @abc.abstractproperty - def revocation_reason(self): + @property + @abc.abstractmethod + def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: """ The reason the certificate was revoked or None if not specified or not revoked. """ - @abc.abstractproperty - def this_update(self): + @property + @abc.abstractmethod + def this_update(self) -> datetime.datetime: """ The most recent time at which the status being indicated is known by the responder to have been correct """ - @abc.abstractproperty - def next_update(self): + @property + @abc.abstractmethod + def next_update(self) -> typing.Optional[datetime.datetime]: """ The time when newer information will be available """ - @abc.abstractproperty - def issuer_key_hash(self): + @property + @abc.abstractmethod + def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ - @abc.abstractproperty - def issuer_name_hash(self): + @property + @abc.abstractmethod + def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ - @abc.abstractproperty - def hash_algorithm(self): + @property + @abc.abstractmethod + def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ - @abc.abstractproperty - def serial_number(self): + @property + @abc.abstractmethod + def serial_number(self) -> int: """ The serial number of the cert whose status is being checked """ - @abc.abstractproperty - def extensions(self): + @property + @abc.abstractmethod + def extensions(self) -> x509.Extensions: """ The list of response extensions. Not single response extensions. """ - @abc.abstractproperty - def single_extensions(self): + @property + @abc.abstractmethod + def single_extensions(self) -> x509.Extensions: """ The list of single response extensions. Not response extensions. """ + + @abc.abstractmethod + def public_bytes(self, encoding: serialization.Encoding) -> bytes: + """ + Serializes the response to DER + """ + + +class OCSPRequestBuilder: + def __init__( + self, + request: typing.Optional[ + typing.Tuple[ + x509.Certificate, x509.Certificate, hashes.HashAlgorithm + ] + ] = None, + request_hash: typing.Optional[ + typing.Tuple[bytes, bytes, int, hashes.HashAlgorithm] + ] = None, + extensions: typing.List[x509.Extension[x509.ExtensionType]] = [], + ) -> None: + self._request = request + self._request_hash = request_hash + self._extensions = extensions + + def add_certificate( + self, + cert: x509.Certificate, + issuer: x509.Certificate, + algorithm: hashes.HashAlgorithm, + ) -> OCSPRequestBuilder: + if self._request is not None or self._request_hash is not None: + raise ValueError("Only one certificate can be added to a request") + + _verify_algorithm(algorithm) + if not isinstance(cert, x509.Certificate) or not isinstance( + issuer, x509.Certificate + ): + raise TypeError("cert and issuer must be a Certificate") + + return OCSPRequestBuilder( + (cert, issuer, algorithm), self._request_hash, self._extensions + ) + + def add_certificate_by_hash( + self, + issuer_name_hash: bytes, + issuer_key_hash: bytes, + serial_number: int, + algorithm: hashes.HashAlgorithm, + ) -> OCSPRequestBuilder: + if self._request is not None or self._request_hash is not None: + raise ValueError("Only one certificate can be added to a request") + + if not isinstance(serial_number, int): + raise TypeError("serial_number must be an integer") + + _verify_algorithm(algorithm) + utils._check_bytes("issuer_name_hash", issuer_name_hash) + utils._check_bytes("issuer_key_hash", issuer_key_hash) + if algorithm.digest_size != len( + issuer_name_hash + ) or algorithm.digest_size != len(issuer_key_hash): + raise ValueError( + "issuer_name_hash and issuer_key_hash must be the same length " + "as the digest size of the algorithm" + ) + + return OCSPRequestBuilder( + self._request, + (issuer_name_hash, issuer_key_hash, serial_number, algorithm), + self._extensions, + ) + + def add_extension( + self, extval: x509.ExtensionType, critical: bool + ) -> OCSPRequestBuilder: + if not isinstance(extval, x509.ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = x509.Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return OCSPRequestBuilder( + self._request, self._request_hash, self._extensions + [extension] + ) + + def build(self) -> OCSPRequest: + if self._request is None and self._request_hash is None: + raise ValueError("You must add a certificate before building") + + return ocsp.create_ocsp_request(self) + + +class OCSPResponseBuilder: + def __init__( + self, + response: typing.Optional[_SingleResponse] = None, + responder_id: typing.Optional[ + typing.Tuple[x509.Certificate, OCSPResponderEncoding] + ] = None, + certs: typing.Optional[typing.List[x509.Certificate]] = None, + extensions: typing.List[x509.Extension[x509.ExtensionType]] = [], + ): + self._response = response + self._responder_id = responder_id + self._certs = certs + self._extensions = extensions + + def add_response( + self, + cert: x509.Certificate, + issuer: x509.Certificate, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: typing.Optional[datetime.datetime], + revocation_time: typing.Optional[datetime.datetime], + revocation_reason: typing.Optional[x509.ReasonFlags], + ) -> OCSPResponseBuilder: + if self._response is not None: + raise ValueError("Only one response per OCSPResponse.") + + singleresp = _SingleResponse( + cert, + issuer, + algorithm, + cert_status, + this_update, + next_update, + revocation_time, + revocation_reason, + ) + return OCSPResponseBuilder( + singleresp, + self._responder_id, + self._certs, + self._extensions, + ) + + def responder_id( + self, encoding: OCSPResponderEncoding, responder_cert: x509.Certificate + ) -> OCSPResponseBuilder: + if self._responder_id is not None: + raise ValueError("responder_id can only be set once") + if not isinstance(responder_cert, x509.Certificate): + raise TypeError("responder_cert must be a Certificate") + if not isinstance(encoding, OCSPResponderEncoding): + raise TypeError( + "encoding must be an element from OCSPResponderEncoding" + ) + + return OCSPResponseBuilder( + self._response, + (responder_cert, encoding), + self._certs, + self._extensions, + ) + + def certificates( + self, certs: typing.Iterable[x509.Certificate] + ) -> OCSPResponseBuilder: + if self._certs is not None: + raise ValueError("certificates may only be set once") + certs = list(certs) + if len(certs) == 0: + raise ValueError("certs must not be an empty list") + if not all(isinstance(x, x509.Certificate) for x in certs): + raise TypeError("certs must be a list of Certificates") + return OCSPResponseBuilder( + self._response, + self._responder_id, + certs, + self._extensions, + ) + + def add_extension( + self, extval: x509.ExtensionType, critical: bool + ) -> OCSPResponseBuilder: + if not isinstance(extval, x509.ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = x509.Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return OCSPResponseBuilder( + self._response, + self._responder_id, + self._certs, + self._extensions + [extension], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: typing.Optional[hashes.HashAlgorithm], + ) -> OCSPResponse: + if self._response is None: + raise ValueError("You must add a response before signing") + if self._responder_id is None: + raise ValueError("You must add a responder_id before signing") + + return ocsp.create_ocsp_response( + OCSPResponseStatus.SUCCESSFUL, self, private_key, algorithm + ) + + @classmethod + def build_unsuccessful( + cls, response_status: OCSPResponseStatus + ) -> OCSPResponse: + if not isinstance(response_status, OCSPResponseStatus): + raise TypeError( + "response_status must be an item from OCSPResponseStatus" + ) + if response_status is OCSPResponseStatus.SUCCESSFUL: + raise ValueError("response_status cannot be SUCCESSFUL") + + return ocsp.create_ocsp_response(response_status, None, None, None) + + +def load_der_ocsp_request(data: bytes) -> OCSPRequest: + return ocsp.load_der_ocsp_request(data) + + +def load_der_ocsp_response(data: bytes) -> OCSPResponse: + return ocsp.load_der_ocsp_response(data) diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index 2bf606e50d6b..cda50cced5c4 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -2,264 +2,32 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - -from cryptography.hazmat._oid import ObjectIdentifier -from cryptography.hazmat.primitives import hashes - - -class ExtensionOID(object): - SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9") - SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14") - KEY_USAGE = ObjectIdentifier("2.5.29.15") - SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") - ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") - BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") - NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30") - CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31") - CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32") - POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33") - AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35") - POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36") - EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37") - FRESHEST_CRL = ObjectIdentifier("2.5.29.46") - INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54") - ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28") - AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1") - SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11") - OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") - TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24") - CRL_NUMBER = ObjectIdentifier("2.5.29.20") - DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27") - PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier( - "1.3.6.1.4.1.11129.2.4.2" - ) - PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3") - SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") - - -class OCSPExtensionOID(object): - NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2") - - -class CRLEntryExtensionOID(object): - CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") - CRL_REASON = ObjectIdentifier("2.5.29.21") - INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") - - -class NameOID(object): - COMMON_NAME = ObjectIdentifier("2.5.4.3") - COUNTRY_NAME = ObjectIdentifier("2.5.4.6") - LOCALITY_NAME = ObjectIdentifier("2.5.4.7") - STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") - STREET_ADDRESS = ObjectIdentifier("2.5.4.9") - ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") - ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") - SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") - SURNAME = ObjectIdentifier("2.5.4.4") - GIVEN_NAME = ObjectIdentifier("2.5.4.42") - TITLE = ObjectIdentifier("2.5.4.12") - GENERATION_QUALIFIER = ObjectIdentifier("2.5.4.44") - X500_UNIQUE_IDENTIFIER = ObjectIdentifier("2.5.4.45") - DN_QUALIFIER = ObjectIdentifier("2.5.4.46") - PSEUDONYM = ObjectIdentifier("2.5.4.65") - USER_ID = ObjectIdentifier("0.9.2342.19200300.100.1.1") - DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25") - EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1") - JURISDICTION_COUNTRY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.3") - JURISDICTION_LOCALITY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.1") - JURISDICTION_STATE_OR_PROVINCE_NAME = ObjectIdentifier( - "1.3.6.1.4.1.311.60.2.1.2" - ) - BUSINESS_CATEGORY = ObjectIdentifier("2.5.4.15") - POSTAL_ADDRESS = ObjectIdentifier("2.5.4.16") - POSTAL_CODE = ObjectIdentifier("2.5.4.17") - INN = ObjectIdentifier("1.2.643.3.131.1.1") - OGRN = ObjectIdentifier("1.2.643.100.1") - SNILS = ObjectIdentifier("1.2.643.100.3") - UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") - - -class SignatureAlgorithmOID(object): - RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4") - RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5") - # This is an alternate OID for RSA with SHA1 that is occasionally seen - _RSA_WITH_SHA1 = ObjectIdentifier("1.3.14.3.2.29") - RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14") - RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11") - RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12") - RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13") - RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") - ECDSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10045.4.1") - ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1") - ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2") - ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3") - ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4") - DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3") - DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1") - DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2") - ED25519 = ObjectIdentifier("1.3.101.112") - ED448 = ObjectIdentifier("1.3.101.113") - GOSTR3411_94_WITH_3410_2001 = ObjectIdentifier("1.2.643.2.2.3") - GOSTR3410_2012_WITH_3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") - GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") - - -_SIG_OIDS_TO_HASH = { - SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), - SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), - SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), - SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(), - SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(), - SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(), - SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(), - SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(), - SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(), - SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(), - SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(), - SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(), - SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(), - SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(), - SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256(), - SignatureAlgorithmOID.ED25519: None, - SignatureAlgorithmOID.ED448: None, - SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: None, - SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: None, - SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: None, -} - - -class ExtendedKeyUsageOID(object): - SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") - CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") - CODE_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.3") - EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4") - TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8") - OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9") - ANY_EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37.0") - - -class AuthorityInformationAccessOID(object): - CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") - OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") - - -class SubjectInformationAccessOID(object): - CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5") - - -class CertificatePoliciesOID(object): - CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1") - CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2") - ANY_POLICY = ObjectIdentifier("2.5.29.32.0") - - -class AttributeOID(object): - CHALLENGE_PASSWORD = ObjectIdentifier("1.2.840.113549.1.9.7") - UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") - - -_OID_NAMES = { - NameOID.COMMON_NAME: "commonName", - NameOID.COUNTRY_NAME: "countryName", - NameOID.LOCALITY_NAME: "localityName", - NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName", - NameOID.STREET_ADDRESS: "streetAddress", - NameOID.ORGANIZATION_NAME: "organizationName", - NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName", - NameOID.SERIAL_NUMBER: "serialNumber", - NameOID.SURNAME: "surname", - NameOID.GIVEN_NAME: "givenName", - NameOID.TITLE: "title", - NameOID.GENERATION_QUALIFIER: "generationQualifier", - NameOID.X500_UNIQUE_IDENTIFIER: "x500UniqueIdentifier", - NameOID.DN_QUALIFIER: "dnQualifier", - NameOID.PSEUDONYM: "pseudonym", - NameOID.USER_ID: "userID", - NameOID.DOMAIN_COMPONENT: "domainComponent", - NameOID.EMAIL_ADDRESS: "emailAddress", - NameOID.JURISDICTION_COUNTRY_NAME: "jurisdictionCountryName", - NameOID.JURISDICTION_LOCALITY_NAME: "jurisdictionLocalityName", - NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME: ( - "jurisdictionStateOrProvinceName" - ), - NameOID.BUSINESS_CATEGORY: "businessCategory", - NameOID.POSTAL_ADDRESS: "postalAddress", - NameOID.POSTAL_CODE: "postalCode", - NameOID.INN: "INN", - NameOID.OGRN: "OGRN", - NameOID.SNILS: "SNILS", - NameOID.UNSTRUCTURED_NAME: "unstructuredName", - SignatureAlgorithmOID.RSA_WITH_MD5: "md5WithRSAEncryption", - SignatureAlgorithmOID.RSA_WITH_SHA1: "sha1WithRSAEncryption", - SignatureAlgorithmOID.RSA_WITH_SHA224: "sha224WithRSAEncryption", - SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", - SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", - SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", - SignatureAlgorithmOID.RSASSA_PSS: "RSASSA-PSS", - SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", - SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", - SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", - SignatureAlgorithmOID.ECDSA_WITH_SHA384: "ecdsa-with-SHA384", - SignatureAlgorithmOID.ECDSA_WITH_SHA512: "ecdsa-with-SHA512", - SignatureAlgorithmOID.DSA_WITH_SHA1: "dsa-with-sha1", - SignatureAlgorithmOID.DSA_WITH_SHA224: "dsa-with-sha224", - SignatureAlgorithmOID.DSA_WITH_SHA256: "dsa-with-sha256", - SignatureAlgorithmOID.ED25519: "ed25519", - SignatureAlgorithmOID.ED448: "ed448", - SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: ( - "GOST R 34.11-94 with GOST R 34.10-2001" - ), - SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: ( - "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)" - ), - SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( - "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" - ), - ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", - ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", - ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", - ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection", - ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping", - ExtendedKeyUsageOID.OCSP_SIGNING: "OCSPSigning", - ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", - ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", - ExtensionOID.KEY_USAGE: "keyUsage", - ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", - ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", - ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", - ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( - "signedCertificateTimestampList" - ), - ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: ( - "signedCertificateTimestampList" - ), - ExtensionOID.PRECERT_POISON: "ctPoison", - CRLEntryExtensionOID.CRL_REASON: "cRLReason", - CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", - CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", - ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", - ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", - ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", - ExtensionOID.POLICY_MAPPINGS: "policyMappings", - ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "authorityKeyIdentifier", - ExtensionOID.POLICY_CONSTRAINTS: "policyConstraints", - ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", - ExtensionOID.FRESHEST_CRL: "freshestCRL", - ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", - ExtensionOID.ISSUING_DISTRIBUTION_POINT: ("issuingDistributionPoint"), - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", - ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", - ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", - ExtensionOID.CRL_NUMBER: "cRLNumber", - ExtensionOID.DELTA_CRL_INDICATOR: "deltaCRLIndicator", - ExtensionOID.TLS_FEATURE: "TLSFeature", - AuthorityInformationAccessOID.OCSP: "OCSP", - AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers", - SubjectInformationAccessOID.CA_REPOSITORY: "caRepository", - CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps", - CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice", - OCSPExtensionOID.NONCE: "OCSPNonce", - AttributeOID.CHALLENGE_PASSWORD: "challengePassword", -} +from __future__ import annotations + +from cryptography.hazmat._oid import ( + AttributeOID, + AuthorityInformationAccessOID, + CertificatePoliciesOID, + CRLEntryExtensionOID, + ExtendedKeyUsageOID, + ExtensionOID, + NameOID, + ObjectIdentifier, + OCSPExtensionOID, + SignatureAlgorithmOID, + SubjectInformationAccessOID, +) + +__all__ = [ + "AttributeOID", + "AuthorityInformationAccessOID", + "CRLEntryExtensionOID", + "CertificatePoliciesOID", + "ExtendedKeyUsageOID", + "ExtensionOID", + "NameOID", + "OCSPExtensionOID", + "ObjectIdentifier", + "SignatureAlgorithmOID", + "SubjectInformationAccessOID", +] diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock new file mode 100644 index 000000000000..5dcbe68034c8 --- /dev/null +++ b/src/rust/Cargo.lock @@ -0,0 +1,501 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "asn1" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c19b9324de5b815b6487e0f8098312791b09de0dbf3d5c2db1fe2d95bab973" +dependencies = [ + "asn1_derive", +] + +[[package]] +name = "asn1_derive" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a045c3ccad89f244a86bd1e6cf1a7bf645296e7692698b056399b6efd4639407" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cryptography-cffi" +version = "0.1.0" +dependencies = [ + "cc", + "openssl-sys", + "pyo3", +] + +[[package]] +name = "cryptography-openssl" +version = "0.1.0" +dependencies = [ + "foreign-types", + "foreign-types-shared", + "openssl", + "openssl-sys", +] + +[[package]] +name = "cryptography-rust" +version = "0.1.0" +dependencies = [ + "asn1", + "cc", + "cryptography-cffi", + "cryptography-openssl", + "cryptography-x509", + "foreign-types-shared", + "once_cell", + "openssl", + "openssl-sys", + "ouroboros", + "pem", + "pyo3", +] + +[[package]] +name = "cryptography-x509" +version = "0.1.0" +dependencies = [ + "asn1", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "indoc" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" + +[[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ouroboros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" +dependencies = [ + "aliasable", + "ouroboros_macro", +] + +[[package]] +name = "ouroboros_macro" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b1ac5b3731ba34fdaa9785f8d74d17448cd18f30cf19e0c7e7b1fdb5272109" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb946f5ac61bb61a5014924910d936ebd2b23b705f7a4a3c40b05c720b079a3" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd4d7c5337821916ea2a1d21d1092e8443cf34879e53a0ac653fbb98f44ff65c" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d39c55dab3fc5a4b25bbd1ac10a2da452c4aca13bb450f22818a002e29648d" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97daff08a4c48320587b5224cc98d609e3c27b6d437315bd40b605c98eeb5918" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml new file mode 100644 index 000000000000..01fba147e759 --- /dev/null +++ b/src/rust/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "cryptography-rust" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.56.0" + +[dependencies] +once_cell = "1" +pyo3 = { version = "0.18", features = ["abi3-py37"] } +asn1 = { version = "0.15.2", default-features = false } +cryptography-cffi = { path = "cryptography-cffi" } +cryptography-x509 = { path = "cryptography-x509" } +cryptography-openssl = { path = "cryptography-openssl" } +pem = "1.1" +ouroboros = "0.15" +openssl = "0.10.54" +openssl-sys = "0.9.88" +foreign-types-shared = "0.1" + +[build-dependencies] +cc = "1.0.72" + +[features] +extension-module = ["pyo3/extension-module"] +default = ["extension-module"] + +[lib] +name = "cryptography_rust" +crate-type = ["cdylib"] + +[profile.release] +overflow-checks = true + +[workspace] +members = ["cryptography-cffi", "cryptography-openssl", "cryptography-x509"] diff --git a/src/rust/build.rs b/src/rust/build.rs new file mode 100644 index 000000000000..574560394d88 --- /dev/null +++ b/src/rust/build.rs @@ -0,0 +1,20 @@ +// 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::env; + +#[allow(clippy::unusual_byte_groupings)] +fn main() { + if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { + let version = u64::from_str_radix(&version, 16).unwrap(); + + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); + if version >= 0x3_07_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER"); + } + } + if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); + } +} diff --git a/src/rust/cryptography-cffi/Cargo.toml b/src/rust/cryptography-cffi/Cargo.toml new file mode 100644 index 000000000000..65051c2a4627 --- /dev/null +++ b/src/rust/cryptography-cffi/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cryptography-cffi" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.56.0" + +[dependencies] +pyo3 = { version = "0.18", features = ["abi3-py37"] } +openssl-sys = "0.9.88" + +[build-dependencies] +cc = "1.0.72" diff --git a/src/rust/cryptography-cffi/build.rs b/src/rust/cryptography-cffi/build.rs new file mode 100644 index 000000000000..07590ad2e593 --- /dev/null +++ b/src/rust/cryptography-cffi/build.rs @@ -0,0 +1,128 @@ +// 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::env; +use std::path::Path; +use std::process::Command; + +fn main() { + let target = env::var("TARGET").unwrap(); + let openssl_static = env::var("OPENSSL_STATIC") + .map(|x| x == "1") + .unwrap_or(false); + if target.contains("apple") && openssl_static { + // On (older) OSX we need to link against the clang runtime, + // which is hidden in some non-default path. + // + // More details at https://github.com/alexcrichton/curl-rust/issues/279. + if let Some(path) = macos_link_search_path() { + println!("cargo:rustc-link-lib=clang_rt.osx"); + println!("cargo:rustc-link-search={}", path); + } + } + + let out_dir = env::var("OUT_DIR").unwrap(); + // FIXME: maybe pyo3-build-config should provide a way to do this? + let python = env::var("PYO3_PYTHON").unwrap_or_else(|_| "python3".to_string()); + println!("cargo:rerun-if-env-changed=PYO3_PYTHON"); + println!("cargo:rerun-if-changed=../../_cffi_src/"); + println!("cargo:rerun-if-changed=../../cryptography/__about__.py"); + let output = Command::new(&python) + .env("OUT_DIR", &out_dir) + .arg("../../_cffi_src/build_openssl.py") + .output() + .expect("failed to execute build_openssl.py"); + if !output.status.success() { + panic!( + "failed to run build_openssl.py, stdout: \n{}\nstderr: \n{}\n", + String::from_utf8(output.stdout).unwrap(), + String::from_utf8(output.stderr).unwrap() + ); + } + + let python_impl = run_python_script( + &python, + "import platform; print(platform.python_implementation(), end='')", + ) + .unwrap(); + println!("cargo:rustc-cfg=python_implementation=\"{}\"", python_impl); + let python_include = run_python_script( + &python, + "import sysconfig; print(sysconfig.get_path('include'), end='')", + ) + .unwrap(); + let openssl_include = + std::env::var_os("DEP_OPENSSL_INCLUDE").expect("unable to find openssl include path"); + let openssl_c = Path::new(&out_dir).join("_openssl.c"); + + let mut build = cc::Build::new(); + build + .file(openssl_c) + .include(python_include) + .include(openssl_include) + .flag_if_supported("-Wconversion") + .flag_if_supported("-Wno-error=sign-conversion") + .flag_if_supported("-Wno-unused-parameter"); + + // Enable abi3 mode if we're not using PyPy. + if python_impl != "PyPy" { + // cp37 (Python 3.7 to help our grep when we some day drop 3.7 support) + build.define("Py_LIMITED_API", "0x030700f0"); + } + + if cfg!(windows) { + build.define("WIN32_LEAN_AND_MEAN", None); + } + + build.compile("_openssl.a"); +} + +/// Run a python script using the specified interpreter binary. +fn run_python_script(interpreter: impl AsRef, script: &str) -> Result { + let interpreter = interpreter.as_ref(); + let out = Command::new(interpreter) + .env("PYTHONIOENCODING", "utf-8") + .arg("-c") + .arg(script) + .output(); + + match out { + Err(err) => Err(format!( + "failed to run the Python interpreter at {}: {}", + interpreter.display(), + err + )), + Ok(ok) if !ok.status.success() => Err(format!( + "Python script failed: {}", + String::from_utf8(ok.stderr).expect("failed to parse Python script stderr as utf-8") + )), + Ok(ok) => Ok( + String::from_utf8(ok.stdout).expect("failed to parse Python script stdout as utf-8") + ), + } +} + +fn macos_link_search_path() -> Option { + let output = Command::new("clang") + .arg("--print-search-dirs") + .output() + .ok()?; + if !output.status.success() { + println!( + "failed to run 'clang --print-search-dirs', continuing without a link search path" + ); + return None; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("libraries: =") { + let path = line.split('=').nth(1)?; + return Some(format!("{}/lib/darwin", path)); + } + } + + println!("failed to determine link search path, continuing without it"); + None +} diff --git a/src/rust/cryptography-cffi/src/lib.rs b/src/rust/cryptography-cffi/src/lib.rs new file mode 100644 index 000000000000..e263d53d8769 --- /dev/null +++ b/src/rust/cryptography-cffi/src/lib.rs @@ -0,0 +1,31 @@ +// 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. + +#[cfg(not(python_implementation = "PyPy"))] +use pyo3::FromPyPointer; + +#[cfg(python_implementation = "PyPy")] +extern "C" { + fn Cryptography_make_openssl_module() -> std::os::raw::c_int; +} +#[cfg(not(python_implementation = "PyPy"))] +extern "C" { + fn PyInit__openssl() -> *mut pyo3::ffi::PyObject; +} + +pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyModule> { + #[cfg(python_implementation = "PyPy")] + let openssl_mod = unsafe { + let res = Cryptography_make_openssl_module(); + assert_eq!(res, 0); + pyo3::types::PyModule::import(py, "_openssl")? + }; + #[cfg(not(python_implementation = "PyPy"))] + let openssl_mod = unsafe { + let ptr = PyInit__openssl(); + pyo3::types::PyModule::from_owned_ptr(py, ptr) + }; + + Ok(openssl_mod) +} diff --git a/src/rust/cryptography-openssl/Cargo.toml b/src/rust/cryptography-openssl/Cargo.toml new file mode 100644 index 000000000000..c85f406ae616 --- /dev/null +++ b/src/rust/cryptography-openssl/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cryptography-openssl" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.56.0" + +[dependencies] +openssl = "0.10.54" +ffi = { package = "openssl-sys", version = "0.9.85" } +foreign-types = "0.3" +foreign-types-shared = "0.1" diff --git a/src/rust/cryptography-openssl/build.rs b/src/rust/cryptography-openssl/build.rs new file mode 100644 index 000000000000..a0b4566a753c --- /dev/null +++ b/src/rust/cryptography-openssl/build.rs @@ -0,0 +1,24 @@ +// 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::env; + +#[allow(clippy::unusual_byte_groupings)] +fn main() { + if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { + let version = u64::from_str_radix(&version, 16).unwrap(); + + if version >= 0x3_00_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_300_OR_GREATER"); + } + } + + if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); + } + + if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); + } +} diff --git a/src/rust/cryptography-openssl/src/fips.rs b/src/rust/cryptography-openssl/src/fips.rs new file mode 100644 index 000000000000..29c4c789d838 --- /dev/null +++ b/src/rust/cryptography-openssl/src/fips.rs @@ -0,0 +1,32 @@ +// 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. + +#[cfg(all( + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, + not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) +))] +use std::ptr; + +pub fn is_enabled() -> bool { + #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] + { + return false; + } + + #[cfg(all( + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, + not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) + ))] + unsafe { + ffi::EVP_default_properties_is_fips_enabled(ptr::null_mut()) == 1 + } + + #[cfg(all( + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) + ))] + { + return openssl::fips::enabled(); + } +} diff --git a/src/rust/cryptography-openssl/src/hmac.rs b/src/rust/cryptography-openssl/src/hmac.rs new file mode 100644 index 000000000000..b30de478688d --- /dev/null +++ b/src/rust/cryptography-openssl/src/hmac.rs @@ -0,0 +1,93 @@ +// 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::{cvt, cvt_p, OpenSSLResult}; +use foreign_types_shared::{ForeignType, ForeignTypeRef}; +use std::ptr; + +foreign_types::foreign_type! { + type CType = ffi::HMAC_CTX; + fn drop = ffi::HMAC_CTX_free; + + pub struct Hmac; + pub struct HmacRef; +} + +unsafe impl Sync for Hmac {} +unsafe impl Send for Hmac {} + +impl Hmac { + pub fn new(key: &[u8], md: openssl::hash::MessageDigest) -> OpenSSLResult { + unsafe { + let h = Hmac::from_ptr(cvt_p(ffi::HMAC_CTX_new())?); + cvt(ffi::HMAC_Init_ex( + h.as_ptr(), + key.as_ptr().cast(), + key.len() + .try_into() + .expect("Key too long for OpenSSL's length type"), + md.as_ptr(), + ptr::null_mut(), + ))?; + Ok(h) + } + } +} + +impl HmacRef { + pub fn update(&mut self, data: &[u8]) -> OpenSSLResult<()> { + unsafe { + cvt(ffi::HMAC_Update(self.as_ptr(), data.as_ptr(), data.len()))?; + } + Ok(()) + } + + pub fn finish(&mut self) -> OpenSSLResult { + let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; + let mut len = ffi::EVP_MAX_MD_SIZE as std::os::raw::c_uint; + unsafe { + cvt(ffi::HMAC_Final(self.as_ptr(), buf.as_mut_ptr(), &mut len))?; + } + Ok(DigestBytes { + buf, + len: len.try_into().unwrap(), + }) + } + + pub fn copy(&self) -> OpenSSLResult { + unsafe { + let h = Hmac::from_ptr(cvt_p(ffi::HMAC_CTX_new())?); + cvt(ffi::HMAC_CTX_copy(h.as_ptr(), self.as_ptr()))?; + Ok(h) + } + } +} + +pub struct DigestBytes { + buf: [u8; ffi::EVP_MAX_MD_SIZE as usize], + len: usize, +} + +impl std::ops::Deref for DigestBytes { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + &self.buf[..self.len] + } +} + +#[cfg(test)] +mod tests { + use super::DigestBytes; + + #[test] + fn test_digest_bytes() { + let d = DigestBytes { + buf: [19; ffi::EVP_MAX_MD_SIZE as usize], + len: 12, + }; + assert_eq!(&*d, b"\x13\x13\x13\x13\x13\x13\x13\x13\x13\x13\x13\x13"); + } +} diff --git a/src/rust/cryptography-openssl/src/lib.rs b/src/rust/cryptography-openssl/src/lib.rs new file mode 100644 index 000000000000..0a2b48149e0f --- /dev/null +++ b/src/rust/cryptography-openssl/src/lib.rs @@ -0,0 +1,37 @@ +// 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 mod fips; +pub mod hmac; + +pub type OpenSSLResult = Result; + +#[inline] +fn cvt(r: std::os::raw::c_int) -> Result { + if r <= 0 { + Err(openssl::error::ErrorStack::get()) + } else { + Ok(r) + } +} + +#[inline] +fn cvt_p(r: *mut T) -> Result<*mut T, openssl::error::ErrorStack> { + if r.is_null() { + Err(openssl::error::ErrorStack::get()) + } else { + Ok(r) + } +} + +#[cfg(test)] +mod tests { + use std::ptr; + + #[test] + fn test_cvt() { + assert!(crate::cvt(-1).is_err()); + assert!(crate::cvt_p(ptr::null_mut::<()>()).is_err()); + } +} diff --git a/src/rust/cryptography-x509/Cargo.toml b/src/rust/cryptography-x509/Cargo.toml new file mode 100644 index 000000000000..017d51dd44a3 --- /dev/null +++ b/src/rust/cryptography-x509/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cryptography-x509" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.56.0" + +[dependencies] +asn1 = { version = "0.15.2", default-features = false } diff --git a/src/rust/cryptography-x509/src/certificate.rs b/src/rust/cryptography-x509/src/certificate.rs new file mode 100644 index 000000000000..2a5616e93ef9 --- /dev/null +++ b/src/rust/cryptography-x509/src/certificate.rs @@ -0,0 +1,48 @@ +// 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::common; +use crate::extensions; +use crate::extensions::Extensions; +use crate::name; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +pub struct Certificate<'a> { + pub tbs_cert: TbsCertificate<'a>, + pub signature_alg: common::AlgorithmIdentifier<'a>, + pub signature: asn1::BitString<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +pub struct TbsCertificate<'a> { + #[explicit(0)] + #[default(0)] + pub version: u8, + pub serial: asn1::BigInt<'a>, + pub signature_alg: common::AlgorithmIdentifier<'a>, + + pub issuer: name::Name<'a>, + pub validity: Validity, + pub subject: name::Name<'a>, + + pub spki: common::SubjectPublicKeyInfo<'a>, + #[implicit(1)] + pub issuer_unique_id: Option>, + #[implicit(2)] + pub subject_unique_id: Option>, + #[explicit(3)] + pub raw_extensions: Option>, +} + +impl<'a> TbsCertificate<'a> { + pub fn extensions(&'a self) -> Result, asn1::ObjectIdentifier> { + Extensions::from_raw_extensions(self.raw_extensions.as_ref()) + } +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +pub struct Validity { + pub not_before: common::Time, + pub not_after: common::Time, +} diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs new file mode 100644 index 000000000000..a882d985e9cb --- /dev/null +++ b/src/rust/cryptography-x509/src/common.rs @@ -0,0 +1,333 @@ +// 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::oid; +use asn1::Asn1DefinedByWritable; +use std::marker::PhantomData; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone, Eq, Debug)] +pub struct AlgorithmIdentifier<'a> { + pub oid: asn1::DefinedByMarker, + #[defined_by(oid)] + pub params: AlgorithmParameters<'a>, +} + +impl AlgorithmIdentifier<'_> { + pub fn oid(&self) -> &asn1::ObjectIdentifier { + self.params.item() + } +} + +#[derive(asn1::Asn1DefinedByRead, asn1::Asn1DefinedByWrite, PartialEq, Eq, Hash, Clone, Debug)] +pub enum AlgorithmParameters<'a> { + #[defined_by(oid::SHA1_OID)] + Sha1(Option), + #[defined_by(oid::SHA224_OID)] + Sha224(Option), + #[defined_by(oid::SHA256_OID)] + Sha256(Option), + #[defined_by(oid::SHA384_OID)] + Sha384(Option), + #[defined_by(oid::SHA512_OID)] + Sha512(Option), + #[defined_by(oid::SHA3_224_OID)] + Sha3_224(Option), + #[defined_by(oid::SHA3_256_OID)] + Sha3_256(Option), + #[defined_by(oid::SHA3_384_OID)] + Sha3_384(Option), + #[defined_by(oid::SHA3_512_OID)] + Sha3_512(Option), + + #[defined_by(oid::ED25519_OID)] + Ed25519, + #[defined_by(oid::ED448_OID)] + Ed448, + + // These ECDSA algorithms should have no parameters, + // but Java 11 (up to at least 11.0.19) encodes them + // with NULL parameters. The JDK team is looking to + // backport the fix as of June 2023. + #[defined_by(oid::ECDSA_WITH_SHA224_OID)] + EcDsaWithSha224(Option), + #[defined_by(oid::ECDSA_WITH_SHA256_OID)] + EcDsaWithSha256(Option), + #[defined_by(oid::ECDSA_WITH_SHA384_OID)] + EcDsaWithSha384(Option), + #[defined_by(oid::ECDSA_WITH_SHA512_OID)] + EcDsaWithSha512(Option), + + #[defined_by(oid::ECDSA_WITH_SHA3_224_OID)] + EcDsaWithSha3_224, + #[defined_by(oid::ECDSA_WITH_SHA3_256_OID)] + EcDsaWithSha3_256, + #[defined_by(oid::ECDSA_WITH_SHA3_384_OID)] + EcDsaWithSha3_384, + #[defined_by(oid::ECDSA_WITH_SHA3_512_OID)] + EcDsaWithSha3_512, + + #[defined_by(oid::RSA_WITH_SHA1_OID)] + RsaWithSha1(Option), + #[defined_by(oid::RSA_WITH_SHA1_ALT_OID)] + RsaWithSha1Alt(Option), + + #[defined_by(oid::RSA_WITH_SHA224_OID)] + RsaWithSha224(Option), + #[defined_by(oid::RSA_WITH_SHA256_OID)] + RsaWithSha256(Option), + #[defined_by(oid::RSA_WITH_SHA384_OID)] + RsaWithSha384(Option), + #[defined_by(oid::RSA_WITH_SHA512_OID)] + RsaWithSha512(Option), + + #[defined_by(oid::RSA_WITH_SHA3_224_OID)] + RsaWithSha3_224(Option), + #[defined_by(oid::RSA_WITH_SHA3_256_OID)] + RsaWithSha3_256(Option), + #[defined_by(oid::RSA_WITH_SHA3_384_OID)] + RsaWithSha3_384(Option), + #[defined_by(oid::RSA_WITH_SHA3_512_OID)] + RsaWithSha3_512(Option), + + // RsaPssParameters must be present in Certificate::tbs_cert::signature_alg::params + // and Certificate::signature_alg::params, but Certificate::tbs_cert::spki::algorithm::oid + // also uses RSASSA_PSS_OID and the params field is omitted since it has no meaning there. + #[defined_by(oid::RSASSA_PSS_OID)] + RsaPss(Option>>), + + #[defined_by(oid::DSA_WITH_SHA224_OID)] + DsaWithSha224, + #[defined_by(oid::DSA_WITH_SHA256_OID)] + DsaWithSha256, + #[defined_by(oid::DSA_WITH_SHA384_OID)] + DsaWithSha384, + #[defined_by(oid::DSA_WITH_SHA512_OID)] + DsaWithSha512, + + #[default] + Other(asn1::ObjectIdentifier, Option>), +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +pub struct SubjectPublicKeyInfo<'a> { + _algorithm: AlgorithmIdentifier<'a>, + pub subject_public_key: asn1::BitString<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] +pub struct AttributeTypeValue<'a> { + pub type_id: asn1::ObjectIdentifier, + pub value: RawTlv<'a>, +} + +// Like `asn1::Tlv` but doesn't store `full_data` so it can be constructed from +// an un-encoded tag and value. +#[derive(Hash, PartialEq, Eq, Clone)] +pub struct RawTlv<'a> { + tag: asn1::Tag, + value: &'a [u8], +} + +impl<'a> RawTlv<'a> { + pub fn new(tag: asn1::Tag, value: &'a [u8]) -> Self { + RawTlv { tag, value } + } + + pub fn tag(&self) -> asn1::Tag { + self.tag + } + pub fn data(&self) -> &'a [u8] { + self.value + } +} +impl<'a> asn1::Asn1Readable<'a> for RawTlv<'a> { + fn parse(parser: &mut asn1::Parser<'a>) -> asn1::ParseResult { + let tlv = parser.read_element::>()?; + Ok(RawTlv::new(tlv.tag(), tlv.data())) + } + + fn can_parse(_tag: asn1::Tag) -> bool { + true + } +} +impl<'a> asn1::Asn1Writable for RawTlv<'a> { + fn write(&self, w: &mut asn1::Writer<'_>) -> asn1::WriteResult { + w.write_tlv(self.tag, move |dest| dest.push_slice(self.value)) + } +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] +pub enum Time { + UtcTime(asn1::UtcTime), + GeneralizedTime(asn1::GeneralizedTime), +} + +impl Time { + pub fn as_datetime(&self) -> &asn1::DateTime { + match self { + Time::UtcTime(data) => data.as_datetime(), + Time::GeneralizedTime(data) => data.as_datetime(), + } + } +} + +#[derive(Hash, PartialEq, Clone)] +pub enum Asn1ReadableOrWritable<'a, T, U> { + Read(T, PhantomData<&'a ()>), + Write(U, PhantomData<&'a ()>), +} + +impl<'a, T, U> Asn1ReadableOrWritable<'a, T, U> { + pub fn new_read(v: T) -> Self { + Asn1ReadableOrWritable::Read(v, PhantomData) + } + + pub fn new_write(v: U) -> Self { + Asn1ReadableOrWritable::Write(v, PhantomData) + } + + pub fn unwrap_read(&self) -> &T { + match self { + Asn1ReadableOrWritable::Read(v, _) => v, + Asn1ReadableOrWritable::Write(_, _) => panic!("unwrap_read called on a Write value"), + } + } +} + +impl<'a, T: asn1::SimpleAsn1Readable<'a>, U> asn1::SimpleAsn1Readable<'a> + for Asn1ReadableOrWritable<'a, T, U> +{ + const TAG: asn1::Tag = T::TAG; + fn parse_data(data: &'a [u8]) -> asn1::ParseResult { + Ok(Self::new_read(T::parse_data(data)?)) + } +} + +impl<'a, T: asn1::SimpleAsn1Writable, U: asn1::SimpleAsn1Writable> asn1::SimpleAsn1Writable + for Asn1ReadableOrWritable<'a, T, U> +{ + const TAG: asn1::Tag = U::TAG; + fn write_data(&self, w: &mut asn1::WriteBuf) -> asn1::WriteResult { + match self { + Asn1ReadableOrWritable::Read(v, _) => T::write_data(v, w), + Asn1ReadableOrWritable::Write(v, _) => U::write_data(v, w), + } + } +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct DssSignature<'a> { + pub r: asn1::BigUint<'a>, + pub s: asn1::BigUint<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct DHParams<'a> { + pub p: asn1::BigUint<'a>, + pub g: asn1::BigUint<'a>, + pub q: Option>, +} +// RSA-PSS ASN.1 default hash algorithm +pub const PSS_SHA1_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Sha1(Some(())), +}; + +// This is defined as an AlgorithmIdentifier in RFC 4055, +// but the mask generation algorithm **must** contain an AlgorithmIdentifier +// in its params, so we define it this way. +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] +pub struct MaskGenAlgorithm<'a> { + pub oid: asn1::ObjectIdentifier, + pub params: AlgorithmIdentifier<'a>, +} + +// RSA-PSS ASN.1 default mask gen algorithm +pub const PSS_SHA1_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { + oid: oid::MGF1_OID, + params: PSS_SHA1_HASH_ALG, +}; + +// From RFC 4055 section 3.1: +// RSASSA-PSS-params ::= SEQUENCE { +// hashAlgorithm [0] HashAlgorithm DEFAULT +// sha1Identifier, +// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT +// mgf1SHA1Identifier, +// saltLength [2] INTEGER DEFAULT 20, +// trailerField [3] INTEGER DEFAULT 1 } +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] +pub struct RsaPssParameters<'a> { + #[explicit(0)] + #[default(PSS_SHA1_HASH_ALG)] + pub hash_algorithm: AlgorithmIdentifier<'a>, + #[explicit(1)] + #[default(PSS_SHA1_MASK_GEN_ALG)] + pub mask_gen_algorithm: MaskGenAlgorithm<'a>, + #[explicit(2)] + #[default(20u16)] + pub salt_length: u16, + #[explicit(3)] + #[default(1u8)] + pub _trailer_field: u8, +} + +/// A VisibleString ASN.1 element whose contents is not validated as meeting the +/// requirements (visible characters of IA5), and instead is only known to be +/// valid UTF-8. +pub struct UnvalidatedVisibleString<'a>(pub &'a str); + +impl<'a> UnvalidatedVisibleString<'a> { + pub fn as_str(&self) -> &'a str { + self.0 + } +} + +impl<'a> asn1::SimpleAsn1Readable<'a> for UnvalidatedVisibleString<'a> { + const TAG: asn1::Tag = asn1::VisibleString::TAG; + fn parse_data(data: &'a [u8]) -> asn1::ParseResult { + Ok(UnvalidatedVisibleString( + std::str::from_utf8(data) + .map_err(|_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?, + )) + } +} + +impl<'a> asn1::SimpleAsn1Writable for UnvalidatedVisibleString<'a> { + const TAG: asn1::Tag = asn1::VisibleString::TAG; + fn write_data(&self, _: &mut asn1::WriteBuf) -> asn1::WriteResult { + unimplemented!(); + } +} + +#[cfg(test)] +mod tests { + use super::{Asn1ReadableOrWritable, RawTlv, UnvalidatedVisibleString}; + use asn1::Asn1Readable; + + #[test] + #[should_panic] + fn test_unvalidated_visible_string_write() { + let v = UnvalidatedVisibleString("foo"); + asn1::write_single(&v).unwrap(); + } + + #[test] + #[should_panic] + fn test_asn1_readable_or_writable_unwrap_read() { + Asn1ReadableOrWritable::::new_write(17).unwrap_read(); + } + + #[test] + fn test_asn1_readable_or_writable_write_read_data() { + let v = Asn1ReadableOrWritable::::new_read(17); + assert_eq!(&asn1::write_single(&v).unwrap(), b"\x02\x01\x11"); + } + + #[test] + fn test_raw_tlv_can_parse() { + let t = asn1::Tag::from_bytes(&[0]).unwrap().0; + assert!(RawTlv::can_parse(t)); + } +} diff --git a/src/rust/cryptography-x509/src/crl.rs b/src/rust/cryptography-x509/src/crl.rs new file mode 100644 index 000000000000..c81a3c4a95fd --- /dev/null +++ b/src/rust/cryptography-x509/src/crl.rs @@ -0,0 +1,73 @@ +// 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::{ + common, + extensions::{self}, + name, +}; + +pub type ReasonFlags<'a> = + Option, asn1::OwnedBitString>>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] +pub struct CertificateRevocationList<'a> { + pub tbs_cert_list: TBSCertList<'a>, + pub signature_algorithm: common::AlgorithmIdentifier<'a>, + pub signature_value: asn1::BitString<'a>, +} + +pub type RevokedCertificates<'a> = Option< + common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, RevokedCertificate<'a>>, + asn1::SequenceOfWriter<'a, RevokedCertificate<'a>, Vec>>, + >, +>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] +pub struct TBSCertList<'a> { + pub version: Option, + pub signature: common::AlgorithmIdentifier<'a>, + pub issuer: name::Name<'a>, + pub this_update: common::Time, + pub next_update: Option, + pub revoked_certificates: RevokedCertificates<'a>, + #[explicit(0)] + pub raw_crl_extensions: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] +pub struct RevokedCertificate<'a> { + pub user_certificate: asn1::BigUint<'a>, + pub revocation_date: common::Time, + pub raw_crl_entry_extensions: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct IssuingDistributionPoint<'a> { + #[explicit(0)] + pub distribution_point: Option>, + + #[implicit(1)] + #[default(false)] + pub only_contains_user_certs: bool, + + #[implicit(2)] + #[default(false)] + pub only_contains_ca_certs: bool, + + #[implicit(3)] + pub only_some_reasons: ReasonFlags<'a>, + + #[implicit(4)] + #[default(false)] + pub indirect_crl: bool, + + #[implicit(5)] + #[default(false)] + pub only_contains_attribute_certs: bool, +} + +pub type CRLReason = asn1::Enumerated; diff --git a/src/rust/cryptography-x509/src/csr.rs b/src/rust/cryptography-x509/src/csr.rs new file mode 100644 index 000000000000..d2cf9b5e2739 --- /dev/null +++ b/src/rust/cryptography-x509/src/csr.rs @@ -0,0 +1,70 @@ +// 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::common; +use crate::extensions; +use crate::name; +use crate::oid; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct Csr<'a> { + pub csr_info: CertificationRequestInfo<'a>, + pub signature_alg: common::AlgorithmIdentifier<'a>, + pub signature: asn1::BitString<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct CertificationRequestInfo<'a> { + pub version: u8, + pub subject: name::Name<'a>, + pub spki: common::SubjectPublicKeyInfo<'a>, + #[implicit(0, required)] + pub attributes: Attributes<'a>, +} + +impl CertificationRequestInfo<'_> { + pub fn get_extension_attribute( + &self, + ) -> Result>, asn1::ParseError> { + for attribute in self.attributes.unwrap_read().clone() { + if attribute.type_id == oid::EXTENSION_REQUEST + || attribute.type_id == oid::MS_EXTENSION_REQUEST + { + check_attribute_length(attribute.values.unwrap_read().clone())?; + let val = attribute.values.unwrap_read().clone().next().unwrap(); + let exts = asn1::parse_single(val.full_data())?; + return Ok(Some(exts)); + } + } + Ok(None) + } +} + +pub fn check_attribute_length<'a>( + values: asn1::SetOf<'a, asn1::Tlv<'a>>, +) -> Result<(), asn1::ParseError> { + if values.count() > 1 { + // TODO: We should raise a more specific error here + // Only single-valued attributes are supported + Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue)) + } else { + Ok(()) + } +} + +pub type Attributes<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SetOf<'a, Attribute<'a>>, + asn1::SetOfWriter<'a, Attribute<'a>, Vec>>, +>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct Attribute<'a> { + pub type_id: asn1::ObjectIdentifier, + pub values: common::Asn1ReadableOrWritable< + 'a, + asn1::SetOf<'a, asn1::Tlv<'a>>, + asn1::SetOfWriter<'a, common::RawTlv<'a>, [common::RawTlv<'a>; 1]>, + >, +} diff --git a/src/rust/cryptography-x509/src/extensions.rs b/src/rust/cryptography-x509/src/extensions.rs new file mode 100644 index 000000000000..51c283af352c --- /dev/null +++ b/src/rust/cryptography-x509/src/extensions.rs @@ -0,0 +1,255 @@ +// 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::collections::HashSet; + +use crate::common; +use crate::crl; +use crate::name; + +pub type RawExtensions<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, Extension<'a>>, + asn1::SequenceOfWriter<'a, Extension<'a>, Vec>>, +>; + +/// An invariant-enforcing wrapper for `RawExtensions`. +/// +/// In particular, an `Extensions` cannot be constructed from a `RawExtensions` +/// that contains duplicated extensions (by OID). +pub struct Extensions<'a>(Option>); + +impl<'a> Extensions<'a> { + /// Create an `Extensions` from the given `RawExtensions`. + /// + /// Returns an `Err` variant containing the first duplicated extension's + /// OID, if there are any duplicates. + pub fn from_raw_extensions( + raw: Option<&RawExtensions<'a>>, + ) -> Result { + match raw { + Some(raw_exts) => { + let mut seen_oids = HashSet::new(); + + for ext in raw_exts.unwrap_read().clone() { + if !seen_oids.insert(ext.extn_id.clone()) { + return Err(ext.extn_id); + } + } + + Ok(Self(Some(raw_exts.clone()))) + } + None => Ok(Self(None)), + } + } + + /// Retrieves the extension identified by the given OID, + /// or None if the extension is not present (or no extensions are present). + pub fn get_extension(&self, oid: &asn1::ObjectIdentifier) -> Option { + self.0 + .as_ref() + .and_then(|exts| exts.unwrap_read().clone().find(|ext| &ext.extn_id == oid)) + } + + /// Returns a reference to the underlying extensions. + pub fn as_raw(&self) -> &Option> { + &self.0 + } +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] +pub struct Extension<'a> { + pub extn_id: asn1::ObjectIdentifier, + #[default(false)] + pub critical: bool, + pub extn_value: &'a [u8], +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct PolicyConstraints { + #[implicit(0)] + pub require_explicit_policy: Option, + #[implicit(1)] + pub inhibit_policy_mapping: Option, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct AccessDescription<'a> { + pub access_method: asn1::ObjectIdentifier, + pub access_location: name::GeneralName<'a>, +} + +pub type SequenceOfAccessDescriptions<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, AccessDescription<'a>>, + asn1::SequenceOfWriter<'a, AccessDescription<'a>, Vec>>, +>; + +// Needed due to clippy type complexity warning. +type SequenceOfPolicyQualifiers<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, + asn1::SequenceOfWriter<'a, PolicyQualifierInfo<'a>, Vec>>, +>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct PolicyInformation<'a> { + pub policy_identifier: asn1::ObjectIdentifier, + pub policy_qualifiers: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct PolicyQualifierInfo<'a> { + pub policy_qualifier_id: asn1::ObjectIdentifier, + pub qualifier: Qualifier<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub enum Qualifier<'a> { + CpsUri(asn1::IA5String<'a>), + UserNotice(UserNotice<'a>), +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct UserNotice<'a> { + pub notice_ref: Option>, + pub explicit_text: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct NoticeReference<'a> { + pub organization: DisplayText<'a>, + pub notice_numbers: common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, asn1::BigUint<'a>>, + asn1::SequenceOfWriter<'a, asn1::BigUint<'a>, Vec>>, + >, +} + +// DisplayText also allows BMPString, which we currently do not support. +#[allow(clippy::enum_variant_names)] +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub enum DisplayText<'a> { + IA5String(asn1::IA5String<'a>), + Utf8String(asn1::Utf8String<'a>), + // Not validated due to certificates with UTF-8 in VisibleString. See PR #8884 + VisibleString(common::UnvalidatedVisibleString<'a>), + BmpString(asn1::BMPString<'a>), +} + +// Needed due to clippy type complexity warning. +pub type SequenceOfSubtrees<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, GeneralSubtree<'a>>, + asn1::SequenceOfWriter<'a, GeneralSubtree<'a>, Vec>>, +>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct NameConstraints<'a> { + #[implicit(0)] + pub permitted_subtrees: Option>, + + #[implicit(1)] + pub excluded_subtrees: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct GeneralSubtree<'a> { + pub base: name::GeneralName<'a>, + + #[implicit(0)] + #[default(0u64)] + pub minimum: u64, + + #[implicit(1)] + pub maximum: Option, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct MSCertificateTemplate { + pub template_id: asn1::ObjectIdentifier, + pub major_version: Option, + pub minor_version: Option, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct DistributionPoint<'a> { + #[explicit(0)] + pub distribution_point: Option>, + + #[implicit(1)] + pub reasons: crl::ReasonFlags<'a>, + + #[implicit(2)] + pub crl_issuer: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub enum DistributionPointName<'a> { + #[implicit(0)] + FullName(name::SequenceOfGeneralName<'a>), + + #[implicit(1)] + NameRelativeToCRLIssuer( + common::Asn1ReadableOrWritable< + 'a, + asn1::SetOf<'a, common::AttributeTypeValue<'a>>, + asn1::SetOfWriter< + 'a, + common::AttributeTypeValue<'a>, + Vec>, + >, + >, + ), +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct AuthorityKeyIdentifier<'a> { + #[implicit(0)] + pub key_identifier: Option<&'a [u8]>, + #[implicit(1)] + pub authority_cert_issuer: Option>, + #[implicit(2)] + pub authority_cert_serial_number: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct BasicConstraints { + #[default(false)] + pub ca: bool, + pub path_length: Option, +} + +#[cfg(test)] +mod tests { + use asn1::SequenceOfWriter; + + use crate::oid::{AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID}; + + use super::{BasicConstraints, Extension, Extensions}; + + #[test] + fn test_get_extension() { + let extension_value = BasicConstraints { + ca: true, + path_length: Some(3), + }; + let extension = Extension { + extn_id: BASIC_CONSTRAINTS_OID, + critical: true, + extn_value: &asn1::write_single(&extension_value).unwrap(), + }; + let extensions = SequenceOfWriter::new(vec![extension]); + + let der = asn1::write_single(&extensions).unwrap(); + + let extensions: Extensions = + Extensions::from_raw_extensions(Some(&asn1::parse_single(&der).unwrap())).unwrap(); + + assert!(&extensions.get_extension(&BASIC_CONSTRAINTS_OID).is_some()); + assert!(&extensions + .get_extension(&AUTHORITY_KEY_IDENTIFIER_OID) + .is_none()); + } +} diff --git a/src/rust/cryptography-x509/src/lib.rs b/src/rust/cryptography-x509/src/lib.rs new file mode 100644 index 000000000000..131c3fd156eb --- /dev/null +++ b/src/rust/cryptography-x509/src/lib.rs @@ -0,0 +1,18 @@ +// 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. + +#![forbid(unsafe_code)] +// These can be removed once our MSRV is >1.60 +#![allow(renamed_and_removed_lints, clippy::eval_order_dependence)] + +pub mod certificate; +pub mod common; +pub mod crl; +pub mod csr; +pub mod extensions; +pub mod name; +pub mod ocsp_req; +pub mod ocsp_resp; +pub mod oid; +pub mod pkcs7; diff --git a/src/rust/cryptography-x509/src/name.rs b/src/rust/cryptography-x509/src/name.rs new file mode 100644 index 000000000000..f53e342cbf33 --- /dev/null +++ b/src/rust/cryptography-x509/src/name.rs @@ -0,0 +1,88 @@ +// 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::common; + +pub type Name<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, asn1::SetOf<'a, common::AttributeTypeValue<'a>>>, + asn1::SequenceOfWriter< + 'a, + asn1::SetOfWriter<'a, common::AttributeTypeValue<'a>, Vec>>, + Vec< + asn1::SetOfWriter< + 'a, + common::AttributeTypeValue<'a>, + Vec>, + >, + >, + >, +>; + +/// An IA5String ASN.1 element whose contents is not validated as meeting the +/// requirements (ASCII characters only), and instead is only known to be +/// valid UTF-8. +pub struct UnvalidatedIA5String<'a>(pub &'a str); + +impl<'a> asn1::SimpleAsn1Readable<'a> for UnvalidatedIA5String<'a> { + const TAG: asn1::Tag = asn1::IA5String::TAG; + fn parse_data(data: &'a [u8]) -> asn1::ParseResult { + Ok(UnvalidatedIA5String(std::str::from_utf8(data).map_err( + |_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue), + )?)) + } +} + +impl<'a> asn1::SimpleAsn1Writable for UnvalidatedIA5String<'a> { + const TAG: asn1::Tag = asn1::IA5String::TAG; + fn write_data(&self, dest: &mut asn1::WriteBuf) -> asn1::WriteResult { + dest.push_slice(self.0.as_bytes()) + } +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] +pub struct OtherName<'a> { + pub type_id: asn1::ObjectIdentifier, + #[explicit(0, required)] + pub value: asn1::Tlv<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub enum GeneralName<'a> { + #[implicit(0)] + OtherName(OtherName<'a>), + + #[implicit(1)] + RFC822Name(UnvalidatedIA5String<'a>), + + #[implicit(2)] + DNSName(UnvalidatedIA5String<'a>), + + #[implicit(3)] + // unsupported + X400Address(asn1::Sequence<'a>), + + // Name is explicit per RFC 5280 Appendix A.1. + #[explicit(4)] + DirectoryName(Name<'a>), + + #[implicit(5)] + // unsupported + EDIPartyName(asn1::Sequence<'a>), + + #[implicit(6)] + UniformResourceIdentifier(UnvalidatedIA5String<'a>), + + #[implicit(7)] + IPAddress(&'a [u8]), + + #[implicit(8)] + RegisteredID(asn1::ObjectIdentifier), +} + +pub(crate) type SequenceOfGeneralName<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, GeneralName<'a>>, + asn1::SequenceOfWriter<'a, GeneralName<'a>, Vec>>, +>; diff --git a/src/rust/cryptography-x509/src/ocsp_req.rs b/src/rust/cryptography-x509/src/ocsp_req.rs new file mode 100644 index 000000000000..ba54d391f506 --- /dev/null +++ b/src/rust/cryptography-x509/src/ocsp_req.rs @@ -0,0 +1,50 @@ +// 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::{ + common, + extensions::{self}, + name, +}; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct TBSRequest<'a> { + #[explicit(0)] + #[default(0)] + pub version: u8, + #[explicit(1)] + pub requestor_name: Option>, + pub request_list: common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, Request<'a>>, + asn1::SequenceOfWriter<'a, Request<'a>>, + >, + #[explicit(2)] + pub raw_request_extensions: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct Request<'a> { + pub req_cert: CertID<'a>, + #[explicit(0)] + pub single_request_extensions: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct CertID<'a> { + pub hash_algorithm: common::AlgorithmIdentifier<'a>, + pub issuer_name_hash: &'a [u8], + pub issuer_key_hash: &'a [u8], + pub serial_number: asn1::BigInt<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct OCSPRequest<'a> { + pub tbs_request: TBSRequest<'a>, + // Parsing out the full structure, which includes the entirety of a + // certificate is more trouble than it's worth, since it's not in the + // Python API. + #[explicit(0)] + pub optional_signature: Option>, +} diff --git a/src/rust/cryptography-x509/src/ocsp_resp.rs b/src/rust/cryptography-x509/src/ocsp_resp.rs new file mode 100644 index 000000000000..21f01e2c7375 --- /dev/null +++ b/src/rust/cryptography-x509/src/ocsp_resp.rs @@ -0,0 +1,91 @@ +// 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::{ + certificate, common, crl, + extensions::{self}, + name, ocsp_req, +}; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct OCSPResponse<'a> { + pub response_status: asn1::Enumerated, + #[explicit(0)] + pub response_bytes: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct ResponseBytes<'a> { + pub response_type: asn1::ObjectIdentifier, + pub response: asn1::OctetStringEncoded>, +} + +pub type OCSPCerts<'a> = Option< + common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, certificate::Certificate<'a>>, + asn1::SequenceOfWriter<'a, certificate::Certificate<'a>, Vec>>, + >, +>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct BasicOCSPResponse<'a> { + pub tbs_response_data: ResponseData<'a>, + pub signature_algorithm: common::AlgorithmIdentifier<'a>, + pub signature: asn1::BitString<'a>, + #[explicit(0)] + pub certs: OCSPCerts<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct ResponseData<'a> { + #[explicit(0)] + #[default(0)] + pub version: u8, + pub responder_id: ResponderId<'a>, + pub produced_at: asn1::GeneralizedTime, + pub responses: common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, SingleResponse<'a>>, + asn1::SequenceOfWriter<'a, SingleResponse<'a>, Vec>>, + >, + #[explicit(1)] + pub raw_response_extensions: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub enum ResponderId<'a> { + #[explicit(1)] + ByName(name::Name<'a>), + #[explicit(2)] + ByKey(&'a [u8]), +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct SingleResponse<'a> { + pub cert_id: ocsp_req::CertID<'a>, + pub cert_status: CertStatus, + pub this_update: asn1::GeneralizedTime, + #[explicit(0)] + pub next_update: Option, + #[explicit(1)] + pub raw_single_extensions: Option>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub enum CertStatus { + #[implicit(0)] + Good(()), + #[implicit(1)] + Revoked(RevokedInfo), + #[implicit(2)] + Unknown(()), +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct RevokedInfo { + pub revocation_time: asn1::GeneralizedTime, + #[explicit(0)] + pub revocation_reason: Option, +} diff --git a/src/rust/cryptography-x509/src/oid.rs b/src/rust/cryptography-x509/src/oid.rs new file mode 100644 index 000000000000..ac80b9a31365 --- /dev/null +++ b/src/rust/cryptography-x509/src/oid.rs @@ -0,0 +1,99 @@ +// 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 const EXTENSION_REQUEST: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 14); +pub const MS_EXTENSION_REQUEST: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 311, 2, 1, 14); +pub const MS_CERTIFICATE_TEMPLATE: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 311, 21, 7); +pub const PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 2); +pub const PRECERT_POISON_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 3); +pub const SIGNED_CERTIFICATE_TIMESTAMPS_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 5); +pub const AUTHORITY_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 1); +pub const SUBJECT_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 11); +pub const TLS_FEATURE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 24); +pub const CP_CPS_URI_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 2, 1); +pub const CP_USER_NOTICE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 2, 2); +pub const NONCE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 2); +pub const OCSP_NO_CHECK_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 5); +pub const SUBJECT_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 14); +pub const KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 15); +pub const SUBJECT_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 17); +pub const ISSUER_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 18); +pub const BASIC_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 19); +pub const CRL_NUMBER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 20); +pub const CRL_REASON_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 21); +pub const INVALIDITY_DATE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 24); +pub const DELTA_CRL_INDICATOR_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 27); +pub const ISSUING_DISTRIBUTION_POINT_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 28); +pub const CERTIFICATE_ISSUER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 29); +pub const NAME_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 30); +pub const CRL_DISTRIBUTION_POINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 31); +pub const CERTIFICATE_POLICIES_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 32); +pub const AUTHORITY_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 35); +pub const POLICY_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 36); +pub const EXTENDED_KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37); +pub const FRESHEST_CRL_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 46); +pub const INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 54); +pub const ACCEPTABLE_RESPONSES_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 4); + +// Signing methods +pub const ECDSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 1); +pub const ECDSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 2); +pub const ECDSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 3); +pub const ECDSA_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 4); +pub const ECDSA_WITH_SHA3_224_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 9); +pub const ECDSA_WITH_SHA3_256_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 10); +pub const ECDSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 11); +pub const ECDSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 12); + +pub const RSA_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 5); +pub const RSA_WITH_SHA1_ALT_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 14, 3, 2, 29); +pub const RSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 14); +pub const RSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 11); +pub const RSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 12); +pub const RSA_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 13); +pub const RSA_WITH_SHA3_224_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 13); +pub const RSA_WITH_SHA3_256_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 14); +pub const RSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 15); +pub const RSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 16); + +pub const DSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 1); +pub const DSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 2); +pub const DSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 3); +pub const DSA_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 4); + +pub const ED25519_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 112); +pub const ED448_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 113); + +// Hashes +pub const SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 14, 3, 2, 26); +pub const SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 4); +pub const SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 1); +pub const SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 2); +pub const SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 3); +pub const SHA3_224_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 224); +pub const SHA3_256_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 256); +pub const SHA3_384_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 384); +pub const SHA3_512_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 512); + +pub const MGF1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 8); +pub const RSASSA_PSS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 10); diff --git a/src/rust/cryptography-x509/src/pkcs7.rs b/src/rust/cryptography-x509/src/pkcs7.rs new file mode 100644 index 000000000000..c5b7a9e3f650 --- /dev/null +++ b/src/rust/cryptography-x509/src/pkcs7.rs @@ -0,0 +1,60 @@ +// 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::{certificate, common, csr, name}; + +pub const PKCS7_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 1); +pub const PKCS7_SIGNED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 2); + +#[derive(asn1::Asn1Write)] +pub struct ContentInfo<'a> { + pub _content_type: asn1::DefinedByMarker, + + #[defined_by(_content_type)] + pub content: Content<'a>, +} + +#[derive(asn1::Asn1DefinedByWrite)] +pub enum Content<'a> { + #[defined_by(PKCS7_SIGNED_DATA_OID)] + SignedData(asn1::Explicit<'a, Box>, 0>), + #[defined_by(PKCS7_DATA_OID)] + Data(Option>), +} + +#[derive(asn1::Asn1Write)] +pub struct SignedData<'a> { + pub version: u8, + pub digest_algorithms: asn1::SetOfWriter<'a, common::AlgorithmIdentifier<'a>>, + pub content_info: ContentInfo<'a>, + #[implicit(0)] + pub certificates: Option>>, + + // We don't ever supply any of these, so for now, don't fill out the fields. + #[implicit(1)] + pub crls: Option>>, + + pub signer_infos: asn1::SetOfWriter<'a, SignerInfo<'a>>, +} + +#[derive(asn1::Asn1Write)] +pub struct SignerInfo<'a> { + pub version: u8, + pub issuer_and_serial_number: IssuerAndSerialNumber<'a>, + pub digest_algorithm: common::AlgorithmIdentifier<'a>, + #[implicit(0)] + pub authenticated_attributes: Option>, + + pub digest_encryption_algorithm: common::AlgorithmIdentifier<'a>, + pub encrypted_digest: &'a [u8], + + #[implicit(1)] + pub unauthenticated_attributes: Option>, +} + +#[derive(asn1::Asn1Write)] +pub struct IssuerAndSerialNumber<'a> { + pub issuer: name::Name<'a>, + pub serial_number: asn1::BigInt<'a>, +} diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs new file mode 100644 index 000000000000..12827ccca5a3 --- /dev/null +++ b/src/rust/src/asn1.rs @@ -0,0 +1,191 @@ +// 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::error::{CryptographyError, CryptographyResult}; +use asn1::SimpleAsn1Readable; +use cryptography_x509::certificate::Certificate; +use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo, Time}; +use cryptography_x509::name::Name; +use pyo3::basic::CompareOp; +use pyo3::types::IntoPyDict; +use pyo3::ToPyObject; + +pub(crate) fn py_oid_to_oid(py_oid: &pyo3::PyAny) -> pyo3::PyResult { + Ok(py_oid + .downcast::>()? + .borrow() + .oid + .clone()) +} + +pub(crate) fn oid_to_py_oid<'p>( + py: pyo3::Python<'p>, + oid: &asn1::ObjectIdentifier, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + Ok(pyo3::Py::new(py, crate::oid::ObjectIdentifier { oid: oid.clone() })?.into_ref(py)) +} + +#[pyo3::prelude::pyfunction] +fn parse_spki_for_data( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { + let spki = asn1::parse_single::>(data)?; + if spki.subject_public_key.padding_bits() != 0 { + return Err(pyo3::exceptions::PyValueError::new_err("Invalid public key encoding").into()); + } + + Ok(pyo3::types::PyBytes::new(py, spki.subject_public_key.as_bytes()).to_object(py)) +} + +pub(crate) fn big_byte_slice_to_py_int<'p>( + py: pyo3::Python<'p>, + v: &'_ [u8], +) -> pyo3::PyResult<&'p pyo3::PyAny> { + let int_type = py.get_type::(); + let kwargs = [("signed", true)].into_py_dict(py); + int_type.call_method(pyo3::intern!(py, "from_bytes"), (v, "big"), Some(kwargs)) +} + +#[pyo3::prelude::pyfunction] +fn decode_dss_signature( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { + let sig = asn1::parse_single::>(data)?; + + Ok(( + big_byte_slice_to_py_int(py, sig.r.as_bytes())?, + big_byte_slice_to_py_int(py, sig.s.as_bytes())?, + ) + .to_object(py)) +} + +pub(crate) fn py_uint_to_big_endian_bytes<'p>( + py: pyo3::Python<'p>, + v: &'p pyo3::types::PyLong, +) -> pyo3::PyResult<&'p [u8]> { + let zero = (0).to_object(py); + if v.rich_compare(zero, CompareOp::Lt)?.is_true()? { + return Err(pyo3::exceptions::PyValueError::new_err( + "Negative integers are not supported", + )); + } + + // Round the length up so that we prefix an extra \x00. This ensures that + // integers that'd have the high bit set in their first octet are not + // encoded as negative in DER. + let n = v + .call_method0(pyo3::intern!(py, "bit_length"))? + .extract::()? + / 8 + + 1; + v.call_method1(pyo3::intern!(py, "to_bytes"), (n, "big"))? + .extract() +} + +pub(crate) fn encode_der_data<'p>( + py: pyo3::Python<'p>, + pem_tag: String, + data: Vec, + 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"))?) { + Ok(pyo3::types::PyBytes::new(py, &data)) + } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + Ok(pyo3::types::PyBytes::new( + py, + &pem::encode_config( + &pem::Pem { + tag: pem_tag, + contents: data, + }, + pem::EncodeConfig { + line_ending: pem::LineEnding::LF, + }, + ) + .into_bytes(), + )) + } else { + Err( + pyo3::exceptions::PyTypeError::new_err("encoding must be Encoding.DER or Encoding.PEM") + .into(), + ) + } +} + +#[pyo3::prelude::pyfunction] +fn encode_dss_signature( + py: pyo3::Python<'_>, + r: &pyo3::types::PyLong, + s: &pyo3::types::PyLong, +) -> CryptographyResult { + let sig = DssSignature { + r: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, r)?).unwrap(), + s: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, s)?).unwrap(), + }; + let result = asn1::write_single(&sig)?; + Ok(pyo3::types::PyBytes::new(py, &result).to_object(py)) +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.asn1")] +struct TestCertificate { + #[pyo3(get)] + not_before_tag: u8, + #[pyo3(get)] + not_after_tag: u8, + #[pyo3(get)] + issuer_value_tags: Vec, + #[pyo3(get)] + subject_value_tags: Vec, +} + +fn parse_name_value_tags(rdns: &Name<'_>) -> Vec { + let mut tags = vec![]; + for rdn in rdns.unwrap_read().clone() { + let mut attributes = rdn.collect::>(); + assert_eq!(attributes.len(), 1); + + tags.push(attributes.pop().unwrap().value.tag().as_u8().unwrap()); + } + tags +} + +fn time_tag(t: &Time) -> u8 { + match t { + Time::UtcTime(_) => asn1::UtcTime::TAG.as_u8().unwrap(), + Time::GeneralizedTime(_) => asn1::GeneralizedTime::TAG.as_u8().unwrap(), + } +} + +#[pyo3::prelude::pyfunction] +fn test_parse_certificate(data: &[u8]) -> Result { + let cert = asn1::parse_single::>(data)?; + + Ok(TestCertificate { + not_before_tag: time_tag(&cert.tbs_cert.validity.not_before), + not_after_tag: time_tag(&cert.tbs_cert.validity.not_after), + issuer_value_tags: parse_name_value_tags(&cert.tbs_cert.issuer), + subject_value_tags: parse_name_value_tags(&cert.tbs_cert.subject), + }) +} + +pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let submod = pyo3::prelude::PyModule::new(py, "asn1")?; + submod.add_function(pyo3::wrap_pyfunction!(parse_spki_for_data, submod)?)?; + + submod.add_function(pyo3::wrap_pyfunction!(decode_dss_signature, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(encode_dss_signature, submod)?)?; + + submod.add_function(pyo3::wrap_pyfunction!(test_parse_certificate, submod)?)?; + + Ok(submod) +} diff --git a/src/rust/src/backend/dh.rs b/src/rust/src/backend/dh.rs new file mode 100644 index 000000000000..d5993ff5a056 --- /dev/null +++ b/src/rust/src/backend/dh.rs @@ -0,0 +1,438 @@ +// 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::asn1::encode_der_data; +use crate::backend::utils; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509; +use cryptography_x509::common; +use foreign_types_shared::ForeignTypeRef; + +const MIN_MODULUS_SIZE: u32 = 512; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] +struct DHPrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] +struct DHPublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] +struct DHParameters { + dh: openssl::dh::Dh, +} + +#[pyo3::prelude::pyfunction] +fn generate_parameters(generator: u32, key_size: u32) -> CryptographyResult { + if key_size < MIN_MODULUS_SIZE { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "DH key_size must be at least {} bits", + MIN_MODULUS_SIZE + )), + )); + } + if generator != 2 && generator != 5 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("DH generator must be 2 or 5"), + )); + } + + let dh = openssl::dh::Dh::generate_params(key_size, generator) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Unable to generate DH parameters"))?; + Ok(DHParameters { dh }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> DHPrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DHPrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> DHPublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DHPublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn from_der_parameters(data: &[u8]) -> CryptographyResult { + let asn1_params = asn1::parse_single::>(data)?; + + let p = openssl::bn::BigNum::from_slice(asn1_params.p.as_bytes())?; + let q = asn1_params + .q + .map(|q| openssl::bn::BigNum::from_slice(q.as_bytes())) + .transpose()?; + let g = openssl::bn::BigNum::from_slice(asn1_params.g.as_bytes())?; + + Ok(DHParameters { + dh: openssl::dh::Dh::from_pqg(p, q, g)?, + }) +} + +#[pyo3::prelude::pyfunction] +fn from_pem_parameters(data: &[u8]) -> CryptographyResult { + let parsed = x509::find_in_pem( + data, + |p| p.tag == "DH PARAMETERS" || p.tag == "X9.42 DH PARAMETERS", + "Valid PEM but no BEGIN DH PARAMETERS/END DH PARAMETERS delimiters. Are you sure this is a DH parameters?", + )?; + + from_der_parameters(&parsed.contents) +} + +fn dh_parameters_from_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult> { + let p = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "p"))?)?; + let q = numbers + .getattr(pyo3::intern!(py, "q"))? + .extract::>()? + .map(|v| utils::py_int_to_bn(py, v)) + .transpose()?; + let g = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "g"))?)?; + + Ok(openssl::dh::Dh::from_pqg(p, q, g)?) +} + +#[pyo3::prelude::pyfunction] +fn from_private_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let public_numbers = numbers.getattr(pyo3::intern!(py, "public_numbers"))?; + let parameter_numbers = public_numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + + let dh = dh_parameters_from_numbers(py, parameter_numbers)?; + + let pub_key = utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "y"))?)?; + let priv_key = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "x"))?)?; + + let dh = dh.set_key(pub_key, priv_key)?; + if !dh.check_key()? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "DH private numbers did not pass safety checks.", + ), + )); + } + + let pkey = openssl::pkey::PKey::from_dh(dh)?; + Ok(DHPrivateKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_public_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let parameter_numbers = numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + let dh = dh_parameters_from_numbers(py, parameter_numbers)?; + + let pub_key = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "y"))?)?; + + let pkey = openssl::pkey::PKey::from_dh(dh.set_public_key(pub_key)?)?; + + Ok(DHPublicKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_parameter_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let dh = dh_parameters_from_numbers(py, numbers)?; + Ok(DHParameters { dh }) +} + +fn clone_dh( + dh: &openssl::dh::Dh, +) -> CryptographyResult> { + let p = dh.prime_p().to_owned()?; + let q = dh.prime_q().map(|q| q.to_owned()).transpose()?; + let g = dh.generator().to_owned()?; + Ok(openssl::dh::Dh::from_pqg(p, q, g)?) +} + +#[pyo3::prelude::pymethods] +impl DHPrivateKey { + #[getter] + fn key_size(&self) -> i32 { + self.pkey.dh().unwrap().prime_p().num_bits() + } + + fn exchange<'p>( + &self, + py: pyo3::Python<'p>, + public_key: &DHPublicKey, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; + deriver + .set_peer(&public_key.pkey) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; + + Ok(pyo3::types::PyBytes::new_with(py, deriver.len()?, |b| { + let n = deriver.derive(b).unwrap(); + + let pad = b.len() - n; + if pad > 0 { + b.copy_within(0..n, pad); + for c in b.iter_mut().take(pad) { + *c = 0; + } + } + Ok(()) + })?) + } + + fn private_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let dh = self.pkey.dh().unwrap(); + + let py_p = utils::bn_to_py_int(py, dh.prime_p())?; + let py_q = dh + .prime_q() + .map(|q| utils::bn_to_py_int(py, q)) + .transpose()?; + let py_g = utils::bn_to_py_int(py, dh.generator())?; + + 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), + )?) + } + + fn public_key(&self) -> CryptographyResult { + let orig_dh = self.pkey.dh().unwrap(); + let dh = clone_dh(&orig_dh)?; + + let pkey = + openssl::pkey::PKey::from_dh(dh.set_public_key(orig_dh.public_key().to_owned()?)?)?; + + Ok(DHPublicKey { pkey }) + } + + fn parameters(&self) -> CryptographyResult { + Ok(DHParameters { + dh: clone_dh(&self.pkey.dh().unwrap())?, + }) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + 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"))?) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "DH private keys support only PKCS8 serialization", + ), + )); + } + + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + true, + false, + ) + } +} + +#[pyo3::prelude::pymethods] +impl DHPublicKey { + #[getter] + fn key_size(&self) -> i32 { + self.pkey.dh().unwrap().prime_p().num_bits() + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + 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"))?) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "DH public keys support only SubjectPublicKeyInfo serialization", + ), + )); + } + + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) + } + + fn parameters(&self) -> CryptographyResult { + Ok(DHParameters { + dh: clone_dh(&self.pkey.dh().unwrap())?, + }) + } + + fn public_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let dh = self.pkey.dh().unwrap(); + + let py_p = utils::bn_to_py_int(py, dh.prime_p())?; + let py_q = dh + .prime_q() + .map(|q| utils::bn_to_py_int(py, q)) + .transpose()?; + let py_g = utils::bn_to_py_int(py, dh.generator())?; + + 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))?; + + Ok(dh_mod.call_method1( + pyo3::intern!(py, "DHPublicNumbers"), + (py_pub_key, parameter_numbers), + )?) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, DHPublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + 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")), + } + } +} + +#[pyo3::prelude::pymethods] +impl DHParameters { + fn generate_private_key(&self) -> CryptographyResult { + let dh = clone_dh(&self.dh)?.generate_key()?; + Ok(DHPrivateKey { + pkey: openssl::pkey::PKey::from_dh(dh)?, + }) + } + + fn parameter_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let py_p = utils::bn_to_py_int(py, self.dh.prime_p())?; + let py_q = self + .dh + .prime_q() + .map(|q| utils::bn_to_py_int(py, q)) + .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))?) + } + + fn parameter_bytes<'p>( + &self, + py: pyo3::Python<'p>, + 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"))?) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Only PKCS3 serialization is supported"), + )); + } + + let p_bytes = utils::bn_to_big_endian_bytes(self.dh.prime_p())?; + let q_bytes = self + .dh + .prime_q() + .map(utils::bn_to_big_endian_bytes) + .transpose()?; + let g_bytes = utils::bn_to_big_endian_bytes(self.dh.generator())?; + let asn1dh_params = common::DHParams { + p: asn1::BigUint::new(&p_bytes).unwrap(), + q: q_bytes.as_ref().map(|q| asn1::BigUint::new(q).unwrap()), + g: asn1::BigUint::new(&g_bytes).unwrap(), + }; + let data = asn1::write_single(&asn1dh_params)?; + let tag = if q_bytes.is_none() { + "DH PARAMETERS" + } else { + "X9.42 DH PARAMETERS" + }; + encode_der_data(py, tag.to_string(), data, encoding) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "dh")?; + m.add_function(pyo3::wrap_pyfunction!(generate_parameters, m)?)?; + 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!(from_der_parameters, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_pem_parameters, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_private_numbers, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_numbers, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_parameter_numbers, m)?)?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + m.add("MIN_MODULUS_SIZE", MIN_MODULUS_SIZE)?; + + Ok(m) +} diff --git a/src/rust/src/backend/dsa.rs b/src/rust/src/backend/dsa.rs new file mode 100644 index 000000000000..59a5a676d5d5 --- /dev/null +++ b/src/rust/src/backend/dsa.rs @@ -0,0 +1,333 @@ +// 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::utils; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.dsa", + name = "DSAPrivateKey" +)] +struct DsaPrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.dsa", + name = "DSAPublicKey" +)] +struct DsaPublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.dsa", + name = "DSAParameters" +)] +struct DsaParameters { + dsa: openssl::dsa::Dsa, +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> DsaPrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DsaPrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> DsaPublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DsaPublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn generate_parameters(key_size: u32) -> CryptographyResult { + let dsa = openssl::dsa::Dsa::generate_params(key_size)?; + Ok(DsaParameters { dsa }) +} + +#[pyo3::prelude::pyfunction] +fn from_private_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let public_numbers = numbers.getattr(pyo3::intern!(py, "public_numbers"))?; + let parameter_numbers = public_numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + + let dsa = openssl::dsa::Dsa::from_private_components( + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "p"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "q"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "g"))?)?, + utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "x"))?)?, + utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "y"))?)?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_dsa(dsa)?; + Ok(DsaPrivateKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_public_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let parameter_numbers = numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + + let dsa = openssl::dsa::Dsa::from_public_components( + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "p"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "q"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "g"))?)?, + utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "y"))?)?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_dsa(dsa)?; + Ok(DsaPublicKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_parameter_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let dsa = openssl::dsa::Dsa::from_pqg( + 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, "g"))?)?, + ) + .unwrap(); + Ok(DsaParameters { dsa }) +} + +fn clone_dsa_params( + d: &openssl::dsa::Dsa, +) -> Result, openssl::error::ErrorStack> { + openssl::dsa::Dsa::from_pqg(d.p().to_owned()?, d.q().to_owned()?, d.g().to_owned()?) +} + +#[pyo3::prelude::pymethods] +impl DsaPrivateKey { + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + 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), + )? + .extract()?; + + let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + signer.sign_init()?; + let mut sig = vec![]; + signer.sign_to_vec(data, &mut sig)?; + Ok(pyo3::types::PyBytes::new(py, &sig)) + } + + #[getter] + fn key_size(&self) -> i32 { + self.pkey.dsa().unwrap().p().num_bits() + } + + fn public_key(&self) -> CryptographyResult { + let priv_dsa = self.pkey.dsa()?; + let pub_dsa = openssl::dsa::Dsa::from_public_components( + priv_dsa.p().to_owned()?, + priv_dsa.q().to_owned()?, + priv_dsa.g().to_owned()?, + priv_dsa.pub_key().to_owned()?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_dsa(pub_dsa)?; + Ok(DsaPublicKey { pkey }) + } + + fn parameters(&self) -> CryptographyResult { + let dsa = clone_dsa_params(&self.pkey.dsa().unwrap())?; + Ok(DsaParameters { dsa }) + } + + fn private_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let dsa = self.pkey.dsa().unwrap(); + + let py_p = utils::bn_to_py_int(py, dsa.p())?; + let py_q = utils::bn_to_py_int(py, dsa.q())?; + let py_g = utils::bn_to_py_int(py, dsa.g())?; + + 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), + )?) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + 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 DsaPublicKey { + fn verify( + &self, + py: pyo3::Python<'_>, + signature: &[u8], + 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), + )? + .extract()?; + + let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + verifier.verify_init()?; + let valid = verifier.verify(data, signature).unwrap_or(false); + if !valid { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err(()), + )); + } + + Ok(()) + } + + #[getter] + fn key_size(&self) -> i32 { + self.pkey.dsa().unwrap().p().num_bits() + } + + fn parameters(&self) -> CryptographyResult { + let dsa = clone_dsa_params(&self.pkey.dsa().unwrap())?; + Ok(DsaParameters { dsa }) + } + + fn public_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let dsa = self.pkey.dsa().unwrap(); + + let py_p = utils::bn_to_py_int(py, dsa.p())?; + let py_q = utils::bn_to_py_int(py, dsa.q())?; + let py_g = utils::bn_to_py_int(py, dsa.g())?; + + 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), + )?) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + 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<'_, DsaPublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + 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")), + } + } +} + +#[pyo3::prelude::pymethods] +impl DsaParameters { + fn generate_private_key(&self) -> CryptographyResult { + let dsa = clone_dsa_params(&self.dsa)?.generate_key()?; + let pkey = openssl::pkey::PKey::from_dsa(dsa)?; + Ok(DsaPrivateKey { pkey }) + } + + fn parameter_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let py_p = utils::bn_to_py_int(py, self.dsa.p())?; + 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))?) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "dsa")?; + 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_parameters, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_private_numbers, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_numbers, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_parameter_numbers, m)?)?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/ed25519.rs b/src/rust/src/backend/ed25519.rs new file mode 100644 index 000000000000..7bee88104482 --- /dev/null +++ b/src/rust/src/backend/ed25519.rs @@ -0,0 +1,177 @@ +// 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::utils; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +struct Ed25519PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +struct Ed25519PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(Ed25519PrivateKey { + pkey: openssl::pkey::PKey::generate_ed25519()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> Ed25519PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed25519PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> Ed25519PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed25519PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { + let pkey = openssl::pkey::PKey::private_key_from_raw_bytes( + data.as_bytes(), + openssl::pkey::Id::ED25519, + ) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err("An Ed25519 private key is 32 bytes long") + })?; + Ok(Ed25519PrivateKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { + let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::ED25519) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err("An Ed25519 public key is 32 bytes long") + })?; + Ok(Ed25519PublicKey { pkey }) +} + +#[pyo3::prelude::pymethods] +impl Ed25519PrivateKey { + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + data: &[u8], + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; + Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { + let n = signer + .sign_oneshot(b, data) + .map_err(CryptographyError::from)?; + assert_eq!(n, b.len()); + Ok(()) + })?) + } + + fn public_key(&self) -> CryptographyResult { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(Ed25519PublicKey { + pkey: openssl::pkey::PKey::public_key_from_raw_bytes( + &raw_bytes, + openssl::pkey::Id::ED25519, + )?, + }) + } + + fn private_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + 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, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl Ed25519PublicKey { + fn verify(&self, signature: &[u8], data: &[u8]) -> CryptographyResult<()> { + let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? + .verify_oneshot(signature, data)?; + + if !valid { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err(()), + )); + } + + Ok(()) + } + + fn public_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + 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, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, Ed25519PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + 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")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "ed25519")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + 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!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/ed448.rs b/src/rust/src/backend/ed448.rs new file mode 100644 index 000000000000..c0c621a321c3 --- /dev/null +++ b/src/rust/src/backend/ed448.rs @@ -0,0 +1,175 @@ +// 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::utils; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +struct Ed448PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +struct Ed448PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(Ed448PrivateKey { + pkey: openssl::pkey::PKey::generate_ed448()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> Ed448PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed448PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> Ed448PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed448PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { + let pkey = + openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::ED448) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err("An Ed448 private key is 56 bytes long") + })?; + Ok(Ed448PrivateKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { + let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::ED448) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err("An Ed448 public key is 57 bytes long") + })?; + Ok(Ed448PublicKey { pkey }) +} + +#[pyo3::prelude::pymethods] +impl Ed448PrivateKey { + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + data: &[u8], + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; + Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { + let n = signer + .sign_oneshot(b, data) + .map_err(CryptographyError::from)?; + assert_eq!(n, b.len()); + Ok(()) + })?) + } + + fn public_key(&self) -> CryptographyResult { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(Ed448PublicKey { + pkey: openssl::pkey::PKey::public_key_from_raw_bytes( + &raw_bytes, + openssl::pkey::Id::ED448, + )?, + }) + } + + fn private_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + 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, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl Ed448PublicKey { + fn verify(&self, signature: &[u8], data: &[u8]) -> CryptographyResult<()> { + let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? + .verify_oneshot(signature, data)?; + + if !valid { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err(()), + )); + } + + Ok(()) + } + + fn public_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + 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, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, Ed448PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + 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")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "ed448")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + 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!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/hashes.rs b/src/rust/src/backend/hashes.rs new file mode 100644 index 000000000000..d9157d6e8a18 --- /dev/null +++ b/src/rust/src/backend/hashes.rs @@ -0,0 +1,143 @@ +// 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::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use std::borrow::Cow; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")] +struct Hash { + #[pyo3(get)] + algorithm: pyo3::Py, + ctx: Option, +} + +pub(crate) fn already_finalized_error() -> CryptographyError { + CryptographyError::from(exceptions::AlreadyFinalized::new_err( + "Context was already finalized.", + )) +} + +impl Hash { + fn get_ctx(&self) -> CryptographyResult<&openssl::hash::Hasher> { + if let Some(ctx) = self.ctx.as_ref() { + return Ok(ctx); + }; + Err(already_finalized_error()) + } + + fn get_mut_ctx(&mut self) -> CryptographyResult<&mut openssl::hash::Hasher> { + if let Some(ctx) = self.ctx.as_mut() { + return Ok(ctx); + } + Err(already_finalized_error()) + } +} + +pub(crate) fn message_digest_from_algorithm( + py: pyo3::Python<'_>, + algorithm: &pyo3::PyAny, +) -> CryptographyResult { + 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)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err("Expected instance of hashes.HashAlgorithm."), + )); + } + + let name = algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?; + let openssl_name = if name == "blake2b" || name == "blake2s" { + let digest_size = algorithm + .getattr(pyo3::intern!(py, "digest_size"))? + .extract::()?; + Cow::Owned(format!("{}{}", name, digest_size * 8)) + } else { + Cow::Borrowed(name) + }; + + match openssl::hash::MessageDigest::from_name(&openssl_name) { + Some(md) => Ok(md), + None => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!("{} is not a supported hash on this backend", name), + exceptions::Reasons::UNSUPPORTED_HASH, + )), + )), + } +} + +#[pyo3::pymethods] +impl Hash { + #[new] + #[pyo3(signature = (algorithm, backend=None))] + fn new( + py: pyo3::Python<'_>, + algorithm: &pyo3::PyAny, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let md = message_digest_from_algorithm(py, algorithm)?; + let ctx = openssl::hash::Hasher::new(md)?; + + Ok(Hash { + algorithm: algorithm.into(), + ctx: Some(ctx), + }) + } + + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + self.get_mut_ctx()?.update(data.as_bytes())?; + Ok(()) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> 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)? { + let ctx = self.get_mut_ctx()?; + let digest_size = algorithm + .getattr(pyo3::intern!(py, "digest_size"))? + .extract::()?; + let result = pyo3::types::PyBytes::new_with(py, digest_size, |b| { + ctx.finish_xof(b).unwrap(); + Ok(()) + })?; + self.ctx = None; + return Ok(result); + } + } + + let data = self.get_mut_ctx()?.finish()?; + self.ctx = None; + Ok(pyo3::types::PyBytes::new(py, &data)) + } + + fn copy(&self, py: pyo3::Python<'_>) -> CryptographyResult { + Ok(Hash { + algorithm: self.algorithm.clone_ref(py), + ctx: Some(self.get_ctx()?.clone()), + }) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "hashes")?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/hmac.rs b/src/rust/src/backend/hmac.rs new file mode 100644 index 000000000000..13509b859024 --- /dev/null +++ b/src/rust/src/backend/hmac.rs @@ -0,0 +1,95 @@ +// 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::{already_finalized_error, message_digest_from_algorithm}; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.hmac", + name = "HMAC" +)] +struct Hmac { + #[pyo3(get)] + algorithm: pyo3::Py, + ctx: Option, +} + +impl Hmac { + fn get_ctx(&self) -> CryptographyResult<&cryptography_openssl::hmac::Hmac> { + if let Some(ctx) = self.ctx.as_ref() { + return Ok(ctx); + }; + Err(already_finalized_error()) + } + + fn get_mut_ctx(&mut self) -> CryptographyResult<&mut cryptography_openssl::hmac::Hmac> { + if let Some(ctx) = self.ctx.as_mut() { + return Ok(ctx); + } + Err(already_finalized_error()) + } +} + +#[pyo3::pymethods] +impl Hmac { + #[new] + #[pyo3(signature = (key, algorithm, backend=None))] + fn new( + py: pyo3::Python<'_>, + key: CffiBuf<'_>, + algorithm: &pyo3::PyAny, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let md = message_digest_from_algorithm(py, algorithm)?; + let ctx = cryptography_openssl::hmac::Hmac::new(key.as_bytes(), md)?; + + Ok(Hmac { + ctx: Some(ctx), + algorithm: algorithm.into(), + }) + } + + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + self.get_mut_ctx()?.update(data.as_bytes())?; + Ok(()) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let data = self.get_mut_ctx()?.finish()?; + self.ctx = None; + Ok(pyo3::types::PyBytes::new(py, &data)) + } + + fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { + let actual = self.finalize(py)?.as_bytes(); + if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err("Signature did not match digest."), + )); + } + + Ok(()) + } + + fn copy(&self, py: pyo3::Python<'_>) -> CryptographyResult { + Ok(Hmac { + ctx: Some(self.get_ctx()?.copy()?), + algorithm: self.algorithm.clone_ref(py), + }) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "hmac")?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/kdf.rs b/src/rust/src/backend/kdf.rs new file mode 100644 index 000000000000..de527f4671da --- /dev/null +++ b/src/rust/src/backend/kdf.rs @@ -0,0 +1,60 @@ +// 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; +use crate::buf::CffiBuf; +use crate::error::CryptographyResult; + +#[pyo3::prelude::pyfunction] +fn derive_pbkdf2_hmac<'p>( + py: pyo3::Python<'p>, + key_material: CffiBuf<'_>, + algorithm: &pyo3::PyAny, + salt: &[u8], + iterations: usize, + length: usize, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let md = hashes::message_digest_from_algorithm(py, algorithm)?; + + Ok(pyo3::types::PyBytes::new_with(py, length, |b| { + openssl::pkcs5::pbkdf2_hmac(key_material.as_bytes(), salt, iterations, md, b).unwrap(); + Ok(()) + })?) +} + +#[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] +#[pyo3::prelude::pyfunction] +#[allow(clippy::too_many_arguments)] +fn derive_scrypt<'p>( + py: pyo3::Python<'p>, + key_material: CffiBuf<'_>, + salt: &[u8], + n: u64, + r: u64, + p: u64, + max_mem: u64, + length: usize, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + Ok(pyo3::types::PyBytes::new_with(py, length, |b| { + openssl::pkcs5::scrypt(key_material.as_bytes(), salt, n, r, p, max_mem, b).map_err(|_| { + // memory required formula explained here: + // https://blog.filippo.io/the-scrypt-parameters/ + let min_memory = 128 * n * r / (1024 * 1024); + pyo3::exceptions::PyMemoryError::new_err(format!( + "Not enough memory to derive key. These parameters require {}MB of memory.", + min_memory + )) + }) + })?) +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "kdf")?; + + m.add_function(pyo3::wrap_pyfunction!(derive_pbkdf2_hmac, m)?)?; + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] + m.add_function(pyo3::wrap_pyfunction!(derive_scrypt, m)?)?; + + Ok(m) +} diff --git a/src/rust/src/backend/mod.rs b/src/rust/src/backend/mod.rs new file mode 100644 index 000000000000..765b0ab199f4 --- /dev/null +++ b/src/rust/src/backend/mod.rs @@ -0,0 +1,42 @@ +// 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(crate) mod dh; +pub(crate) mod dsa; +#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] +pub(crate) mod ed25519; +#[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] +pub(crate) mod ed448; +pub(crate) mod hashes; +pub(crate) mod hmac; +pub(crate) mod kdf; +pub(crate) mod poly1305; +pub(crate) mod utils; +#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] +pub(crate) mod x25519; +#[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] +pub(crate) mod x448; + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_submodule(dh::create_module(module.py())?)?; + module.add_submodule(dsa::create_module(module.py())?)?; + + #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] + module.add_submodule(ed25519::create_module(module.py())?)?; + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + module.add_submodule(ed448::create_module(module.py())?)?; + + #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] + module.add_submodule(x25519::create_module(module.py())?)?; + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + module.add_submodule(x448::create_module(module.py())?)?; + + module.add_submodule(poly1305::create_module(module.py())?)?; + + module.add_submodule(hashes::create_module(module.py())?)?; + module.add_submodule(hmac::create_module(module.py())?)?; + module.add_submodule(kdf::create_module(module.py())?)?; + + Ok(()) +} diff --git a/src/rust/src/backend/poly1305.rs b/src/rust/src/backend/poly1305.rs new file mode 100644 index 000000000000..17d279a4023f --- /dev/null +++ b/src/rust/src/backend/poly1305.rs @@ -0,0 +1,127 @@ +// 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::already_finalized_error; +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>, +} + +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()) + } +} + +#[pyo3::pymethods] +impl Poly1305 { + #[new] + fn new(key: CffiBuf<'_>) -> CryptographyResult { + #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] + { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "poly1305 is not supported by this version of OpenSSL.", + exceptions::Reasons::UNSUPPORTED_MAC, + )), + )); + } + + #[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(|_| { + 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") + })?, + ), + }) + } + } + + #[staticmethod] + fn generate_tag<'p>( + py: pyo3::Python<'p>, + key: CffiBuf<'_>, + data: CffiBuf<'_>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut p = Poly1305::new(key)?; + p.update(data)?; + p.finalize(py) + } + + #[staticmethod] + fn verify_tag( + py: pyo3::Python<'_>, + key: CffiBuf<'_>, + data: CffiBuf<'_>, + tag: &[u8], + ) -> CryptographyResult<()> { + let mut p = Poly1305::new(key)?; + p.update(data)?; + p.verify(py, tag) + } + + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + self.get_mut_signer()?.update(data.as_bytes())?; + Ok(()) + } + + 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) + } + + fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { + let actual = self.finalize(py)?.as_bytes(); + if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err("Value did not match computed tag."), + )); + } + + Ok(()) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "poly1305")?; + + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/utils.rs b/src/rust/src/backend/utils.rs new file mode 100644 index 000000000000..dea36117182b --- /dev/null +++ b/src/rust/src/backend/utils.rs @@ -0,0 +1,302 @@ +// 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::error::{CryptographyError, CryptographyResult}; + +pub(crate) fn py_int_to_bn( + py: pyo3::Python<'_>, + v: &pyo3::PyAny, +) -> CryptographyResult { + let n = v + .call_method0(pyo3::intern!(py, "bit_length"))? + .extract::()? + / 8 + + 1; + let bytes: &[u8] = v + .call_method1(pyo3::intern!(py, "to_bytes"), (n, pyo3::intern!(py, "big")))? + .extract()?; + + Ok(openssl::bn::BigNum::from_slice(bytes)?) +} + +pub(crate) fn bn_to_py_int<'p>( + py: pyo3::Python<'p>, + b: &openssl::bn::BigNumRef, +) -> CryptographyResult<&'p pyo3::PyAny> { + assert!(!b.is_negative()); + + let int_type = py.get_type::(); + Ok(int_type.call_method1( + pyo3::intern!(py, "from_bytes"), + (b.to_vec(), pyo3::intern!(py, "big")), + )?) +} + +pub(crate) fn bn_to_big_endian_bytes(b: &openssl::bn::BigNumRef) -> CryptographyResult> { + Ok(b.to_vec_padded(b.num_bits() / 8 + 1)?) +} + +#[allow(clippy::too_many_arguments)] +pub(crate) fn pkey_private_bytes<'p>( + py: pyo3::Python<'p>, + key_obj: &pyo3::PyAny, + pkey: &openssl::pkey::PKey, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + 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()?; + + if !encoding.is_instance(encoding_class)? { + 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)? { + 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)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Encryption algorithm must be a KeySerializationEncryption instance", + ), + )); + } + + #[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"))?)) + { + 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)? + { + 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()" + ))); + } + let raw_bytes = pkey.raw_private_key()?; + return Ok(pyo3::types::PyBytes::new(py, &raw_bytes)); + } + + let password = if encryption_algorithm.is_instance(no_encryption_class)? { + b"" + } else if encryption_algorithm.is_instance(best_available_encryption_class)? { + encryption_algorithm + .getattr(pyo3::intern!(py, "password"))? + .extract::<&[u8]>()? + } else { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Unsupported encryption type"), + )); + }; + + if password.len() > 1023 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Passwords longer than 1023 bytes are not supported by this backend", + ), + )); + } + + if format.is(private_format_class.getattr(pyo3::intern!(py, "PKCS8"))?) { + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + let pem_bytes = if password.is_empty() { + pkey.private_key_to_pem_pkcs8()? + } else { + pkey.private_key_to_pem_pkcs8_passphrase( + openssl::symm::Cipher::aes_256_cbc(), + password, + )? + }; + return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); + } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + let der_bytes = if password.is_empty() { + pkey.private_key_to_pkcs8()? + } else { + pkey.private_key_to_pkcs8_passphrase( + openssl::symm::Cipher::aes_256_cbc(), + password, + )? + }; + return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); + } + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Unsupported encoding for PKCS8"), + )); + } + + 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"))?) { + let pem_bytes = if password.is_empty() { + dsa.private_key_to_pem()? + } else { + dsa.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(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + 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 = dsa.private_key_to_der()?; + return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); + } + } + } + + // 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), + )? + .extract()?); + } + + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "OpenSSH private key format can only be used with PEM encoding", + ), + )); + } + + Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("format is invalid with this key"), + )) +} + +pub(crate) fn pkey_public_bytes<'p>( + py: pyo3::Python<'p>, + key_obj: &pyo3::PyAny, + pkey: &openssl::pkey::PKey, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + 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)? { + 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)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "format must be an item from the PublicFormat enum", + ), + )); + } + + #[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"))?)) + { + if !encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) + || !format.is(public_format_class.getattr(pyo3::intern!(py, "Raw"))?) + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "When using Raw both encoding and format must be Raw", + ), + )); + } + let raw_bytes = pkey.raw_public_key()?; + return Ok(pyo3::types::PyBytes::new(py, &raw_bytes)); + } + + // SubjectPublicKeyInfo + PEM/DER + if format.is(public_format_class.getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?) { + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + 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"))?) { + let der_bytes = pkey.public_key_to_der()?; + return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); + } + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "SubjectPublicKeyInfo 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,))? + .extract()?); + } + + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "OpenSSH format must be used with OpenSSH encoding", + ), + )); + } + + Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("format is invalid with this key"), + )) +} diff --git a/src/rust/src/backend/x25519.rs b/src/rust/src/backend/x25519.rs new file mode 100644 index 000000000000..f27c0594ab3c --- /dev/null +++ b/src/rust/src/backend/x25519.rs @@ -0,0 +1,166 @@ +// 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::utils; +use crate::buf::CffiBuf; +use crate::error::CryptographyResult; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +struct X25519PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +struct X25519PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(X25519PrivateKey { + pkey: openssl::pkey::PKey::generate_x25519()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> X25519PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X25519PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> X25519PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X25519PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { + let pkey = + openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X25519) + .map_err(|e| { + pyo3::exceptions::PyValueError::new_err(format!( + "An X25519 private key is 32 bytes long: {}", + e + )) + })?; + Ok(X25519PrivateKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { + let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::X25519) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err("An X25519 public key is 32 bytes long") + })?; + Ok(X25519PublicKey { pkey }) +} + +#[pyo3::prelude::pymethods] +impl X25519PrivateKey { + fn exchange<'p>( + &self, + py: pyo3::Python<'p>, + public_key: &X25519PublicKey, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; + deriver.set_peer(&public_key.pkey)?; + + Ok(pyo3::types::PyBytes::new_with(py, deriver.len()?, |b| { + let n = deriver.derive(b).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Error computing shared key.") + })?; + assert_eq!(n, b.len()); + Ok(()) + })?) + } + + fn public_key(&self) -> CryptographyResult { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(X25519PublicKey { + pkey: openssl::pkey::PKey::public_key_from_raw_bytes( + &raw_bytes, + openssl::pkey::Id::X25519, + )?, + }) + } + + fn private_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + 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, + false, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl X25519PublicKey { + fn public_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + 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, false, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, X25519PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + 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")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "x25519")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + 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!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/x448.rs b/src/rust/src/backend/x448.rs new file mode 100644 index 000000000000..97e52ee6cc95 --- /dev/null +++ b/src/rust/src/backend/x448.rs @@ -0,0 +1,165 @@ +// 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::utils; +use crate::buf::CffiBuf; +use crate::error::CryptographyResult; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x448")] +struct X448PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x448")] +struct X448PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(X448PrivateKey { + pkey: openssl::pkey::PKey::generate_x448()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> X448PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X448PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> X448PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X448PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { + let pkey = + openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X448) + .map_err(|e| { + pyo3::exceptions::PyValueError::new_err(format!( + "An X448 private key is 56 bytes long: {}", + e + )) + })?; + Ok(X448PrivateKey { pkey }) +} +#[pyo3::prelude::pyfunction] +fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { + let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::X448) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err("An X448 public key is 32 bytes long") + })?; + Ok(X448PublicKey { pkey }) +} + +#[pyo3::prelude::pymethods] +impl X448PrivateKey { + fn exchange<'p>( + &self, + py: pyo3::Python<'p>, + public_key: &X448PublicKey, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; + deriver.set_peer(&public_key.pkey)?; + + Ok(pyo3::types::PyBytes::new_with(py, deriver.len()?, |b| { + let n = deriver.derive(b).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Error computing shared key.") + })?; + assert_eq!(n, b.len()); + Ok(()) + })?) + } + + fn public_key(&self) -> CryptographyResult { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(X448PublicKey { + pkey: openssl::pkey::PKey::public_key_from_raw_bytes( + &raw_bytes, + openssl::pkey::Id::X448, + )?, + }) + } + + fn private_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + 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, + false, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl X448PublicKey { + fn public_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + 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, false, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, X448PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + 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")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "x448")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + 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!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/buf.rs b/src/rust/src/buf.rs new file mode 100644 index 000000000000..b7afcf047da4 --- /dev/null +++ b/src/rust/src/buf.rs @@ -0,0 +1,49 @@ +// 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::{ptr, slice}; + +pub(crate) struct CffiBuf<'p> { + _pyobj: &'p pyo3::PyAny, + _bufobj: &'p pyo3::PyAny, + buf: &'p [u8], +} + +impl CffiBuf<'_> { + pub(crate) fn as_bytes(&self) -> &[u8] { + self.buf + } +} + +impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { + fn extract(pyobj: &'a pyo3::PyAny) -> pyo3::PyResult { + 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,))? + .extract()?; + + let len = bufobj.len()?; + let ptr = if len == 0 { + ptr::NonNull::dangling().as_ptr() + } else { + ptrval as *const u8 + }; + + Ok(CffiBuf { + _pyobj: pyobj, + _bufobj: bufobj, + // SAFETY: _extract_buffer_length ensures that we have a valid ptr + // and length (and we ensure we meet slice's requirements for + // 0-length slices above), we're keeping pyobj alive which ensures + // the buffer is valid. But! There is no actually guarantee + // against concurrent mutation. See + // https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ + // for details. This is the same as our cffi status quo ante, so + // we're doing an unsound thing and living with it. + buf: unsafe { slice::from_raw_parts(ptr, len) }, + }) + } +} diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs new file mode 100644 index 000000000000..6699520cb397 --- /dev/null +++ b/src/rust/src/error.rs @@ -0,0 +1,144 @@ +// 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::{exceptions, OpenSSLError}; +use pyo3::ToPyObject; + +pub enum CryptographyError { + Asn1Parse(asn1::ParseError), + Asn1Write(asn1::WriteError), + Py(pyo3::PyErr), + OpenSSL(openssl::error::ErrorStack), +} + +impl From for CryptographyError { + fn from(e: asn1::ParseError) -> CryptographyError { + CryptographyError::Asn1Parse(e) + } +} + +impl From for CryptographyError { + fn from(e: asn1::WriteError) -> CryptographyError { + CryptographyError::Asn1Write(e) + } +} + +impl From for CryptographyError { + fn from(e: pyo3::PyErr) -> CryptographyError { + CryptographyError::Py(e) + } +} + +impl From> for CryptographyError { + fn from(e: pyo3::PyDowncastError<'_>) -> CryptographyError { + CryptographyError::Py(e.into()) + } +} + +impl From for CryptographyError { + fn from(e: openssl::error::ErrorStack) -> CryptographyError { + CryptographyError::OpenSSL(e) + } +} + +impl From for CryptographyError { + fn from(e: pem::PemError) -> CryptographyError { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!( + "Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. {:?}", + e + ))) + } +} + +impl From for pyo3::PyErr { + fn from(e: CryptographyError) -> pyo3::PyErr { + match e { + CryptographyError::Asn1Parse(asn1_error) => pyo3::exceptions::PyValueError::new_err( + format!("error parsing asn1 value: {:?}", asn1_error), + ), + CryptographyError::Asn1Write(asn1::WriteError::AllocationError) => { + pyo3::exceptions::PyMemoryError::new_err( + "failed to allocate memory while performing ASN.1 serialization", + ) + } + CryptographyError::Py(py_error) => py_error, + CryptographyError::OpenSSL(error_stack) => pyo3::Python::with_gil(|py| { + let errors = pyo3::types::PyList::empty(py); + for e in error_stack.errors() { + errors + .append( + pyo3::PyCell::new(py, OpenSSLError { e: e.clone() }) + .expect("Failed to create OpenSSLError"), + ) + .expect("Failed to append to list"); + } + exceptions::InternalError::new_err(( + format!( + "Unknown OpenSSL error. This error is commonly encountered + when another library is not cleaning up the OpenSSL error + stack. If you are using cryptography with another library + that uses OpenSSL try disabling it before reporting a bug. + Otherwise please file an issue at + https://github.com/pyca/cryptography/issues with + information on how to reproduce this. ({:?})", + errors + ), + errors.to_object(py), + )) + }), + } + } +} + +impl CryptographyError { + pub(crate) fn add_location(self, loc: asn1::ParseLocation) -> Self { + match self { + CryptographyError::Py(e) => CryptographyError::Py(e), + CryptographyError::Asn1Parse(e) => CryptographyError::Asn1Parse(e.add_location(loc)), + CryptographyError::Asn1Write(e) => CryptographyError::Asn1Write(e), + CryptographyError::OpenSSL(e) => CryptographyError::OpenSSL(e), + } + } +} + +// The primary purpose of this alias is for brevity to keep function signatures +// to a single-line as a work around for coverage issues. See +// https://github.com/pyca/cryptography/pull/6173 +pub(crate) type CryptographyResult = Result; + +#[cfg(test)] +mod tests { + use super::CryptographyError; + + #[test] + fn test_cryptographyerror_from() { + pyo3::prepare_freethreaded_python(); + pyo3::Python::with_gil(|py| { + let e: CryptographyError = asn1::WriteError::AllocationError.into(); + assert!(matches!( + e, + CryptographyError::Asn1Write(asn1::WriteError::AllocationError) + )); + let py_e: pyo3::PyErr = e.into(); + assert!(py_e.is_instance_of::(py)); + + let e: CryptographyError = + pyo3::PyDowncastError::new(py.None().as_ref(py), "abc").into(); + assert!(matches!(e, CryptographyError::Py(_))); + }) + } + + #[test] + fn test_cryptographyerror_add_location() { + let py_err = pyo3::PyErr::new::("Error!"); + CryptographyError::Py(py_err).add_location(asn1::ParseLocation::Field("meh")); + + let asn1_write_err = asn1::WriteError::AllocationError; + CryptographyError::Asn1Write(asn1_write_err) + .add_location(asn1::ParseLocation::Field("meh")); + + let openssl_error = openssl::error::ErrorStack::get(); + CryptographyError::from(openssl_error).add_location(asn1::ParseLocation::Field("meh")); + } +} diff --git a/src/rust/src/exceptions.rs b/src/rust/src/exceptions.rs new file mode 100644 index 000000000000..ec1e18c7ff9c --- /dev/null +++ b/src/rust/src/exceptions.rs @@ -0,0 +1,40 @@ +// 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. + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.exceptions", + name = "_Reasons" +)] +#[allow(non_camel_case_types)] +pub(crate) enum Reasons { + BACKEND_MISSING_INTERFACE, + UNSUPPORTED_HASH, + UNSUPPORTED_CIPHER, + UNSUPPORTED_PADDING, + UNSUPPORTED_MGF, + UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + UNSUPPORTED_ELLIPTIC_CURVE, + UNSUPPORTED_SERIALIZATION, + UNSUPPORTED_X509, + UNSUPPORTED_EXCHANGE_ALGORITHM, + UNSUPPORTED_DIFFIE_HELLMAN, + UNSUPPORTED_MAC, +} + +pyo3::import_exception!(cryptography.exceptions, AlreadyFinalized); +pyo3::import_exception!(cryptography.exceptions, InternalError); +pyo3::import_exception!(cryptography.exceptions, InvalidSignature); +pyo3::import_exception!(cryptography.exceptions, UnsupportedAlgorithm); +pyo3::import_exception!(cryptography.x509, AttributeNotFound); +pyo3::import_exception!(cryptography.x509, DuplicateExtension); +pyo3::import_exception!(cryptography.x509, UnsupportedGeneralNameType); +pyo3::import_exception!(cryptography.x509, InvalidVersion); + +pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let submod = pyo3::prelude::PyModule::new(py, "exceptions")?; + + submod.add_class::()?; + + Ok(submod) +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs new file mode 100644 index 000000000000..4d88e2813b50 --- /dev/null +++ b/src/rust/src/lib.rs @@ -0,0 +1,190 @@ +// 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. + +#![deny(rust_2018_idioms)] + +mod asn1; +mod backend; +mod buf; +mod error; +mod exceptions; +pub(crate) mod oid; +mod pkcs7; +mod pool; +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(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 { + Ok(format!( + "", + 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() +} + +#[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_class::()?; + m.add_class::()?; + + m.add_submodule(asn1::create_submodule(py)?)?; + m.add_submodule(pkcs7::create_submodule(py)?)?; + m.add_submodule(exceptions::create_submodule(py)?)?; + + let x509_mod = pyo3::prelude::PyModule::new(py, "x509")?; + crate::x509::certificate::add_to_module(x509_mod)?; + crate::x509::common::add_to_module(x509_mod)?; + crate::x509::crl::add_to_module(x509_mod)?; + crate::x509::csr::add_to_module(x509_mod)?; + crate::x509::sct::add_to_module(x509_mod)?; + m.add_submodule(x509_mod)?; + + let ocsp_mod = pyo3::prelude::PyModule::new(py, "ocsp")?; + crate::x509::ocsp_req::add_to_module(ocsp_mod)?; + crate::x509::ocsp_resp::add_to_module(ocsp_mod)?; + m.add_submodule(ocsp_mod)?; + + m.add_submodule(cryptography_cffi::create_module(py)?)?; + + 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!(is_fips_enabled, m)?)?; + openssl_mod.add_class::()?; + 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 new file mode 100644 index 000000000000..f6dae6122bbf --- /dev/null +++ b/src/rust/src/oid.rs @@ -0,0 +1,76 @@ +// 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::error::CryptographyResult; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] +pub(crate) struct ObjectIdentifier { + pub(crate) oid: asn1::ObjectIdentifier, +} + +#[pyo3::pymethods] +impl ObjectIdentifier { + #[new] + fn new(value: &str) -> CryptographyResult { + let oid = asn1::ObjectIdentifier::from_string(value) + .ok_or_else(|| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?; + Ok(ObjectIdentifier { oid }) + } + + #[getter] + fn dotted_string<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyString { + pyo3::types::PyString::new(py, &self.oid.to_string()) + } + + #[getter] + fn _name<'p>( + 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")) + } + + fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { + slf + } + + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let self_clone = pyo3::PyCell::new( + py, + ObjectIdentifier { + oid: self.oid.clone(), + }, + )?; + let name = ObjectIdentifier::_name(self_clone.borrow(), py)?.extract::<&str>()?; + Ok(format!( + "", + self.oid, name + )) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, ObjectIdentifier>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.oid == other.oid), + pyo3::basic::CompareOp::Ne => Ok(self.oid != other.oid), + _ => Err(pyo3::exceptions::PyTypeError::new_err( + "ObjectIdentifiers cannot be ordered", + )), + } + } + + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.oid.hash(&mut hasher); + hasher.finish() + } +} diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs new file mode 100644 index 000000000000..d2c500a72de7 --- /dev/null +++ b/src/rust/src/pkcs7.rs @@ -0,0 +1,389 @@ +// 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::asn1::encode_der_data; +use crate::buf::CffiBuf; +use crate::error::CryptographyResult; +use crate::x509; +use cryptography_x509::csr::Attribute; +use cryptography_x509::{common, oid, pkcs7}; +use once_cell::sync::Lazy; +use std::borrow::Cow; +use std::collections::HashMap; +use std::ops::Deref; + +const PKCS7_CONTENT_TYPE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 3); +const PKCS7_MESSAGE_DIGEST_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 4); +const PKCS7_SIGNING_TIME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 5); +const PKCS7_SMIME_CAP_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 15); + +const AES_256_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 42); +const AES_192_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 22); +const AES_128_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 2); + +static OIDS_TO_MIC_NAME: Lazy> = Lazy::new(|| { + let mut h = HashMap::new(); + h.insert(&oid::SHA224_OID, "sha-224"); + h.insert(&oid::SHA256_OID, "sha-256"); + h.insert(&oid::SHA384_OID, "sha-384"); + h.insert(&oid::SHA512_OID, "sha-512"); + h +}); + +#[pyo3::prelude::pyfunction] +fn serialize_certificates<'p>( + py: pyo3::Python<'p>, + py_certs: Vec>, + encoding: &'p pyo3::PyAny, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + if py_certs.is_empty() { + return Err(pyo3::exceptions::PyTypeError::new_err( + "certs must be a list of certs with length >= 1", + ) + .into()); + } + + let raw_certs = py_certs + .iter() + .map(|c| c.raw.borrow_value_public()) + .collect::>(); + + let signed_data = pkcs7::SignedData { + version: 1, + digest_algorithms: asn1::SetOfWriter::new(&[]), + content_info: pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: pkcs7::Content::Data(Some(asn1::Explicit::new(b""))), + }, + certificates: Some(asn1::SetOfWriter::new(&raw_certs)), + crls: None, + signer_infos: asn1::SetOfWriter::new(&[]), + }; + + let content_info = pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: pkcs7::Content::SignedData(asn1::Explicit::new(Box::new(signed_data))), + }; + let content_info_bytes = asn1::write_single(&content_info)?; + + encode_der_data(py, "PKCS7".to_string(), content_info_bytes, encoding) +} + +#[pyo3::prelude::pyfunction] +fn sign_and_serialize<'p>( + py: pyo3::Python<'p>, + builder: &'p pyo3::PyAny, + 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 (data_with_header, data_without_header) = + if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "Binary"))?)? { + ( + Cow::Borrowed(raw_data.as_bytes()), + Cow::Borrowed(raw_data.as_bytes()), + ) + } else { + smime_canonicalize(raw_data.as_bytes(), text_mode) + }; + + let content_type_bytes = asn1::write_single(&pkcs7::PKCS7_DATA_OID)?; + let now = x509::common::datetime_now(py)?; + let signing_time_bytes = asn1::write_single(&x509::certificate::time_from_datetime(now)?)?; + let smime_cap_bytes = asn1::write_single(&asn1::SequenceOfWriter::new([ + // Subset of values OpenSSL provides: + // https://github.com/openssl/openssl/blob/667a8501f0b6e5705fd611d5bb3ca24848b07154/crypto/pkcs7/pk7_smime.c#L150 + // removing all the ones that are bad cryptography + AES_256_CBC_OID, + AES_192_CBC_OID, + AES_128_CBC_OID, + ]))?; + + let py_signers: Vec<( + pyo3::PyRef<'p, x509::certificate::Certificate>, + &pyo3::PyAny, + &pyo3::PyAny, + )> = builder.getattr(pyo3::intern!(py, "_signers"))?.extract()?; + + let py_certs: Vec> = builder + .getattr(pyo3::intern!(py, "_additional_certs"))? + .extract()?; + + let mut signer_infos = vec![]; + let mut digest_algs = vec![]; + let mut certs = py_certs + .iter() + .map(|p| p.raw.borrow_value_public()) + .collect::>(); + 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"))?)? + { + ( + None, + x509::sign::sign_data( + py, + py_private_key, + py_hash_alg, + py.None().into_ref(py), + &data_with_header, + )?, + ) + } else { + let mut authenticated_attrs = vec![ + Attribute { + type_id: PKCS7_CONTENT_TYPE_OID, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + asn1::parse_single(&content_type_bytes).unwrap(), + ])), + }, + Attribute { + type_id: PKCS7_SIGNING_TIME_OID, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + asn1::parse_single(&signing_time_bytes).unwrap(), + ])), + }, + ]; + + let digest = + asn1::write_single(&x509::ocsp::hash_data(py, py_hash_alg, &data_with_header)?)?; + // Gross hack: copy to PyBytes to extend the lifetime to 'p + let digest_bytes = pyo3::types::PyBytes::new(py, &digest); + authenticated_attrs.push(Attribute { + type_id: PKCS7_MESSAGE_DIGEST_OID, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + asn1::parse_single(digest_bytes.as_bytes()).unwrap(), + ])), + }); + + if !options.contains(pkcs7_options.getattr(pyo3::intern!(py, "NoCapabilities"))?)? { + authenticated_attrs.push(Attribute { + type_id: PKCS7_SMIME_CAP_OID, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + asn1::parse_single(&smime_cap_bytes).unwrap(), + ])), + }); + } + + let signed_data = + asn1::write_single(&asn1::SetOfWriter::new(authenticated_attrs.as_slice()))?; + + ( + Some(common::Asn1ReadableOrWritable::new_write( + asn1::SetOfWriter::new(authenticated_attrs), + )), + x509::sign::sign_data( + py, + py_private_key, + py_hash_alg, + py.None().into_ref(py), + &signed_data, + )?, + ) + }; + + let digest_alg = x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[py_hash_alg + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?] + .clone(); + // Technically O(n^2), but no one will have that many signers. + if !digest_algs.contains(&digest_alg) { + digest_algs.push(digest_alg.clone()); + } + certs.push(cert.raw.borrow_value_public()); + + signer_infos.push(pkcs7::SignerInfo { + version: 1, + issuer_and_serial_number: pkcs7::IssuerAndSerialNumber { + issuer: cert.raw.borrow_value_public().tbs_cert.issuer.clone(), + serial_number: cert.raw.borrow_value_public().tbs_cert.serial, + }, + digest_algorithm: digest_alg, + authenticated_attributes: authenticated_attrs, + digest_encryption_algorithm: x509::sign::compute_signature_algorithm( + py, + py_private_key, + py_hash_alg, + py.None().into_ref(py), + )?, + encrypted_digest: signature, + unauthenticated_attributes: None, + }); + } + + 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 signed_data = pkcs7::SignedData { + version: 1, + digest_algorithms: asn1::SetOfWriter::new(&digest_algs), + content_info: pkcs7::ContentInfo { + _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"))?)? { + None + } else { + Some(asn1::SetOfWriter::new(&certs)) + }, + crls: None, + signer_infos: asn1::SetOfWriter::new(&signer_infos), + }; + + let content_info = pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: pkcs7::Content::SignedData(asn1::Explicit::new(Box::new(signed_data))), + }; + 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"))?) { + let mic_algs = digest_algs + .iter() + .map(|d| OIDS_TO_MIC_NAME[&d.oid()]) + .collect::>() + .join(","); + let smime_encode = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization.pkcs7" + ))? + .getattr(pyo3::intern!(py, "_smime_encode"))?; + Ok(smime_encode + .call1((&*data_without_header, &*ci_bytes, mic_algs, text_mode))? + .extract()?) + } else { + // Handles the DER, PEM, and error cases + encode_der_data(py, "PKCS7".to_string(), ci_bytes, encoding) + } +} + +fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [u8]>) { + let mut new_data_with_header = vec![]; + let mut new_data_without_header = vec![]; + if text_mode { + new_data_with_header.extend_from_slice(b"Content-Type: text/plain\r\n\r\n"); + } + + let mut last_idx = 0; + for (i, c) in data.iter().copied().enumerate() { + if c == b'\n' && (i == 0 || data[i - 1] != b'\r') { + new_data_with_header.extend_from_slice(&data[last_idx..i]); + new_data_with_header.push(b'\r'); + new_data_with_header.push(b'\n'); + + new_data_without_header.extend_from_slice(&data[last_idx..i]); + new_data_without_header.push(b'\r'); + new_data_without_header.push(b'\n'); + last_idx = i + 1; + } + } + // If there's stuff in new_data, that means we need to copy the rest of + // data over. + if !new_data_with_header.is_empty() { + new_data_with_header.extend_from_slice(&data[last_idx..]); + new_data_without_header.extend_from_slice(&data[last_idx..]); + ( + Cow::Owned(new_data_with_header), + Cow::Owned(new_data_without_header), + ) + } else { + (Cow::Borrowed(data), Cow::Borrowed(data)) + } +} + +pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let submod = pyo3::prelude::PyModule::new(py, "pkcs7")?; + + submod.add_function(pyo3::wrap_pyfunction!(serialize_certificates, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(sign_and_serialize, submod)?)?; + + Ok(submod) +} + +#[cfg(test)] +mod tests { + use super::smime_canonicalize; + use std::borrow::Cow; + use std::ops::Deref; + + #[test] + fn test_smime_canonicalize() { + for ( + input, + text_mode, + expected_with_header, + expected_without_header, + expected_is_borrowed, + ) in [ + // Values with text_mode=false + (b"" as &[u8], false, b"" as &[u8], b"" as &[u8], true), + (b"\n", false, b"\r\n", b"\r\n", false), + (b"abc", false, b"abc", b"abc", true), + ( + b"abc\r\ndef\n", + false, + b"abc\r\ndef\r\n", + b"abc\r\ndef\r\n", + false, + ), + (b"abc\r\n", false, b"abc\r\n", b"abc\r\n", true), + ( + b"abc\ndef\n", + false, + b"abc\r\ndef\r\n", + b"abc\r\ndef\r\n", + false, + ), + // Values with text_mode=true + (b"", true, b"Content-Type: text/plain\r\n\r\n", b"", false), + ( + b"abc", + true, + b"Content-Type: text/plain\r\n\r\nabc", + b"abc", + false, + ), + ( + b"abc\n", + true, + b"Content-Type: text/plain\r\n\r\nabc\r\n", + b"abc\r\n", + false, + ), + ] { + let (result_with_header, result_without_header) = smime_canonicalize(input, text_mode); + assert_eq!(result_with_header.deref(), expected_with_header); + assert_eq!(result_without_header.deref(), expected_without_header); + assert_eq!( + matches!(result_with_header, Cow::Borrowed(_)), + expected_is_borrowed + ); + assert_eq!( + matches!(result_without_header, Cow::Borrowed(_)), + expected_is_borrowed + ); + } + } +} diff --git a/src/rust/src/pool.rs b/src/rust/src/pool.rs new file mode 100644 index 000000000000..0f45bed4640d --- /dev/null +++ b/src/rust/src/pool.rs @@ -0,0 +1,81 @@ +// 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::cell::Cell; + +// An object pool that can contain a single object and will dynamically +// allocate new objects to fulfill requests if the pool'd object is already in +// use. +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] +pub(crate) struct FixedPool { + create_fn: pyo3::PyObject, + + value: Cell>, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] +struct PoolAcquisition { + pool: pyo3::Py, + + value: pyo3::PyObject, + fresh: bool, +} + +#[pyo3::pymethods] +impl FixedPool { + #[new] + fn new(py: pyo3::Python<'_>, create: pyo3::PyObject) -> pyo3::PyResult { + let value = create.call0(py)?; + + Ok(FixedPool { + create_fn: create, + + value: Cell::new(Some(value)), + }) + } + + fn acquire(slf: pyo3::Py, py: pyo3::Python<'_>) -> pyo3::PyResult { + let v = slf.as_ref(py).borrow().value.replace(None); + if let Some(value) = v { + Ok(PoolAcquisition { + pool: slf, + value, + fresh: false, + }) + } else { + let value = slf.as_ref(py).borrow().create_fn.call0(py)?; + Ok(PoolAcquisition { + pool: slf, + value, + fresh: true, + }) + } + } + + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.create_fn)?; + Ok(()) + } +} + +#[pyo3::pymethods] +impl PoolAcquisition { + fn __enter__(&self, py: pyo3::Python<'_>) -> pyo3::PyObject { + self.value.clone_ref(py) + } + + fn __exit__( + &self, + py: pyo3::Python<'_>, + _exc_type: &pyo3::PyAny, + _exc_value: &pyo3::PyAny, + _exc_tb: &pyo3::PyAny, + ) -> pyo3::PyResult<()> { + let pool = self.pool.as_ref(py).borrow(); + if !self.fresh { + pool.value.replace(Some(self.value.clone_ref(py))); + } + Ok(()) + } +} diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs new file mode 100644 index 000000000000..9204d730ba03 --- /dev/null +++ b/src/rust/src/x509/certificate.rs @@ -0,0 +1,1000 @@ +// 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::asn1::{ + big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, +}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::{extensions, sct, sign}; +use crate::{exceptions, x509}; +use cryptography_x509::common::{AlgorithmParameters, Asn1ReadableOrWritable}; +use cryptography_x509::extensions::Extension; +use cryptography_x509::extensions::{ + AuthorityKeyIdentifier, BasicConstraints, DisplayText, DistributionPoint, + DistributionPointName, MSCertificateTemplate, NameConstraints, PolicyConstraints, + PolicyInformation, PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions, + SequenceOfSubtrees, UserNotice, +}; +use cryptography_x509::{common, name, oid}; +use pyo3::{IntoPy, ToPyObject}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +#[ouroboros::self_referencing] +pub(crate) struct OwnedCertificate { + data: pyo3::Py, + + #[borrows(data)] + #[covariant] + value: cryptography_x509::certificate::Certificate<'this>, +} + +impl OwnedCertificate { + // Re-expose ::new with `pub(crate)` visibility. + pub(crate) fn new_public( + data: pyo3::Py, + value_ref_builder: impl for<'this> FnOnce( + &'this pyo3::Py, + ) + -> cryptography_x509::certificate::Certificate<'this>, + ) -> OwnedCertificate { + OwnedCertificate::new(data, value_ref_builder) + } + + pub(crate) fn borrow_value_public(&self) -> &cryptography_x509::certificate::Certificate<'_> { + self.borrow_value() + } +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +pub(crate) struct Certificate { + pub(crate) raw: OwnedCertificate, + pub(crate) cached_extensions: Option, +} + +#[pyo3::prelude::pymethods] +impl Certificate { + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.raw.borrow_value().hash(&mut hasher); + hasher.finish() + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, Certificate>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.raw.borrow_value() == other.raw.borrow_value()), + pyo3::basic::CompareOp::Ne => Ok(self.raw.borrow_value() != other.raw.borrow_value()), + _ => Err(pyo3::exceptions::PyTypeError::new_err( + "Certificates cannot be ordered", + )), + } + } + + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let subject = self.subject(py)?; + let subject_repr = subject.repr()?.extract::<&str>()?; + Ok(format!("", subject_repr)) + } + + fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { + slf + } + + fn public_key<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + // This makes an unnecessary copy. It'd be nice to get rid of it. + let serialized = pyo3::types::PyBytes::new( + py, + &asn1::write_single(&self.raw.borrow_value().tbs_cert.spki)?, + ); + Ok(py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "load_der_public_key"))? + .call1((serialized,))?) + } + + fn fingerprint<'p>( + &self, + py: pyo3::Python<'p>, + algorithm: pyo3::PyObject, + ) -> CryptographyResult<&'p pyo3::PyAny> { + let hasher = py + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "Hash"))? + .call1((algorithm,))?; + // This makes an unnecessary copy. It'd be nice to get rid of it. + let serialized = + pyo3::types::PyBytes::new(py, &asn1::write_single(&self.raw.borrow_value())?); + hasher.call_method1(pyo3::intern!(py, "update"), (serialized,))?; + Ok(hasher.call_method0(pyo3::intern!(py, "finalize"))?) + } + + fn public_bytes<'p>( + &self, + py: pyo3::Python<'p>, + encoding: &'p pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = asn1::write_single(self.raw.borrow_value())?; + + encode_der_data(py, "CERTIFICATE".to_string(), result, encoding) + } + + #[getter] + fn serial_number<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { + let bytes = self.raw.borrow_value().tbs_cert.serial.as_bytes(); + warn_if_negative_serial(py, bytes)?; + Ok(big_byte_slice_to_py_int(py, bytes)?) + } + + #[getter] + fn version<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, CryptographyError> { + let version = &self.raw.borrow_value().tbs_cert.version; + cert_version(py, *version) + } + + #[getter] + fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + Ok( + x509::parse_name(py, &self.raw.borrow_value().tbs_cert.issuer) + .map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer")))?, + ) + } + + #[getter] + fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + Ok( + x509::parse_name(py, &self.raw.borrow_value().tbs_cert.subject) + .map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))?, + ) + } + + #[getter] + fn tbs_certificate_bytes<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = asn1::write_single(&self.raw.borrow_value().tbs_cert)?; + Ok(pyo3::types::PyBytes::new(py, &result)) + } + + #[getter] + fn tbs_precertificate_bytes<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let val = self.raw.borrow_value(); + let mut tbs_precert = val.tbs_cert.clone(); + // Remove the SCT list extension + match val.tbs_cert.extensions() { + Ok(extensions) => { + let readable_extensions = match extensions.as_raw() { + Some(raw_exts) => raw_exts.unwrap_read().clone(), + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Could not find any extensions in TBS certificate", + ), + )) + } + }; + let ext_count = readable_extensions.len(); + let filtered_extensions: Vec> = readable_extensions + .filter(|x| x.extn_id != oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID) + .collect(); + if filtered_extensions.len() == ext_count { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Could not find pre-certificate SCT list extension", + ), + )); + } + let filtered_extensions: RawExtensions<'_> = Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(filtered_extensions), + ); + + tbs_precert.raw_extensions = Some(filtered_extensions); + let result = asn1::write_single(&tbs_precert)?; + Ok(pyo3::types::PyBytes::new(py, &result)) + } + Err(oid) => { + let oid_obj = oid_to_py_oid(py, &oid)?; + Err(exceptions::DuplicateExtension::new_err(( + format!("Duplicate {} extension found", oid), + oid_obj.into_py(py), + )) + .into()) + } + } + } + + #[getter] + fn signature<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyBytes { + pyo3::types::PyBytes::new(py, self.raw.borrow_value().signature.as_bytes()) + } + + #[getter] + fn not_valid_before<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let dt = &self + .raw + .borrow_value() + .tbs_cert + .validity + .not_before + .as_datetime(); + x509::datetime_to_py(py, dt) + } + + #[getter] + fn not_valid_after<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let dt = &self + .raw + .borrow_value() + .tbs_cert + .validity + .not_after + .as_datetime(); + x509::datetime_to_py(py, dt) + } + + #[getter] + fn signature_hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { + sign::identify_signature_hash_algorithm(py, &self.raw.borrow_value().signature_alg) + } + + #[getter] + fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + oid_to_py_oid(py, self.raw.borrow_value().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_value().signature_alg) + } + + #[getter] + fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + x509::parse_and_cache_extensions( + py, + &mut self.cached_extensions, + &self.raw.borrow_value().tbs_cert.raw_extensions, + |oid, ext_data| match *oid { + oid::PRECERT_POISON_OID => { + asn1::parse_single::<()>(ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "PrecertPoison"))? + .call0()?, + )) + } + oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID => { + let contents = asn1::parse_single::<&[u8]>(ext_data)?; + let scts = sct::parse_scts(py, contents, sct::LogEntryType::PreCertificate)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!( + py, + "PrecertificateSignedCertificateTimestamps" + ))? + .call1((scts,))?, + )) + } + _ => parse_cert_ext(py, oid.clone(), ext_data), + }, + ) + } + + fn verify_directly_issued_by( + &self, + py: pyo3::Python<'_>, + issuer: pyo3::PyRef<'_, Certificate>, + ) -> CryptographyResult<()> { + if self.raw.borrow_value().tbs_cert.signature_alg != self.raw.borrow_value().signature_alg { + return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + "Inner and outer signature algorithms do not match. This is an invalid certificate." + ))); + }; + if self.raw.borrow_value().tbs_cert.issuer != issuer.raw.borrow_value().tbs_cert.subject { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Issuer certificate subject does not match certificate issuer.", + ), + )); + }; + sign::verify_signature_with_signature_algorithm( + py, + issuer.public_key(py)?, + &self.raw.borrow_value().signature_alg, + self.raw.borrow_value().signature.as_bytes(), + &asn1::write_single(&self.raw.borrow_value().tbs_cert)?, + ) + } +} + +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"))?), + _ => Err(CryptographyError::from( + exceptions::InvalidVersion::new_err(( + format!("{} is not a valid X509 version", version), + version, + )), + )), + } +} + +#[pyo3::prelude::pyfunction] +fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> CryptographyResult { + // We support both PEM header strings that OpenSSL does + // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L32-L33 + let parsed = x509::find_in_pem( + data, + |p| p.tag == "CERTIFICATE" || p.tag == "X509 CERTIFICATE", + "Valid PEM but no BEGIN CERTIFICATE/END CERTIFICATE delimiters. Are you sure this is a certificate?", + )?; + load_der_x509_certificate( + py, + pyo3::types::PyBytes::new(py, &parsed.contents).into_py(py), + ) +} + +#[pyo3::prelude::pyfunction] +fn load_pem_x509_certificates( + py: pyo3::Python<'_>, + data: &[u8], +) -> CryptographyResult> { + let certs = pem::parse_many(data)? + .iter() + .filter(|p| p.tag == "CERTIFICATE" || p.tag == "X509 CERTIFICATE") + .map(|p| { + load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &p.contents).into_py(py)) + }) + .collect::, _>>()?; + + if certs.is_empty() { + return Err(CryptographyError::from(pem::PemError::MalformedFraming)); + } + + Ok(certs) +} + +#[pyo3::prelude::pyfunction] +fn load_der_x509_certificate( + py: pyo3::Python<'_>, + data: pyo3::Py, +) -> CryptographyResult { + let raw = OwnedCertificate::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; + // Parse cert version immediately so we can raise error on parse if it is invalid. + cert_version(py, raw.borrow_value().tbs_cert.version)?; + // determine if the serial is negative and raise a warning if it is. We want to drop support + // for this sort of invalid encoding eventually. + warn_if_negative_serial(py, raw.borrow_value().tbs_cert.serial.as_bytes())?; + // determine if the signature algorithm has incorrect parameters and raise a warning if it + // does. this is a bug in JDK11 and we want to drop support for it eventually. + warn_if_invalid_ecdsa_params(py, raw.borrow_value().signature_alg.params.clone())?; + warn_if_invalid_ecdsa_params(py, raw.borrow_value().tbs_cert.signature_alg.params.clone())?; + + Ok(Certificate { + raw, + cached_extensions: None, + }) +} + +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"))?; + pyo3::PyErr::warn( + py, + cryptography_warning, + "Parsed a negative serial number, which is disallowed by RFC 5280.", + 1, + )?; + } + Ok(()) +} + +fn warn_if_invalid_ecdsa_params( + py: pyo3::Python<'_>, + params: AlgorithmParameters<'_>, +) -> pyo3::PyResult<()> { + match params { + AlgorithmParameters::EcDsaWithSha224(Some(..)) + | AlgorithmParameters::EcDsaWithSha256(Some(..)) + | AlgorithmParameters::EcDsaWithSha384(Some(..)) + | AlgorithmParameters::EcDsaWithSha512(Some(..)) => { + let cryptography_warning = py + .import(pyo3::intern!(py, "cryptography.utils"))? + .getattr(pyo3::intern!(py, "DeprecatedIn41"))?; + pyo3::PyErr::warn( + py, + cryptography_warning, + "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 JDK16+ or the latest JDK11 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 for more details.", + 2, + )?; + } + _ => {} + } + Ok(()) +} + +fn parse_display_text( + py: pyo3::Python<'_>, + text: DisplayText<'_>, +) -> pyo3::PyResult { + match text { + DisplayText::IA5String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)), + 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"))?; + pyo3::PyErr::warn( + py, + cryptography_warning, + "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, + )?; + } + Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)) + } + DisplayText::BmpString(o) => { + let py_bytes = pyo3::types::PyBytes::new(py, o.as_utf16_be_bytes()); + // TODO: do the string conversion in rust perhaps + Ok(py_bytes + .call_method1( + pyo3::intern!(py, "decode"), + (pyo3::intern!(py, "utf_16_be"),), + )? + .to_object(py)) + } + } +} + +fn parse_user_notice( + py: pyo3::Python<'_>, + un: UserNotice<'_>, +) -> Result { + 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(), + }; + let nr = match un.notice_ref { + Some(data) => { + let org = parse_display_text(py, data.organization)?; + let numbers = pyo3::types::PyList::empty(py); + 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))? + .to_object(py) + } + None => py.None(), + }; + Ok(x509_module + .call_method1(pyo3::intern!(py, "UserNotice"), (nr, et))? + .to_object(py)) +} + +fn parse_policy_qualifiers<'a>( + py: pyo3::Python<'_>, + policy_qualifiers: &asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, +) -> Result { + let py_pq = pyo3::types::PyList::empty(py); + for pqi in policy_qualifiers.clone() { + let qualifier = match pqi.qualifier { + Qualifier::CpsUri(data) => { + if pqi.policy_qualifier_id == oid::CP_CPS_URI_OID { + pyo3::types::PyString::new(py, data.as_str()).to_object(py) + } else { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "CpsUri ASN.1 structure found but OID did not match", + ), + )); + } + } + Qualifier::UserNotice(un) => { + if pqi.policy_qualifier_id != oid::CP_USER_NOTICE_OID { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "UserNotice ASN.1 structure found but OID did not match", + ), + )); + } + parse_user_notice(py, un)? + } + }; + py_pq.append(qualifier)?; + } + Ok(py_pq.to_object(py)) +} + +fn parse_cp(py: pyo3::Python<'_>, ext_data: &[u8]) -> Result { + let cp = asn1::parse_single::>>(ext_data)?; + 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); + let py_pqis = match policyinfo.policy_qualifiers { + Some(policy_qualifiers) => { + parse_policy_qualifiers(py, policy_qualifiers.unwrap_read())? + } + None => py.None(), + }; + let pi = x509_module + .call_method1(pyo3::intern!(py, "PolicyInformation"), (pi_oid, py_pqis))? + .to_object(py); + certificate_policies.append(pi)?; + } + Ok(certificate_policies.to_object(py)) +} + +fn parse_general_subtrees( + py: pyo3::Python<'_>, + subtrees: SequenceOfSubtrees<'_>, +) -> Result { + let gns = pyo3::types::PyList::empty(py); + for gs in subtrees.unwrap_read().clone() { + gns.append(x509::parse_general_name(py, gs.base)?)?; + } + Ok(gns.to_object(py)) +} + +pub(crate) fn parse_distribution_point_name( + py: pyo3::Python<'_>, + dp: DistributionPointName<'_>, +) -> Result<(pyo3::PyObject, pyo3::PyObject), CryptographyError> { + Ok(match dp { + DistributionPointName::FullName(data) => ( + x509::parse_general_names(py, data.unwrap_read())?, + py.None(), + ), + DistributionPointName::NameRelativeToCRLIssuer(data) => { + (py.None(), x509::parse_rdn(py, data.unwrap_read())?) + } + }) +} + +fn parse_distribution_point( + py: pyo3::Python<'_>, + dp: DistributionPoint<'_>, +) -> Result { + let (full_name, relative_name) = match dp.distribution_point { + Some(data) => parse_distribution_point_name(py, data)?, + None => (py.None(), py.None()), + }; + let reasons = + parse_distribution_point_reasons(py, dp.reasons.as_ref().map(|v| v.unwrap_read()))?; + let crl_issuer = match dp.crl_issuer { + 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"))? + .call1((full_name, relative_name, reasons, crl_issuer))? + .to_object(py)) +} + +pub(crate) fn parse_distribution_points( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { + let dps = asn1::parse_single::>>(data)?; + let py_dps = pyo3::types::PyList::empty(py); + for dp in dps { + let py_dp = parse_distribution_point(py, dp)?; + py_dps.append(py_dp)?; + } + Ok(py_dps.to_object(py)) +} + +pub(crate) fn parse_distribution_point_reasons( + py: pyo3::Python<'_>, + reasons: Option<&asn1::BitString<'_>>, +) -> Result { + let reason_bit_mapping = py + .import(pyo3::intern!(py, "cryptography.x509.extensions"))? + .getattr(pyo3::intern!(py, "_REASON_BIT_MAPPING"))?; + Ok(match reasons { + Some(bs) => { + let mut vec = Vec::new(); + for i in 1..=8 { + if bs.has_bit_set(i) { + vec.push(reason_bit_mapping.get_item(i)?); + } + } + pyo3::types::PyFrozenSet::new(py, &vec)?.to_object(py) + } + None => py.None(), + }) +} + +pub(crate) fn encode_distribution_point_reasons( + py: pyo3::Python<'_>, + py_reasons: &pyo3::PyAny, +) -> pyo3::PyResult { + let reason_flag_mapping = py + .import(pyo3::intern!(py, "cryptography.x509.extensions"))? + .getattr(pyo3::intern!(py, "_CRLREASONFLAGS"))?; + + let mut bits = vec![0, 0]; + for py_reason in py_reasons.iter()? { + let bit = reason_flag_mapping + .get_item(py_reason?)? + .extract::()?; + set_bit(&mut bits, bit, true) + } + if bits[1] == 0 { + bits.truncate(1); + } + let unused_bits = bits.last().unwrap().trailing_zeros() as u8; + Ok(asn1::OwnedBitString::new(bits, unused_bits).unwrap()) +} + +pub(crate) fn parse_authority_key_identifier<'p>( + py: pyo3::Python<'p>, + ext_data: &[u8], +) -> Result<&'p pyo3::PyAny, CryptographyError> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + let aki = asn1::parse_single::>(ext_data)?; + let serial = match aki.authority_cert_serial_number { + Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.to_object(py), + None => py.None(), + }; + let issuer = match aki.authority_cert_issuer { + Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, + None => py.None(), + }; + Ok(x509_module + .getattr(pyo3::intern!(py, "AuthorityKeyIdentifier"))? + .call1((aki.key_identifier, issuer, serial))?) +} + +pub(crate) fn parse_access_descriptions( + py: pyo3::Python<'_>, + ext_data: &[u8], +) -> Result { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + let ads = pyo3::types::PyList::empty(py); + let parsed = asn1::parse_single::>(ext_data)?; + 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"))? + .call1((py_oid, gn))? + .to_object(py); + ads.append(ad)?; + } + Ok(ads.to_object(py)) +} + +pub fn parse_cert_ext<'p>( + py: pyo3::Python<'p>, + oid: asn1::ObjectIdentifier, + ext_data: &[u8], +) -> CryptographyResult> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + match oid { + oid::SUBJECT_ALTERNATIVE_NAME_OID => { + let gn_seq = + asn1::parse_single::>>(ext_data)?; + let sans = x509::parse_general_names(py, &gn_seq)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "SubjectAlternativeName"))? + .call1((sans,))?, + )) + } + oid::ISSUER_ALTERNATIVE_NAME_OID => { + let gn_seq = + asn1::parse_single::>>(ext_data)?; + let ians = x509::parse_general_names(py, &gn_seq)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "IssuerAlternativeName"))? + .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 features = pyo3::types::PyList::empty(py); + for feature in asn1::parse_single::>(ext_data)? { + 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,))?, + )) + } + oid::SUBJECT_KEY_IDENTIFIER_OID => { + let identifier = asn1::parse_single::<&[u8]>(ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "SubjectKeyIdentifier"))? + .call1((identifier,))?, + )) + } + oid::EXTENDED_KEY_USAGE_OID => { + let ekus = pyo3::types::PyList::empty(py); + for oid in asn1::parse_single::>(ext_data)? + { + let oid_obj = oid_to_py_oid(py, &oid)?; + ekus.append(oid_obj)?; + } + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "ExtendedKeyUsage"))? + .call1((ekus,))?, + )) + } + oid::KEY_USAGE_OID => { + let kus = asn1::parse_single::>(ext_data)?; + let digital_signature = kus.has_bit_set(0); + let content_comitment = kus.has_bit_set(1); + let key_encipherment = kus.has_bit_set(2); + let data_encipherment = kus.has_bit_set(3); + let key_agreement = kus.has_bit_set(4); + let key_cert_sign = kus.has_bit_set(5); + let crl_sign = kus.has_bit_set(6); + let encipher_only = kus.has_bit_set(7); + let decipher_only = kus.has_bit_set(8); + Ok(Some( + x509_module.getattr(pyo3::intern!(py, "KeyUsage"))?.call1(( + digital_signature, + content_comitment, + key_encipherment, + data_encipherment, + key_agreement, + key_cert_sign, + crl_sign, + encipher_only, + decipher_only, + ))?, + )) + } + oid::AUTHORITY_INFORMATION_ACCESS_OID => { + let ads = parse_access_descriptions(py, ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))? + .call1((ads,))?, + )) + } + oid::SUBJECT_INFORMATION_ACCESS_OID => { + let ads = parse_access_descriptions(py, ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "SubjectInformationAccess"))? + .call1((ads,))?, + )) + } + oid::CERTIFICATE_POLICIES_OID => { + let cp = parse_cp(py, ext_data)?; + Ok(Some(x509_module.call_method1( + pyo3::intern!(py, "CertificatePolicies"), + (cp,), + )?)) + } + oid::POLICY_CONSTRAINTS_OID => { + let pc = asn1::parse_single::(ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "PolicyConstraints"))? + .call1((pc.require_explicit_policy, pc.inhibit_policy_mapping))?, + )) + } + oid::OCSP_NO_CHECK_OID => { + asn1::parse_single::<()>(ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "OCSPNoCheck"))? + .call0()?, + )) + } + oid::INHIBIT_ANY_POLICY_OID => { + let bignum = asn1::parse_single::>(ext_data)?; + let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "InhibitAnyPolicy"))? + .call1((pynum,))?, + )) + } + oid::BASIC_CONSTRAINTS_OID => { + let bc = asn1::parse_single::(ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "BasicConstraints"))? + .call1((bc.ca, bc.path_length))?, + )) + } + oid::AUTHORITY_KEY_IDENTIFIER_OID => { + Ok(Some(parse_authority_key_identifier(py, ext_data)?)) + } + oid::CRL_DISTRIBUTION_POINTS_OID => { + let dp = parse_distribution_points(py, ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "CRLDistributionPoints"))? + .call1((dp,))?, + )) + } + oid::FRESHEST_CRL_OID => { + let dp = parse_distribution_points(py, ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "FreshestCRL"))? + .call1((dp,))?, + )) + } + oid::NAME_CONSTRAINTS_OID => { + let nc = asn1::parse_single::>(ext_data)?; + let permitted_subtrees = match nc.permitted_subtrees { + Some(data) => parse_general_subtrees(py, data)?, + None => py.None(), + }; + let excluded_subtrees = match nc.excluded_subtrees { + Some(data) => parse_general_subtrees(py, data)?, + None => py.None(), + }; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "NameConstraints"))? + .call1((permitted_subtrees, excluded_subtrees))?, + )) + } + oid::MS_CERTIFICATE_TEMPLATE => { + let ms_cert_tpl = asn1::parse_single::(ext_data)?; + 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(None), + } +} + +pub(crate) fn time_from_py( + py: pyo3::Python<'_>, + val: &pyo3::PyAny, +) -> CryptographyResult { + let dt = x509::py_to_datetime(py, val)?; + time_from_datetime(dt) +} + +pub(crate) fn time_from_datetime(dt: asn1::DateTime) -> CryptographyResult { + if dt.year() >= 2050 { + Ok(common::Time::GeneralizedTime(asn1::GeneralizedTime::new( + dt, + )?)) + } else { + Ok(common::Time::UtcTime(asn1::UtcTime::new(dt).unwrap())) + } +} + +#[pyo3::prelude::pyfunction] +fn create_x509_certificate( + py: pyo3::Python<'_>, + builder: &pyo3::PyAny, + private_key: &pyo3::PyAny, + hash_algorithm: &pyo3::PyAny, + rsa_padding: &pyo3::PyAny, +) -> CryptographyResult { + 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 spki_bytes = builder + .getattr(pyo3::intern!(py, "_public_key"))? + .call_method1( + pyo3::intern!(py, "public_bytes"), + (der_encoding, spki_format), + )? + .extract::<&[u8]>()?; + + let py_serial = builder + .getattr(pyo3::intern!(py, "_serial_number"))? + .extract()?; + + let py_issuer_name = builder.getattr(pyo3::intern!(py, "_issuer_name"))?; + let py_subject_name = builder.getattr(pyo3::intern!(py, "_subject_name"))?; + let py_not_before = builder.getattr(pyo3::intern!(py, "_not_valid_before"))?; + let py_not_after = builder.getattr(pyo3::intern!(py, "_not_valid_after"))?; + + let tbs_cert = cryptography_x509::certificate::TbsCertificate { + version: builder + .getattr(pyo3::intern!(py, "_version"))? + .getattr(pyo3::intern!(py, "value"))? + .extract()?, + serial: asn1::BigInt::new(py_uint_to_big_endian_bytes(py, py_serial)?).unwrap(), + signature_alg: sigalg.clone(), + issuer: x509::common::encode_name(py, py_issuer_name)?, + validity: cryptography_x509::certificate::Validity { + not_before: time_from_py(py, py_not_before)?, + not_after: time_from_py(py, py_not_after)?, + }, + subject: x509::common::encode_name(py, py_subject_name)?, + spki: asn1::parse_single(spki_bytes)?, + issuer_unique_id: None, + subject_unique_id: None, + raw_extensions: x509::common::encode_extensions( + py, + builder.getattr(pyo3::intern!(py, "_extensions"))?, + extensions::encode_extension, + )?, + }; + + let tbs_bytes = asn1::write_single(&tbs_cert)?; + let signature = + x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?; + let data = asn1::write_single(&cryptography_x509::certificate::Certificate { + tbs_cert, + signature_alg: sigalg, + signature: asn1::BitString::new(signature, 0).unwrap(), + })?; + load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) +} + +pub(crate) fn set_bit(vals: &mut [u8], n: usize, set: bool) { + let idx = n / 8; + let v = 1 << (7 - (n & 0x07)); + if set { + vals[idx] |= v; + } +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_function(pyo3::wrap_pyfunction!(load_der_x509_certificate, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_certificate, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_certificates, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_x509_certificate, module)?)?; + + module.add_class::()?; + + Ok(()) +} diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs new file mode 100644 index 000000000000..8ceb518846d1 --- /dev/null +++ b/src/rust/src/x509/common.rs @@ -0,0 +1,562 @@ +// 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::asn1::{oid_to_py_oid, py_oid_to_oid}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, x509}; +use cryptography_x509::common::{Asn1ReadableOrWritable, AttributeTypeValue, RawTlv}; +use cryptography_x509::extensions::{AccessDescription, Extension, Extensions, RawExtensions}; +use cryptography_x509::name::{GeneralName, Name, OtherName, UnvalidatedIA5String}; +use pyo3::types::IntoPyDict; +use pyo3::{IntoPy, ToPyObject}; + +/// Parse all sections in a PEM file and return the first matching section. +/// If no matching sections are found, return an error. +pub(crate) fn find_in_pem( + data: &[u8], + filter_fn: fn(&pem::Pem) -> bool, + no_match_err: &'static str, +) -> Result { + let all_sections = pem::parse_many(data)?; + if all_sections.is_empty() { + return Err(CryptographyError::from(pem::PemError::MalformedFraming)); + } + all_sections.into_iter().find(filter_fn).ok_or_else(|| { + CryptographyError::from(pyo3::exceptions::PyValueError::new_err(no_match_err)) + }) +} + +pub(crate) fn encode_name<'p>( + py: pyo3::Python<'p>, + py_name: &'p pyo3::PyAny, +) -> pyo3::PyResult> { + let mut rdns = vec![]; + + for py_rdn in py_name.getattr(pyo3::intern!(py, "rdns"))?.iter()? { + let py_rdn = py_rdn?; + let mut attrs = vec![]; + + for py_attr in py_rdn.iter()? { + attrs.push(encode_name_entry(py, py_attr?)?); + } + rdns.push(asn1::SetOfWriter::new(attrs)); + } + Ok(Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(rdns), + )) +} + +pub(crate) fn encode_name_entry<'p>( + py: pyo3::Python<'p>, + py_name_entry: &'p pyo3::PyAny, +) -> CryptographyResult> { + 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::()?; + 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"))?) { + "utf_16_be" + } else if attr_type.is(asn1_type.getattr(pyo3::intern!(py, "UniversalString"))?) { + "utf_32_be" + } else { + "utf8" + }; + py_name_entry + .getattr(pyo3::intern!(py, "value"))? + .call_method1(pyo3::intern!(py, "encode"), (encoding,))? + .extract()? + } else { + py_name_entry + .getattr(pyo3::intern!(py, "value"))? + .extract()? + }; + let oid = py_oid_to_oid(py_name_entry.getattr(pyo3::intern!(py, "oid"))?)?; + + Ok(AttributeTypeValue { + type_id: oid, + value: RawTlv::new(asn1::Tag::from_bytes(&[tag])?.0, value), + }) +} + +#[pyo3::prelude::pyfunction] +fn encode_name_bytes<'p>( + py: pyo3::Python<'p>, + py_name: &'p pyo3::PyAny, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let name = encode_name(py, py_name)?; + let result = asn1::write_single(&name)?; + Ok(pyo3::types::PyBytes::new(py, &result)) +} + +pub(crate) fn encode_general_names<'a>( + py: pyo3::Python<'a>, + py_gns: &'a pyo3::PyAny, +) -> Result>, CryptographyError> { + let mut gns = vec![]; + for el in py_gns.iter()? { + let gn = encode_general_name(py, el?)?; + gns.push(gn) + } + Ok(gns) +} + +pub(crate) fn encode_general_name<'a>( + py: pyo3::Python<'a>, + gn: &'a pyo3::PyAny, +) -> Result, 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"))?) { + Ok(GeneralName::DNSName(UnvalidatedIA5String( + gn_value.extract::<&str>()?, + ))) + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RFC822Name"))?) { + Ok(GeneralName::RFC822Name(UnvalidatedIA5String( + gn_value.extract::<&str>()?, + ))) + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "DirectoryName"))?) { + let name = encode_name(py, gn_value)?; + Ok(GeneralName::DirectoryName(name)) + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "OtherName"))?) { + 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| { + pyo3::exceptions::PyValueError::new_err(format!( + "OtherName value must be valid DER: {:?}", + e + )) + })?, + })) + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "UniformResourceIdentifier"))?) { + Ok(GeneralName::UniformResourceIdentifier( + UnvalidatedIA5String(gn_value.extract::<&str>()?), + )) + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "IPAddress"))?) { + Ok(GeneralName::IPAddress( + gn.call_method0(pyo3::intern!(py, "_packed"))? + .extract::<&[u8]>()?, + )) + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RegisteredID"))?) { + let oid = py_oid_to_oid(gn_value)?; + Ok(GeneralName::RegisteredID(oid)) + } else { + Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Unsupported GeneralName type"), + )) + } +} + +pub(crate) fn encode_access_descriptions<'a>( + py: pyo3::Python<'a>, + py_ads: &'a pyo3::PyAny, +) -> CryptographyResult> { + let mut ads = vec![]; + for py_ad in py_ads.iter()? { + let py_ad = py_ad?; + let access_method = py_oid_to_oid(py_ad.getattr(pyo3::intern!(py, "access_method"))?)?; + let access_location = + encode_general_name(py, py_ad.getattr(pyo3::intern!(py, "access_location"))?)?; + ads.push(AccessDescription { + access_method, + access_location, + }); + } + Ok(asn1::write_single(&asn1::SequenceOfWriter::new(ads))?) +} + +pub(crate) fn parse_name<'p>( + py: pyo3::Python<'p>, + name: &Name<'_>, +) -> 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.unwrap_read().clone() { + let py_rdn = parse_rdn(py, &rdn)?; + py_rdns.append(py_rdn)?; + } + Ok(x509_module.call_method1(pyo3::intern!(py, "Name"), (py_rdns,))?) +} + +fn parse_name_attribute( + py: pyo3::Python<'_>, + attribute: AttributeTypeValue<'_>, +) -> Result { + 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() + .as_u8() + .ok_or_else(|| { + CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + "Long-form tags are not supported in NameAttribute values", + )) + })? + .to_object(py); + let py_tag = tag_enum.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()), + // BMPString tag value + Some(30) => { + let py_bytes = pyo3::types::PyBytes::new(py, attribute.value.data()); + py_bytes.call_method1(pyo3::intern!(py, "decode"), ("utf_16_be",))? + } + // UniversalString + Some(28) => { + let py_bytes = pyo3::types::PyBytes::new(py, attribute.value.data()); + py_bytes.call_method1(pyo3::intern!(py, "decode"), ("utf_32_be",))? + } + _ => { + let parsed = std::str::from_utf8(attribute.value.data()) + .map_err(|_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?; + pyo3::types::PyString::new(py, parsed) + } + }; + let kwargs = [("_validate", false)].into_py_dict(py); + Ok(x509_module + .call_method( + pyo3::intern!(py, "NameAttribute"), + (oid, py_data, py_tag), + Some(kwargs), + )? + .to_object(py)) +} + +pub(crate) fn parse_rdn<'a>( + py: pyo3::Python<'_>, + rdn: &asn1::SetOf<'a, AttributeTypeValue<'a>>, +) -> Result { + 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,))? + .to_object(py)) +} + +pub(crate) fn parse_general_name( + py: pyo3::Python<'_>, + gn: GeneralName<'_>, +) -> Result { + 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()), + )? + .to_object(py) + } + GeneralName::RFC822Name(data) => x509_module + .getattr(pyo3::intern!(py, "RFC822Name"))? + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? + .to_object(py), + GeneralName::DNSName(data) => x509_module + .getattr(pyo3::intern!(py, "DNSName"))? + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? + .to_object(py), + GeneralName::DirectoryName(data) => { + let py_name = parse_name(py, &data)?; + x509_module + .call_method1(pyo3::intern!(py, "DirectoryName"), (py_name,))? + .to_object(py) + } + GeneralName::UniformResourceIdentifier(data) => x509_module + .getattr(pyo3::intern!(py, "UniformResourceIdentifier"))? + .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) + } else { + // if it's not an IPv4 or IPv6 we assume it's an IPNetwork and + // verify length in this function. + create_ip_network(py, data)? + } + } + 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) + } + _ => { + return Err(CryptographyError::from( + exceptions::UnsupportedGeneralNameType::new_err( + "x400Address/EDIPartyName are not supported types", + ), + )) + } + }; + Ok(py_gn) +} + +pub(crate) fn parse_general_names<'a>( + py: pyo3::Python<'_>, + gn_seq: &asn1::SequenceOf<'a, GeneralName<'a>>, +) -> Result { + let gns = pyo3::types::PyList::empty(py); + for gn in gn_seq.clone() { + let py_gn = parse_general_name(py, gn)?; + gns.append(py_gn)?; + } + Ok(gns.to_object(py)) +} + +fn create_ip_network( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { + 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()); + ipv4_netmask(num) + } + 32 => { + let num = u128::from_be_bytes(data[16..].try_into().unwrap()); + ipv6_netmask(num) + } + _ => Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + 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 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)) +} + +fn ipv4_netmask(num: u32) -> Result { + // we invert and check leading zeros because leading_ones wasn't stabilized + // until 1.46.0. When we raise our MSRV we should change this + if (!num).leading_zeros() + num.trailing_zeros() != 32 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid netmask"), + )); + } + Ok((!num).leading_zeros()) +} + +fn ipv6_netmask(num: u128) -> Result { + // we invert and check leading zeros because leading_ones wasn't stabilized + // until 1.46.0. When we raise our MSRV we should change this + if (!num).leading_zeros() + num.trailing_zeros() != 128 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid netmask"), + )); + } + Ok((!num).leading_zeros()) +} + +pub(crate) fn parse_and_cache_extensions< + 'p, + F: Fn(&asn1::ObjectIdentifier, &[u8]) -> Result, CryptographyError>, +>( + py: pyo3::Python<'p>, + cached_extensions: &mut Option, + raw_extensions: &Option>, + parse_ext: F, +) -> pyo3::PyResult { + if let Some(cached) = cached_extensions { + return Ok(cached.clone_ref(py)); + } + + let extensions = match Extensions::from_raw_extensions(raw_extensions.as_ref()) { + Ok(extensions) => extensions, + Err(oid) => { + let oid_obj = oid_to_py_oid(py, &oid)?; + return Err(exceptions::DuplicateExtension::new_err(( + format!("Duplicate {} extension found", oid), + oid_obj.into_py(py), + ))); + } + }; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + let exts = pyo3::types::PyList::empty(py); + if let Some(extensions) = extensions.as_raw() { + for raw_ext in extensions.unwrap_read().clone() { + let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?; + + let extn_value = match parse_ext(&raw_ext.extn_id, raw_ext.extn_value)? { + Some(e) => e, + None => x509_module.call_method1( + pyo3::intern!(py, "UnrecognizedExtension"), + (oid_obj, raw_ext.extn_value), + )?, + }; + let ext_obj = x509_module.call_method1( + pyo3::intern!(py, "Extension"), + (oid_obj, raw_ext.critical, extn_value), + )?; + exts.append(ext_obj)?; + } + } + let extensions = x509_module + .call_method1(pyo3::intern!(py, "Extensions"), (exts,))? + .to_object(py); + *cached_extensions = Some(extensions.clone_ref(py)); + Ok(extensions) +} + +pub(crate) fn encode_extensions< + 'p, + F: Fn( + pyo3::Python<'_>, + &asn1::ObjectIdentifier, + &pyo3::PyAny, + ) -> CryptographyResult>>, +>( + py: pyo3::Python<'p>, + py_exts: &'p pyo3::PyAny, + encode_ext: F, +) -> pyo3::PyResult>> { + 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)? { + exts.push(Extension { + extn_id: oid, + critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, + extn_value: ext_val + .getattr(pyo3::intern!(py, "value"))? + .extract::<&[u8]>()?, + }); + continue; + } + match encode_ext(py, &oid, ext_val)? { + Some(data) => { + // TODO: extra copy + let py_data = pyo3::types::PyBytes::new(py, &data); + exts.push(Extension { + extn_id: oid, + critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, + extn_value: py_data.as_bytes(), + }) + } + None => { + return Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + oid + ))) + } + } + } + if exts.is_empty() { + return Ok(None); + } + Ok(Some(Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(exts), + ))) +} + +#[pyo3::prelude::pyfunction] +fn encode_extension_value<'p>( + py: pyo3::Python<'p>, + py_ext: &'p pyo3::PyAny, +) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { + let oid = py_oid_to_oid(py_ext.getattr(pyo3::intern!(py, "oid"))?)?; + + if let Some(data) = x509::extensions::encode_extension(py, &oid, py_ext)? { + // TODO: extra copy + let py_data = pyo3::types::PyBytes::new(py, &data); + return Ok(py_data); + } + + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + oid + ))) +} + +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(), + )) +} + +pub(crate) fn py_to_datetime( + py: pyo3::Python<'_>, + val: &pyo3::PyAny, +) -> pyo3::PyResult { + Ok(asn1::DateTime::new( + val.getattr(pyo3::intern!(py, "year"))?.extract()?, + val.getattr(pyo3::intern!(py, "month"))?.extract()?, + val.getattr(pyo3::intern!(py, "day"))?.extract()?, + val.getattr(pyo3::intern!(py, "hour"))?.extract()?, + val.getattr(pyo3::intern!(py, "minute"))?.extract()?, + val.getattr(pyo3::intern!(py, "second"))?.extract()?, + ) + .unwrap()) +} + +pub(crate) fn datetime_now(py: pyo3::Python<'_>) -> pyo3::PyResult { + py_to_datetime( + py, + py.import(pyo3::intern!(py, "datetime"))? + .getattr(pyo3::intern!(py, "datetime"))? + .call_method0(pyo3::intern!(py, "utcnow"))?, + ) +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_function(pyo3::wrap_pyfunction!(encode_extension_value, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(encode_name_bytes, module)?)?; + + Ok(()) +} diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs new file mode 100644 index 000000000000..92301503563f --- /dev/null +++ b/src/rust/src/x509/crl.rs @@ -0,0 +1,659 @@ +// 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::asn1::{ + big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, +}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::{certificate, extensions, sign}; +use crate::{exceptions, x509}; +use cryptography_x509::{common, crl, name, oid}; +use pyo3::{IntoPy, ToPyObject}; +use std::sync::Arc; + +#[pyo3::prelude::pyfunction] +fn load_der_x509_crl( + py: pyo3::Python<'_>, + data: pyo3::Py, +) -> Result { + let owned = OwnedCertificateRevocationList::try_new(data, |data| { + asn1::parse_single(data.as_bytes(py)) + })?; + + let version = owned.borrow_value().tbs_cert_list.version.unwrap_or(1); + if version != 1 { + return Err(CryptographyError::from( + exceptions::InvalidVersion::new_err(( + format!("{} is not a valid CRL version", version), + version, + )), + )); + } + + Ok(CertificateRevocationList { + owned: Arc::new(owned), + revoked_certs: pyo3::once_cell::GILOnceCell::new(), + cached_extensions: None, + }) +} + +#[pyo3::prelude::pyfunction] +fn load_pem_x509_crl( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { + let block = x509::find_in_pem( + data, + |p| p.tag == "X509 CRL", + "Valid PEM but no BEGIN X509 CRL/END X509 delimiters. Are you sure this is a CRL?", + )?; + load_der_x509_crl( + py, + pyo3::types::PyBytes::new(py, &block.contents).into_py(py), + ) +} + +#[ouroboros::self_referencing] +struct OwnedCertificateRevocationList { + data: pyo3::Py, + #[borrows(data)] + #[covariant] + value: crl::CertificateRevocationList<'this>, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +struct CertificateRevocationList { + owned: Arc, + + revoked_certs: pyo3::once_cell::GILOnceCell>, + cached_extensions: Option, +} + +impl CertificateRevocationList { + fn public_bytes_der(&self) -> CryptographyResult> { + Ok(asn1::write_single(self.owned.borrow_value())?) + } + + fn revoked_cert(&self, py: pyo3::Python<'_>, idx: usize) -> RevokedCertificate { + RevokedCertificate { + owned: self.revoked_certs.get(py).unwrap()[idx].clone(), + cached_extensions: None, + } + } + + fn len(&self) -> usize { + self.owned + .borrow_value() + .tbs_cert_list + .revoked_certificates + .as_ref() + .map_or(0, |v| v.unwrap_read().len()) + } +} + +#[pyo3::prelude::pymethods] +impl CertificateRevocationList { + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, CertificateRevocationList>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => { + Ok(self.owned.borrow_value() == other.owned.borrow_value()) + } + pyo3::basic::CompareOp::Ne => { + Ok(self.owned.borrow_value() != other.owned.borrow_value()) + } + _ => Err(pyo3::exceptions::PyTypeError::new_err( + "CRLs cannot be ordered", + )), + } + } + + fn __len__(&self) -> usize { + self.len() + } + + fn __iter__(&self) -> CRLIterator { + CRLIterator { + contents: OwnedCRLIteratorData::try_new(Arc::clone(&self.owned), |v| { + Ok::<_, ()>( + v.borrow_value() + .tbs_cert_list + .revoked_certificates + .as_ref() + .map(|v| v.unwrap_read().clone()), + ) + }) + .unwrap(), + } + } + + fn __getitem__( + &self, + py: pyo3::Python<'_>, + idx: &pyo3::PyAny, + ) -> pyo3::PyResult { + self.revoked_certs.get_or_init(py, || { + let mut revoked_certs = vec![]; + let mut it = self.__iter__(); + while let Some(c) = it.__next__() { + revoked_certs.push(c.owned); + } + revoked_certs + }); + + if idx.is_instance_of::()? { + let indices = idx + .downcast::()? + .indices(self.len().try_into().unwrap())?; + let result = pyo3::types::PyList::empty(py); + for i in (indices.start..indices.stop).step_by(indices.step.try_into().unwrap()) { + let revoked_cert = pyo3::PyCell::new(py, self.revoked_cert(py, i as usize))?; + result.append(revoked_cert)?; + } + Ok(result.to_object(py)) + } else { + let mut idx = idx.extract::()?; + if idx < 0 { + idx += self.len() as isize; + } + if idx >= (self.len() as isize) || idx < 0 { + return Err(pyo3::exceptions::PyIndexError::new_err(())); + } + Ok(pyo3::PyCell::new(py, self.revoked_cert(py, idx as usize))?.to_object(py)) + } + } + + fn fingerprint<'p>( + &self, + py: pyo3::Python<'p>, + algorithm: pyo3::PyObject, + ) -> pyo3::PyResult<&'p pyo3::PyAny> { + let hashes_mod = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; + let h = hashes_mod + .getattr(pyo3::intern!(py, "Hash"))? + .call1((algorithm,))?; + + let data = self.public_bytes_der()?; + h.call_method1(pyo3::intern!(py, "update"), (data.as_slice(),))?; + h.call_method0(pyo3::intern!(py, "finalize")) + } + + #[getter] + fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + oid_to_py_oid(py, self.owned.borrow_value().signature_algorithm.oid()) + } + + #[getter] + fn signature_hash_algorithm<'p>( + &self, + 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) + { + Ok(v) => Ok(v), + Err(_) => Err(exceptions::UnsupportedAlgorithm::new_err(format!( + "Signature algorithm OID: {} not recognized", + self.owned.borrow_value().signature_algorithm.oid(), + ))), + } + } + + #[getter] + fn signature(&self) -> &[u8] { + self.owned.borrow_value().signature_value.as_bytes() + } + + #[getter] + fn tbs_certlist_bytes<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let b = asn1::write_single(&self.owned.borrow_value().tbs_cert_list)?; + Ok(pyo3::types::PyBytes::new(py, &b)) + } + + fn public_bytes<'p>( + &self, + py: pyo3::Python<'p>, + encoding: &'p pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = asn1::write_single(self.owned.borrow_value())?; + + encode_der_data(py, "X509 CRL".to_string(), result, encoding) + } + + #[getter] + fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + Ok(x509::parse_name( + py, + &self.owned.borrow_value().tbs_cert_list.issuer, + )?) + } + + #[getter] + fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + match &self.owned.borrow_value().tbs_cert_list.next_update { + Some(t) => x509::datetime_to_py(py, t.as_datetime()), + None => Ok(py.None().into_ref(py)), + } + } + + #[getter] + fn last_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + x509::datetime_to_py( + py, + self.owned + .borrow_value() + .tbs_cert_list + .this_update + .as_datetime(), + ) + } + + #[getter] + fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let tbs_cert_list = &self.owned.borrow_value().tbs_cert_list; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + x509::parse_and_cache_extensions( + py, + &mut self.cached_extensions, + &tbs_cert_list.raw_crl_extensions, + |oid, ext_data| match *oid { + oid::CRL_NUMBER_OID => { + let bignum = asn1::parse_single::>(ext_data)?; + let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "CRLNumber"))? + .call1((pynum,))?, + )) + } + oid::DELTA_CRL_INDICATOR_OID => { + let bignum = asn1::parse_single::>(ext_data)?; + let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "DeltaCRLIndicator"))? + .call1((pynum,))?, + )) + } + oid::ISSUER_ALTERNATIVE_NAME_OID => { + let gn_seq = asn1::parse_single::>>( + ext_data, + )?; + let ians = x509::parse_general_names(py, &gn_seq)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "IssuerAlternativeName"))? + .call1((ians,))?, + )) + } + oid::AUTHORITY_INFORMATION_ACCESS_OID => { + let ads = certificate::parse_access_descriptions(py, ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))? + .call1((ads,))?, + )) + } + oid::AUTHORITY_KEY_IDENTIFIER_OID => Ok(Some( + certificate::parse_authority_key_identifier(py, ext_data)?, + )), + oid::ISSUING_DISTRIBUTION_POINT_OID => { + let idp = asn1::parse_single::>(ext_data)?; + let (full_name, relative_name) = match idp.distribution_point { + Some(data) => certificate::parse_distribution_point_name(py, data)?, + None => (py.None(), py.None()), + }; + let py_reasons = if let Some(reasons) = idp.only_some_reasons { + certificate::parse_distribution_point_reasons( + py, + Some(reasons.unwrap_read()), + )? + } 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, + ))?, + )) + } + oid::FRESHEST_CRL_OID => { + let dp = certificate::parse_distribution_points(py, ext_data)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "FreshestCRL"))? + .call1((dp,))?, + )) + } + _ => Ok(None), + }, + ) + } + + fn get_revoked_certificate_by_serial_number( + &mut self, + py: pyo3::Python<'_>, + serial: &pyo3::types::PyLong, + ) -> pyo3::PyResult> { + let serial_bytes = py_uint_to_big_endian_bytes(py, serial)?; + let owned = OwnedRevokedCertificate::try_new(Arc::clone(&self.owned), |v| { + let certs = match &v.borrow_value().tbs_cert_list.revoked_certificates { + Some(certs) => certs.unwrap_read().clone(), + None => return Err(()), + }; + + // TODO: linear scan. Make a hash or bisect! + for cert in certs { + if serial_bytes == cert.user_certificate.as_bytes() { + return Ok(cert); + } + } + Err(()) + }); + match owned { + Ok(o) => Ok(Some(RevokedCertificate { + owned: o, + cached_extensions: None, + })), + Err(()) => Ok(None), + } + } + + fn is_signature_valid<'p>( + slf: pyo3::PyRef<'_, Self>, + py: pyo3::Python<'p>, + public_key: &'p pyo3::PyAny, + ) -> CryptographyResult { + if slf.owned.borrow_value().tbs_cert_list.signature + != slf.owned.borrow_value().signature_algorithm + { + return Ok(false); + }; + + // Error on invalid public key -- below we treat any error as just + // being an invalid signature. + sign::identify_public_key_type(py, public_key)?; + + Ok(sign::verify_signature_with_signature_algorithm( + py, + public_key, + &slf.owned.borrow_value().signature_algorithm, + slf.owned.borrow_value().signature_value.as_bytes(), + &asn1::write_single(&slf.owned.borrow_value().tbs_cert_list)?, + ) + .is_ok()) + } +} + +#[ouroboros::self_referencing] +struct OwnedCRLIteratorData { + data: Arc, + #[borrows(data)] + #[covariant] + value: Option>>, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +struct CRLIterator { + contents: OwnedCRLIteratorData, +} + +// Open-coded implementation of the API discussed in +// https://github.com/joshua-maros/ouroboros/issues/38 +fn try_map_arc_data_mut_crl_iterator( + it: &mut OwnedCRLIteratorData, + f: impl for<'this> FnOnce( + &'this OwnedCertificateRevocationList, + &mut Option>>, + ) -> Result, E>, +) -> Result { + OwnedRevokedCertificate::try_new(Arc::clone(it.borrow_data()), |inner_it| { + it.with_value_mut(|value| f(inner_it, unsafe { std::mem::transmute(value) })) + }) +} + +#[pyo3::prelude::pymethods] +impl CRLIterator { + fn __len__(&self) -> usize { + self.contents.borrow_value().clone().map_or(0, |v| v.len()) + } + + fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } + + fn __next__(&mut self) -> Option { + let revoked = try_map_arc_data_mut_crl_iterator(&mut self.contents, |_data, v| match v { + Some(v) => match v.next() { + Some(revoked) => Ok(revoked), + None => Err(()), + }, + None => Err(()), + }) + .ok()?; + Some(RevokedCertificate { + owned: revoked, + cached_extensions: None, + }) + } +} + +#[ouroboros::self_referencing] +struct OwnedRevokedCertificate { + data: Arc, + #[borrows(data)] + #[covariant] + value: crl::RevokedCertificate<'this>, +} + +impl Clone for OwnedRevokedCertificate { + fn clone(&self) -> OwnedRevokedCertificate { + // This is safe because `Arc::clone` ensures the data is alive, but + // Rust doesn't understand the lifetime relationship it produces. + // Open-coded implementation of the API discussed in + // https://github.com/joshua-maros/ouroboros/issues/38 + OwnedRevokedCertificate::new(Arc::clone(self.borrow_data()), |_| unsafe { + std::mem::transmute(self.borrow_value().clone()) + }) + } +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +struct RevokedCertificate { + owned: OwnedRevokedCertificate, + cached_extensions: Option, +} + +#[pyo3::prelude::pymethods] +impl RevokedCertificate { + #[getter] + fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + big_byte_slice_to_py_int(py, self.owned.borrow_value().user_certificate.as_bytes()) + } + + #[getter] + fn revocation_date<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + x509::datetime_to_py(py, self.owned.borrow_value().revocation_date.as_datetime()) + } + + #[getter] + fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + x509::parse_and_cache_extensions( + py, + &mut self.cached_extensions, + &self.owned.borrow_value().raw_crl_entry_extensions, + |oid, ext_data| parse_crl_entry_ext(py, oid.clone(), ext_data), + ) + } +} + +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", + 2 => "ca_compromise", + 3 => "affiliation_changed", + 4 => "superseded", + 5 => "cessation_of_operation", + 6 => "certificate_hold", + 8 => "remove_from_crl", + 9 => "privilege_withdrawn", + 10 => "aa_compromise", + value => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "Unsupported reason code: {}", + value + )), + )) + } + }; + Ok(x509_module + .getattr(pyo3::intern!(py, "ReasonFlags"))? + .getattr(flag_name)?) +} + +pub fn parse_crl_entry_ext<'p>( + py: pyo3::Python<'p>, + oid: asn1::ObjectIdentifier, + data: &[u8], +) -> CryptographyResult> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + match oid { + oid::CRL_REASON_OID => { + let flags = parse_crl_reason_flags(py, &asn1::parse_single::(data)?)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "CRLReason"))? + .call1((flags,))?, + )) + } + oid::CERTIFICATE_ISSUER_OID => { + let gn_seq = asn1::parse_single::>>(data)?; + let gns = x509::parse_general_names(py, &gn_seq)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "CertificateIssuer"))? + .call1((gns,))?, + )) + } + oid::INVALIDITY_DATE_OID => { + let time = asn1::parse_single::(data)?; + let py_dt = x509::datetime_to_py(py, time.as_datetime())?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "InvalidityDate"))? + .call1((py_dt,))?, + )) + } + _ => Ok(None), + } +} + +#[pyo3::prelude::pyfunction] +fn create_x509_crl( + py: pyo3::Python<'_>, + builder: &pyo3::PyAny, + private_key: &pyo3::PyAny, + hash_algorithm: &pyo3::PyAny, +) -> CryptographyResult { + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + )?; + let mut revoked_certs = vec![]; + for py_revoked_cert in builder + .getattr(pyo3::intern!(py, "_revoked_certificates"))? + .iter()? + { + let py_revoked_cert = py_revoked_cert?; + let serial_number = py_revoked_cert + .getattr(pyo3::intern!(py, "serial_number"))? + .extract()?; + let py_revocation_date = py_revoked_cert.getattr(pyo3::intern!(py, "revocation_date"))?; + revoked_certs.push(crl::RevokedCertificate { + user_certificate: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, serial_number)?) + .unwrap(), + revocation_date: x509::certificate::time_from_py(py, py_revocation_date)?, + raw_crl_entry_extensions: x509::common::encode_extensions( + py, + py_revoked_cert.getattr(pyo3::intern!(py, "extensions"))?, + extensions::encode_extension, + )?, + }); + } + + let py_issuer_name = builder.getattr(pyo3::intern!(py, "_issuer_name"))?; + let py_this_update = builder.getattr(pyo3::intern!(py, "_last_update"))?; + let py_next_update = builder.getattr(pyo3::intern!(py, "_next_update"))?; + let tbs_cert_list = crl::TBSCertList { + version: Some(1), + signature: sigalg.clone(), + issuer: x509::common::encode_name(py, py_issuer_name)?, + this_update: x509::certificate::time_from_py(py, py_this_update)?, + next_update: Some(x509::certificate::time_from_py(py, py_next_update)?), + revoked_certificates: if revoked_certs.is_empty() { + None + } else { + Some(common::Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(revoked_certs), + )) + }, + raw_crl_extensions: x509::common::encode_extensions( + py, + builder.getattr(pyo3::intern!(py, "_extensions"))?, + extensions::encode_extension, + )?, + }; + + let tbs_bytes = asn1::write_single(&tbs_cert_list)?; + let signature = x509::sign::sign_data( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + &tbs_bytes, + )?; + let data = asn1::write_single(&crl::CertificateRevocationList { + tbs_cert_list, + signature_algorithm: sigalg, + signature_value: asn1::BitString::new(signature, 0).unwrap(), + })?; + load_der_x509_crl(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_function(pyo3::wrap_pyfunction!(load_der_x509_crl, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_crl, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_x509_crl, module)?)?; + + module.add_class::()?; + module.add_class::()?; + + Ok(()) +} diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs new file mode 100644 index 000000000000..2e7797f49baa --- /dev/null +++ b/src/rust/src/x509/csr.rs @@ -0,0 +1,396 @@ +// 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::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 asn1::SimpleAsn1Readable; +use cryptography_x509::csr::{check_attribute_length, Attribute, CertificationRequestInfo, Csr}; +use cryptography_x509::{common, oid}; +use pyo3::IntoPy; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +#[ouroboros::self_referencing] +struct OwnedCsr { + data: pyo3::Py, + #[borrows(data)] + #[covariant] + value: Csr<'this>, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +struct CertificateSigningRequest { + raw: OwnedCsr, + cached_extensions: Option, +} + +#[pyo3::prelude::pymethods] +impl CertificateSigningRequest { + fn __hash__(&self, py: pyo3::Python<'_>) -> u64 { + let mut hasher = DefaultHasher::new(); + self.raw.borrow_data().as_bytes(py).hash(&mut hasher); + hasher.finish() + } + + fn __richcmp__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, CertificateSigningRequest>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => { + Ok(self.raw.borrow_data().as_bytes(py) == other.raw.borrow_data().as_bytes(py)) + } + pyo3::basic::CompareOp::Ne => { + Ok(self.raw.borrow_data().as_bytes(py) != other.raw.borrow_data().as_bytes(py)) + } + _ => Err(pyo3::exceptions::PyTypeError::new_err( + "CSRs cannot be ordered", + )), + } + } + + fn public_key<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + // This makes an unnecessary copy. It'd be nice to get rid of it. + let serialized = pyo3::types::PyBytes::new( + py, + &asn1::write_single(&self.raw.borrow_value().csr_info.spki)?, + ); + Ok(py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "load_der_public_key"))? + .call1((serialized,))?) + } + + #[getter] + fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + Ok(x509::parse_name( + py, + &self.raw.borrow_value().csr_info.subject, + )?) + } + + #[getter] + fn tbs_certrequest_bytes<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = asn1::write_single(&self.raw.borrow_value().csr_info)?; + Ok(pyo3::types::PyBytes::new(py, &result)) + } + + #[getter] + fn signature<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyBytes { + pyo3::types::PyBytes::new(py, self.raw.borrow_value().signature.as_bytes()) + } + + #[getter] + fn signature_hash_algorithm<'p>( + &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_value().signature_alg.oid() + )), + )), + } + } + + #[getter] + fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + oid_to_py_oid(py, self.raw.borrow_value().signature_alg.oid()) + } + + fn public_bytes<'p>( + &self, + py: pyo3::Python<'p>, + encoding: &'p pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = asn1::write_single(self.raw.borrow_value())?; + + encode_der_data(py, "CERTIFICATE REQUEST".to_string(), result, encoding) + } + + fn get_attribute_for_oid<'p>( + &self, + 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 rust_oid = py_oid_to_oid(oid)?; + for attribute in self + .raw + .borrow_value() + .csr_info + .attributes + .unwrap_read() + .clone() + { + if rust_oid == attribute.type_id { + check_attribute_length(attribute.values.unwrap_read().clone()).map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Only single-valued attributes are supported", + ) + })?; + let val = attribute.values.unwrap_read().clone().next().unwrap(); + // We allow utf8string, printablestring, and ia5string at this time + if val.tag() == asn1::Utf8String::TAG + || val.tag() == asn1::PrintableString::TAG + || val.tag() == asn1::IA5String::TAG + { + return Ok(pyo3::types::PyBytes::new(py, val.data())); + } + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "OID {} has a disallowed ASN.1 type: {:?}", + oid, + val.tag() + ))); + } + } + Err(exceptions::AttributeNotFound::new_err(( + format!("No {} attribute was found", oid), + oid.into_py(py), + ))) + } + + #[getter] + fn attributes<'p>(&mut self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let pyattrs = pyo3::types::PyList::empty(py); + for attribute in self + .raw + .borrow_value() + .csr_info + .attributes + .unwrap_read() + .clone() + { + check_attribute_length(attribute.values.unwrap_read().clone()).map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Only single-valued attributes are supported", + ) + })?; + let oid = oid_to_py_oid(py, &attribute.type_id)?; + let val = attribute.values.unwrap_read().clone().next().unwrap(); + let serialized = pyo3::types::PyBytes::new(py, val.data()); + let tag = val.tag().as_u8().ok_or_else(|| { + CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + "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))?; + pyattrs.append(pyattr)?; + } + py.import(pyo3::intern!(py, "cryptography.x509"))? + .call_method1(pyo3::intern!(py, "Attributes"), (pyattrs,)) + } + + #[getter] + fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let raw_exts = self + .raw + .borrow_value() + .csr_info + .get_extension_attribute() + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Only single-valued attributes are supported", + ) + })?; + + x509::parse_and_cache_extensions( + py, + &mut self.cached_extensions, + &raw_exts, + |oid, ext_data| certificate::parse_cert_ext(py, oid.clone(), ext_data), + ) + } + + #[getter] + fn is_signature_valid( + slf: pyo3::PyRef<'_, Self>, + py: pyo3::Python<'_>, + ) -> CryptographyResult { + let public_key = slf.public_key(py)?; + Ok(sign::verify_signature_with_signature_algorithm( + py, + public_key, + &slf.raw.borrow_value().signature_alg, + slf.raw.borrow_value().signature.as_bytes(), + &asn1::write_single(&slf.raw.borrow_value().csr_info)?, + ) + .is_ok()) + } +} + +#[pyo3::prelude::pyfunction] +fn load_pem_x509_csr( + py: pyo3::Python<'_>, + data: &[u8], +) -> CryptographyResult { + // We support both PEM header strings that OpenSSL does + // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L35-L36 + let parsed = x509::find_in_pem( + data, + |p| p.tag == "CERTIFICATE REQUEST" || p.tag == "NEW CERTIFICATE REQUEST", + "Valid PEM but no BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters. Are you sure this is a CSR?", + )?; + load_der_x509_csr( + py, + pyo3::types::PyBytes::new(py, &parsed.contents).into_py(py), + ) +} + +#[pyo3::prelude::pyfunction] +fn load_der_x509_csr( + py: pyo3::Python<'_>, + data: pyo3::Py, +) -> CryptographyResult { + let raw = OwnedCsr::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; + + let version = raw.borrow_value().csr_info.version; + if version != 0 { + return Err(CryptographyError::from( + exceptions::InvalidVersion::new_err(( + format!("{} is not a valid CSR version", version), + version, + )), + )); + } + + Ok(CertificateSigningRequest { + raw, + cached_extensions: None, + }) +} + +#[pyo3::prelude::pyfunction] +fn create_x509_csr( + py: pyo3::Python<'_>, + builder: &pyo3::PyAny, + private_key: &pyo3::PyAny, + hash_algorithm: &pyo3::PyAny, +) -> CryptographyResult { + 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 spki_bytes = private_key + .call_method0(pyo3::intern!(py, "public_key"))? + .call_method1( + pyo3::intern!(py, "public_bytes"), + (der_encoding, spki_format), + )? + .extract::<&[u8]>()?; + + let mut attrs = vec![]; + let ext_bytes; + if let Some(exts) = x509::common::encode_extensions( + py, + builder.getattr(pyo3::intern!(py, "_extensions"))?, + x509::extensions::encode_extension, + )? { + ext_bytes = asn1::write_single(&exts)?; + attrs.push(Attribute { + type_id: (oid::EXTENSION_REQUEST).clone(), + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + asn1::parse_single(&ext_bytes)?, + ])), + }) + } + + for py_attr in builder.getattr(pyo3::intern!(py, "_attributes"))?.iter()? { + let (py_oid, value, tag): (&pyo3::PyAny, &[u8], Option) = py_attr?.extract()?; + let oid = py_oid_to_oid(py_oid)?; + let tag = if let Some(tag) = tag { + asn1::Tag::from_bytes(&[tag])?.0 + } else { + if std::str::from_utf8(value).is_err() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Attribute values must be valid utf-8.", + ), + )); + } + asn1::Utf8String::TAG + }; + + attrs.push(Attribute { + type_id: oid, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + common::RawTlv::new(tag, value), + ])), + }) + } + + let py_subject_name = builder.getattr(pyo3::intern!(py, "_subject_name"))?; + + let csr_info = CertificationRequestInfo { + version: 0, + subject: x509::common::encode_name(py, py_subject_name)?, + spki: asn1::parse_single(spki_bytes)?, + attributes: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(attrs)), + }; + + 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 data = asn1::write_single(&Csr { + csr_info, + signature_alg: sigalg, + signature: asn1::BitString::new(signature, 0).unwrap(), + })?; + load_der_x509_csr(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_function(pyo3::wrap_pyfunction!(load_der_x509_csr, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_csr, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_x509_csr, module)?)?; + + module.add_class::()?; + + Ok(()) +} diff --git a/src/rust/src/x509/extensions.rs b/src/rust/src/x509/extensions.rs new file mode 100644 index 000000000000..98d1bd63b910 --- /dev/null +++ b/src/rust/src/x509/extensions.rs @@ -0,0 +1,514 @@ +// 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::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 cryptography_x509::{common, crl, extensions, oid}; + +fn encode_general_subtrees<'a>( + py: pyo3::Python<'a>, + subtrees: &'a pyo3::PyAny, +) -> Result>, CryptographyError> { + if subtrees.is_none() { + Ok(None) + } else { + let mut subtree_seq = vec![]; + for name in subtrees.iter()? { + let gn = x509::common::encode_general_name(py, name?)?; + subtree_seq.push(extensions::GeneralSubtree { + base: gn, + minimum: 0, + maximum: None, + }); + } + Ok(Some(common::Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(subtree_seq), + ))) + } +} + +pub(crate) fn encode_authority_key_identifier<'a>( + py: pyo3::Python<'a>, + py_aki: &'a pyo3::PyAny, +) -> CryptographyResult> { + #[derive(pyo3::prelude::FromPyObject)] + struct PyAuthorityKeyIdentifier<'a> { + key_identifier: Option<&'a [u8]>, + authority_cert_issuer: Option<&'a pyo3::PyAny>, + authority_cert_serial_number: Option<&'a pyo3::types::PyLong>, + } + let aki = py_aki.extract::>()?; + let authority_cert_issuer = if let Some(authority_cert_issuer) = aki.authority_cert_issuer { + let gns = x509::common::encode_general_names(py, authority_cert_issuer)?; + Some(common::Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(gns), + )) + } else { + None + }; + let authority_cert_serial_number = + if let Some(authority_cert_serial_number) = aki.authority_cert_serial_number { + let serial_bytes = py_uint_to_big_endian_bytes(py, authority_cert_serial_number)?; + Some(asn1::BigUint::new(serial_bytes).unwrap()) + } else { + None + }; + Ok(asn1::write_single(&extensions::AuthorityKeyIdentifier { + authority_cert_issuer, + authority_cert_serial_number, + key_identifier: aki.key_identifier, + })?) +} + +pub(crate) fn encode_distribution_points<'p>( + py: pyo3::Python<'p>, + py_dps: &'p pyo3::PyAny, +) -> CryptographyResult> { + #[derive(pyo3::prelude::FromPyObject)] + struct PyDistributionPoint<'a> { + crl_issuer: Option<&'a pyo3::PyAny>, + full_name: Option<&'a pyo3::PyAny>, + relative_name: Option<&'a pyo3::PyAny>, + reasons: Option<&'a pyo3::PyAny>, + } + + let mut dps = vec![]; + for py_dp in py_dps.iter()? { + let py_dp = py_dp?.extract::>()?; + + let crl_issuer = if let Some(py_crl_issuer) = py_dp.crl_issuer { + let gns = x509::common::encode_general_names(py, py_crl_issuer)?; + Some(common::Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(gns), + )) + } else { + None + }; + let distribution_point = if let Some(py_full_name) = py_dp.full_name { + let gns = x509::common::encode_general_names(py, py_full_name)?; + Some(extensions::DistributionPointName::FullName( + common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), + )) + } else if let Some(py_relative_name) = py_dp.relative_name { + let mut name_entries = vec![]; + for py_name_entry in py_relative_name.iter()? { + name_entries.push(x509::common::encode_name_entry(py, py_name_entry?)?); + } + Some(extensions::DistributionPointName::NameRelativeToCRLIssuer( + common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), + )) + } else { + None + }; + let reasons = if let Some(py_reasons) = py_dp.reasons { + let reasons = certificate::encode_distribution_point_reasons(py, py_reasons)?; + Some(common::Asn1ReadableOrWritable::new_write(reasons)) + } else { + None + }; + dps.push(extensions::DistributionPoint { + crl_issuer, + distribution_point, + reasons, + }); + } + Ok(asn1::write_single(&asn1::SequenceOfWriter::new(dps))?) +} + +fn encode_basic_constraints(ext: &pyo3::PyAny) -> CryptographyResult> { + #[derive(pyo3::prelude::FromPyObject)] + struct PyBasicConstraints { + ca: bool, + path_length: Option, + } + let pybc = ext.extract::()?; + let bc = extensions::BasicConstraints { + ca: pybc.ca, + path_length: pybc.path_length, + }; + Ok(asn1::write_single(&bc)?) +} + +fn encode_key_usage(py: pyo3::Python<'_>, ext: &pyo3::PyAny) -> CryptographyResult> { + let mut bs = [0, 0]; + certificate::set_bit( + &mut bs, + 0, + ext.getattr(pyo3::intern!(py, "digital_signature"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 1, + ext.getattr(pyo3::intern!(py, "content_commitment"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 2, + ext.getattr(pyo3::intern!(py, "key_encipherment"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 3, + ext.getattr(pyo3::intern!(py, "data_encipherment"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 4, + ext.getattr(pyo3::intern!(py, "key_agreement"))?.is_true()?, + ); + certificate::set_bit( + &mut bs, + 5, + ext.getattr(pyo3::intern!(py, "key_cert_sign"))?.is_true()?, + ); + certificate::set_bit( + &mut bs, + 6, + ext.getattr(pyo3::intern!(py, "crl_sign"))?.is_true()?, + ); + if ext.getattr(pyo3::intern!(py, "key_agreement"))?.is_true()? { + certificate::set_bit( + &mut bs, + 7, + ext.getattr(pyo3::intern!(py, "encipher_only"))?.is_true()?, + ); + certificate::set_bit( + &mut bs, + 8, + ext.getattr(pyo3::intern!(py, "decipher_only"))?.is_true()?, + ); + } + let (bits, unused_bits) = if bs[1] == 0 { + if bs[0] == 0 { + (&[][..], 0) + } else { + (&bs[..1], bs[0].trailing_zeros() as u8) + } + } else { + (&bs[..], bs[1].trailing_zeros() as u8) + }; + let v = asn1::BitString::new(bits, unused_bits).unwrap(); + Ok(asn1::write_single(&v)?) +} + +fn encode_certificate_policies( + py: pyo3::Python<'_>, + ext: &pyo3::PyAny, +) -> CryptographyResult> { + let mut policy_informations = vec![]; + for py_policy_info in ext.iter()? { + let py_policy_info = py_policy_info?; + let py_policy_qualifiers = + py_policy_info.getattr(pyo3::intern!(py, "policy_qualifiers"))?; + let qualifiers = if py_policy_qualifiers.is_true()? { + let mut qualifiers = vec![]; + for py_qualifier in py_policy_qualifiers.iter()? { + let py_qualifier = py_qualifier?; + let qualifier = if py_qualifier.is_instance_of::()? { + let cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { + Some(s) => s, + None => { + return Err(pyo3::exceptions::PyValueError::new_err( + "Qualifier must be an ASCII-string.", + ) + .into()) + } + }; + extensions::PolicyQualifierInfo { + policy_qualifier_id: (oid::CP_CPS_URI_OID).clone(), + qualifier: extensions::Qualifier::CpsUri(cps_uri), + } + } else { + let py_notice = py_qualifier.getattr(pyo3::intern!(py, "notice_reference"))?; + let notice_ref = if py_notice.is_true()? { + let mut notice_numbers = vec![]; + for py_num in py_notice + .getattr(pyo3::intern!(py, "notice_numbers"))? + .iter()? + { + let bytes = py_uint_to_big_endian_bytes(ext.py(), py_num?.downcast()?)?; + notice_numbers.push(asn1::BigUint::new(bytes).unwrap()); + } + + Some(extensions::NoticeReference { + organization: extensions::DisplayText::Utf8String( + asn1::Utf8String::new( + py_notice + .getattr(pyo3::intern!(py, "organization"))? + .extract()?, + ), + ), + notice_numbers: common::Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(notice_numbers), + ), + }) + } else { + None + }; + let py_explicit_text = + py_qualifier.getattr(pyo3::intern!(py, "explicit_text"))?; + let explicit_text = if py_explicit_text.is_true()? { + Some(extensions::DisplayText::Utf8String(asn1::Utf8String::new( + py_explicit_text.extract()?, + ))) + } else { + None + }; + + extensions::PolicyQualifierInfo { + policy_qualifier_id: (oid::CP_USER_NOTICE_OID).clone(), + qualifier: extensions::Qualifier::UserNotice(extensions::UserNotice { + notice_ref, + explicit_text, + }), + } + }; + qualifiers.push(qualifier); + } + Some(common::Asn1ReadableOrWritable::new_write( + asn1::SequenceOfWriter::new(qualifiers), + )) + } else { + None + }; + let py_policy_id = py_policy_info.getattr(pyo3::intern!(py, "policy_identifier"))?; + policy_informations.push(extensions::PolicyInformation { + policy_identifier: py_oid_to_oid(py_policy_id)?, + policy_qualifiers: qualifiers, + }); + } + Ok(asn1::write_single(&asn1::SequenceOfWriter::new( + policy_informations, + ))?) +} + +fn encode_issuing_distribution_point( + py: pyo3::Python<'_>, + ext: &pyo3::PyAny, +) -> CryptographyResult> { + let only_some_reasons = if ext + .getattr(pyo3::intern!(py, "only_some_reasons"))? + .is_true()? + { + let py_reasons = ext.getattr(pyo3::intern!(py, "only_some_reasons"))?; + let reasons = certificate::encode_distribution_point_reasons(ext.py(), py_reasons)?; + Some(common::Asn1ReadableOrWritable::new_write(reasons)) + } else { + None + }; + let distribution_point = if ext.getattr(pyo3::intern!(py, "full_name"))?.is_true()? { + let py_full_name = ext.getattr(pyo3::intern!(py, "full_name"))?; + let gns = x509::common::encode_general_names(ext.py(), py_full_name)?; + Some(extensions::DistributionPointName::FullName( + common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), + )) + } else if ext.getattr(pyo3::intern!(py, "relative_name"))?.is_true()? { + let mut name_entries = vec![]; + for py_name_entry in ext.getattr(pyo3::intern!(py, "relative_name"))?.iter()? { + name_entries.push(x509::common::encode_name_entry(ext.py(), py_name_entry?)?); + } + Some(extensions::DistributionPointName::NameRelativeToCRLIssuer( + common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), + )) + } else { + None + }; + + let idp = crl::IssuingDistributionPoint { + distribution_point, + indirect_crl: ext.getattr(pyo3::intern!(py, "indirect_crl"))?.extract()?, + only_contains_attribute_certs: ext + .getattr(pyo3::intern!(py, "only_contains_attribute_certs"))? + .extract()?, + only_contains_ca_certs: ext + .getattr(pyo3::intern!(py, "only_contains_ca_certs"))? + .extract()?, + only_contains_user_certs: ext + .getattr(pyo3::intern!(py, "only_contains_user_certs"))? + .extract()?, + only_some_reasons, + }; + Ok(asn1::write_single(&idp)?) +} + +fn encode_oid_sequence(ext: &pyo3::PyAny) -> CryptographyResult> { + let mut oids = vec![]; + for el in ext.iter()? { + let oid = py_oid_to_oid(el?)?; + oids.push(oid); + } + Ok(asn1::write_single(&asn1::SequenceOfWriter::new(oids))?) +} + +fn encode_tls_features(py: pyo3::Python<'_>, ext: &pyo3::PyAny) -> CryptographyResult> { + // Ideally we'd skip building up a vec and just write directly into the + // writer. This isn't possible at the moment because the callback to write + // an asn1::Sequence can't return an error, and we need to handle errors + // from Python. + let mut els = vec![]; + for el in ext.iter()? { + els.push(el?.getattr(pyo3::intern!(py, "value"))?.extract::()?); + } + + Ok(asn1::write_single(&asn1::SequenceOfWriter::new(els))?) +} + +fn encode_scts(ext: &pyo3::PyAny) -> CryptographyResult> { + let mut length = 0; + for sct in ext.iter()? { + let sct = sct?.downcast::>()?; + length += sct.borrow().sct_data.len() + 2; + } + + let mut result = vec![]; + result.extend_from_slice(&(length as u16).to_be_bytes()); + for sct in ext.iter()? { + let sct = sct?.downcast::>()?; + result.extend_from_slice(&(sct.borrow().sct_data.len() as u16).to_be_bytes()); + result.extend_from_slice(&sct.borrow().sct_data); + } + Ok(asn1::write_single(&result.as_slice())?) +} + +pub(crate) fn encode_extension( + py: pyo3::Python<'_>, + oid: &asn1::ObjectIdentifier, + ext: &pyo3::PyAny, +) -> CryptographyResult>> { + match oid { + &oid::BASIC_CONSTRAINTS_OID => { + let der = encode_basic_constraints(ext)?; + Ok(Some(der)) + } + &oid::SUBJECT_KEY_IDENTIFIER_OID => { + let digest = ext + .getattr(pyo3::intern!(py, "digest"))? + .extract::<&[u8]>()?; + Ok(Some(asn1::write_single(&digest)?)) + } + &oid::KEY_USAGE_OID => { + let der = encode_key_usage(py, ext)?; + Ok(Some(der)) + } + &oid::AUTHORITY_INFORMATION_ACCESS_OID | &oid::SUBJECT_INFORMATION_ACCESS_OID => { + let der = x509::common::encode_access_descriptions(ext.py(), ext)?; + Ok(Some(der)) + } + &oid::EXTENDED_KEY_USAGE_OID | &oid::ACCEPTABLE_RESPONSES_OID => { + let der = encode_oid_sequence(ext)?; + Ok(Some(der)) + } + &oid::CERTIFICATE_POLICIES_OID => { + let der = encode_certificate_policies(py, ext)?; + Ok(Some(der)) + } + &oid::POLICY_CONSTRAINTS_OID => { + let pc = extensions::PolicyConstraints { + require_explicit_policy: ext + .getattr(pyo3::intern!(py, "require_explicit_policy"))? + .extract()?, + inhibit_policy_mapping: ext + .getattr(pyo3::intern!(py, "inhibit_policy_mapping"))? + .extract()?, + }; + Ok(Some(asn1::write_single(&pc)?)) + } + &oid::NAME_CONSTRAINTS_OID => { + let permitted = ext.getattr(pyo3::intern!(py, "permitted_subtrees"))?; + let excluded = ext.getattr(pyo3::intern!(py, "excluded_subtrees"))?; + let nc = extensions::NameConstraints { + permitted_subtrees: encode_general_subtrees(ext.py(), permitted)?, + excluded_subtrees: encode_general_subtrees(ext.py(), excluded)?, + }; + Ok(Some(asn1::write_single(&nc)?)) + } + &oid::INHIBIT_ANY_POLICY_OID => { + let intval = ext + .getattr(pyo3::intern!(py, "skip_certs"))? + .downcast::()?; + let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; + Ok(Some(asn1::write_single( + &asn1::BigUint::new(bytes).unwrap(), + )?)) + } + &oid::ISSUER_ALTERNATIVE_NAME_OID | &oid::SUBJECT_ALTERNATIVE_NAME_OID => { + let gns = x509::common::encode_general_names(ext.py(), ext)?; + Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) + } + &oid::AUTHORITY_KEY_IDENTIFIER_OID => { + let der = encode_authority_key_identifier(ext.py(), ext)?; + Ok(Some(der)) + } + &oid::FRESHEST_CRL_OID | &oid::CRL_DISTRIBUTION_POINTS_OID => { + let der = encode_distribution_points(ext.py(), ext)?; + Ok(Some(der)) + } + &oid::OCSP_NO_CHECK_OID => Ok(Some(asn1::write_single(&())?)), + &oid::TLS_FEATURE_OID => { + let der = encode_tls_features(py, ext)?; + Ok(Some(der)) + } + &oid::PRECERT_POISON_OID => Ok(Some(asn1::write_single(&())?)), + &oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID + | &oid::SIGNED_CERTIFICATE_TIMESTAMPS_OID => { + let der = encode_scts(ext)?; + 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"))? + .get_item(ext.getattr(pyo3::intern!(py, "reason"))?)? + .extract::()?; + Ok(Some(asn1::write_single(&asn1::Enumerated::new(value))?)) + } + &oid::CERTIFICATE_ISSUER_OID => { + let gns = x509::common::encode_general_names(ext.py(), ext)?; + Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) + } + &oid::INVALIDITY_DATE_OID => { + let dt = x509::py_to_datetime(py, ext.getattr(pyo3::intern!(py, "invalidity_date"))?)?; + Ok(Some(asn1::write_single(&asn1::GeneralizedTime::new(dt)?)?)) + } + &oid::CRL_NUMBER_OID | &oid::DELTA_CRL_INDICATOR_OID => { + let intval = ext + .getattr(pyo3::intern!(py, "crl_number"))? + .downcast::()?; + let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; + Ok(Some(asn1::write_single( + &asn1::BigUint::new(bytes).unwrap(), + )?)) + } + &oid::ISSUING_DISTRIBUTION_POINT_OID => { + let der = encode_issuing_distribution_point(py, ext)?; + Ok(Some(der)) + } + &oid::NONCE_OID => { + let nonce = ext + .getattr(pyo3::intern!(py, "nonce"))? + .extract::<&[u8]>()?; + Ok(Some(asn1::write_single(&nonce)?)) + } + &oid::MS_CERTIFICATE_TEMPLATE => { + let py_template_id = ext.getattr(pyo3::intern!(py, "template_id"))?; + let mstpl = extensions::MSCertificateTemplate { + template_id: py_oid_to_oid(py_template_id)?, + major_version: ext.getattr(pyo3::intern!(py, "major_version"))?.extract()?, + minor_version: ext.getattr(pyo3::intern!(py, "minor_version"))?.extract()?, + }; + Ok(Some(asn1::write_single(&mstpl)?)) + } + _ => Ok(None), + } +} diff --git a/src/rust/src/x509/mod.rs b/src/rust/src/x509/mod.rs new file mode 100644 index 000000000000..c43bf9023e71 --- /dev/null +++ b/src/rust/src/x509/mod.rs @@ -0,0 +1,19 @@ +// 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(crate) mod certificate; +pub(crate) mod common; +pub(crate) mod crl; +pub(crate) mod csr; +pub(crate) mod extensions; +pub(crate) mod ocsp; +pub(crate) mod ocsp_req; +pub(crate) mod ocsp_resp; +pub(crate) mod sct; +pub(crate) mod sign; + +pub(crate) use common::{ + datetime_to_py, find_in_pem, parse_and_cache_extensions, parse_general_name, + parse_general_names, parse_name, parse_rdn, py_to_datetime, +}; diff --git a/src/rust/src/x509/ocsp.rs b/src/rust/src/x509/ocsp.rs new file mode 100644 index 000000000000..05ea096078bb --- /dev/null +++ b/src/rust/src/x509/ocsp.rs @@ -0,0 +1,127 @@ +// 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::error::CryptographyResult; +use crate::x509; +use crate::x509::certificate::Certificate; +use cryptography_x509::common; +use cryptography_x509::ocsp_req::CertID; +use once_cell::sync::Lazy; +use std::collections::HashMap; + +pub(crate) static ALGORITHM_PARAMETERS_TO_HASH: Lazy< + HashMap, &str>, +> = Lazy::new(|| { + let mut h = HashMap::new(); + h.insert(common::AlgorithmParameters::Sha1(Some(())), "SHA1"); + h.insert(common::AlgorithmParameters::Sha224(Some(())), "SHA224"); + h.insert(common::AlgorithmParameters::Sha256(Some(())), "SHA256"); + h.insert(common::AlgorithmParameters::Sha384(Some(())), "SHA384"); + h.insert(common::AlgorithmParameters::Sha512(Some(())), "SHA512"); + h +}); + +pub(crate) static HASH_NAME_TO_ALGORITHM_IDENTIFIERS: Lazy< + HashMap<&str, common::AlgorithmIdentifier<'_>>, +> = Lazy::new(|| { + let mut h = HashMap::new(); + h.insert( + "sha1", + common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Sha1(Some(())), + }, + ); + h.insert( + "sha224", + common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Sha224(Some(())), + }, + ); + h.insert( + "sha256", + common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Sha256(Some(())), + }, + ); + h.insert( + "sha384", + common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Sha384(Some(())), + }, + ); + h.insert( + "sha512", + common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Sha512(Some(())), + }, + ); + h +}); + +pub(crate) fn certid_new<'p>( + py: pyo3::Python<'p>, + cert: &'p Certificate, + issuer: &'p Certificate, + hash_algorithm: &'p pyo3::PyAny, +) -> CryptographyResult> { + let issuer_der = asn1::write_single(&cert.raw.borrow_value_public().tbs_cert.issuer)?; + let issuer_name_hash = hash_data(py, hash_algorithm, &issuer_der)?; + let issuer_key_hash = hash_data( + py, + hash_algorithm, + issuer + .raw + .borrow_value_public() + .tbs_cert + .spki + .subject_public_key + .as_bytes(), + )?; + + Ok(CertID { + hash_algorithm: x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[hash_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?] + .clone(), + issuer_name_hash, + issuer_key_hash, + serial_number: cert.raw.borrow_value_public().tbs_cert.serial, + }) +} + +pub(crate) fn certid_new_from_hash<'p>( + py: pyo3::Python<'p>, + issuer_name_hash: &'p [u8], + issuer_key_hash: &'p [u8], + serial_number: asn1::BigInt<'p>, + hash_algorithm: &'p pyo3::PyAny, +) -> CryptographyResult> { + Ok(CertID { + hash_algorithm: x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[hash_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?] + .clone(), + issuer_name_hash, + issuer_key_hash, + serial_number, + }) +} + +pub(crate) fn hash_data<'p>( + py: pyo3::Python<'p>, + py_hash_alg: &'p pyo3::PyAny, + data: &[u8], +) -> pyo3::PyResult<&'p [u8]> { + let hash = py + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "Hash"))? + .call1((py_hash_alg,))?; + hash.call_method1(pyo3::intern!(py, "update"), (data,))?; + hash.call_method0(pyo3::intern!(py, "finalize"))?.extract() +} diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs new file mode 100644 index 000000000000..1571524edfeb --- /dev/null +++ b/src/rust/src/x509/ocsp_req.rs @@ -0,0 +1,246 @@ +// 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::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 cryptography_x509::{common, ocsp_req, oid}; +use pyo3::IntoPy; + +#[ouroboros::self_referencing] +struct OwnedOCSPRequest { + data: pyo3::Py, + #[borrows(data)] + #[covariant] + value: ocsp_req::OCSPRequest<'this>, +} + +#[pyo3::prelude::pyfunction] +fn load_der_ocsp_request( + py: pyo3::Python<'_>, + data: pyo3::Py, +) -> CryptographyResult { + let raw = OwnedOCSPRequest::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; + + if raw + .borrow_value() + .tbs_request + .request_list + .unwrap_read() + .len() + != 1 + { + return Err(CryptographyError::from( + pyo3::exceptions::PyNotImplementedError::new_err( + "OCSP request contains more than one request", + ), + )); + } + + Ok(OCSPRequest { + raw, + cached_extensions: None, + }) +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +struct OCSPRequest { + raw: OwnedOCSPRequest, + + cached_extensions: Option, +} + +impl OCSPRequest { + fn cert_id(&self) -> ocsp_req::CertID<'_> { + self.raw + .borrow_value() + .tbs_request + .request_list + .unwrap_read() + .clone() + .next() + .unwrap() + .req_cert + } +} + +#[pyo3::prelude::pymethods] +impl OCSPRequest { + #[getter] + fn issuer_name_hash(&self) -> &[u8] { + self.cert_id().issuer_name_hash + } + + #[getter] + fn issuer_key_hash(&self) -> &[u8] { + self.cert_id().issuer_key_hash + } + + #[getter] + fn hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> 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()?), + None => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(format!( + "Signature algorithm OID: {} not recognized", + cert_id.hash_algorithm.oid() + )), + )), + } + } + + #[getter] + fn serial_number<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { + let bytes = self.cert_id().serial_number.as_bytes(); + Ok(big_byte_slice_to_py_int(py, bytes)?) + } + + #[getter] + fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let tbs_request = &self.raw.borrow_value().tbs_request; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + x509::parse_and_cache_extensions( + py, + &mut self.cached_extensions, + &tbs_request.raw_request_extensions, + |oid, value| { + match *oid { + oid::NONCE_OID => { + // This is a disaster. RFC 2560 says that the contents of the nonce is + // just the raw extension value. This is nonsense, since they're always + // supposed to be ASN.1 TLVs. RFC 6960 correctly specifies that the + // nonce is an OCTET STRING, and so you should unwrap the TLV to get + // the nonce. So we try parsing as a TLV and fall back to just using + // the raw value. + let nonce = asn1::parse_single::<&[u8]>(value).unwrap_or(value); + Ok(Some( + x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?, + )) + } + oid::ACCEPTABLE_RESPONSES_OID => { + let oids = asn1::parse_single::< + asn1::SequenceOf<'_, asn1::ObjectIdentifier>, + >(value)?; + let py_oids = pyo3::types::PyList::empty(py); + for oid in oids { + py_oids.append(oid_to_py_oid(py, &oid)?)?; + } + + Ok(Some(x509_module.call_method1( + pyo3::intern!(py, "OCSPAcceptableResponses"), + (py_oids,), + )?)) + } + _ => Ok(None), + } + }, + ) + } + + fn public_bytes<'p>( + &self, + 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) { + return Err(pyo3::exceptions::PyValueError::new_err( + "The only allowed encoding value is Encoding.DER", + ) + .into()); + } + let result = asn1::write_single(self.raw.borrow_value())?; + Ok(pyo3::types::PyBytes::new(py, &result)) + } +} + +#[pyo3::prelude::pyfunction] +fn create_ocsp_request( + py: pyo3::Python<'_>, + builder: &pyo3::PyAny, +) -> CryptographyResult { + let builder_request = builder.getattr(pyo3::intern!(py, "_request"))?; + + // Declare outside the if-block so the lifetimes are right. + let (py_cert, py_issuer, py_hash): ( + pyo3::PyRef<'_, x509::certificate::Certificate>, + pyo3::PyRef<'_, x509::certificate::Certificate>, + &pyo3::PyAny, + ); + let req_cert = if !builder_request.is_none() { + let tuple = builder_request.extract::<( + pyo3::PyRef<'_, x509::certificate::Certificate>, + pyo3::PyRef<'_, x509::certificate::Certificate>, + &pyo3::PyAny, + )>()?; + py_cert = tuple.0; + py_issuer = tuple.1; + py_hash = tuple.2; + ocsp::certid_new(py, &py_cert, &py_issuer, py_hash)? + } else { + let (issuer_name_hash, issuer_key_hash, py_serial, py_hash): ( + &[u8], + &[u8], + &pyo3::types::PyLong, + &pyo3::PyAny, + ) = builder + .getattr(pyo3::intern!(py, "_request_hash"))? + .extract()?; + let serial_number = asn1::BigInt::new(py_uint_to_big_endian_bytes(py, py_serial)?).unwrap(); + ocsp::certid_new_from_hash( + py, + issuer_name_hash, + issuer_key_hash, + serial_number, + py_hash, + )? + }; + + let extensions = x509::common::encode_extensions( + py, + builder.getattr(pyo3::intern!(py, "_extensions"))?, + extensions::encode_extension, + )?; + let reqs = [ocsp_req::Request { + req_cert, + single_request_extensions: None, + }]; + let ocsp_req = ocsp_req::OCSPRequest { + tbs_request: ocsp_req::TBSRequest { + version: 0, + requestor_name: None, + request_list: common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( + &reqs, + )), + raw_request_extensions: extensions, + }, + optional_signature: None, + }; + let data = asn1::write_single(&ocsp_req)?; + load_der_ocsp_request(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_function(pyo3::wrap_pyfunction!(load_der_ocsp_request, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_ocsp_request, module)?)?; + + Ok(()) +} diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs new file mode 100644 index 000000000000..721e0313a613 --- /dev/null +++ b/src/rust/src/x509/ocsp_resp.rs @@ -0,0 +1,855 @@ +// 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::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 cryptography_x509::ocsp_resp::SingleResponse; +use cryptography_x509::{common, ocsp_resp, oid}; +use pyo3::IntoPy; +use std::sync::Arc; + +const BASIC_RESPONSE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 1); + +#[pyo3::prelude::pyfunction] +fn load_der_ocsp_response( + py: pyo3::Python<'_>, + data: pyo3::Py, +) -> Result { + let raw = OwnedOCSPResponse::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; + + let response = raw.borrow_value(); + match response.response_status.value() { + SUCCESSFUL_RESPONSE => match response.response_bytes { + Some(ref bytes) => { + if bytes.response_type != BASIC_RESPONSE_OID { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Successful OCSP response does not contain a BasicResponse", + ), + )); + } + } + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Successful OCSP response does not contain a BasicResponse", + ), + )) + } + }, + MALFORMED_REQUEST_RESPOSNE + | INTERNAL_ERROR_RESPONSE + | TRY_LATER_RESPONSE + | SIG_REQUIRED_RESPONSE + | UNAUTHORIZED_RESPONSE => {} + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("OCSP response has an unknown status code"), + )) + } + }; + Ok(OCSPResponse { + raw: Arc::new(raw), + cached_extensions: None, + cached_single_extensions: None, + }) +} + +#[ouroboros::self_referencing] +struct OwnedOCSPResponse { + data: pyo3::Py, + #[borrows(data)] + #[covariant] + value: ocsp_resp::OCSPResponse<'this>, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +struct OCSPResponse { + raw: Arc, + + cached_extensions: Option, + cached_single_extensions: Option, +} + +impl OCSPResponse { + fn requires_successful_response(&self) -> pyo3::PyResult<&ocsp_resp::BasicOCSPResponse<'_>> { + match self.raw.borrow_value().response_bytes.as_ref() { + Some(b) => Ok(b.response.get()), + None => Err(pyo3::exceptions::PyValueError::new_err( + "OCSP response status is not successful so the property has no value", + )), + } + } +} + +const SUCCESSFUL_RESPONSE: u32 = 0; +const MALFORMED_REQUEST_RESPOSNE: u32 = 1; +const INTERNAL_ERROR_RESPONSE: u32 = 2; +const TRY_LATER_RESPONSE: u32 = 3; +// 4 is unused +const SIG_REQUIRED_RESPONSE: u32 = 5; +const UNAUTHORIZED_RESPONSE: u32 = 6; + +#[pyo3::prelude::pymethods] +impl OCSPResponse { + #[getter] + fn responses(&self) -> Result { + self.requires_successful_response()?; + Ok(OCSPResponseIterator { + contents: OwnedOCSPResponseIteratorData::try_new(Arc::clone(&self.raw), |v| { + Ok::<_, ()>( + v.borrow_value() + .response_bytes + .as_ref() + .unwrap() + .response + .get() + .tbs_response_data + .responses + .unwrap_read() + .clone(), + ) + }) + .unwrap(), + }) + } + + #[getter] + fn response_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let status = self.raw.borrow_value().response_status.value(); + let attr = if status == SUCCESSFUL_RESPONSE { + "SUCCESSFUL" + } else if status == MALFORMED_REQUEST_RESPOSNE { + "MALFORMED_REQUEST" + } else if status == INTERNAL_ERROR_RESPONSE { + "INTERNAL_ERROR" + } else if status == TRY_LATER_RESPONSE { + "TRY_LATER" + } else if status == SIG_REQUIRED_RESPONSE { + "SIG_REQUIRED" + } else { + assert_eq!(status, UNAUTHORIZED_RESPONSE); + "UNAUTHORIZED" + }; + py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))? + .getattr(pyo3::intern!(py, "OCSPResponseStatus"))? + .getattr(attr) + } + + #[getter] + fn responder_name<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + match resp.tbs_response_data.responder_id { + ocsp_resp::ResponderId::ByName(ref name) => Ok(x509::parse_name(py, name)?), + ocsp_resp::ResponderId::ByKey(_) => Ok(py.None().into_ref(py)), + } + } + + #[getter] + fn responder_key_hash<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + match resp.tbs_response_data.responder_id { + ocsp_resp::ResponderId::ByKey(key_hash) => { + Ok(pyo3::types::PyBytes::new(py, key_hash).as_ref()) + } + ocsp_resp::ResponderId::ByName(_) => Ok(py.None().into_ref(py)), + } + } + + #[getter] + fn produced_at<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + x509::datetime_to_py(py, resp.tbs_response_data.produced_at.as_datetime()) + } + + #[getter] + fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + oid_to_py_oid(py, resp.signature_algorithm.oid()) + } + + #[getter] + fn signature_hash_algorithm<'p>( + &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(_) => { + let exc_messsage = format!( + "Signature algorithm OID: {} not recognized", + self.requires_successful_response()? + .signature_algorithm + .oid() + ); + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(exc_messsage), + )) + } + } + } + + #[getter] + fn signature<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { + let resp = self.requires_successful_response()?; + Ok(pyo3::types::PyBytes::new(py, resp.signature.as_bytes())) + } + + #[getter] + fn tbs_response_bytes<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let resp = self.requires_successful_response()?; + let result = asn1::write_single(&resp.tbs_response_data)?; + Ok(pyo3::types::PyBytes::new(py, &result)) + } + + #[getter] + fn certificates<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, CryptographyError> { + let resp = self.requires_successful_response()?; + let py_certs = pyo3::types::PyList::empty(py); + let certs = match &resp.certs { + Some(certs) => certs.unwrap_read(), + None => return Ok(py_certs), + }; + for i in 0..certs.len() { + // TODO: O(n^2), don't have too many certificates! + let raw_cert = map_arc_data_ocsp_response(py, &self.raw, |_data, resp| { + resp.response_bytes + .as_ref() + .unwrap() + .response + .get() + .certs + .as_ref() + .unwrap() + .unwrap_read() + .clone() + .nth(i) + .unwrap() + }); + py_certs.append(pyo3::PyCell::new( + py, + x509::certificate::Certificate { + raw: raw_cert, + cached_extensions: None, + }, + )?)?; + } + Ok(py_certs) + } + + #[getter] + fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_serial_number(&single_resp, py) + } + + #[getter] + fn issuer_key_hash(&self) -> Result<&[u8], CryptographyError> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + Ok(single_resp.cert_id.issuer_key_hash) + } + + #[getter] + fn issuer_name_hash(&self) -> Result<&[u8], CryptographyError> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + Ok(single_resp.cert_id.issuer_name_hash) + } + + #[getter] + fn hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_hash_algorithm(&single_resp, py) + } + + #[getter] + fn certificate_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_certificate_status(&single_resp, py) + } + + #[getter] + fn revocation_time<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_revocation_time(&single_resp, py) + } + + #[getter] + fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_revocation_reason(&single_resp, py) + } + + #[getter] + fn this_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_this_update(&single_resp, py) + } + + #[getter] + fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_next_update(&single_resp, py) + } + + #[getter] + fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + self.requires_successful_response()?; + + let response_data = &self + .raw + .borrow_value() + .response_bytes + .as_ref() + .unwrap() + .response + .get() + .tbs_response_data; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + x509::parse_and_cache_extensions( + py, + &mut self.cached_extensions, + &response_data.raw_response_extensions, + |oid, ext_data| { + match oid { + &oid::NONCE_OID => { + // This is a disaster. RFC 2560 says that the contents of the nonce is + // just the raw extension value. This is nonsense, since they're always + // supposed to be ASN.1 TLVs. RFC 6960 correctly specifies that the + // nonce is an OCTET STRING, and so you should unwrap the TLV to get + // the nonce. So we try parsing as a TLV and fall back to just using + // the raw value. + let nonce = asn1::parse_single::<&[u8]>(ext_data).unwrap_or(ext_data); + Ok(Some( + x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?, + )) + } + _ => Ok(None), + } + }, + ) + } + + #[getter] + fn single_extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + self.requires_successful_response()?; + let single_resp = single_response( + self.raw + .borrow_value() + .response_bytes + .as_ref() + .unwrap() + .response + .get(), + )?; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + x509::parse_and_cache_extensions( + py, + &mut self.cached_single_extensions, + &single_resp.raw_single_extensions, + |oid, ext_data| match oid { + &oid::SIGNED_CERTIFICATE_TIMESTAMPS_OID => { + let contents = asn1::parse_single::<&[u8]>(ext_data)?; + let scts = sct::parse_scts(py, contents, sct::LogEntryType::Certificate)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "SignedCertificateTimestamps"))? + .call1((scts,))?, + )) + } + _ => crl::parse_crl_entry_ext(py, oid.clone(), ext_data), + }, + ) + } + + fn public_bytes<'p>( + &self, + 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) { + return Err(pyo3::exceptions::PyValueError::new_err( + "The only allowed encoding value is Encoding.DER", + ) + .into()); + } + let result = asn1::write_single(self.raw.borrow_value())?; + Ok(pyo3::types::PyBytes::new(py, &result)) + } +} + +// Open-coded implementation of the API discussed in +// https://github.com/joshua-maros/ouroboros/issues/38 +fn map_arc_data_ocsp_response( + py: pyo3::Python<'_>, + it: &OwnedOCSPResponse, + f: impl for<'this> FnOnce( + &'this [u8], + &ocsp_resp::OCSPResponse<'this>, + ) -> cryptography_x509::certificate::Certificate<'this>, +) -> certificate::OwnedCertificate { + certificate::OwnedCertificate::new_public(it.borrow_data().clone_ref(py), |inner_it| { + it.with(|value| { + f(inner_it.as_bytes(py), unsafe { + std::mem::transmute(value.value) + }) + }) + }) +} +fn try_map_arc_data_mut_ocsp_response_iterator( + it: &mut OwnedOCSPResponseIteratorData, + f: impl for<'this> FnOnce( + &'this OwnedOCSPResponse, + &mut asn1::SequenceOf<'this, ocsp_resp::SingleResponse<'this>>, + ) -> Result, E>, +) -> Result { + OwnedSingleResponse::try_new(Arc::clone(it.borrow_data()), |inner_it| { + it.with_value_mut(|value| f(inner_it, unsafe { std::mem::transmute(value) })) + }) +} + +fn single_response<'a>( + resp: &ocsp_resp::BasicOCSPResponse<'a>, +) -> Result, CryptographyError> { + let responses = resp.tbs_response_data.responses.unwrap_read(); + let num_responses = responses.len(); + + if num_responses != 1 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "OCSP response contains {} SINGLERESP structures. Use .response_iter to iterate through them", + num_responses + )) + )); + } + + Ok(responses.clone().next().unwrap()) +} + +fn singleresp_py_serial_number<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + big_byte_slice_to_py_int(py, resp.cert_id.serial_number.as_bytes()) +} + +fn singleresp_py_certificate_status<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + let attr = match resp.cert_status { + ocsp_resp::CertStatus::Good(_) => pyo3::intern!(py, "GOOD"), + 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) +} + +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()?), + None => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(format!( + "Signature algorithm OID: {} not recognized", + resp.cert_id.hash_algorithm.oid() + )), + )), + } +} + +fn singleresp_py_this_update<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + x509::datetime_to_py(py, resp.this_update.as_datetime()) +} + +fn singleresp_py_next_update<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + match &resp.next_update { + Some(v) => x509::datetime_to_py(py, v.as_datetime()), + None => Ok(py.None().into_ref(py)), + } +} + +fn singleresp_py_revocation_reason<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> CryptographyResult<&'p pyo3::PyAny> { + match &resp.cert_status { + ocsp_resp::CertStatus::Revoked(revoked_info) => match revoked_info.revocation_reason { + Some(ref v) => crl::parse_crl_reason_flags(py, v), + None => Ok(py.None().into_ref(py)), + }, + ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { + Ok(py.None().into_ref(py)) + } + } +} + +fn singleresp_py_revocation_time<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + match &resp.cert_status { + ocsp_resp::CertStatus::Revoked(revoked_info) => { + x509::datetime_to_py(py, revoked_info.revocation_time.as_datetime()) + } + ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { + Ok(py.None().into_ref(py)) + } + } +} + +#[pyo3::prelude::pyfunction] +fn create_ocsp_response( + py: pyo3::Python<'_>, + status: &pyo3::PyAny, + builder: &pyo3::PyAny, + private_key: &pyo3::PyAny, + hash_algorithm: &pyo3::PyAny, +) -> CryptographyResult { + let response_status = status + .getattr(pyo3::intern!(py, "value"))? + .extract::()?; + + let py_cert: pyo3::PyRef<'_, x509::certificate::Certificate>; + let py_issuer: pyo3::PyRef<'_, x509::certificate::Certificate>; + let borrowed_cert; + let py_certs: Option>>; + 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"))? + .extract()?; + py_issuer = py_single_resp + .getattr(pyo3::intern!(py, "_issuer"))? + .extract()?; + let py_cert_hash_algorithm = py_single_resp.getattr(pyo3::intern!(py, "_algorithm"))?; + let (responder_cert, responder_encoding): ( + &pyo3::PyCell, + &pyo3::PyAny, + ) = builder + .getattr(pyo3::intern!(py, "_responder_id"))? + .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"))?) + { + ocsp_resp::CertStatus::Good(()) + } else if py_cert_status.is(ocsp_mod + .getattr(pyo3::intern!(py, "OCSPCertStatus"))? + .getattr(pyo3::intern!(py, "UNKNOWN"))?) + { + 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"))? + .get_item(py_single_resp.getattr(pyo3::intern!(py, "_revocation_reason"))?)? + .extract::()?; + Some(asn1::Enumerated::new(value)) + } else { + None + }; + // REVOKED + let py_revocation_time = + py_single_resp.getattr(pyo3::intern!(py, "_revocation_time"))?; + let revocation_time = + asn1::GeneralizedTime::new(py_to_datetime(py, py_revocation_time)?)?; + ocsp_resp::CertStatus::Revoked(ocsp_resp::RevokedInfo { + revocation_time, + revocation_reason, + }) + }; + let next_update = if !py_single_resp + .getattr(pyo3::intern!(py, "_next_update"))? + .is_none() + { + let py_next_update = py_single_resp.getattr(pyo3::intern!(py, "_next_update"))?; + Some(asn1::GeneralizedTime::new(py_to_datetime( + py, + py_next_update, + )?)?) + } else { + None + }; + let py_this_update = py_single_resp.getattr(pyo3::intern!(py, "_this_update"))?; + let this_update = asn1::GeneralizedTime::new(py_to_datetime(py, py_this_update)?)?; + + let responses = vec![SingleResponse { + cert_id: ocsp::certid_new(py, &py_cert, &py_issuer, py_cert_hash_algorithm)?, + cert_status, + next_update, + this_update, + raw_single_extensions: None, + }]; + + 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()?; + ocsp_resp::ResponderId::ByKey(ocsp::hash_data( + py, + sha1, + borrowed_cert + .raw + .borrow_value_public() + .tbs_cert + .spki + .subject_public_key + .as_bytes(), + )?) + } else { + ocsp_resp::ResponderId::ByName( + borrowed_cert + .raw + .borrow_value_public() + .tbs_cert + .subject + .clone(), + ) + }; + + let tbs_response_data = ocsp_resp::ResponseData { + version: 0, + produced_at: asn1::GeneralizedTime::new(x509::common::datetime_now(py)?)?, + responder_id, + responses: common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( + responses, + )), + raw_response_extensions: x509::common::encode_extensions( + py, + builder.getattr(pyo3::intern!(py, "_extensions"))?, + extensions::encode_extension, + )?, + }; + + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + )?; + let tbs_bytes = asn1::write_single(&tbs_response_data)?; + let signature = x509::sign::sign_data( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + &tbs_bytes, + )?; + + if !responder_cert + .call_method0(pyo3::intern!(py, "public_key"))? + .eq(private_key.call_method0(pyo3::intern!(py, "public_key"))?)? + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Certificate public key and provided private key do not match", + ), + )); + } + + py_certs = builder.getattr(pyo3::intern!(py, "_certs"))?.extract()?; + let certs = py_certs.as_ref().map(|py_certs| { + common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( + py_certs + .iter() + .map(|c| c.raw.borrow_value_public().clone()) + .collect(), + )) + }); + + let basic_resp = ocsp_resp::BasicOCSPResponse { + tbs_response_data, + signature: asn1::BitString::new(signature, 0).unwrap(), + signature_algorithm: sigalg, + certs, + }; + Some(ocsp_resp::ResponseBytes { + response_type: (BASIC_RESPONSE_OID).clone(), + response: asn1::OctetStringEncoded::new(basic_resp), + }) + } else { + None + }; + + let resp = ocsp_resp::OCSPResponse { + response_status: asn1::Enumerated::new(response_status), + response_bytes, + }; + let data = asn1::write_single(&resp)?; + load_der_ocsp_response(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_function(pyo3::wrap_pyfunction!(load_der_ocsp_response, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_ocsp_response, module)?)?; + + Ok(()) +} + +#[ouroboros::self_referencing] +struct OwnedOCSPResponseIteratorData { + data: Arc, + #[borrows(data)] + #[covariant] + value: asn1::SequenceOf<'this, SingleResponse<'this>>, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +struct OCSPResponseIterator { + contents: OwnedOCSPResponseIteratorData, +} + +#[pyo3::prelude::pymethods] +impl OCSPResponseIterator { + fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } + + fn __next__(&mut self) -> Option { + let single_resp = + try_map_arc_data_mut_ocsp_response_iterator(&mut self.contents, |_data, v| { + match v.next() { + Some(single_resp) => Ok(single_resp), + None => Err(()), + } + }) + .ok()?; + Some(OCSPSingleResponse { raw: single_resp }) + } +} + +#[ouroboros::self_referencing] +struct OwnedSingleResponse { + data: Arc, + #[borrows(data)] + #[covariant] + value: ocsp_resp::SingleResponse<'this>, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +struct OCSPSingleResponse { + raw: OwnedSingleResponse, +} + +impl OCSPSingleResponse { + fn single_response(&self) -> &SingleResponse<'_> { + self.raw.borrow_value() + } +} + +#[pyo3::prelude::pymethods] +impl OCSPSingleResponse { + #[getter] + fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + singleresp_py_serial_number(self.single_response(), py) + } + + #[getter] + fn issuer_key_hash(&self) -> &[u8] { + let single_resp = self.single_response(); + single_resp.cert_id.issuer_key_hash + } + + #[getter] + fn issuer_name_hash(&self) -> &[u8] { + let single_resp = self.single_response(); + single_resp.cert_id.issuer_name_hash + } + + #[getter] + fn hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { + let single_resp = self.single_response(); + singleresp_py_hash_algorithm(single_resp, py) + } + + #[getter] + fn certificate_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let single_resp = self.single_response(); + singleresp_py_certificate_status(single_resp, py) + } + + #[getter] + fn revocation_time<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let single_resp = self.single_response(); + singleresp_py_revocation_time(single_resp, py) + } + + #[getter] + fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + let single_resp = self.single_response(); + singleresp_py_revocation_reason(single_resp, py) + } + + #[getter] + fn this_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let single_resp = self.single_response(); + singleresp_py_this_update(single_resp, py) + } + + #[getter] + fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let single_resp = self.single_response(); + singleresp_py_next_update(single_resp, py) + } +} diff --git a/src/rust/src/x509/sct.rs b/src/rust/src/x509/sct.rs new file mode 100644 index 000000000000..a13785bf3fb1 --- /dev/null +++ b/src/rust/src/x509/sct.rs @@ -0,0 +1,352 @@ +// 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::error::CryptographyError; +use pyo3::types::IntoPyDict; +use pyo3::ToPyObject; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +struct TLSReader<'a> { + data: &'a [u8], +} + +impl<'a> TLSReader<'a> { + fn new(data: &'a [u8]) -> TLSReader<'a> { + TLSReader { data } + } + + fn is_empty(&self) -> bool { + self.data.is_empty() + } + + fn read_byte(&mut self) -> Result { + Ok(self.read_exact(1)?[0]) + } + + fn read_exact(&mut self, length: usize) -> Result<&'a [u8], CryptographyError> { + if length > self.data.len() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid SCT length"), + )); + } + let (result, data) = self.data.split_at(length); + self.data = data; + Ok(result) + } + + fn read_length_prefixed(&mut self) -> Result, CryptographyError> { + let length = u16::from_be_bytes(self.read_exact(2)?.try_into().unwrap()); + Ok(TLSReader::new(self.read_exact(length.into())?)) + } +} + +#[derive(Clone)] +pub(crate) enum LogEntryType { + Certificate, + PreCertificate, +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum HashAlgorithm { + Md5, + Sha1, + Sha224, + Sha256, + Sha384, + Sha512, +} + +impl TryFrom for HashAlgorithm { + type Error = pyo3::PyErr; + + fn try_from(value: u8) -> Result { + Ok(match value { + 1 => HashAlgorithm::Md5, + 2 => HashAlgorithm::Sha1, + 3 => HashAlgorithm::Sha224, + 4 => HashAlgorithm::Sha256, + 5 => HashAlgorithm::Sha384, + 6 => HashAlgorithm::Sha512, + _ => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "Invalid/unsupported hash algorithm for SCT: {}", + value + ))) + } + }) + } +} + +impl HashAlgorithm { + fn to_attr(&self) -> &'static str { + match self { + HashAlgorithm::Md5 => "MD5", + HashAlgorithm::Sha1 => "SHA1", + HashAlgorithm::Sha224 => "SHA224", + HashAlgorithm::Sha256 => "SHA256", + HashAlgorithm::Sha384 => "SHA384", + HashAlgorithm::Sha512 => "SHA512", + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum SignatureAlgorithm { + Rsa, + Dsa, + Ecdsa, +} + +impl SignatureAlgorithm { + fn to_attr(&self) -> &'static str { + match self { + SignatureAlgorithm::Rsa => "RSA", + SignatureAlgorithm::Dsa => "DSA", + SignatureAlgorithm::Ecdsa => "ECDSA", + } + } +} + +impl TryFrom for SignatureAlgorithm { + type Error = pyo3::PyErr; + + fn try_from(value: u8) -> Result { + Ok(match value { + 1 => SignatureAlgorithm::Rsa, + 2 => SignatureAlgorithm::Dsa, + 3 => SignatureAlgorithm::Ecdsa, + _ => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "Invalid/unsupported signature algorithm for SCT: {}", + value + ))) + } + }) + } +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +pub(crate) struct Sct { + log_id: [u8; 32], + timestamp: u64, + entry_type: LogEntryType, + hash_algorithm: HashAlgorithm, + signature_algorithm: SignatureAlgorithm, + // TODO: These could be 'self references back into sct_data with ouroboros. + signature: Vec, + extension_bytes: Vec, + pub(crate) sct_data: Vec, +} + +#[pyo3::prelude::pymethods] +impl Sct { + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, Sct>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.sct_data == other.sct_data), + pyo3::basic::CompareOp::Ne => Ok(self.sct_data != other.sct_data), + _ => Err(pyo3::exceptions::PyTypeError::new_err( + "SCTs cannot be ordered", + )), + } + } + + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.sct_data.hash(&mut hasher); + hasher.finish() + } + + #[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")) + } + + #[getter] + fn log_id(&self) -> &[u8] { + &self.log_id + } + + #[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 + .call_method1( + pyo3::intern!(py, "utcfromtimestamp"), + (self.timestamp / 1000,), + )? + .call_method( + "replace", + (), + Some(vec![("microsecond", self.timestamp % 1000 * 1000)].into_py_dict(py)), + ) + } + + #[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) + } + + #[getter] + fn signature_hash_algorithm<'p>( + &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()) + } + + #[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()) + } + + #[getter] + fn signature(&self) -> &[u8] { + &self.signature + } + + #[getter] + fn extension_bytes(&self) -> &[u8] { + &self.extension_bytes + } +} + +pub(crate) fn parse_scts( + py: pyo3::Python<'_>, + data: &[u8], + entry_type: LogEntryType, +) -> Result { + let mut reader = TLSReader::new(data).read_length_prefixed()?; + + let py_scts = pyo3::types::PyList::empty(py); + while !reader.is_empty() { + let mut sct_data = reader.read_length_prefixed()?; + let raw_sct_data = sct_data.data.to_vec(); + let version = sct_data.read_byte()?; + if version != 0 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid SCT version"), + )); + } + let log_id = sct_data.read_exact(32)?.try_into().unwrap(); + let timestamp = u64::from_be_bytes(sct_data.read_exact(8)?.try_into().unwrap()); + let extension_bytes = sct_data.read_length_prefixed()?.data.to_vec(); + let hash_algorithm = sct_data.read_byte()?.try_into()?; + let signature_algorithm = sct_data.read_byte()?.try_into()?; + + let signature = sct_data.read_length_prefixed()?.data.to_vec(); + + let sct = Sct { + log_id, + timestamp, + entry_type: entry_type.clone(), + hash_algorithm, + signature_algorithm, + signature, + extension_bytes, + sct_data: raw_sct_data, + }; + py_scts.append(pyo3::PyCell::new(py, sct)?)?; + } + Ok(py_scts.to_object(py)) +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_class::()?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_algorithm_try_from() { + for (n, ha) in &[ + (1_u8, HashAlgorithm::Md5), + (2_u8, HashAlgorithm::Sha1), + (3_u8, HashAlgorithm::Sha224), + (4_u8, HashAlgorithm::Sha256), + (5_u8, HashAlgorithm::Sha384), + (6_u8, HashAlgorithm::Sha512), + ] { + let res = HashAlgorithm::try_from(*n).unwrap(); + assert_eq!(&res, ha); + } + + // We don't support "none" hash algorithms. + assert!(HashAlgorithm::try_from(0).is_err()); + assert!(HashAlgorithm::try_from(7).is_err()); + } + + #[test] + fn test_hash_algorithm_to_attr() { + for (ha, attr) in &[ + (HashAlgorithm::Md5, "MD5"), + (HashAlgorithm::Sha1, "SHA1"), + (HashAlgorithm::Sha224, "SHA224"), + (HashAlgorithm::Sha256, "SHA256"), + (HashAlgorithm::Sha384, "SHA384"), + (HashAlgorithm::Sha512, "SHA512"), + ] { + assert_eq!(ha.to_attr(), *attr); + } + } + + #[test] + fn test_signature_algorithm_try_from() { + for (n, ha) in &[ + (1_u8, SignatureAlgorithm::Rsa), + (2_u8, SignatureAlgorithm::Dsa), + (3_u8, SignatureAlgorithm::Ecdsa), + ] { + let res = SignatureAlgorithm::try_from(*n).unwrap(); + assert_eq!(&res, ha); + } + + // We don't support "anonymous" signature algorithms. + assert!(SignatureAlgorithm::try_from(0).is_err()); + assert!(SignatureAlgorithm::try_from(4).is_err()); + } + + #[test] + fn test_signature_algorithm_to_attr() { + for (sa, attr) in &[ + (SignatureAlgorithm::Rsa, "RSA"), + (SignatureAlgorithm::Dsa, "DSA"), + (SignatureAlgorithm::Ecdsa, "ECDSA"), + ] { + assert_eq!(sa.to_attr(), *attr); + } + } +} diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs new file mode 100644 index 000000000000..4b03a2d9ab8e --- /dev/null +++ b/src/rust/src/x509/sign.rs @@ -0,0 +1,765 @@ +// 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::asn1::oid_to_py_oid; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use cryptography_x509::{common, oid}; +use once_cell::sync::Lazy; +use std::collections::HashMap; + +// This is similar to a hashmap in ocsp.rs but contains more hash algorithms +// that aren't allowable in OCSP +static HASH_OIDS_TO_HASH: Lazy> = Lazy::new(|| { + let mut h = HashMap::new(); + h.insert(&oid::SHA1_OID, "SHA1"); + h.insert(&oid::SHA224_OID, "SHA224"); + h.insert(&oid::SHA256_OID, "SHA256"); + h.insert(&oid::SHA384_OID, "SHA384"); + h.insert(&oid::SHA512_OID, "SHA512"); + h.insert(&oid::SHA3_224_OID, "SHA3_224"); + h.insert(&oid::SHA3_256_OID, "SHA3_256"); + h.insert(&oid::SHA3_384_OID, "SHA3_384"); + h.insert(&oid::SHA3_512_OID, "SHA3_512"); + h +}); + +#[derive(Debug, PartialEq)] +pub(crate) enum KeyType { + Rsa, + Dsa, + Ec, + Ed25519, + Ed448, +} + +enum HashType { + None, + Sha224, + Sha256, + Sha384, + Sha512, + Sha3_224, + Sha3_256, + Sha3_384, + Sha3_512, +} + +fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::PyResult { + 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)? { + Ok(KeyType::Rsa) + } else if private_key.is_instance(dsa_key_type)? { + Ok(KeyType::Dsa) + } else if private_key.is_instance(ec_key_type)? { + Ok(KeyType::Ec) + } else if private_key.is_instance(ed25519_key_type)? { + Ok(KeyType::Ed25519) + } else if private_key.is_instance(ed448_key_type)? { + Ok(KeyType::Ed448) + } else { + Err(pyo3::exceptions::PyTypeError::new_err( + "Key must be an rsa, dsa, ec, ed25519, or ed448 private key.", + )) + } +} + +fn identify_hash_type( + py: pyo3::Python<'_>, + hash_algorithm: &pyo3::PyAny, +) -> pyo3::PyResult { + if hash_algorithm.is_none() { + 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)? { + return Err(pyo3::exceptions::PyTypeError::new_err( + "Algorithm must be a registered hash algorithm.", + )); + } + + match hash_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract()? + { + "sha224" => Ok(HashType::Sha224), + "sha256" => Ok(HashType::Sha256), + "sha384" => Ok(HashType::Sha384), + "sha512" => Ok(HashType::Sha512), + "sha3-224" => Ok(HashType::Sha3_224), + "sha3-256" => Ok(HashType::Sha3_256), + "sha3-384" => Ok(HashType::Sha3_384), + "sha3-512" => Ok(HashType::Sha3_512), + name => Err(exceptions::UnsupportedAlgorithm::new_err(format!( + "Hash algorithm {:?} not supported for signatures", + name + ))), + } +} + +fn compute_pss_salt_length<'p>( + py: pyo3::Python<'p>, + private_key: &'p pyo3::PyAny, + hash_algorithm: &'p pyo3::PyAny, + rsa_padding: &'p pyo3::PyAny, +) -> pyo3::PyResult { + 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"))? + .call1((private_key, hash_algorithm))? + .extract::() + } else if py_saltlen.is_instance(digestlen)? { + hash_algorithm + .getattr(pyo3::intern!(py, "digest_size"))? + .extract::() + } else if py_saltlen.is_instance(py.get_type::())? { + py_saltlen.extract::() + } else { + Err(pyo3::exceptions::PyTypeError::new_err( + "salt_length must be an int, MaxLength, or DigestLength.", + )) + } +} + +pub(crate) fn compute_signature_algorithm<'p>( + py: pyo3::Python<'p>, + private_key: &'p pyo3::PyAny, + hash_algorithm: &'p pyo3::PyAny, + rsa_padding: &'p pyo3::PyAny, +) -> pyo3::PyResult> { + 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)? { + let hash_alg_params = identify_alg_params_for_hash_type(hash_type)?; + let hash_algorithm_id = common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: hash_alg_params, + }; + let salt_length = compute_pss_salt_length(py, private_key, hash_algorithm, rsa_padding)?; + let py_mgf_alg = rsa_padding + .getattr(pyo3::intern!(py, "_mgf"))? + .getattr(pyo3::intern!(py, "_algorithm"))?; + let mgf_hash_type = identify_hash_type(py, py_mgf_alg)?; + let mgf_alg = common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: identify_alg_params_for_hash_type(mgf_hash_type)?, + }; + let params = + common::AlgorithmParameters::RsaPss(Some(Box::new(common::RsaPssParameters { + hash_algorithm: hash_algorithm_id, + mask_gen_algorithm: common::MaskGenAlgorithm { + oid: oid::MGF1_OID, + params: mgf_alg, + }, + salt_length, + _trailer_field: 1, + }))); + + return Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params, + }); + } + // It's not an RSA PSS signature, so we compute the signature algorithm from + // the union of key type and hash type. + match (key_type, hash_type) { + (KeyType::Ed25519, HashType::None) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Ed25519, + }), + (KeyType::Ed448, HashType::None) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Ed448, + }), + (KeyType::Ed25519 | KeyType::Ed448, _) => Err(pyo3::exceptions::PyValueError::new_err( + "Algorithm must be None when signing via ed25519 or ed448", + )), + + (KeyType::Ec, HashType::Sha224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha224(None), + }), + (KeyType::Ec, HashType::Sha256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha256(None), + }), + (KeyType::Ec, HashType::Sha384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha384(None), + }), + (KeyType::Ec, HashType::Sha512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha512(None), + }), + (KeyType::Ec, HashType::Sha3_224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_224, + }), + (KeyType::Ec, HashType::Sha3_256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_256, + }), + (KeyType::Ec, HashType::Sha3_384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_384, + }), + (KeyType::Ec, HashType::Sha3_512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_512, + }), + + (KeyType::Rsa, HashType::Sha224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha224(Some(())), + }), + (KeyType::Rsa, HashType::Sha256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha256(Some(())), + }), + (KeyType::Rsa, HashType::Sha384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha384(Some(())), + }), + (KeyType::Rsa, HashType::Sha512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha512(Some(())), + }), + (KeyType::Rsa, HashType::Sha3_224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_224(Some(())), + }), + (KeyType::Rsa, HashType::Sha3_256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_256(Some(())), + }), + (KeyType::Rsa, HashType::Sha3_384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_384(Some(())), + }), + (KeyType::Rsa, HashType::Sha3_512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_512(Some(())), + }), + + (KeyType::Dsa, HashType::Sha224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha224, + }), + (KeyType::Dsa, HashType::Sha256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha256, + }), + (KeyType::Dsa, HashType::Sha384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha384, + }), + (KeyType::Dsa, HashType::Sha512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha512, + }), + ( + KeyType::Dsa, + HashType::Sha3_224 | HashType::Sha3_256 | HashType::Sha3_384 | HashType::Sha3_512, + ) => Err(exceptions::UnsupportedAlgorithm::new_err( + "SHA3 hashes are not supported with DSA keys", + )), + (_, HashType::None) => Err(pyo3::exceptions::PyTypeError::new_err( + "Algorithm must be a registered hash algorithm, not None.", + )), + } +} + +pub(crate) fn sign_data<'p>( + py: pyo3::Python<'p>, + private_key: &'p pyo3::PyAny, + hash_algorithm: &'p pyo3::PyAny, + rsa_padding: &'p pyo3::PyAny, + data: &[u8], +) -> pyo3::PyResult<&'p [u8]> { + let key_type = identify_key_type(py, private_key)?; + + let signature = match key_type { + KeyType::Ed25519 | KeyType::Ed448 => { + 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,))?; + 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()?; + } + private_key.call_method1(pyo3::intern!(py, "sign"), (data, padding, hash_algorithm))? + } + KeyType::Dsa => { + private_key.call_method1(pyo3::intern!(py, "sign"), (data, hash_algorithm))? + } + }; + signature.extract() +} + +pub(crate) fn verify_signature_with_signature_algorithm<'p>( + py: pyo3::Python<'p>, + issuer_public_key: &'p pyo3::PyAny, + signature_algorithm: &common::AlgorithmIdentifier<'_>, + signature: &[u8], + data: &[u8], +) -> CryptographyResult<()> { + let key_type = identify_public_key_type(py, issuer_public_key)?; + let sig_key_type = identify_key_type_for_algorithm_params(&signature_algorithm.params)?; + if key_type != sig_key_type { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Signature algorithm does not match issuer key type", + ), + )); + } + let py_signature_algorithm_parameters = + identify_signature_algorithm_parameters(py, signature_algorithm)?; + let py_signature_hash_algorithm = identify_signature_hash_algorithm(py, signature_algorithm)?; + match key_type { + KeyType::Ed25519 | KeyType::Ed448 => { + issuer_public_key.call_method1(pyo3::intern!(py, "verify"), (signature, data))? + } + KeyType::Ec => issuer_public_key.call_method1( + pyo3::intern!(py, "verify"), + (signature, data, py_signature_algorithm_parameters), + )?, + KeyType::Rsa => issuer_public_key.call_method1( + pyo3::intern!(py, "verify"), + ( + signature, + data, + py_signature_algorithm_parameters, + py_signature_hash_algorithm, + ), + )?, + KeyType::Dsa => issuer_public_key.call_method1( + pyo3::intern!(py, "verify"), + (signature, data, py_signature_hash_algorithm), + )?, + }; + Ok(()) +} + +pub(crate) fn identify_public_key_type( + py: pyo3::Python<'_>, + public_key: &pyo3::PyAny, +) -> pyo3::PyResult { + 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)? { + Ok(KeyType::Rsa) + } else if public_key.is_instance(dsa_key_type)? { + Ok(KeyType::Dsa) + } else if public_key.is_instance(ec_key_type)? { + Ok(KeyType::Ec) + } else if public_key.is_instance(ed25519_key_type)? { + Ok(KeyType::Ed25519) + } else if public_key.is_instance(ed448_key_type)? { + Ok(KeyType::Ed448) + } else { + Err(pyo3::exceptions::PyTypeError::new_err( + "Key must be an rsa, dsa, ec, ed25519, or ed448 public key.", + )) + } +} + +fn identify_key_type_for_algorithm_params( + params: &common::AlgorithmParameters<'_>, +) -> pyo3::PyResult { + match params { + common::AlgorithmParameters::RsaWithSha224(..) + | common::AlgorithmParameters::RsaWithSha256(..) + | common::AlgorithmParameters::RsaWithSha384(..) + | common::AlgorithmParameters::RsaWithSha512(..) + | common::AlgorithmParameters::RsaWithSha3_224(..) + | common::AlgorithmParameters::RsaWithSha3_256(..) + | common::AlgorithmParameters::RsaWithSha3_384(..) + | common::AlgorithmParameters::RsaWithSha3_512(..) + | common::AlgorithmParameters::RsaPss(..) => Ok(KeyType::Rsa), + common::AlgorithmParameters::EcDsaWithSha224(..) + | common::AlgorithmParameters::EcDsaWithSha256(..) + | common::AlgorithmParameters::EcDsaWithSha384(..) + | common::AlgorithmParameters::EcDsaWithSha512(..) + | common::AlgorithmParameters::EcDsaWithSha3_224 + | common::AlgorithmParameters::EcDsaWithSha3_256 + | common::AlgorithmParameters::EcDsaWithSha3_384 + | common::AlgorithmParameters::EcDsaWithSha3_512 => Ok(KeyType::Ec), + common::AlgorithmParameters::Ed25519 => Ok(KeyType::Ed25519), + common::AlgorithmParameters::Ed448 => Ok(KeyType::Ed448), + common::AlgorithmParameters::DsaWithSha224 + | common::AlgorithmParameters::DsaWithSha256 + | common::AlgorithmParameters::DsaWithSha384 + | common::AlgorithmParameters::DsaWithSha512 => Ok(KeyType::Dsa), + _ => Err(pyo3::exceptions::PyValueError::new_err( + "Unsupported signature algorithm", + )), + } +} + +fn identify_alg_params_for_hash_type( + hash_type: HashType, +) -> pyo3::PyResult> { + match hash_type { + HashType::Sha224 => Ok(common::AlgorithmParameters::Sha224(Some(()))), + HashType::Sha256 => Ok(common::AlgorithmParameters::Sha256(Some(()))), + HashType::Sha384 => Ok(common::AlgorithmParameters::Sha384(Some(()))), + HashType::Sha512 => Ok(common::AlgorithmParameters::Sha512(Some(()))), + HashType::Sha3_224 => Ok(common::AlgorithmParameters::Sha3_224(Some(()))), + HashType::Sha3_256 => Ok(common::AlgorithmParameters::Sha3_256(Some(()))), + HashType::Sha3_384 => Ok(common::AlgorithmParameters::Sha3_384(Some(()))), + HashType::Sha3_512 => Ok(common::AlgorithmParameters::Sha3_512(Some(()))), + HashType::None => Err(pyo3::exceptions::PyTypeError::new_err( + "Algorithm must be a registered hash algorithm, not None.", + )), + } +} + +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()?), + None => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(format!( + "Signature algorithm OID: {} not recognized", + &oid + )), + )), + } +} + +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"))?; + match &signature_algorithm.params { + common::AlgorithmParameters::RsaPss(opt_pss) => { + let pss = opt_pss.as_ref().ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Invalid RSA PSS parameters") + })?; + hash_oid_py_hash(py, pss.hash_algorithm.oid().clone()) + } + _ => { + let py_sig_alg_oid = oid_to_py_oid(py, signature_algorithm.oid())?; + let hash_alg = sig_oids_to_hash.get_item(py_sig_alg_oid); + match hash_alg { + Ok(data) => Ok(data), + Err(_) => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(format!( + "Signature algorithm OID: {} not recognized", + signature_algorithm.oid() + )), + )), + } + } + } +} + +pub(crate) fn identify_signature_algorithm_parameters<'p>( + py: pyo3::Python<'p>, + signature_algorithm: &common::AlgorithmIdentifier<'_>, +) -> CryptographyResult<&'p pyo3::PyAny> { + match &signature_algorithm.params { + common::AlgorithmParameters::RsaPss(opt_pss) => { + let pss = opt_pss.as_ref().ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Invalid RSA PSS parameters") + })?; + if pss.mask_gen_algorithm.oid != oid::MGF1_OID { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "Unsupported mask generation OID: {}", + pss.mask_gen_algorithm.oid + )), + )); + } + 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))?) + } + common::AlgorithmParameters::RsaWithSha1(_) + | common::AlgorithmParameters::RsaWithSha1Alt(_) + | common::AlgorithmParameters::RsaWithSha224(_) + | common::AlgorithmParameters::RsaWithSha256(_) + | common::AlgorithmParameters::RsaWithSha384(_) + | common::AlgorithmParameters::RsaWithSha512(_) + | common::AlgorithmParameters::RsaWithSha3_224(_) + | 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) + } + common::AlgorithmParameters::EcDsaWithSha224(_) + | common::AlgorithmParameters::EcDsaWithSha256(_) + | common::AlgorithmParameters::EcDsaWithSha384(_) + | common::AlgorithmParameters::EcDsaWithSha512(_) + | common::AlgorithmParameters::EcDsaWithSha3_224 + | common::AlgorithmParameters::EcDsaWithSha3_256 + | common::AlgorithmParameters::EcDsaWithSha3_384 + | common::AlgorithmParameters::EcDsaWithSha3_512 => { + 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(py.None().into_ref(py)), + } +} + +#[cfg(test)] +mod tests { + use super::{ + identify_alg_params_for_hash_type, identify_key_type_for_algorithm_params, HashType, + KeyType, + }; + use cryptography_x509::{common, oid}; + + #[test] + fn test_identify_key_type_for_algorithm_params() { + for (params, keytype) in [ + ( + &common::AlgorithmParameters::RsaWithSha224(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::RsaWithSha256(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::RsaWithSha384(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::RsaWithSha512(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::RsaWithSha3_224(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::RsaWithSha3_256(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::RsaWithSha3_384(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::RsaWithSha3_512(Some(())), + KeyType::Rsa, + ), + ( + &common::AlgorithmParameters::EcDsaWithSha224(None), + KeyType::Ec, + ), + ( + &common::AlgorithmParameters::EcDsaWithSha256(None), + KeyType::Ec, + ), + ( + &common::AlgorithmParameters::EcDsaWithSha384(None), + KeyType::Ec, + ), + ( + &common::AlgorithmParameters::EcDsaWithSha512(None), + KeyType::Ec, + ), + (&common::AlgorithmParameters::EcDsaWithSha3_224, KeyType::Ec), + (&common::AlgorithmParameters::EcDsaWithSha3_256, KeyType::Ec), + (&common::AlgorithmParameters::EcDsaWithSha3_384, KeyType::Ec), + (&common::AlgorithmParameters::EcDsaWithSha3_512, KeyType::Ec), + (&common::AlgorithmParameters::Ed25519, KeyType::Ed25519), + (&common::AlgorithmParameters::Ed448, KeyType::Ed448), + (&common::AlgorithmParameters::DsaWithSha224, KeyType::Dsa), + (&common::AlgorithmParameters::DsaWithSha256, KeyType::Dsa), + (&common::AlgorithmParameters::DsaWithSha384, KeyType::Dsa), + (&common::AlgorithmParameters::DsaWithSha512, KeyType::Dsa), + ] { + assert_eq!( + identify_key_type_for_algorithm_params(params).unwrap(), + keytype + ); + } + assert!( + identify_key_type_for_algorithm_params(&common::AlgorithmParameters::Other( + oid::TLS_FEATURE_OID, + None + )) + .is_err() + ); + } + + #[test] + fn test_identify_alg_params_for_hash_type() { + for (hash, params) in [ + ( + HashType::Sha224, + common::AlgorithmParameters::Sha224(Some(())), + ), + ( + HashType::Sha256, + common::AlgorithmParameters::Sha256(Some(())), + ), + ( + HashType::Sha384, + common::AlgorithmParameters::Sha384(Some(())), + ), + ( + HashType::Sha512, + common::AlgorithmParameters::Sha512(Some(())), + ), + ( + HashType::Sha3_224, + common::AlgorithmParameters::Sha3_224(Some(())), + ), + ( + HashType::Sha3_256, + common::AlgorithmParameters::Sha3_256(Some(())), + ), + ( + HashType::Sha3_384, + common::AlgorithmParameters::Sha3_384(Some(())), + ), + ( + HashType::Sha3_512, + common::AlgorithmParameters::Sha3_512(Some(())), + ), + ] { + assert_eq!(identify_alg_params_for_hash_type(hash).unwrap(), params); + } + } +} diff --git a/tests/__init__.py b/tests/__init__.py index 4b540884df72..b509336233c2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/tests/bench/__init__.py b/tests/bench/__init__.py new file mode 100644 index 000000000000..b509336233c2 --- /dev/null +++ b/tests/bench/__init__.py @@ -0,0 +1,3 @@ +# 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. diff --git a/tests/bench/test_aead.py b/tests/bench/test_aead.py new file mode 100644 index 000000000000..f93c4e8892eb --- /dev/null +++ b/tests/bench/test_aead.py @@ -0,0 +1,102 @@ +# 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 pytest + +from cryptography.hazmat.primitives.ciphers.aead import ( + AESCCM, + AESGCM, + AESOCB3, + AESSIV, + ChaCha20Poly1305, +) + +from ..hazmat.primitives.test_aead import _aead_supported + + +@pytest.mark.skipif( + not _aead_supported(ChaCha20Poly1305), + reason="Requires OpenSSL with ChaCha20Poly1305 support", +) +def test_chacha20poly1305_encrypt(benchmark): + chacha = ChaCha20Poly1305(b"\x00" * 32) + benchmark(chacha.encrypt, b"\x00" * 12, b"hello world plaintext", b"") + + +@pytest.mark.skipif( + not _aead_supported(ChaCha20Poly1305), + reason="Requires OpenSSL with ChaCha20Poly1305 support", +) +def test_chacha20poly1305_decrypt(benchmark): + chacha = ChaCha20Poly1305(b"\x00" * 32) + ct = chacha.encrypt(b"\x00" * 12, b"hello world plaintext", b"") + benchmark(chacha.decrypt, b"\x00" * 12, ct, b"") + + +def test_aesgcm_encrypt(benchmark): + aes = AESGCM(b"\x00" * 32) + benchmark(aes.encrypt, b"\x00" * 12, b"hello world plaintext", None) + + +def test_aesgcm_decrypt(benchmark): + aes = AESGCM(b"\x00" * 32) + ct = aes.encrypt(b"\x00" * 12, b"hello world plaintext", None) + benchmark(aes.decrypt, b"\x00" * 12, ct, None) + + +@pytest.mark.skipif( + not _aead_supported(AESSIV), + reason="Requires OpenSSL with AES-SIV support", +) +def test_aessiv_encrypt(benchmark): + aes = AESSIV(b"\x00" * 32) + benchmark(aes.encrypt, b"hello world plaintext", None) + + +@pytest.mark.skipif( + not _aead_supported(AESSIV), + reason="Requires OpenSSL with AES-SIV support", +) +def test_aessiv_decrypt(benchmark): + aes = AESSIV(b"\x00" * 32) + ct = aes.encrypt(b"hello world plaintext", None) + benchmark(aes.decrypt, ct, None) + + +@pytest.mark.skipif( + not _aead_supported(AESOCB3), + reason="Requires OpenSSL with AES-OCB3 support", +) +def test_aesocb3_encrypt(benchmark): + aes = AESOCB3(b"\x00" * 32) + benchmark(aes.encrypt, b"\x00" * 12, b"hello world plaintext", None) + + +@pytest.mark.skipif( + not _aead_supported(AESOCB3), + reason="Requires OpenSSL with AES-OCB3 support", +) +def test_aesocb3_decrypt(benchmark): + aes = AESOCB3(b"\x00" * 32) + ct = aes.encrypt(b"\x00" * 12, b"hello world plaintext", None) + benchmark(aes.decrypt, b"\x00" * 12, ct, None) + + +@pytest.mark.skipif( + not _aead_supported(AESCCM), + reason="Requires OpenSSL with AES-CCM support", +) +def test_aesccm_encrypt(benchmark): + aes = AESCCM(b"\x00" * 32) + benchmark(aes.encrypt, b"\x00" * 12, b"hello world plaintext", None) + + +@pytest.mark.skipif( + not _aead_supported(AESCCM), + reason="Requires OpenSSL with AES-CCM support", +) +def test_aesccm_decrypt(benchmark): + aes = AESCCM(b"\x00" * 32) + ct = aes.encrypt(b"\x00" * 12, b"hello world plaintext", None) + benchmark(aes.decrypt, b"\x00" * 12, ct, None) diff --git a/tests/bench/test_ec_load.py b/tests/bench/test_ec_load.py new file mode 100644 index 000000000000..568dbd96f449 --- /dev/null +++ b/tests/bench/test_ec_load.py @@ -0,0 +1,13 @@ +# 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 ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 + + +def test_load_ec_public_numbers(benchmark): + benchmark(EC_KEY_SECP256R1.public_numbers.public_key) + + +def test_load_ec_private_numbers(benchmark): + benchmark(EC_KEY_SECP256R1.private_key) diff --git a/tests/bench/test_hashes.py b/tests/bench/test_hashes.py new file mode 100644 index 000000000000..49ca5be30d6b --- /dev/null +++ b/tests/bench/test_hashes.py @@ -0,0 +1,14 @@ +# 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 import hashes + + +def test_sha256(benchmark): + def bench(): + h = hashes.Hash(hashes.SHA256()) + h.update(b"I love hashing. So much. The best.") + return h.finalize() + + benchmark(bench) diff --git a/tests/bench/test_hmac.py b/tests/bench/test_hmac.py new file mode 100644 index 000000000000..b5b1e33bd8b9 --- /dev/null +++ b/tests/bench/test_hmac.py @@ -0,0 +1,14 @@ +# 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 import hashes, hmac + + +def test_hmac_sha256(benchmark): + def bench(): + h = hmac.HMAC(b"my extremely secure key", hashes.SHA256()) + h.update(b"I love hashing. So much. The best.") + return h.finalize() + + benchmark(bench) diff --git a/tests/bench/test_x509.py b/tests/bench/test_x509.py new file mode 100644 index 000000000000..87a60af0f597 --- /dev/null +++ b/tests/bench/test_x509.py @@ -0,0 +1,42 @@ +# 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 + +from cryptography import x509 + +from ..utils import load_vectors_from_file + + +def test_object_identier_constructor(benchmark): + benchmark(x509.ObjectIdentifier, "1.3.6.1.4.1.11129.2.4.5") + + +def test_aki_public_bytes(benchmark): + aki = x509.AuthorityKeyIdentifier( + key_identifier=b"\x00" * 16, + authority_cert_issuer=None, + authority_cert_serial_number=None, + ) + benchmark(aki.public_bytes) + + +def test_load_der_certificate(benchmark): + cert_bytes = load_vectors_from_file( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + loader=lambda pemfile: pemfile.read(), + mode="rb", + ) + + benchmark(x509.load_der_x509_certificate, cert_bytes) + + +def test_load_pem_certificate(benchmark): + cert_bytes = load_vectors_from_file( + os.path.join("x509", "cryptography.io.pem"), + loader=lambda pemfile: pemfile.read(), + mode="rb", + ) + + benchmark(x509.load_pem_x509_certificate, cert_bytes) diff --git a/tests/conftest.py b/tests/conftest.py index 4e3124fa76ea..0e128a16513e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,42 +2,32 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +import contextlib import pytest from cryptography.hazmat.backends.openssl import backend as openssl_backend -from .utils import ( - check_backend_support, - load_wycheproof_tests, - skip_if_wycheproof_none, -) +from .utils import check_backend_support + + +def pytest_configure(config): + if config.getoption("--enable-fips"): + openssl_backend._enable_fips() def pytest_report_header(config): return "\n".join( [ - "OpenSSL: {}".format(openssl_backend.openssl_version_text()), - "FIPS Enabled: {}".format(openssl_backend._fips_enabled), + f"OpenSSL: {openssl_backend.openssl_version_text()}", + f"FIPS Enabled: {openssl_backend._fips_enabled}", ] ) def pytest_addoption(parser): parser.addoption("--wycheproof-root", default=None) - - -def pytest_generate_tests(metafunc): - if "wycheproof" in metafunc.fixturenames: - wycheproof = metafunc.config.getoption("--wycheproof-root") - skip_if_wycheproof_none(wycheproof) - - testcases = [] - marker = metafunc.definition.get_closest_marker("wycheproof_tests") - for path in marker.args: - testcases.extend(load_wycheproof_tests(wycheproof, path)) - metafunc.parametrize("wycheproof", testcases) + parser.addoption("--enable-fips", default=False) def pytest_runtest_setup(item): @@ -48,18 +38,30 @@ def pytest_runtest_setup(item): @pytest.fixture() def backend(request): - required_interfaces = [ - mark.kwargs["interface"] - for mark in request.node.iter_markers("requires_backend_interface") - ] - if not all( - isinstance(openssl_backend, iface) for iface in required_interfaces - ): - pytest.skip( - "OpenSSL doesn't implement required interfaces: {}".format( - required_interfaces - ) - ) - check_backend_support(openssl_backend, request) - return openssl_backend + + # Ensure the error stack is clear before the test + errors = openssl_backend._consume_errors() + assert not errors + yield openssl_backend + # Ensure the error stack is clear after the test + errors = openssl_backend._consume_errors() + assert not errors + + +@pytest.fixture() +def subtests(): + # This is a miniature version of the pytest-subtests package, but + # optimized for lower overhead. + # + # When tests are skipped, these are not logged in the final pytest output. + yield SubTests() + + +class SubTests: + @contextlib.contextmanager + def test(self): + try: + yield + except pytest.skip.Exception: + pass diff --git a/tests/deprecated_module.py b/tests/deprecated_module.py new file mode 100644 index 000000000000..421af3d502bf --- /dev/null +++ b/tests/deprecated_module.py @@ -0,0 +1,18 @@ +# 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 import utils + +# This module exists to test `cryptography.utils.deprecated` + +DEPRECATED = 3 +utils.deprecated( + DEPRECATED, + __name__, + "Test Deprecated Object", + DeprecationWarning, + name="DEPRECATED", +) + +NOT_DEPRECATED = 12 diff --git a/tests/doubles.py b/tests/doubles.py index 2ff1942f5b98..511c8a50e358 100644 --- a/tests/doubles.py +++ b/tests/doubles.py @@ -2,42 +2,48 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function -from cryptography import utils from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.ciphers import CipherAlgorithm +from cryptography.hazmat.primitives.ciphers import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) from cryptography.hazmat.primitives.ciphers.modes import Mode -@utils.register_interface(CipherAlgorithm) -class DummyCipherAlgorithm(object): +class DummyCipherAlgorithm(CipherAlgorithm): name = "dummy-cipher" block_size = 128 - key_size = None + key_size = 256 + key_sizes = frozenset([256]) -@utils.register_interface(Mode) -class DummyMode(object): +class DummyBlockCipherAlgorithm(DummyCipherAlgorithm, BlockCipherAlgorithm): + def __init__(self, _: object) -> None: + pass + + name = "dummy-block-cipher" + + +class DummyMode(Mode): name = "dummy-mode" - def validate_for_algorithm(self, algorithm): + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: pass -@utils.register_interface(hashes.HashAlgorithm) -class DummyHashAlgorithm(object): +class DummyHashAlgorithm(hashes.HashAlgorithm): name = "dummy-hash" block_size = None - digest_size = None + digest_size = 32 -@utils.register_interface(serialization.KeySerializationEncryption) -class DummyKeySerializationEncryption(object): +class DummyKeySerializationEncryption( + serialization.KeySerializationEncryption +): pass -@utils.register_interface(padding.AsymmetricPadding) -class DummyAsymmetricPadding(object): +class DummyAsymmetricPadding(padding.AsymmetricPadding): name = "dummy-padding" diff --git a/tests/hazmat/__init__.py b/tests/hazmat/__init__.py index 4b540884df72..b509336233c2 100644 --- a/tests/hazmat/__init__.py +++ b/tests/hazmat/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/tests/hazmat/backends/__init__.py b/tests/hazmat/backends/__init__.py index 4b540884df72..b509336233c2 100644 --- a/tests/hazmat/backends/__init__.py +++ b/tests/hazmat/backends/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/tests/hazmat/backends/test_no_backend.py b/tests/hazmat/backends/test_no_backend.py deleted file mode 100644 index 67866929e71d..000000000000 --- a/tests/hazmat/backends/test_no_backend.py +++ /dev/null @@ -1,16 +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 absolute_import, division, print_function - -from cryptography.hazmat.backends import _get_backend, default_backend - - -def test_get_backend_no_backend(): - assert _get_backend(None) is default_backend() - - -def test_get_backend(): - faux_backend = object() - assert _get_backend(faux_backend) is faux_backend diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 2f7e7bebfd0c..c8fa1efa21f5 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -2,64 +2,66 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import itertools import os -import subprocess -import sys -import textwrap import pytest -from cryptography import x509 from cryptography.exceptions import InternalError, _Reasons -from cryptography.hazmat.backends.interfaces import DHBackend, RSABackend -from cryptography.hazmat.backends.openssl.backend import Backend, backend +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import dh, dsa, padding +from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import CBC -from ..primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 from ...doubles import ( DummyAsymmetricPadding, + DummyBlockCipherAlgorithm, DummyCipherAlgorithm, DummyHashAlgorithm, DummyMode, ) +from ...hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 from ...utils import ( - load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) -from ...x509.test_x509 import _load_cert + +# Make ruff happy since we're importing fixtures that pytest patches in as +# func args +__all__ = ["rsa_key_512", "rsa_key_2048"] def skip_if_libre_ssl(openssl_version): - if u"LibreSSL" in openssl_version: + if "LibreSSL" in openssl_version: pytest.skip("LibreSSL hard-codes RAND_bytes to use arc4random.") -class TestLibreSkip(object): +class TestLibreSkip: def test_skip_no(self): - assert skip_if_libre_ssl(u"OpenSSL 1.0.2h 3 May 2016") is None + assert skip_if_libre_ssl("OpenSSL 1.0.2h 3 May 2016") is None def test_skip_yes(self): with pytest.raises(pytest.skip.Exception): - skip_if_libre_ssl(u"LibreSSL 2.1.6") + skip_if_libre_ssl("LibreSSL 2.1.6") -class DummyMGF(object): +class DummyMGF(padding.MGF): _salt_length = 0 + _algorithm = hashes.SHA1() -class TestOpenSSL(object): +class TestOpenSSL: def test_backend_exists(self): assert backend + def test_is_default_backend(self): + assert backend is default_backend() + def test_openssl_version_text(self): """ This test checks the value of OPENSSL_VERSION_TEXT. @@ -69,32 +71,46 @@ def test_openssl_version_text(self): if it starts with OpenSSL or LibreSSL as that appears to be true for every OpenSSL-alike. """ - assert backend.openssl_version_text().startswith( - "OpenSSL" - ) or backend.openssl_version_text().startswith("LibreSSL") + version = backend.openssl_version_text() + assert version.startswith(("OpenSSL", "LibreSSL", "BoringSSL")) + + # Verify the correspondence between these two. And do it in a way that + # ensures coverage. + if version.startswith("LibreSSL"): + assert backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + if backend._lib.CRYPTOGRAPHY_IS_LIBRESSL: + assert version.startswith("LibreSSL") + + if version.startswith("BoringSSL"): + assert backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + if backend._lib.CRYPTOGRAPHY_IS_BORINGSSL: + assert version.startswith("BoringSSL") def test_openssl_version_number(self): assert backend.openssl_version_number() > 0 def test_supports_cipher(self): - assert backend.cipher_supported(None, None) is False + assert ( + backend.cipher_supported(DummyCipherAlgorithm(), DummyMode()) + is False + ) def test_register_duplicate_cipher_adapter(self): with pytest.raises(ValueError): backend.register_cipher_adapter(AES, CBC, None) @pytest.mark.parametrize("mode", [DummyMode(), None]) - def test_nonexistent_cipher(self, mode): - b = Backend() - b.register_cipher_adapter( - DummyCipherAlgorithm, - type(mode), + def test_nonexistent_cipher(self, mode, backend, monkeypatch): + # We can't use register_cipher_adapter because backend is a + # global singleton and we want to revert the change after the test + monkeypatch.setitem( + backend._cipher_registry, + (DummyCipherAlgorithm, type(mode)), lambda backend, cipher, mode: backend._ffi.NULL, ) cipher = Cipher( DummyCipherAlgorithm(), mode, - backend=b, ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.encryptor() @@ -118,7 +134,7 @@ def test_consume_errors(self): assert len(errors) == 10 def test_ssl_ciphers_registered(self): - meth = backend._lib.SSLv23_method() + meth = backend._lib.TLS_method() ctx = backend._lib.SSL_CTX_new(meth) assert ctx != backend._ffi.NULL backend._lib.SSL_CTX_free(ctx) @@ -127,11 +143,6 @@ def test_evp_ciphers_registered(self): cipher = backend._lib.EVP_get_cipherbyname(b"aes-256-cbc") assert cipher != backend._ffi.NULL - def test_error_strings_loaded(self): - buf = backend._ffi.new("char[]", 256) - backend._lib.ERR_error_string_n(101183626, buf, len(buf)) - assert b"data not multiple of block length" in backend._ffi.string(buf) - def test_unknown_error_in_cipher_finalize(self): cipher = Cipher(AES(b"\0" * 16), CBC(b"\0" * 16), backend=backend) enc = cipher.encryptor() @@ -140,16 +151,8 @@ def test_unknown_error_in_cipher_finalize(self): with pytest.raises(InternalError): enc.finalize() - def test_large_key_size_on_new_openssl(self): - parameters = dsa.generate_parameters(2048, backend) - param_num = parameters.parameter_numbers() - assert param_num.p.bit_length() == 2048 - parameters = dsa.generate_parameters(3072, backend) - param_num = parameters.parameter_numbers() - assert param_num.p.bit_length() == 3072 - def test_int_to_bn(self): - value = (2 ** 4242) - 4242 + value = (2**4242) - 4242 bn = backend._int_to_bn(value) assert bn != backend._ffi.NULL bn = backend._ffi.gc(bn, backend._lib.BN_clear_free) @@ -157,174 +160,12 @@ def test_int_to_bn(self): assert bn assert backend._bn_to_int(bn) == value - def test_int_to_bn_inplace(self): - value = (2 ** 4242) - 4242 - bn_ptr = backend._lib.BN_new() - assert bn_ptr != backend._ffi.NULL - bn_ptr = backend._ffi.gc(bn_ptr, backend._lib.BN_free) - bn = backend._int_to_bn(value, bn_ptr) - - assert bn == bn_ptr - assert backend._bn_to_int(bn_ptr) == value - def test_bn_to_int(self): bn = backend._int_to_bn(0) assert backend._bn_to_int(bn) == 0 -@pytest.mark.skipif( - not backend._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE, - reason="Requires OpenSSL with ENGINE support and OpenSSL < 1.1.1d", -) -@pytest.mark.skip_fips(reason="osrandom engine disabled for FIPS") -class TestOpenSSLRandomEngine(object): - def setup(self): - # The default RAND engine is global and shared between - # tests. We make sure that the default engine is osrandom - # before we start each test and restore the global state to - # that engine in teardown. - current_default = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(current_default) - assert name == backend._lib.Cryptography_osrandom_engine_name - - def teardown(self): - # we need to reset state to being default. backend is a shared global - # for all these tests. - backend.activate_osrandom_engine() - current_default = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(current_default) - assert name == backend._lib.Cryptography_osrandom_engine_name - - @pytest.mark.skipif( - sys.executable is None, reason="No Python interpreter available." - ) - def test_osrandom_engine_is_default(self, tmpdir): - engine_printer = textwrap.dedent( - """ - import sys - from cryptography.hazmat.backends.openssl.backend import backend - - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - sys.stdout.write(backend._ffi.string(name).decode('ascii')) - res = backend._lib.ENGINE_free(e) - assert res == 1 - """ - ) - engine_name = tmpdir.join("engine_name") - - # If we're running tests via ``python setup.py test`` in a clean - # environment then all of our dependencies are going to be installed - # into either the current directory or the .eggs directory. However the - # subprocess won't know to activate these dependencies, so we'll get it - # to do so by passing our entire sys.path into the subprocess via the - # PYTHONPATH environment variable. - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join(sys.path) - - with engine_name.open("w") as out: - subprocess.check_call( - [sys.executable, "-c", engine_printer], - env=env, - stdout=out, - stderr=subprocess.PIPE, - ) - - osrandom_engine_name = backend._ffi.string( - backend._lib.Cryptography_osrandom_engine_name - ) - - assert engine_name.read().encode("ascii") == osrandom_engine_name - - def test_osrandom_sanity_check(self): - # This test serves as a check against catastrophic failure. - buf = backend._ffi.new("unsigned char[]", 500) - res = backend._lib.RAND_bytes(buf, 500) - assert res == 1 - assert backend._ffi.buffer(buf)[:] != "\x00" * 500 - - def test_activate_osrandom_no_default(self): - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - backend.activate_osrandom_engine() - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - - def test_activate_builtin_random(self): - e = backend._lib.ENGINE_get_default_RAND() - assert e != backend._ffi.NULL - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - - def test_activate_builtin_random_already_active(self): - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - - def test_osrandom_engine_implementation(self): - name = backend.osrandom_engine_implementation() - assert name in [ - "/dev/urandom", - "CryptGenRandom", - "getentropy", - "getrandom", - ] - if sys.platform.startswith("linux"): - assert name in ["getrandom", "/dev/urandom"] - if sys.platform == "darwin": - assert name in ["getentropy", "/dev/urandom"] - if sys.platform == "win32": - assert name == "CryptGenRandom" - - def test_activate_osrandom_already_default(self): - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - backend.activate_osrandom_engine() - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - - -@pytest.mark.skipif( - backend._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE, - reason="Requires OpenSSL without ENGINE support or OpenSSL >=1.1.1d", -) -class TestOpenSSLNoEngine(object): - def test_no_engine_support(self): - assert ( - backend._ffi.string(backend._lib.Cryptography_osrandom_engine_id) - == b"no-engine-support" - ) - assert ( - backend._ffi.string(backend._lib.Cryptography_osrandom_engine_name) - == b"osrandom_engine disabled" - ) - - def test_activate_builtin_random_does_nothing(self): - backend.activate_builtin_random() - - def test_activate_osrandom_does_nothing(self): - backend.activate_osrandom_engine() - - -class TestOpenSSLRSA(object): +class TestOpenSSLRSA: def test_generate_rsa_parameters_supported(self): assert backend.generate_rsa_parameters_supported(1, 1024) is False assert backend.generate_rsa_parameters_supported(4, 1024) is False @@ -377,18 +218,14 @@ def test_rsa_padding_supported_oaep(self): assert ( backend.rsa_padding_supported( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), ) is True ) - @pytest.mark.skipif( - backend._lib.Cryptography_HAS_RSA_OAEP_MD == 0, - reason="Requires OpenSSL with rsa_oaep_md (1.0.2+)", - ) def test_rsa_padding_supported_oaep_sha2_combinations(self): hashalgs = [ hashes.SHA1(), @@ -398,6 +235,12 @@ def test_rsa_padding_supported_oaep_sha2_combinations(self): hashes.SHA512(), ] for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): + if backend._fips_enabled and ( + isinstance(mgf1alg, hashes.SHA1) + or isinstance(oaepalg, hashes.SHA1) + ): + continue + assert ( backend.rsa_padding_supported( padding.OAEP( @@ -413,7 +256,9 @@ def test_rsa_padding_unsupported_mgf(self): assert ( backend.rsa_padding_supported( padding.OAEP( - mgf=DummyMGF(), algorithm=hashes.SHA1(), label=None + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None, ), ) is False @@ -426,42 +271,9 @@ def test_rsa_padding_unsupported_mgf(self): is False ) - @pytest.mark.skipif( - backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1, - reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)", - ) - def test_unsupported_mgf1_hash_algorithm_decrypt(self): - private_key = RSA_KEY_512.private_key(backend) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt( - b"0" * 64, - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA1(), - label=None, - ), - ) - - @pytest.mark.skipif( - backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1, - reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)", - ) - def test_unsupported_oaep_hash_algorithm_decrypt(self): - private_key = RSA_KEY_512.private_key(backend) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt( - b"0" * 64, - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA256(), - label=None, - ), - ) - - def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_512): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt( + rsa_key_512.decrypt( b"0" * 64, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.MD5()), @@ -471,47 +283,13 @@ def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self): ) -class TestOpenSSLCMAC(object): +class TestOpenSSLCMAC: def test_unsupported_cipher(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - backend.create_cmac_ctx(DummyCipherAlgorithm()) - - -class TestOpenSSLSignX509Certificate(object): - def test_requires_certificate_builder(self): - private_key = RSA_KEY_2048.private_key(backend) - - with pytest.raises(TypeError): - backend.create_x509_certificate( - object(), private_key, DummyHashAlgorithm() - ) - - -class TestOpenSSLSignX509CSR(object): - def test_requires_csr_builder(self): - private_key = RSA_KEY_2048.private_key(backend) - - with pytest.raises(TypeError): - backend.create_x509_csr( - object(), private_key, DummyHashAlgorithm() - ) + backend.create_cmac_ctx(DummyBlockCipherAlgorithm(b"bad")) -class TestOpenSSLSignX509CertificateRevocationList(object): - def test_invalid_builder(self): - private_key = RSA_KEY_2048.private_key(backend) - - with pytest.raises(TypeError): - backend.create_x509_crl(object(), private_key, hashes.SHA256()) - - -class TestOpenSSLCreateRevokedCertificate(object): - def test_invalid_builder(self): - with pytest.raises(TypeError): - backend.create_x509_revoked_certificate(object()) - - -class TestOpenSSLSerializationWithOpenSSL(object): +class TestOpenSSLSerializationWithOpenSSL: def test_pem_password_cb(self): userdata = backend._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") pw = b"abcdefg" @@ -542,7 +320,9 @@ def test_pem_password_cb_no_password(self): def test_unsupported_evp_pkey_type(self): key = backend._create_evp_pkey_gc() with raises_unsupported_algorithm(None): - backend._evp_pkey_to_private_key(key) + backend._evp_pkey_to_private_key( + key, unsafe_skip_rsa_key_validation=False + ) with raises_unsupported_algorithm(None): backend._evp_pkey_to_public_key(key) @@ -558,91 +338,40 @@ def test_very_long_pem_serialization_password(self): ), lambda pemfile: ( backend.load_pem_private_key( - pemfile.read().encode(), password + pemfile.read().encode(), + password, + unsafe_skip_rsa_key_validation=False, ) ), ) -class TestOpenSSLEllipticCurve(object): +class TestOpenSSLEllipticCurve: def test_sn_to_elliptic_curve_not_supported(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): - _sn_to_elliptic_curve(backend, b"fake") + _sn_to_elliptic_curve(backend, "fake") -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSAPEMSerialization(object): - def test_password_length_limit(self): +class TestRSAPEMSerialization: + def test_password_length_limit(self, rsa_key_2048): password = b"x" * 1024 - key = RSA_KEY_2048.private_key(backend) with pytest.raises(ValueError): - key.private_bytes( + rsa_key_2048.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(password), ) -class TestGOSTCertificate(object): - def test_numeric_string_x509_name_entry(self): - cert = _load_cert( - os.path.join("x509", "e-trust.ru.der"), - x509.load_der_x509_certificate, - backend, - ) - if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I: - with pytest.raises(ValueError) as exc: - cert.subject - - # We assert on the message in this case because if the certificate - # fails to load it will also raise a ValueError and this test could - # erroneously pass. - assert str(exc.value) == "Unsupported ASN1 string type. Type: 18" - else: - assert ( - cert.subject.get_attributes_for_oid( - x509.ObjectIdentifier("1.2.643.3.131.1.1") - )[0].value - == "007710474375" - ) - - @pytest.mark.skipif( backend._lib.Cryptography_HAS_EVP_PKEY_DHX == 1, - reason="Requires OpenSSL without EVP_PKEY_DHX (< 1.0.2)", + reason="Requires OpenSSL without EVP_PKEY_DHX", ) -@pytest.mark.requires_backend_interface(interface=DHBackend) -class TestOpenSSLDHSerialization(object): - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( - os.path.join("asymmetric", "DH", "RFC5114.txt"), load_nist_vectors - ), - ) - def test_dh_serialization_with_q_unsupported(self, backend, vector): - parameters = dh.DHParameterNumbers( - int(vector["p"], 16), int(vector["g"], 16), int(vector["q"], 16) - ) - public = dh.DHPublicNumbers(int(vector["ystatcavs"], 16), parameters) - private = dh.DHPrivateNumbers(int(vector["xstatcavs"], 16), public) - private_key = private.private_key(backend) - public_key = private_key.public_key() - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - private_key.private_bytes( - serialization.Encoding.PEM, - serialization.PrivateFormat.PKCS8, - serialization.NoEncryption(), - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - public_key.public_bytes( - serialization.Encoding.PEM, - serialization.PublicFormat.SubjectPublicKeyInfo, - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - parameters.parameters(backend).parameter_bytes( - serialization.Encoding.PEM, serialization.ParameterFormat.PKCS3 - ) - +@pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="Requires DH support", +) +class TestOpenSSLDHSerialization: @pytest.mark.parametrize( ("key_path", "loader_func"), [ diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py index d8bc8660a830..05e8f9480356 100644 --- a/tests/hazmat/backends/test_openssl_memleak.py +++ b/tests/hazmat/backends/test_openssl_memleak.py @@ -2,10 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import json import os +import platform import subprocess import sys import textwrap @@ -14,7 +14,6 @@ from cryptography.hazmat.bindings.openssl.binding import Binding - MEMORY_LEAK_SCRIPT = """ import sys @@ -25,9 +24,11 @@ def main(argv): import cffi - from cryptography.hazmat.bindings._openssl import ffi, lib + from cryptography.hazmat.bindings._rust import _openssl heap = {} + start_heap = {} + start_heap_realloc_delta = [0] # 1-item list so callbacks can mutate it BACKTRACE_ENABLED = False if BACKTRACE_ENABLED: @@ -50,7 +51,9 @@ def symbolize_backtrace(trace): backtrace_ffi.string(symbols[i]).decode() for i in range(length) ] - lib.Cryptography_free_wrapper(symbols, backtrace_ffi.NULL, 0) + _openssl.lib.Cryptography_free_wrapper( + symbols, backtrace_ffi.NULL, 0 + ) return stack else: def backtrace(): @@ -59,64 +62,98 @@ def backtrace(): def symbolize_backtrace(trace): return None - @ffi.callback("void *(size_t, const char *, int)") + @_openssl.ffi.callback("void *(size_t, const char *, int)") def malloc(size, path, line): - ptr = lib.Cryptography_malloc_wrapper(size, path, line) + ptr = _openssl.lib.Cryptography_malloc_wrapper(size, path, line) heap[ptr] = (size, path, line, backtrace()) return ptr - @ffi.callback("void *(void *, size_t, const char *, int)") + @_openssl.ffi.callback("void *(void *, size_t, const char *, int)") def realloc(ptr, size, path, line): - if ptr != ffi.NULL: + if ptr != _openssl.ffi.NULL: del heap[ptr] - new_ptr = lib.Cryptography_realloc_wrapper(ptr, size, path, line) + new_ptr = _openssl.lib.Cryptography_realloc_wrapper( + ptr, size, path, line + ) heap[new_ptr] = (size, path, line, backtrace()) + + # It is possible that something during the test will cause a + # realloc of memory allocated during the startup phase. (This + # was observed in conda-forge Windows builds of this package with + # provider operation_bits pointers in crypto/provider_core.c.) If + # we don't pay attention to that, the realloc'ed pointer will show + # up as a leak; but we also don't want to allow this kind of realloc + # to consume large amounts of additional memory. So we track the + # realloc and the change in memory consumption. + startup_info = start_heap.pop(ptr, None) + if startup_info is not None: + start_heap[new_ptr] = heap[new_ptr] + start_heap_realloc_delta[0] += size - startup_info[0] + return new_ptr - @ffi.callback("void(void *, const char *, int)") + @_openssl.ffi.callback("void(void *, const char *, int)") def free(ptr, path, line): - if ptr != ffi.NULL: + if ptr != _openssl.ffi.NULL: del heap[ptr] - lib.Cryptography_free_wrapper(ptr, path, line) + _openssl.lib.Cryptography_free_wrapper(ptr, path, line) - result = lib.Cryptography_CRYPTO_set_mem_functions(malloc, realloc, free) + result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( + malloc, realloc, free + ) assert result == 1 # Trigger a bunch of initialization stuff. - import cryptography.hazmat.backends.openssl + import hashlib + from cryptography.hazmat.backends.openssl.backend import backend - start_heap = set(heap) + hashlib.sha256() - func(*argv[1:]) - gc.collect() - gc.collect() - gc.collect() + start_heap.update(heap) - if lib.Cryptography_HAS_OPENSSL_CLEANUP: - lib.OPENSSL_cleanup() - - # Swap back to the original functions so that if OpenSSL tries to free - # something from its atexit handle it won't be going through a Python - # function, which will be deallocated when this function returns - result = lib.Cryptography_CRYPTO_set_mem_functions( - ffi.addressof(lib, "Cryptography_malloc_wrapper"), - ffi.addressof(lib, "Cryptography_realloc_wrapper"), - ffi.addressof(lib, "Cryptography_free_wrapper"), - ) - assert result == 1 + try: + func(*argv[1:]) + finally: + gc.collect() + gc.collect() + gc.collect() + + if _openssl.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + _openssl.lib.OSSL_PROVIDER_unload(backend._binding._legacy_provider) + _openssl.lib.OSSL_PROVIDER_unload(backend._binding._default_provider) + + _openssl.lib.OPENSSL_cleanup() + + # Swap back to the original functions so that if OpenSSL tries to free + # something from its atexit handle it won't be going through a Python + # function, which will be deallocated when this function returns + result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( + _openssl.ffi.addressof( + _openssl.lib, "Cryptography_malloc_wrapper" + ), + _openssl.ffi.addressof( + _openssl.lib, "Cryptography_realloc_wrapper" + ), + _openssl.ffi.addressof(_openssl.lib, "Cryptography_free_wrapper"), + ) + assert result == 1 - remaining = set(heap) - start_heap + remaining = set(heap) - set(start_heap) - if remaining: - sys.stdout.write(json.dumps(dict( - (int(ffi.cast("size_t", ptr)), { + # The constant here is the number of additional bytes of memory + # consumption that are allowed in reallocs of start_heap memory. + if remaining or start_heap_realloc_delta[0] > 3072: + info = dict( + (int(_openssl.ffi.cast("size_t", ptr)), { "size": heap[ptr][0], - "path": ffi.string(heap[ptr][1]).decode(), + "path": _openssl.ffi.string(heap[ptr][1]).decode(), "line": heap[ptr][2], "backtrace": symbolize_backtrace(heap[ptr][3]), }) for ptr in remaining - ))) + ) + info["start_heap_realloc_delta"] = start_heap_realloc_delta[0] + sys.stdout.write(json.dumps(info)) sys.stdout.flush() sys.exit(255) @@ -127,10 +164,18 @@ def free(ptr, path, line): def assert_no_memory_leaks(s, argv=[]): env = os.environ.copy() env["PYTHONPATH"] = os.pathsep.join(sys.path) + + # When using pytest-cov it attempts to instrument subprocesses. This + # causes the memleak tests to raise exceptions. + # we don't need coverage so we remove the env vars. + env.pop("COV_CORE_CONFIG", None) + env.pop("COV_CORE_DATAFILE", None) + env.pop("COV_CORE_SOURCE", None) + argv = [ sys.executable, "-c", - "{}\n\n{}".format(s, MEMORY_LEAK_SCRIPT), + f"{s}\n\n{MEMORY_LEAK_SCRIPT}", ] + argv # Shell out to a fresh Python process because OpenSSL does not allow you to # install new memory hooks after the first malloc/free occurs. @@ -140,6 +185,8 @@ def assert_no_memory_leaks(s, argv=[]): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) + assert proc.stdout is not None + assert proc.stderr is not None try: proc.wait() if proc.returncode == 255: @@ -157,14 +204,15 @@ def assert_no_memory_leaks(s, argv=[]): def skip_if_memtesting_not_supported(): return pytest.mark.skipif( - not Binding().lib.Cryptography_HAS_MEM_FUNCTIONS, - reason="Requires OpenSSL memory functions (>=1.1.0)", + not Binding().lib.Cryptography_HAS_MEM_FUNCTIONS + or platform.python_implementation() == "PyPy", + reason="Requires OpenSSL memory functions (>=1.1.0) and not PyPy", ) @pytest.mark.skip_fips(reason="FIPS self-test sets allow_customize = 0") @skip_if_memtesting_not_supported() -class TestAssertNoMemoryLeaks(object): +class TestAssertNoMemoryLeaks: def test_no_leak_no_malloc(self): assert_no_memory_leaks( textwrap.dedent( @@ -217,7 +265,7 @@ def func(): ) def test_errors(self): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="ZeroDivisionError"): assert_no_memory_leaks( textwrap.dedent( """ @@ -230,77 +278,7 @@ def func(): @pytest.mark.skip_fips(reason="FIPS self-test sets allow_customize = 0") @skip_if_memtesting_not_supported() -class TestOpenSSLMemoryLeaks(object): - @pytest.mark.parametrize( - "path", ["x509/PKITS_data/certs/ValidcRLIssuerTest28EE.crt"] - ) - def test_der_x509_certificate_extensions(self, path): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(path): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - - import cryptography_vectors - - with cryptography_vectors.open_vector_file(path, "rb") as f: - cert = x509.load_der_x509_certificate( - f.read(), backend - ) - - cert.extensions - """ - ), - [path], - ) - - @pytest.mark.parametrize("path", ["x509/cryptography.io.pem"]) - def test_pem_x509_certificate_extensions(self, path): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(path): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - - import cryptography_vectors - - with cryptography_vectors.open_vector_file(path, "rb") as f: - cert = x509.load_pem_x509_certificate( - f.read(), backend - ) - - cert.extensions - """ - ), - [path], - ) - - def test_x509_csr_extensions(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import rsa - - private_key = rsa.generate_private_key( - key_size=2048, public_exponent=65537, backend=backend - ) - cert = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([]) - ).add_extension( - x509.OCSPNoCheck(), critical=False - ).sign(private_key, hashes.SHA256(), backend) - - cert.extensions - """ - ) - ) - +class TestOpenSSLMemoryLeaks: def test_ec_private_numbers_private_key(self): assert_no_memory_leaks( textwrap.dedent( @@ -354,31 +332,6 @@ def func(): ) ) - def test_create_ocsp_request(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.x509 import ocsp - import cryptography_vectors - - path = "x509/PKITS_data/certs/ValidcRLIssuerTest28EE.crt" - with cryptography_vectors.open_vector_file(path, "rb") as f: - cert = x509.load_der_x509_certificate( - f.read(), backend - ) - builder = ocsp.OCSPRequestBuilder() - builder = builder.add_certificate( - cert, cert, hashes.SHA1() - ).add_extension(x509.OCSPNonce(b"0000"), False) - req = builder.build() - """ - ) - ) - @pytest.mark.parametrize( "path", ["pkcs12/cert-aes256cbc-no-key.p12", "pkcs12/cert-key-aes256cbc.p12"], @@ -402,119 +355,6 @@ def func(path): [path], ) - def test_create_crl_with_idp(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - import datetime - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.x509.oid import NameOID - - key = ec.generate_private_key(ec.SECP256R1(), backend) - last_update = datetime.datetime(2002, 1, 1, 12, 1) - next_update = datetime.datetime(2030, 1, 1, 12, 1) - idp = x509.IssuingDistributionPoint( - full_name=None, - relative_name=x509.RelativeDistinguishedName([ - x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") - ]), - only_contains_user_certs=False, - only_contains_ca_certs=True, - only_some_reasons=None, - indirect_crl=False, - only_contains_attribute_certs=False, - ) - builder = x509.CertificateRevocationListBuilder().issuer_name( - x509.Name([ - x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" - ) - ]) - ).last_update( - last_update - ).next_update( - next_update - ).add_extension( - idp, True - ) - - crl = builder.sign(key, hashes.SHA256(), backend) - crl.extensions.get_extension_for_class( - x509.IssuingDistributionPoint - ) - """ - ) - ) - - def test_create_certificate_with_extensions(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - import datetime - - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.x509.oid import ( - AuthorityInformationAccessOID, ExtendedKeyUsageOID, NameOID - ) - - private_key = ec.generate_private_key(ec.SECP256R1(), backend) - - not_valid_before = datetime.datetime.now() - not_valid_after = not_valid_before + datetime.timedelta(days=365) - - aia = x509.AuthorityInformationAccess([ - x509.AccessDescription( - AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ]) - sans = [u'*.example.org', u'foobar.example.net'] - san = x509.SubjectAlternativeName(list(map(x509.DNSName, sans))) - - ski = x509.SubjectKeyIdentifier.from_public_key( - private_key.public_key() - ) - eku = x509.ExtendedKeyUsage([ - ExtendedKeyUsageOID.CLIENT_AUTH, - ExtendedKeyUsageOID.SERVER_AUTH, - ExtendedKeyUsageOID.CODE_SIGNING, - ]) - - builder = x509.CertificateBuilder().serial_number( - 777 - ).issuer_name(x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), - ])).subject_name(x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), - ])).public_key( - private_key.public_key() - ).add_extension( - aia, critical=False - ).not_valid_before( - not_valid_before - ).not_valid_after( - not_valid_after - ) - - cert = builder.sign(private_key, hashes.SHA256(), backend) - cert.extensions - """ - ) - ) - def test_write_pkcs12_key_and_certificates(self): assert_no_memory_leaks( textwrap.dedent( diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index ecee34091dc7..c061c9bf11b0 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -2,51 +2,32 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - -import pretend - import pytest from cryptography.exceptions import InternalError +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl.binding import ( Binding, - _consume_errors, + _legacy_provider_error, _openssl_assert, - _verify_openssl_version, _verify_package_version, ) -class TestOpenSSL(object): +class TestOpenSSL: def test_binding_loads(self): binding = Binding() assert binding assert binding.lib assert binding.ffi - def test_crypto_lock_init(self): - b = Binding() - - b.init_static_locks() - lock_cb = b.lib.CRYPTO_get_locking_callback() - if b.lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER: - assert lock_cb == b.ffi.NULL - assert b.lib.Cryptography_HAS_LOCKING_CALLBACKS == 0 - else: - assert lock_cb != b.ffi.NULL - assert b.lib.Cryptography_HAS_LOCKING_CALLBACKS == 1 - - def test_add_engine_more_than_once(self): - b = Binding() - b._register_osrandom_engine() - assert b.lib.ERR_get_error() == 0 - def test_ssl_ctx_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() - assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) + # SSL_OP_ALL is 0 on BoringSSL + if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + assert b.lib.SSL_OP_ALL > 0 + ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) current_options = b.lib.SSL_CTX_get_options(ctx) @@ -58,8 +39,10 @@ def test_ssl_ctx_options(self): def test_ssl_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() - assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) + # SSL_OP_ALL is 0 on BoringSSL + if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + assert b.lib.SSL_OP_ALL > 0 + ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) ssl = b.lib.SSL_new(ctx) @@ -70,25 +53,10 @@ def test_ssl_options(self): assert resp == expected_options assert b.lib.SSL_get_options(ssl) == expected_options - def test_ssl_mode(self): - # Test that we're properly handling 32-bit unsigned on all platforms. - b = Binding() - assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) - assert ctx != b.ffi.NULL - ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) - ssl = b.lib.SSL_new(ctx) - ssl = b.ffi.gc(ssl, b.lib.SSL_free) - current_options = b.lib.SSL_get_mode(ssl) - resp = b.lib.SSL_set_mode(ssl, b.lib.SSL_OP_ALL) - expected_options = current_options | b.lib.SSL_OP_ALL - assert resp == expected_options - assert b.lib.SSL_get_mode(ssl) == expected_options - def test_conditional_removal(self): b = Binding() - if b.lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER: + if not b.lib.CRYPTOGRAPHY_IS_LIBRESSL: assert b.lib.TLS_ST_OK else: with pytest.raises(AttributeError): @@ -107,13 +75,27 @@ def test_openssl_assert_error_on_stack(self): _openssl_assert(b.lib, False) error = exc_info.value.err_code[0] - assert error.code == 101183626 assert error.lib == b.lib.ERR_LIB_EVP - assert error.func == b.lib.EVP_F_EVP_ENCRYPTFINAL_EX assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH - assert b"data not multiple of block length" in error.reason_text + if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + assert b"data not multiple of block length" in error.reason_text + + def test_version_mismatch(self): + with pytest.raises(ImportError): + _verify_package_version("nottherightversion") + + def test_legacy_provider_error(self): + with pytest.raises(RuntimeError): + _legacy_provider_error(False) + + _legacy_provider_error(True) + + def test_rust_internal_error(self): + with pytest.raises(InternalError) as exc_info: + rust_openssl.raise_openssl_error() + + assert len(exc_info.value.err_code) == 0 - def test_check_startup_errors_are_allowed(self): b = Binding() b.lib.ERR_put_error( b.lib.ERR_LIB_EVP, @@ -122,18 +104,11 @@ def test_check_startup_errors_are_allowed(self): b"", -1, ) - b._register_osrandom_engine() - assert _consume_errors(b.lib) == [] - - def test_version_mismatch(self): - with pytest.raises(ImportError): - _verify_package_version("nottherightversion") + with pytest.raises(InternalError) as exc_info: + rust_openssl.raise_openssl_error() - def test_verify_openssl_version(self, monkeypatch): - monkeypatch.delenv("CRYPTOGRAPHY_ALLOW_OPENSSL_102", raising=False) - lib = pretend.stub( - CRYPTOGRAPHY_OPENSSL_LESS_THAN_110=True, - CRYPTOGRAPHY_IS_LIBRESSL=False, - ) - with pytest.raises(RuntimeError): - _verify_openssl_version(lib) + error = exc_info.value.err_code[0] + assert error.lib == b.lib.ERR_LIB_EVP + assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH + if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + assert b"data not multiple of block length" in error.reason_text diff --git a/tests/hazmat/primitives/__init__.py b/tests/hazmat/primitives/__init__.py index 4b540884df72..b509336233c2 100644 --- a/tests/hazmat/primitives/__init__.py +++ b/tests/hazmat/primitives/__init__.py @@ -1,5 +1,3 @@ # 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 absolute_import, division, print_function diff --git a/tests/hazmat/primitives/fixtures_dh.py b/tests/hazmat/primitives/fixtures_dh.py index b766c4265837..3ed52d14d40c 100644 --- a/tests/hazmat/primitives/fixtures_dh.py +++ b/tests/hazmat/primitives/fixtures_dh.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function from cryptography.hazmat.primitives.asymmetric import dh diff --git a/tests/hazmat/primitives/fixtures_dsa.py b/tests/hazmat/primitives/fixtures_dsa.py index d4568ead7306..6675a2c102fc 100644 --- a/tests/hazmat/primitives/fixtures_dsa.py +++ b/tests/hazmat/primitives/fixtures_dsa.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function from cryptography.hazmat.primitives.asymmetric.dsa import ( DSAParameterNumbers, @@ -10,7 +9,6 @@ DSAPublicNumbers, ) - DSA_KEY_1024 = DSAPrivateNumbers( public_numbers=DSAPublicNumbers( parameter_numbers=DSAParameterNumbers( diff --git a/tests/hazmat/primitives/fixtures_ec.py b/tests/hazmat/primitives/fixtures_ec.py index d1d0a46ffe25..fa671ac558c1 100644 --- a/tests/hazmat/primitives/fixtures_ec.py +++ b/tests/hazmat/primitives/fixtures_ec.py @@ -2,11 +2,9 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function from cryptography.hazmat.primitives.asymmetric import ec - EC_KEY_SECT571R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "213997069697108634621868251335076179190383272087548888968788698953" diff --git a/tests/hazmat/primitives/fixtures_rsa.py b/tests/hazmat/primitives/fixtures_rsa.py index 2c0627282130..09b32ab00b50 100644 --- a/tests/hazmat/primitives/fixtures_rsa.py +++ b/tests/hazmat/primitives/fixtures_rsa.py @@ -2,14 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers, ) - RSA_KEY_512 = RSAPrivateNumbers( p=int( "d57846898d5c0de249c08467586cb458fa9bc417cdf297f73cfc52281b787cd9", 16 @@ -41,37 +39,6 @@ ), ) -RSA_KEY_512_ALT = RSAPrivateNumbers( - p=int( - "febe19c29a0b50fefa4f7b1832f84df1caf9be8242da25c9d689e18226e67ce5", 16 - ), - q=int( - "eb616c639dd999feda26517e1c77b6878f363fe828c4e6670ec1787f28b1e731", 16 - ), - d=int( - "80edecfde704a806445a4cc782b85d3f36f17558f385654ea767f006470fdfcbda5e2" - "206839289d3f419b4e4fb8e1acee1b4fb9c591f69b64ec83937f5829241", - 16, - ), - dmp1=int( - "7f4fa06e2a3077a54691cc5216bf13ad40a4b9fa3dd0ea4bca259487484baea5", 16 - ), - dmq1=int( - "35eaa70d5a8711c352ed1c15ab27b0e3f46614d575214535ae279b166597fac1", 16 - ), - iqmp=int( - "cc1f272de6846851ec80cb89a02dbac78f44b47bc08f53b67b4651a3acde8b19", 16 - ), - public_numbers=RSAPublicNumbers( - e=65537, - n=int( - "ea397388b999ef0f7e7416fa000367efd9a0ba0deddd3f8160d1c36d62267f210" - "fbd9c97abeb6654450ff03e7601b8caa6c6f4cba18f0b52c179d17e8f258ad5", - 16, - ), - ), -) - RSA_KEY_522 = RSAPrivateNumbers( p=int( "1a8aab9a069f92b52fdf05824f2846223dc27adfc806716a247a77d4c36885e4bf", @@ -193,49 +160,6 @@ ), ) -RSA_KEY_768 = RSAPrivateNumbers( - p=int( - "f80c0061b607f93206b68e208906498d68c6e396faf457150cf975c8f849848465869" - "7ecd402313397088044c4c2071b", - 16, - ), - q=int( - "e5b5dbecc93c6d306fc14e6aa9737f9be2728bc1a326a8713d2849b34c1cb54c63468" - "3a68abb1d345dbf15a3c492cf55", - 16, - ), - d=int( - "d44601442255ffa331212c60385b5e898555c75c0272632ff42d57c4b16ca97dbca9f" - "d6d99cd2c9fd298df155ed5141b4be06c651934076133331d4564d73faed7ce98e283" - "2f7ce3949bc183be7e7ca34f6dd04a9098b6c73649394b0a76c541", - 16, - ), - dmp1=int( - "a5763406fa0b65929661ce7b2b8c73220e43a5ebbfe99ff15ddf464fd238105ad4f2a" - "c83818518d70627d8908703bb03", - 16, - ), - dmq1=int( - "cb467a9ef899a39a685aecd4d0ad27b0bfdc53b68075363c373d8eb2bed8eccaf3533" - "42f4db735a9e087b7539c21ba9d", - 16, - ), - iqmp=int( - "5fe86bd3aee0c4d09ef11e0530a78a4534c9b833422813b5c934a450c8e564d8097a0" - "6fd74f1ebe2d5573782093f587a", - 16, - ), - public_numbers=RSAPublicNumbers( - e=65537, - n=int( - "de92f1eb5f4abf426b6cac9dd1e9bf57132a4988b4ed3f8aecc15e251028bd6df" - "46eb97c711624af7db15e6430894d1b640c13929329241ee094f5a4fe1a20bc9b" - "75232320a72bc567207ec54d6b48dccb19737cf63acc1021abb337f19130f7", - 16, - ), - ), -) - RSA_KEY_1024 = RSAPrivateNumbers( p=int( "ea4d9d9a1a068be44b9a5f8f6de0512b2c5ba1fb804a4655babba688e6e890b347c1a" diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py index d14dcad9f71f..007ecfe21271 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/test_3des.py @@ -6,18 +6,16 @@ Test using the NIST Test Vectors """ -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( @@ -26,8 +24,7 @@ ), skip_message="Does not support TripleDES CBC", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestTripleDESModeCBC(object): +class TestTripleDESModeCBC: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), @@ -59,8 +56,7 @@ class TestTripleDESModeCBC(object): ), skip_message="Does not support TripleDES OFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestTripleDESModeOFB(object): +class TestTripleDESModeOFB: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), @@ -92,8 +88,7 @@ class TestTripleDESModeOFB(object): ), skip_message="Does not support TripleDES CFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestTripleDESModeCFB(object): +class TestTripleDESModeCFB: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), @@ -125,8 +120,7 @@ class TestTripleDESModeCFB(object): ), skip_message="Does not support TripleDES CFB8", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestTripleDESModeCFB8(object): +class TestTripleDESModeCFB8: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), @@ -158,8 +152,7 @@ class TestTripleDESModeCFB8(object): ), skip_message="Does not support TripleDES ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestTripleDESModeECB(object): +class TestTripleDESModeECB: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "ECB"), diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py index 753c7c192bc9..c6811a496b24 100644 --- a/tests/hazmat/primitives/test_aead.py +++ b/tests/hazmat/primitives/test_aead.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os @@ -10,25 +9,26 @@ import pytest from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, + AESOCB3, + AESSIV, ChaCha20Poly1305, ) -from .utils import _load_all_params from ...utils import ( load_nist_ccm_vectors, load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) +from .utils import _load_all_params -class FakeData(object): +class FakeData(bytes): def __len__(self): - return 2 ** 32 + 1 + return 2**31 def _aead_supported(cls): @@ -43,7 +43,6 @@ def _aead_supported(cls): _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL without ChaCha20Poly1305 support", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) def test_chacha20poly1305_unsupported_on_older_openssl(backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): ChaCha20Poly1305(ChaCha20Poly1305.generate_key()) @@ -53,8 +52,7 @@ def test_chacha20poly1305_unsupported_on_older_openssl(backend): not _aead_supported(ChaCha20Poly1305), reason="Does not support ChaCha20Poly1305", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestChaCha20Poly1305(object): +class TestChaCha20Poly1305: def test_data_too_large(self): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) @@ -72,7 +70,7 @@ def test_generate_key(self): def test_bad_key(self, backend): with pytest.raises(TypeError): - ChaCha20Poly1305(object()) + ChaCha20Poly1305(object()) # type:ignore[arg-type] with pytest.raises(ValueError): ChaCha20Poly1305(b"0" * 31) @@ -122,55 +120,53 @@ def test_associated_data_none_equal_to_empty_bytestring(self, backend): pt2 = chacha.decrypt(nonce, ct2, b"") assert pt1 == pt2 - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( + def test_openssl_vectors(self, subtests, backend): + vectors = load_vectors_from_file( os.path.join("ciphers", "ChaCha20Poly1305", "openssl.txt"), load_nist_vectors, - ), - ) - def test_openssl_vectors(self, vector, backend): - key = binascii.unhexlify(vector["key"]) - nonce = binascii.unhexlify(vector["iv"]) - aad = binascii.unhexlify(vector["aad"]) - tag = binascii.unhexlify(vector["tag"]) - pt = binascii.unhexlify(vector["plaintext"]) - ct = binascii.unhexlify(vector["ciphertext"]) - chacha = ChaCha20Poly1305(key) - if vector.get("result") == b"CIPHERFINAL_ERROR": - with pytest.raises(InvalidTag): - chacha.decrypt(nonce, ct + tag, aad) - else: - computed_pt = chacha.decrypt(nonce, ct + tag, aad) - assert computed_pt == pt - computed_ct = chacha.encrypt(nonce, pt, aad) - assert computed_ct == ct + tag - - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( + ) + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["iv"]) + aad = binascii.unhexlify(vector["aad"]) + tag = binascii.unhexlify(vector["tag"]) + pt = binascii.unhexlify(vector["plaintext"]) + ct = binascii.unhexlify(vector["ciphertext"]) + chacha = ChaCha20Poly1305(key) + if vector.get("result") == b"CIPHERFINAL_ERROR": + with pytest.raises(InvalidTag): + chacha.decrypt(nonce, ct + tag, aad) + else: + computed_pt = chacha.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + computed_ct = chacha.encrypt(nonce, pt, aad) + assert computed_ct == ct + tag + + def test_boringssl_vectors(self, subtests, backend): + vectors = load_vectors_from_file( os.path.join("ciphers", "ChaCha20Poly1305", "boringssl.txt"), load_nist_vectors, - ), - ) - def test_boringssl_vectors(self, vector, backend): - key = binascii.unhexlify(vector["key"]) - nonce = binascii.unhexlify(vector["nonce"]) - if vector["ad"].startswith(b'"'): - aad = vector["ad"][1:-1] - else: - aad = binascii.unhexlify(vector["ad"]) - tag = binascii.unhexlify(vector["tag"]) - if vector["in"].startswith(b'"'): - pt = vector["in"][1:-1] - else: - pt = binascii.unhexlify(vector["in"]) - ct = binascii.unhexlify(vector["ct"].strip(b'"')) - chacha = ChaCha20Poly1305(key) - computed_pt = chacha.decrypt(nonce, ct + tag, aad) - assert computed_pt == pt - computed_ct = chacha.encrypt(nonce, pt, aad) - assert computed_ct == ct + tag + ) + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + if vector["ad"].startswith(b'"'): + aad = vector["ad"][1:-1] + else: + aad = binascii.unhexlify(vector["ad"]) + tag = binascii.unhexlify(vector["tag"]) + if vector["in"].startswith(b'"'): + pt = vector["in"][1:-1] + else: + pt = binascii.unhexlify(vector["in"]) + ct = binascii.unhexlify(vector["ct"].strip(b'"')) + chacha = ChaCha20Poly1305(key) + computed_pt = chacha.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + computed_ct = chacha.encrypt(nonce, pt, aad) + assert computed_ct == ct + tag def test_buffer_protocol(self, backend): key = ChaCha20Poly1305.generate_key() @@ -188,8 +184,11 @@ def test_buffer_protocol(self, backend): assert computed_pt2 == pt -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESCCM(object): +@pytest.mark.skipif( + not _aead_supported(AESCCM), + reason="Does not support AESCCM", +) +class TestAESCCM: def test_data_too_large(self): key = AESCCM.generate_key(128) aesccm = AESCCM(key) @@ -218,7 +217,7 @@ def test_invalid_tag_length(self, backend): AESCCM(key, tag_length=2) with pytest.raises(TypeError): - AESCCM(key, tag_length="notanint") + AESCCM(key, tag_length="notanint") # type:ignore[arg-type] def test_invalid_nonce_length(self, backend): key = AESCCM.generate_key(128) @@ -231,9 +230,8 @@ def test_invalid_nonce_length(self, backend): with pytest.raises(ValueError): aesccm.encrypt(nonce[:6], pt, None) - @pytest.mark.parametrize( - "vector", - _load_all_params( + def test_vectors(self, subtests, backend): + vectors = _load_all_params( os.path.join("ciphers", "AES", "CCM"), [ "DVPT128.rsp", @@ -250,22 +248,22 @@ def test_invalid_nonce_length(self, backend): "VPT256.rsp", ], load_nist_ccm_vectors, - ), - ) - def test_vectors(self, vector, backend): - key = binascii.unhexlify(vector["key"]) - nonce = binascii.unhexlify(vector["nonce"]) - adata = binascii.unhexlify(vector["adata"])[: vector["alen"]] - ct = binascii.unhexlify(vector["ct"]) - pt = binascii.unhexlify(vector["payload"])[: vector["plen"]] - aesccm = AESCCM(key, vector["tlen"]) - if vector.get("fail"): - with pytest.raises(InvalidTag): - aesccm.decrypt(nonce, ct, adata) - else: - computed_pt = aesccm.decrypt(nonce, ct, adata) - assert computed_pt == pt - assert aesccm.encrypt(nonce, pt, adata) == ct + ) + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + adata = binascii.unhexlify(vector["adata"])[: vector["alen"]] + ct = binascii.unhexlify(vector["ct"]) + pt = binascii.unhexlify(vector["payload"])[: vector["plen"]] + aesccm = AESCCM(key, vector["tlen"]) + if vector.get("fail"): + with pytest.raises(InvalidTag): + aesccm.decrypt(nonce, ct, adata) + else: + computed_pt = aesccm.decrypt(nonce, ct, adata) + assert computed_pt == pt + assert aesccm.encrypt(nonce, pt, adata) == ct def test_roundtrip(self, backend): key = AESCCM.generate_key(128) @@ -302,14 +300,14 @@ def test_params_not_bytes(self, nonce, data, associated_data, backend): def test_bad_key(self, backend): with pytest.raises(TypeError): - AESCCM(object()) + AESCCM(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESCCM(b"0" * 31) def test_bad_generate_key(self, backend): with pytest.raises(TypeError): - AESCCM.generate_key(object()) + AESCCM.generate_key(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESCCM.generate_key(129) @@ -360,11 +358,10 @@ def _load_gcm_vectors(): ], load_nist_vectors, ) - return [x for x in vectors if len(x["tag"]) == 32] + return [x for x in vectors if len(x["tag"]) == 32 and len(x["iv"]) >= 16] -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESGCM(object): +class TestAESGCM: def test_data_too_large(self): key = AESGCM.generate_key(128) aesgcm = AESGCM(key) @@ -376,30 +373,32 @@ def test_data_too_large(self): with pytest.raises(OverflowError): aesgcm.encrypt(nonce, b"", FakeData()) - @pytest.mark.parametrize("vector", _load_gcm_vectors()) - def test_vectors(self, backend, vector): - nonce = binascii.unhexlify(vector["iv"]) - - if backend._fips_enabled and len(nonce) != 12: - # Red Hat disables non-96-bit IV support as part of its FIPS - # patches. - pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") - - key = binascii.unhexlify(vector["key"]) - aad = binascii.unhexlify(vector["aad"]) - ct = binascii.unhexlify(vector["ct"]) - pt = binascii.unhexlify(vector.get("pt", b"")) - tag = binascii.unhexlify(vector["tag"]) - aesgcm = AESGCM(key) - if vector.get("fail") is True: - with pytest.raises(InvalidTag): - aesgcm.decrypt(nonce, ct + tag, aad) - else: - computed_ct = aesgcm.encrypt(nonce, pt, aad) - assert computed_ct[:-16] == ct - assert computed_ct[-16:] == tag - computed_pt = aesgcm.decrypt(nonce, ct + tag, aad) - assert computed_pt == pt + def test_vectors(self, backend, subtests): + vectors = _load_gcm_vectors() + for vector in vectors: + with subtests.test(): + nonce = binascii.unhexlify(vector["iv"]) + + if backend._fips_enabled and len(nonce) != 12: + # Red Hat disables non-96-bit IV support as part of its + # FIPS patches. + pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") + + key = binascii.unhexlify(vector["key"]) + aad = binascii.unhexlify(vector["aad"]) + ct = binascii.unhexlify(vector["ct"]) + pt = binascii.unhexlify(vector.get("pt", b"")) + tag = binascii.unhexlify(vector["tag"]) + aesgcm = AESGCM(key) + if vector.get("fail") is True: + with pytest.raises(InvalidTag): + aesgcm.decrypt(nonce, ct + tag, aad) + else: + computed_ct = aesgcm.encrypt(nonce, pt, aad) + assert computed_ct[:-16] == ct + assert computed_ct[-16:] == tag + computed_pt = aesgcm.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt @pytest.mark.parametrize( ("nonce", "data", "associated_data"), @@ -418,22 +417,28 @@ def test_params_not_bytes(self, nonce, data, associated_data, backend): with pytest.raises(TypeError): aesgcm.decrypt(nonce, data, associated_data) - def test_invalid_nonce_length(self, backend): + @pytest.mark.parametrize("length", [7, 129]) + def test_invalid_nonce_length(self, length, backend): + if backend._fips_enabled: + # Red Hat disables non-96-bit IV support as part of its FIPS + # patches. + pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") + key = AESGCM.generate_key(128) aesgcm = AESGCM(key) with pytest.raises(ValueError): - aesgcm.encrypt(b"", b"hi", None) + aesgcm.encrypt(b"\x00" * length, b"hi", None) def test_bad_key(self, backend): with pytest.raises(TypeError): - AESGCM(object()) + AESGCM(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESGCM(b"0" * 31) def test_bad_generate_key(self, backend): with pytest.raises(TypeError): - AESGCM.generate_key(object()) + AESGCM.generate_key(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESGCM.generate_key(129) @@ -459,7 +464,295 @@ def test_buffer_protocol(self, backend): computed_pt = aesgcm.decrypt(nonce, ct, ad) assert computed_pt == pt aesgcm2 = AESGCM(bytearray(key)) - ct2 = aesgcm2.encrypt(bytearray(nonce), pt, ad) + ct2 = aesgcm2.encrypt(bytearray(nonce), bytearray(pt), bytearray(ad)) + assert ct2 == ct + b_nonce = bytearray(nonce) + b_ct2 = bytearray(ct2) + b_ad = bytearray(ad) + computed_pt2 = aesgcm2.decrypt(b_nonce, b_ct2, b_ad) + assert computed_pt2 == pt + aesgcm3 = AESGCM(memoryview(key)) + m_nonce = memoryview(nonce) + m_pt = memoryview(pt) + m_ad = memoryview(ad) + ct3 = aesgcm3.encrypt(m_nonce, m_pt, m_ad) + assert ct3 == ct + m_ct3 = memoryview(ct3) + computed_pt3 = aesgcm3.decrypt(m_nonce, m_ct3, m_ad) + assert computed_pt3 == pt + + +@pytest.mark.skipif( + _aead_supported(AESOCB3), + reason="Requires OpenSSL without AESOCB3 support", +) +def test_aesocb3_unsupported_on_older_openssl(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + AESOCB3(AESOCB3.generate_key(128)) + + +@pytest.mark.skipif( + not _aead_supported(AESOCB3), + reason="Does not support AESOCB3", +) +class TestAESOCB3: + def test_data_too_large(self): + key = AESOCB3.generate_key(128) + aesocb3 = AESOCB3(key) + nonce = b"0" * 12 + + with pytest.raises(OverflowError): + aesocb3.encrypt(nonce, FakeData(), b"") + + with pytest.raises(OverflowError): + aesocb3.encrypt(nonce, b"", FakeData()) + + def test_vectors(self, backend, subtests): + vectors = [] + for f in [ + "rfc7253.txt", + "openssl.txt", + "test-vector-1-nonce104.txt", + "test-vector-1-nonce112.txt", + "test-vector-1-nonce120.txt", + ]: + vectors.extend( + load_vectors_from_file( + os.path.join("ciphers", "AES", "OCB3", f), + load_nist_vectors, + ) + ) + + for vector in vectors: + with subtests.test(): + nonce = binascii.unhexlify(vector["nonce"]) + key = binascii.unhexlify(vector["key"]) + aad = binascii.unhexlify(vector["aad"]) + ct = binascii.unhexlify(vector["ciphertext"]) + pt = binascii.unhexlify(vector.get("plaintext", b"")) + aesocb3 = AESOCB3(key) + computed_ct = aesocb3.encrypt(nonce, pt, aad) + assert computed_ct == ct + computed_pt = aesocb3.decrypt(nonce, ct, aad) + assert computed_pt == pt + + def test_vectors_invalid(self, backend, subtests): + vectors = load_vectors_from_file( + os.path.join("ciphers", "AES", "OCB3", "rfc7253.txt"), + load_nist_vectors, + ) + for vector in vectors: + with subtests.test(): + nonce = binascii.unhexlify(vector["nonce"]) + key = binascii.unhexlify(vector["key"]) + aad = binascii.unhexlify(vector["aad"]) + ct = binascii.unhexlify(vector["ciphertext"]) + aesocb3 = AESOCB3(key) + with pytest.raises(InvalidTag): + badkey = AESOCB3(AESOCB3.generate_key(128)) + badkey.decrypt(nonce, ct, aad) + with pytest.raises(InvalidTag): + aesocb3.decrypt(nonce, b"nonsense", aad) + with pytest.raises(InvalidTag): + aesocb3.decrypt(b"\x00" * 12, ct, aad) + with pytest.raises(InvalidTag): + aesocb3.decrypt(nonce, ct, b"nonsense") + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()], + ], + ) + def test_params_not_bytes(self, nonce, data, associated_data, backend): + key = AESOCB3.generate_key(128) + aesocb3 = AESOCB3(key) + with pytest.raises(TypeError): + aesocb3.encrypt(nonce, data, associated_data) + + with pytest.raises(TypeError): + aesocb3.decrypt(nonce, data, associated_data) + + def test_invalid_nonce_length(self, backend): + key = AESOCB3.generate_key(128) + aesocb3 = AESOCB3(key) + with pytest.raises(ValueError): + aesocb3.encrypt(b"\x00" * 11, b"hi", None) + with pytest.raises(ValueError): + aesocb3.encrypt(b"\x00" * 16, b"hi", None) + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + AESOCB3(object()) # type:ignore[arg-type] + + with pytest.raises(ValueError): + AESOCB3(b"0" * 31) + + def test_bad_generate_key(self, backend): + with pytest.raises(TypeError): + AESOCB3.generate_key(object()) # type:ignore[arg-type] + + with pytest.raises(ValueError): + AESOCB3.generate_key(129) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = AESOCB3.generate_key(128) + aesocb3 = AESOCB3(key) + nonce = os.urandom(12) + ct1 = aesocb3.encrypt(nonce, b"some_data", None) + ct2 = aesocb3.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = aesocb3.decrypt(nonce, ct1, None) + pt2 = aesocb3.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + def test_buffer_protocol(self, backend): + key = AESOCB3.generate_key(128) + aesocb3 = AESOCB3(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = aesocb3.encrypt(nonce, pt, ad) + computed_pt = aesocb3.decrypt(nonce, ct, ad) + assert computed_pt == pt + aesocb3_ = AESOCB3(bytearray(key)) + ct2 = aesocb3_.encrypt(bytearray(nonce), pt, ad) + assert ct2 == ct + computed_pt2 = aesocb3_.decrypt(bytearray(nonce), ct2, ad) + assert computed_pt2 == pt + + +@pytest.mark.skipif( + not _aead_supported(AESSIV), + reason="Does not support AESSIV", +) +class TestAESSIV: + def test_data_too_large(self): + key = AESSIV.generate_key(256) + aessiv = AESSIV(key) + + with pytest.raises(OverflowError): + aessiv.encrypt(FakeData(), None) + + with pytest.raises(OverflowError): + aessiv.encrypt(b"irrelevant", [FakeData()]) + + def test_no_empty_encryption(self): + key = AESSIV.generate_key(256) + aessiv = AESSIV(key) + + with pytest.raises(ValueError): + aessiv.encrypt(b"", None) + + with pytest.raises(ValueError): + aessiv.decrypt(b"", None) + + def test_vectors(self, backend, subtests): + vectors = load_vectors_from_file( + os.path.join("ciphers", "AES", "SIV", "openssl.txt"), + load_nist_vectors, + ) + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + aad1 = vector.get("aad", None) + aad2 = vector.get("aad2", None) + aad3 = vector.get("aad3", None) + aad = [] + for a in [aad1, aad2, aad3]: + if a is not None: + aad.append(binascii.unhexlify(a)) + ct = binascii.unhexlify(vector["ciphertext"]) + tag = binascii.unhexlify(vector["tag"]) + pt = binascii.unhexlify(vector.get("plaintext", b"")) + aessiv = AESSIV(key) + computed_ct = aessiv.encrypt(pt, aad) + assert computed_ct[:16] == tag + assert computed_ct[16:] == ct + computed_pt = aessiv.decrypt(computed_ct, aad) + assert computed_pt == pt + + def test_vectors_invalid(self, backend, subtests): + vectors = load_vectors_from_file( + os.path.join("ciphers", "AES", "SIV", "openssl.txt"), + load_nist_vectors, + ) + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + aad1 = vector.get("aad", None) + aad2 = vector.get("aad2", None) + aad3 = vector.get("aad3", None) + aad = [] + for a in [aad1, aad2, aad3]: + if a is not None: + aad.append(binascii.unhexlify(a)) + + ct = binascii.unhexlify(vector["ciphertext"]) + aessiv = AESSIV(key) + with pytest.raises(InvalidTag): + badkey = AESSIV(AESSIV.generate_key(256)) + badkey.decrypt(ct, aad) + with pytest.raises(InvalidTag): + aessiv.decrypt(ct, aad + [b""]) + with pytest.raises(InvalidTag): + aessiv.decrypt(ct, [b"nonsense"]) + with pytest.raises(InvalidTag): + aessiv.decrypt(b"nonsense", aad) + + @pytest.mark.parametrize( + ("data", "associated_data"), + [ + [object(), [b""]], + [b"data" * 5, [object()]], + [b"data" * 5, b""], + ], + ) + def test_params_not_bytes(self, data, associated_data, backend): + key = AESSIV.generate_key(256) + aessiv = AESSIV(key) + with pytest.raises(TypeError): + aessiv.encrypt(data, associated_data) + + with pytest.raises(TypeError): + aessiv.decrypt(data, associated_data) + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + AESSIV(object()) # type:ignore[arg-type] + + with pytest.raises(ValueError): + AESSIV(b"0" * 31) + + def test_bad_generate_key(self, backend): + with pytest.raises(TypeError): + AESSIV.generate_key(object()) # type:ignore[arg-type] + + with pytest.raises(ValueError): + AESSIV.generate_key(128) + + def test_associated_data_none_equal_to_empty_list(self, backend): + key = AESSIV.generate_key(256) + aessiv = AESSIV(key) + ct1 = aessiv.encrypt(b"some_data", None) + ct2 = aessiv.encrypt(b"some_data", []) + assert ct1 == ct2 + pt1 = aessiv.decrypt(ct1, None) + pt2 = aessiv.decrypt(ct2, []) + assert pt1 == pt2 + + def test_buffer_protocol(self, backend): + key = AESSIV.generate_key(256) + aessiv = AESSIV(key) + pt = b"encrypt me" + ad = [b"additional"] + ct = aessiv.encrypt(pt, ad) + computed_pt = aessiv.decrypt(ct, ad) + assert computed_pt == pt + aessiv = AESSIV(bytearray(key)) + ct2 = aessiv.encrypt(pt, ad) assert ct2 == ct - computed_pt2 = aesgcm2.decrypt(bytearray(nonce), ct2, ad) + computed_pt2 = aessiv.decrypt(ct2, ad) assert computed_pt2 == pt diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index f98ba6fddff8..1f3dfd0014b4 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -2,19 +2,17 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from .utils import _load_all_params, generate_encrypt_test from ...doubles import DummyMode from ...utils import load_nist_vectors +from .utils import _load_all_params, generate_encrypt_test @pytest.mark.supported( @@ -23,15 +21,13 @@ ), skip_message="Does not support AES XTS", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeXTS(object): - @pytest.mark.parametrize( - "vector", +class TestAESModeXTS: + def test_xts_vectors(self, backend, subtests): # This list comprehension excludes any vector that does not have a # data unit length that is divisible by 8. The NIST vectors include # tests for implementations that support encryption of data that is # not divisible modulo 8, but OpenSSL is not such an implementation. - [ + vectors = [ x for x in _load_all_params( os.path.join("ciphers", "AES", "XTS", "tweak-128hexstr"), @@ -39,20 +35,48 @@ class TestAESModeXTS(object): load_nist_vectors, ) if int(x["dataunitlen"]) / 8.0 == int(x["dataunitlen"]) // 8 - ], - ) - def test_xts_vectors(self, vector, backend): - key = binascii.unhexlify(vector["key"]) - tweak = binascii.unhexlify(vector["i"]) - pt = binascii.unhexlify(vector["pt"]) - ct = binascii.unhexlify(vector["ct"]) - cipher = base.Cipher(algorithms.AES(key), modes.XTS(tweak), backend) + ] + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + tweak = binascii.unhexlify(vector["i"]) + pt = binascii.unhexlify(vector["pt"]) + ct = binascii.unhexlify(vector["ct"]) + cipher = base.Cipher( + algorithms.AES(key), modes.XTS(tweak), backend + ) + enc = cipher.encryptor() + computed_ct = enc.update(pt) + enc.finalize() + assert computed_ct == ct + dec = cipher.decryptor() + computed_pt = dec.update(ct) + dec.finalize() + assert computed_pt == pt + + def test_xts_too_short(self, backend): + key = b"thirty_two_byte_keys_are_great!!" + tweak = b"\x00" * 16 + cipher = base.Cipher(algorithms.AES(key), modes.XTS(tweak)) enc = cipher.encryptor() - computed_ct = enc.update(pt) + enc.finalize() - assert computed_ct == ct - dec = cipher.decryptor() - computed_pt = dec.update(ct) + dec.finalize() - assert computed_pt == pt + with pytest.raises(ValueError): + enc.update(b"0" * 15) + + @pytest.mark.supported( + only_if=lambda backend: (not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL), + skip_message="duplicate key encryption error added in OpenSSL 1.1.1d", + ) + def test_xts_no_duplicate_keys_encryption(self, backend): + key = bytes(range(16)) * 2 + tweak = b"\x00" * 16 + cipher = base.Cipher(algorithms.AES(key), modes.XTS(tweak)) + with pytest.raises(ValueError, match="duplicated keys"): + cipher.encryptor() + + def test_xts_unsupported_with_aes128_aes256_classes(self): + with pytest.raises(TypeError): + base.Cipher(algorithms.AES128(b"0" * 16), modes.XTS(b"\x00" * 16)) + + with pytest.raises(TypeError): + base.Cipher(algorithms.AES256(b"0" * 32), modes.XTS(b"\x00" * 16)) @pytest.mark.supported( @@ -61,8 +85,7 @@ def test_xts_vectors(self, vector, backend): ), skip_message="Does not support AES CBC", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeCBC(object): +class TestAESModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CBC"), @@ -94,8 +117,7 @@ class TestAESModeCBC(object): ), skip_message="Does not support AES ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeECB(object): +class TestAESModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "ECB"), @@ -127,8 +149,7 @@ class TestAESModeECB(object): ), skip_message="Does not support AES OFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeOFB(object): +class TestAESModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "OFB"), @@ -160,8 +181,7 @@ class TestAESModeOFB(object): ), skip_message="Does not support AES CFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeCFB(object): +class TestAESModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), @@ -193,8 +213,7 @@ class TestAESModeCFB(object): ), skip_message="Does not support AES CFB8", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeCFB8(object): +class TestAESModeCFB8: test_cfb8 = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), @@ -226,8 +245,7 @@ class TestAESModeCFB8(object): ), skip_message="Does not support AES CTR", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeCTR(object): +class TestAESModeCTR: test_ctr = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CTR"), @@ -250,15 +268,39 @@ class TestAESModeCTR(object): DummyMode(), ], ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) def test_buffer_protocol_alternate_modes(mode, backend): data = bytearray(b"sixteen_byte_msg") key = algorithms.AES(bytearray(os.urandom(32))) if not backend.cipher_supported(key, mode): - pytest.skip("AES in {} mode not supported".format(mode.name)) + pytest.skip(f"AES in {mode.name} mode not supported") cipher = base.Cipher(key, mode, backend) enc = cipher.encryptor() ct = enc.update(data) + enc.finalize() dec = cipher.decryptor() pt = dec.update(ct) + dec.finalize() assert pt == data + + +@pytest.mark.parametrize( + "mode", + [ + modes.ECB(), + modes.CBC(bytearray(b"\x00" * 16)), + modes.CTR(bytearray(b"\x00" * 16)), + modes.OFB(bytearray(b"\x00" * 16)), + modes.CFB(bytearray(b"\x00" * 16)), + modes.CFB8(bytearray(b"\x00" * 16)), + ], +) +@pytest.mark.parametrize("alg_cls", [algorithms.AES128, algorithms.AES256]) +def test_alternate_aes_classes(mode, alg_cls, backend): + alg = alg_cls(b"0" * (alg_cls.key_size // 8)) + if not backend.cipher_supported(alg, mode): + pytest.skip(f"AES in {mode.name} mode not supported") + data = bytearray(b"sixteen_byte_msg") + cipher = base.Cipher(alg, mode, backend) + enc = cipher.encryptor() + ct = enc.update(data) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize() + assert pt == data diff --git a/tests/hazmat/primitives/test_aes_gcm.py b/tests/hazmat/primitives/test_aes_gcm.py index f289f18b11cc..c1154a96292b 100644 --- a/tests/hazmat/primitives/test_aes_gcm.py +++ b/tests/hazmat/primitives/test_aes_gcm.py @@ -2,18 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from .utils import generate_aead_test from ...utils import load_nist_vectors +from .utils import generate_aead_test @pytest.mark.supported( @@ -22,8 +20,7 @@ ), skip_message="Does not support AES GCM", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESModeGCM(object): +class TestAESModeGCM: test_gcm = generate_aead_test( load_nist_vectors, os.path.join("ciphers", "AES", "GCM"), @@ -74,9 +71,11 @@ def test_gcm_ciphertext_limit(self, backend): modes.GCM(b"\x01" * 16), backend=backend, ).encryptor() - encryptor._bytes_processed = modes.GCM._MAX_ENCRYPTED_BYTES - 16 + new_max = modes.GCM._MAX_ENCRYPTED_BYTES - 16 + encryptor._bytes_processed = new_max # type: ignore[attr-defined] encryptor.update(b"0" * 16) - assert encryptor._bytes_processed == modes.GCM._MAX_ENCRYPTED_BYTES + max = modes.GCM._MAX_ENCRYPTED_BYTES + assert encryptor._bytes_processed == max # type: ignore[attr-defined] with pytest.raises(ValueError): encryptor.update(b"0") @@ -86,9 +85,13 @@ def test_gcm_aad_limit(self, backend): modes.GCM(b"\x01" * 16), backend=backend, ).encryptor() - encryptor._aad_bytes_processed = modes.GCM._MAX_AAD_BYTES - 16 + new_max = modes.GCM._MAX_AAD_BYTES - 16 + encryptor._aad_bytes_processed = new_max # type: ignore[attr-defined] encryptor.authenticate_additional_data(b"0" * 16) - assert encryptor._aad_bytes_processed == modes.GCM._MAX_AAD_BYTES + max = modes.GCM._MAX_AAD_BYTES + assert ( + encryptor._aad_bytes_processed == max # type: ignore[attr-defined] + ) with pytest.raises(ValueError): encryptor.authenticate_additional_data(b"0") @@ -99,11 +102,11 @@ def test_gcm_ciphertext_increments(self, backend): backend=backend, ).encryptor() encryptor.update(b"0" * 8) - assert encryptor._bytes_processed == 8 + assert encryptor._bytes_processed == 8 # type: ignore[attr-defined] encryptor.update(b"0" * 7) - assert encryptor._bytes_processed == 15 + assert encryptor._bytes_processed == 15 # type: ignore[attr-defined] encryptor.update(b"0" * 18) - assert encryptor._bytes_processed == 33 + assert encryptor._bytes_processed == 33 # type: ignore[attr-defined] def test_gcm_aad_increments(self, backend): encryptor = base.Cipher( @@ -112,9 +115,13 @@ def test_gcm_aad_increments(self, backend): backend=backend, ).encryptor() encryptor.authenticate_additional_data(b"0" * 8) - assert encryptor._aad_bytes_processed == 8 + assert ( + encryptor._aad_bytes_processed == 8 # type: ignore[attr-defined] + ) encryptor.authenticate_additional_data(b"0" * 18) - assert encryptor._aad_bytes_processed == 26 + assert ( + encryptor._aad_bytes_processed == 26 # type: ignore[attr-defined] + ) def test_gcm_tag_decrypt_none(self, backend): key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") @@ -171,12 +178,13 @@ def test_gcm_tag_decrypt_finalize(self, backend): decryptor.finalize_with_tag(tag) - def test_gcm_tag_decrypt_finalize_tag_length(self, backend): + @pytest.mark.parametrize("tag", [b"tagtooshort", b"toolong" * 12]) + def test_gcm_tag_decrypt_finalize_tag_length(self, tag, backend): decryptor = base.Cipher( algorithms.AES(b"0" * 16), modes.GCM(b"0" * 12), backend=backend ).decryptor() with pytest.raises(ValueError): - decryptor.finalize_with_tag(b"tagtooshort") + decryptor.finalize_with_tag(tag) def test_buffer_protocol(self, backend): data = bytearray(b"helloworld") @@ -195,3 +203,37 @@ def test_buffer_protocol(self, backend): dec.authenticate_additional_data(bytearray(b"foo")) pt = dec.update(ct) + dec.finalize() assert pt == data + + @pytest.mark.parametrize("size", [8, 128]) + def test_gcm_min_max_iv(self, size, backend): + if backend._fips_enabled: + # Red Hat disables non-96-bit IV support as part of its FIPS + # patches. + pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") + + key = os.urandom(16) + iv = b"\x00" * size + + payload = b"data" + encryptor = base.Cipher(algorithms.AES(key), modes.GCM(iv)).encryptor() + ct = encryptor.update(payload) + encryptor.finalize() + tag = encryptor.tag + + decryptor = base.Cipher(algorithms.AES(key), modes.GCM(iv)).decryptor() + pt = decryptor.update(ct) + + decryptor.finalize_with_tag(tag) + assert pt == payload + + @pytest.mark.parametrize("alg", [algorithms.AES128, algorithms.AES256]) + def test_alternate_aes_classes(self, alg, backend): + data = bytearray(b"sixteen_byte_msg") + cipher = base.Cipher( + alg(b"0" * (alg.key_size // 8)), modes.GCM(b"\x00" * 12), backend + ) + enc = cipher.encryptor() + ct = enc.update(data) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize_with_tag(enc.tag) + assert pt == data diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py index de20b7098ae7..b589518adfec 100644 --- a/tests/hazmat/primitives/test_arc4.py +++ b/tests/hazmat/primitives/test_arc4.py @@ -2,18 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms -from .utils import generate_stream_encryption_test from ...utils import load_nist_vectors +from .utils import generate_stream_encryption_test @pytest.mark.supported( @@ -22,8 +20,7 @@ ), skip_message="Does not support ARC4", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestARC4(object): +class TestARC4: test_rfc = generate_stream_encryption_test( load_nist_vectors, os.path.join("ciphers", "ARC4"), diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py index 70bff012fbcb..8ff5a65bdd75 100644 --- a/tests/hazmat/primitives/test_asym_utils.py +++ b/tests/hazmat/primitives/test_asym_utils.py @@ -2,10 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import pytest +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import ( Prehashed, decode_dss_signature, @@ -35,25 +35,27 @@ def test_dss_signature(): def test_encode_dss_non_integer(): - with pytest.raises(ValueError): - encode_dss_signature("h", 3) + with pytest.raises(TypeError): + encode_dss_signature("h", 3) # type: ignore[arg-type] - with pytest.raises(ValueError): - encode_dss_signature("3", "2") + with pytest.raises(TypeError): + encode_dss_signature("3", "2") # type: ignore[arg-type] - with pytest.raises(ValueError): - encode_dss_signature(3, "h") + with pytest.raises(TypeError): + encode_dss_signature(3, "h") # type: ignore[arg-type] - with pytest.raises(ValueError): - encode_dss_signature(3.3, 1.2) + with pytest.raises(TypeError): + encode_dss_signature(3.3, 1.2) # type: ignore[arg-type] - with pytest.raises(ValueError): - encode_dss_signature("hello", "world") + with pytest.raises(TypeError): + encode_dss_signature("hello", "world") # type: ignore[arg-type] def test_encode_dss_negative(): with pytest.raises(ValueError): encode_dss_signature(-1, 0) + with pytest.raises(ValueError): + encode_dss_signature(0, -1) def test_decode_dss_trailing_bytes(): @@ -74,4 +76,9 @@ def test_decode_dss_invalid_asn1(): def test_pass_invalid_prehashed_arg(): with pytest.raises(TypeError): - Prehashed(object()) + Prehashed(object()) # type: ignore[arg-type] + + +def test_prehashed_digest_size(): + p = Prehashed(hashes.SHA256()) + assert p.digest_size == 32 diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 593199315a06..b831de176a0a 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -2,14 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import AlreadyFinalized, _Reasons -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, @@ -17,16 +15,15 @@ modes, ) +from ...doubles import DummyCipherAlgorithm, DummyMode +from ...utils import raises_unsupported_algorithm from .utils import ( generate_aead_exception_test, generate_aead_tag_exception_test, ) -from ...doubles import DummyCipherAlgorithm, DummyMode -from ...utils import raises_unsupported_algorithm -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCipher(object): +class TestCipher: def test_creates_encryptor(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), @@ -46,11 +43,12 @@ def test_creates_decryptor(self, backend): def test_instantiate_with_non_algorithm(self, backend): algorithm = object() with pytest.raises(TypeError): - Cipher(algorithm, mode=None, backend=backend) + Cipher( + algorithm, mode=None, backend=backend # type: ignore[arg-type] + ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCipherContext(object): +class TestCipherContext: def test_use_after_finalize(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), @@ -133,8 +131,7 @@ def test_incorrectly_padded(self, backend): ), skip_message="Does not support AES GCM", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAEADCipherContext(object): +class TestAEADCipherContext: test_aead_exceptions = generate_aead_exception_test( algorithms.AES, modes.GCM, @@ -145,8 +142,7 @@ class TestAEADCipherContext(object): ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestModeValidation(object): +class TestModeValidation: def test_cbc(self, backend): with pytest.raises(ValueError): Cipher( @@ -192,31 +188,31 @@ def test_gcm(self): modes.GCM(b"") -class TestModesRequireBytes(object): +class TestModesRequireBytes: def test_cbc(self): with pytest.raises(TypeError): - modes.CBC([1] * 16) + modes.CBC([1] * 16) # type:ignore[arg-type] def test_cfb(self): with pytest.raises(TypeError): - modes.CFB([1] * 16) + modes.CFB([1] * 16) # type:ignore[arg-type] def test_cfb8(self): with pytest.raises(TypeError): - modes.CFB8([1] * 16) + modes.CFB8([1] * 16) # type:ignore[arg-type] def test_ofb(self): with pytest.raises(TypeError): - modes.OFB([1] * 16) + modes.OFB([1] * 16) # type:ignore[arg-type] def test_ctr(self): with pytest.raises(TypeError): - modes.CTR([1] * 16) + modes.CTR([1] * 16) # type:ignore[arg-type] def test_gcm_iv(self): with pytest.raises(TypeError): - modes.GCM([1] * 16) + modes.GCM([1] * 16) # type:ignore[arg-type] def test_gcm_tag(self): with pytest.raises(TypeError): - modes.GCM(b"\x00" * 16, [1] * 16) + modes.GCM(b"\x00" * 16, [1] * 16) # type:ignore[arg-type] diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py index 5f7480ec9234..b8f34dfcef58 100644 --- a/tests/hazmat/primitives/test_blowfish.py +++ b/tests/hazmat/primitives/test_blowfish.py @@ -2,83 +2,85 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish(b"\x00" * 56), modes.ECB() + algorithms._BlowfishInternal(b"\x00" * 56), modes.ECB() ), skip_message="Does not support Blowfish ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestBlowfishModeECB(object): +class TestBlowfishModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ecb.txt"], - lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda key, **kwargs: algorithms._BlowfishInternal( + binascii.unhexlify(key) + ), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish(b"\x00" * 56), modes.CBC(b"\x00" * 8) + algorithms._BlowfishInternal(b"\x00" * 56), modes.CBC(b"\x00" * 8) ), skip_message="Does not support Blowfish CBC", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestBlowfishModeCBC(object): +class TestBlowfishModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cbc.txt"], - lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda key, **kwargs: algorithms._BlowfishInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish(b"\x00" * 56), modes.OFB(b"\x00" * 8) + algorithms._BlowfishInternal(b"\x00" * 56), modes.OFB(b"\x00" * 8) ), skip_message="Does not support Blowfish OFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestBlowfishModeOFB(object): +class TestBlowfishModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ofb.txt"], - lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda key, **kwargs: algorithms._BlowfishInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish(b"\x00" * 56), modes.CFB(b"\x00" * 8) + algorithms._BlowfishInternal(b"\x00" * 56), modes.CFB(b"\x00" * 8) ), skip_message="Does not support Blowfish CFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestBlowfishModeCFB(object): +class TestBlowfishModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cfb.txt"], - lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda key, **kwargs: algorithms._BlowfishInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) diff --git a/tests/hazmat/primitives/test_camellia.py b/tests/hazmat/primitives/test_camellia.py index b752345d3e44..d6f1fca86e13 100644 --- a/tests/hazmat/primitives/test_camellia.py +++ b/tests/hazmat/primitives/test_camellia.py @@ -2,18 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_cryptrec_vectors, load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( @@ -22,8 +20,7 @@ ), skip_message="Does not support Camellia ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCamelliaModeECB(object): +class TestCamelliaModeECB: test_ecb = generate_encrypt_test( load_cryptrec_vectors, os.path.join("ciphers", "Camellia"), @@ -43,8 +40,7 @@ class TestCamelliaModeECB(object): ), skip_message="Does not support Camellia CBC", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCamelliaModeCBC(object): +class TestCamelliaModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), @@ -60,8 +56,7 @@ class TestCamelliaModeCBC(object): ), skip_message="Does not support Camellia OFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCamelliaModeOFB(object): +class TestCamelliaModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), @@ -77,8 +72,7 @@ class TestCamelliaModeOFB(object): ), skip_message="Does not support Camellia CFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCamelliaModeCFB(object): +class TestCamelliaModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py index eff5d252f594..327a463b60e5 100644 --- a/tests/hazmat/primitives/test_cast5.py +++ b/tests/hazmat/primitives/test_cast5.py @@ -2,83 +2,85 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5(b"\x00" * 16), modes.ECB() + algorithms._CAST5Internal(b"\x00" * 16), modes.ECB() ), skip_message="Does not support CAST5 ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCAST5ModeECB(object): +class TestCAST5ModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ecb.txt"], - lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._CAST5Internal( + binascii.unhexlify(key) + ), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5(b"\x00" * 16), modes.CBC(b"\x00" * 8) + algorithms._CAST5Internal(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support CAST5 CBC", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCAST5ModeCBC(object): +class TestCAST5ModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cbc.txt"], - lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._CAST5Internal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5(b"\x00" * 16), modes.OFB(b"\x00" * 8) + algorithms._CAST5Internal(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support CAST5 OFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCAST5ModeOFB(object): +class TestCAST5ModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ofb.txt"], - lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._CAST5Internal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5(b"\x00" * 16), modes.CFB(b"\x00" * 8) + algorithms._CAST5Internal(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support CAST5 CFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCAST5ModeCFB(object): +class TestCAST5ModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cfb.txt"], - lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._CAST5Internal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py index cb12d3c91ae2..5337465b99c1 100644 --- a/tests/hazmat/primitives/test_chacha20.py +++ b/tests/hazmat/primitives/test_chacha20.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os @@ -10,11 +9,10 @@ import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms -from .utils import _load_all_params from ...utils import load_nist_vectors +from .utils import _load_all_params @pytest.mark.supported( @@ -23,8 +21,7 @@ ), skip_message="Does not support ChaCha20", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestChaCha20(object): +class TestChaCha20: @pytest.mark.parametrize( "vector", _load_all_params( @@ -67,8 +64,8 @@ def test_invalid_nonce(self): algorithms.ChaCha20(b"0" * 32, b"0") with pytest.raises(TypeError): - algorithms.ChaCha20(b"0" * 32, object()) + algorithms.ChaCha20(b"0" * 32, object()) # type:ignore[arg-type] def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - algorithms.ChaCha20(u"0" * 32, b"0" * 16) + algorithms.ChaCha20("0" * 32, b"0" * 16) # type:ignore[arg-type] diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index 4d82f0c13f42..bf3b047dec25 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os @@ -10,18 +9,17 @@ import pytest from cryptography.exceptions import AlreadyFinalized, _Reasons -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import modes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, - Blowfish, - CAST5, Camellia, - IDEA, - SEED, TripleDES, + _BlowfishInternal, + _CAST5Internal, + _IDEAInternal, + _SEEDInternal, ) from ...utils import ( @@ -31,7 +29,7 @@ ) -class TestAES(object): +class TestAES: @pytest.mark.parametrize( ("key", "keysize"), [(b"0" * 32, 128), (b"0" * 48, 192), (b"0" * 64, 256)], @@ -46,11 +44,10 @@ def test_invalid_key_size(self): def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - AES(u"0" * 32) + AES("0" * 32) # type: ignore[arg-type] -class TestAESXTS(object): - @pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESXTS: @pytest.mark.parametrize( "mode", (modes.CBC, modes.CTR, modes.CFB, modes.CFB8, modes.OFB) ) @@ -60,19 +57,25 @@ def test_invalid_key_size_with_mode(self, mode, backend): def test_xts_tweak_not_bytes(self): with pytest.raises(TypeError): - modes.XTS(32) + modes.XTS(32) # type: ignore[arg-type] def test_xts_tweak_too_small(self): with pytest.raises(ValueError): modes.XTS(b"0") - @pytest.mark.requires_backend_interface(interface=CipherBackend) def test_xts_wrong_key_size(self, backend): with pytest.raises(ValueError): ciphers.Cipher(AES(b"0" * 16), modes.XTS(b"0" * 16), backend) -class TestCamellia(object): +class TestGCM: + @pytest.mark.parametrize("size", [7, 129]) + def test_gcm_min_max(self, size): + with pytest.raises(ValueError): + modes.GCM(b"0" * size) + + +class TestCamellia: @pytest.mark.parametrize( ("key", "keysize"), [(b"0" * 32, 128), (b"0" * 48, 192), (b"0" * 64, 256)], @@ -87,10 +90,10 @@ def test_invalid_key_size(self): def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - Camellia(u"0" * 32) + Camellia("0" * 32) # type: ignore[arg-type] -class TestTripleDES(object): +class TestTripleDES: @pytest.mark.parametrize("key", [b"0" * 16, b"0" * 32, b"0" * 48]) def test_key_size(self, key): cipher = TripleDES(binascii.unhexlify(key)) @@ -102,46 +105,46 @@ def test_invalid_key_size(self): def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - TripleDES(u"0" * 16) + TripleDES("0" * 16) # type: ignore[arg-type] -class TestBlowfish(object): +class TestBlowfish: @pytest.mark.parametrize( ("key", "keysize"), [(b"0" * (keysize // 4), keysize) for keysize in range(32, 449, 8)], ) def test_key_size(self, key, keysize): - cipher = Blowfish(binascii.unhexlify(key)) + cipher = _BlowfishInternal(binascii.unhexlify(key)) assert cipher.key_size == keysize def test_invalid_key_size(self): with pytest.raises(ValueError): - Blowfish(binascii.unhexlify(b"0" * 6)) + _BlowfishInternal(binascii.unhexlify(b"0" * 6)) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - Blowfish(u"0" * 8) + _BlowfishInternal("0" * 8) # type: ignore[arg-type] -class TestCAST5(object): +class TestCAST5: @pytest.mark.parametrize( ("key", "keysize"), [(b"0" * (keysize // 4), keysize) for keysize in range(40, 129, 8)], ) def test_key_size(self, key, keysize): - cipher = CAST5(binascii.unhexlify(key)) + cipher = _CAST5Internal(binascii.unhexlify(key)) assert cipher.key_size == keysize def test_invalid_key_size(self): with pytest.raises(ValueError): - CAST5(binascii.unhexlify(b"0" * 34)) + _CAST5Internal(binascii.unhexlify(b"0" * 34)) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - CAST5(u"0" * 10) + _CAST5Internal("0" * 10) # type: ignore[arg-type] -class TestARC4(object): +class TestARC4: @pytest.mark.parametrize( ("key", "keysize"), [ @@ -164,42 +167,55 @@ def test_invalid_key_size(self): def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - ARC4(u"0" * 10) + ARC4("0" * 10) # type: ignore[arg-type] -class TestIDEA(object): +class TestIDEA: def test_key_size(self): - cipher = IDEA(b"\x00" * 16) + cipher = _IDEAInternal(b"\x00" * 16) assert cipher.key_size == 128 def test_invalid_key_size(self): with pytest.raises(ValueError): - IDEA(b"\x00" * 17) + _IDEAInternal(b"\x00" * 17) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - IDEA(u"0" * 16) + _IDEAInternal("0" * 16) # type: ignore[arg-type] -class TestSEED(object): +class TestSEED: def test_key_size(self): - cipher = SEED(b"\x00" * 16) + cipher = _SEEDInternal(b"\x00" * 16) assert cipher.key_size == 128 def test_invalid_key_size(self): with pytest.raises(ValueError): - SEED(b"\x00" * 17) + _SEEDInternal(b"\x00" * 17) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): - SEED(u"0" * 16) + _SEEDInternal("0" * 16) # type: ignore[arg-type] -def test_invalid_backend(): - pretend_backend = object() +def test_invalid_mode_algorithm(): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ciphers.Cipher( + ARC4(b"\x00" * 16), + modes.GCM(b"\x00" * 12), + ) - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), modes.ECB, pretend_backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ciphers.Cipher( + ARC4(b"\x00" * 16), + modes.CBC(b"\x00" * 12), + ) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ciphers.Cipher( + ARC4(b"\x00" * 16), + modes.CTR(b"\x00" * 12), + ) @pytest.mark.supported( @@ -208,8 +224,7 @@ def test_invalid_backend(): ), skip_message="Does not support AES ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCipherUpdateInto(object): +class TestCipherUpdateInto: @pytest.mark.parametrize( "params", load_vectors_from_file( @@ -303,6 +318,14 @@ def test_update_into_buffer_too_small(self, backend): with pytest.raises(ValueError): encryptor.update_into(b"testing", buf) + def test_update_into_immutable(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = b"\x00" * 32 + with pytest.raises((TypeError, BufferError)): + encryptor.update_into(b"testing", buf) + @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) @@ -322,14 +345,18 @@ def test_update_into_auto_chunking(self, backend, monkeypatch): c = ciphers.Cipher(AES(key), modes.ECB(), backend) encryptor = c.encryptor() # Lower max chunk size so we can test chunking - monkeypatch.setattr(encryptor._ctx, "_MAX_CHUNK_SIZE", 40) + monkeypatch.setattr( + encryptor._ctx, "_MAX_CHUNK_SIZE", 40 # type: ignore[attr-defined] + ) buf = bytearray(527) pt = b"abcdefghijklmnopqrstuvwxyz012345" * 16 # 512 bytes processed = encryptor.update_into(pt, buf) assert processed == 512 decryptor = c.decryptor() # Change max chunk size to verify alternate boundaries don't matter - monkeypatch.setattr(decryptor._ctx, "_MAX_CHUNK_SIZE", 73) + monkeypatch.setattr( + decryptor._ctx, "_MAX_CHUNK_SIZE", 73 # type: ignore[attr-defined] + ) decbuf = bytearray(527) decprocessed = decryptor.update_into(buf[:processed], decbuf) assert decbuf[:decprocessed] == pt @@ -341,4 +368,7 @@ def test_max_chunk_size_fits_in_int32(self, backend): key = b"\x00" * 16 c = ciphers.Cipher(AES(key), modes.ECB(), backend) encryptor = c.encryptor() - backend._ffi.new("int *", encryptor._ctx._MAX_CHUNK_SIZE) + backend._ffi.new( + "int *", + encryptor._ctx._MAX_CHUNK_SIZE, # type: ignore[attr-defined] + ) diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py index e4a35df621fd..c9e7fdd88fa1 100644 --- a/tests/hazmat/primitives/test_cmac.py +++ b/tests/hazmat/primitives/test_cmac.py @@ -2,18 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidSignature, - _Reasons, -) -from cryptography.hazmat.backends.interfaces import CMACBackend +from cryptography.exceptions import AlreadyFinalized, InvalidSignature from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, @@ -21,12 +15,7 @@ ) from cryptography.hazmat.primitives.cmac import CMAC -from ...utils import ( - load_nist_vectors, - load_vectors_from_file, - raises_unsupported_algorithm, -) - +from ...utils import load_nist_vectors, load_vectors_from_file vectors_aes128 = load_vectors_from_file( "CMAC/nist-800-38b-aes128.txt", load_nist_vectors @@ -49,8 +38,7 @@ fake_key = b"\x00" * 16 -@pytest.mark.requires_backend_interface(interface=CMACBackend) -class TestCMAC(object): +class TestCMAC: @pytest.mark.supported( only_if=lambda backend: backend.cmac_algorithm_supported( AES(fake_key) @@ -81,7 +69,7 @@ def test_aes_verify(self, backend, params): cmac = CMAC(AES(binascii.unhexlify(key)), backend) cmac.update(binascii.unhexlify(message)) - assert cmac.verify(binascii.unhexlify(output)) is None + cmac.verify(binascii.unhexlify(output)) @pytest.mark.supported( only_if=lambda backend: backend.cmac_algorithm_supported( @@ -123,7 +111,7 @@ def test_3des_verify(self, backend, params): cmac = CMAC(TripleDES(binascii.unhexlify(key)), backend) cmac.update(binascii.unhexlify(message)) - assert cmac.verify(binascii.unhexlify(output)) is None + cmac.verify(binascii.unhexlify(output)) @pytest.mark.supported( only_if=lambda backend: backend.cmac_algorithm_supported( @@ -146,7 +134,7 @@ def test_invalid_verify(self, backend): def test_invalid_algorithm(self, backend): key = b"0102030405" with pytest.raises(TypeError): - CMAC(ARC4(key), backend) + CMAC(ARC4(key), backend) # type: ignore[arg-type] @pytest.mark.supported( only_if=lambda backend: backend.cmac_algorithm_supported( @@ -182,10 +170,10 @@ def test_verify_reject_unicode(self, backend): cmac = CMAC(AES(key), backend) with pytest.raises(TypeError): - cmac.update(u"") + cmac.update("") # type: ignore[arg-type] with pytest.raises(TypeError): - cmac.verify(u"") + cmac.verify("") # type: ignore[arg-type] @pytest.mark.supported( only_if=lambda backend: backend.cmac_algorithm_supported( @@ -213,11 +201,3 @@ def test_buffer_protocol(self, backend): assert cmac.finalize() == binascii.unhexlify( b"a21e6e647bfeaf5ca0a5e1bcd957dfad" ) - - -def test_invalid_backend(): - key = b"2b7e151628aed2a6abf7158809cf4f3c" - pretend_backend = object() - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - CMAC(AES(key), pretend_backend) diff --git a/tests/hazmat/primitives/test_concatkdf.py b/tests/hazmat/primitives/test_concatkdf.py index 271e01175d30..f0dd18828125 100644 --- a/tests/hazmat/primitives/test_concatkdf.py +++ b/tests/hazmat/primitives/test_concatkdf.py @@ -2,26 +2,22 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest -from cryptography.exceptions import AlreadyFinalized, InvalidKey, _Reasons -from cryptography.hazmat.backends.interfaces import HMACBackend -from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHMAC -from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash +from cryptography.hazmat.primitives.kdf.concatkdf import ( + ConcatKDFHash, + ConcatKDFHMAC, +) -from ...utils import raises_unsupported_algorithm - -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestConcatKDFHash(object): +class TestConcatKDFHash: def test_length_limit(self, backend): - big_length = hashes.SHA256().digest_size * (2 ** 32 - 1) + 1 + big_length = hashes.SHA256().digest_size * (2**32 - 1) + 1 with pytest.raises(ValueError): ConcatKDFHash(hashes.SHA256(), big_length, None, backend) @@ -80,7 +76,7 @@ def test_verify(self, backend): ckdf = ConcatKDFHash(hashes.SHA256(), 16, oinfo, backend) - assert ckdf.verify(prk, okm) is None + ckdf.verify(prk, okm) def test_invalid_verify(self, backend): prk = binascii.unhexlify( @@ -100,7 +96,10 @@ def test_invalid_verify(self, backend): def test_unicode_typeerror(self, backend): with pytest.raises(TypeError): ConcatKDFHash( - hashes.SHA256(), 16, otherinfo=u"foo", backend=backend + hashes.SHA256(), + 16, + otherinfo="foo", # type: ignore[arg-type] + backend=backend, ) with pytest.raises(TypeError): @@ -108,27 +107,26 @@ def test_unicode_typeerror(self, backend): hashes.SHA256(), 16, otherinfo=None, backend=backend ) - ckdf.derive(u"foo") + ckdf.derive("foo") # type: ignore[arg-type] with pytest.raises(TypeError): ckdf = ConcatKDFHash( hashes.SHA256(), 16, otherinfo=None, backend=backend ) - ckdf.verify(u"foo", b"bar") + ckdf.verify("foo", b"bar") # type: ignore[arg-type] with pytest.raises(TypeError): ckdf = ConcatKDFHash( hashes.SHA256(), 16, otherinfo=None, backend=backend ) - ckdf.verify(b"foo", u"bar") + ckdf.verify(b"foo", "bar") # type: ignore[arg-type] -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestConcatKDFHMAC(object): +class TestConcatKDFHMAC: def test_length_limit(self, backend): - big_length = hashes.SHA256().digest_size * (2 ** 32 - 1) + 1 + big_length = hashes.SHA256().digest_size * (2**32 - 1) + 1 with pytest.raises(ValueError): ConcatKDFHMAC(hashes.SHA256(), big_length, None, None, backend) @@ -225,7 +223,7 @@ def test_verify(self, backend): ckdf = ConcatKDFHMAC(hashes.SHA512(), 32, None, oinfo, backend) - assert ckdf.verify(prk, okm) is None + ckdf.verify(prk, okm) def test_invalid_verify(self, backend): prk = binascii.unhexlify( @@ -249,7 +247,7 @@ def test_unicode_typeerror(self, backend): ConcatKDFHMAC( hashes.SHA256(), 16, - salt=u"foo", + salt="foo", # type: ignore[arg-type] otherinfo=None, backend=backend, ) @@ -259,7 +257,7 @@ def test_unicode_typeerror(self, backend): hashes.SHA256(), 16, salt=None, - otherinfo=u"foo", + otherinfo="foo", # type: ignore[arg-type] backend=backend, ) @@ -268,27 +266,29 @@ def test_unicode_typeerror(self, backend): hashes.SHA256(), 16, salt=None, otherinfo=None, backend=backend ) - ckdf.derive(u"foo") + ckdf.derive("foo") # type: ignore[arg-type] with pytest.raises(TypeError): ckdf = ConcatKDFHMAC( hashes.SHA256(), 16, salt=None, otherinfo=None, backend=backend ) - ckdf.verify(u"foo", b"bar") + ckdf.verify("foo", b"bar") # type: ignore[arg-type] with pytest.raises(TypeError): ckdf = ConcatKDFHMAC( hashes.SHA256(), 16, salt=None, otherinfo=None, backend=backend ) - ckdf.verify(b"foo", u"bar") + ckdf.verify(b"foo", "bar") # type: ignore[arg-type] - -def test_invalid_backend(): - pretend_backend = object() - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - ConcatKDFHash(hashes.SHA256(), 16, None, pretend_backend) - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - ConcatKDFHMAC(hashes.SHA256(), 16, None, None, pretend_backend) + def test_unsupported_hash_algorithm(self, backend): + # ConcatKDF requires a hash algorithm with an internal block size. + with pytest.raises(TypeError): + ConcatKDFHMAC( + hashes.SHA3_256(), + 16, + salt=None, + otherinfo=None, + backend=backend, + ) diff --git a/tests/hazmat/primitives/test_constant_time.py b/tests/hazmat/primitives/test_constant_time.py index e8e85a840afc..35204cd80735 100644 --- a/tests/hazmat/primitives/test_constant_time.py +++ b/tests/hazmat/primitives/test_constant_time.py @@ -2,23 +2,22 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import pytest from cryptography.hazmat.primitives import constant_time -class TestConstantTimeBytesEq(object): +class TestConstantTimeBytesEq: def test_reject_unicode(self): with pytest.raises(TypeError): - constant_time.bytes_eq(b"foo", u"foo") + constant_time.bytes_eq(b"foo", "foo") # type: ignore[arg-type] with pytest.raises(TypeError): - constant_time.bytes_eq(u"foo", b"foo") + constant_time.bytes_eq("foo", b"foo") # type: ignore[arg-type] with pytest.raises(TypeError): - constant_time.bytes_eq(u"foo", u"foo") + constant_time.bytes_eq("foo", "foo") # type: ignore[arg-type] def test_compares(self): assert constant_time.bytes_eq(b"foo", b"foo") is True diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index 63a7c642ef7a..098d6e142b24 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -2,26 +2,33 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import itertools import os +import typing import pytest -from cryptography.hazmat.backends.interfaces import ( - DERSerializationBackend, - DHBackend, - PEMSerializationBackend, -) from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh -from cryptography.utils import int_from_bytes -from .fixtures_dh import FFDH3072_P from ...doubles import DummyKeySerializationEncryption from ...utils import load_nist_vectors, load_vectors_from_file +from .fixtures_dh import FFDH3072_P + +# RFC 3526 +P_1536 = int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", + 16, +) def _skip_dhx_unsupported(backend, is_dhx): @@ -32,35 +39,39 @@ def _skip_dhx_unsupported(backend, is_dhx): def test_dh_parameternumbers(): - params = dh.DHParameterNumbers(65537, 2) + params = dh.DHParameterNumbers(P_1536, 2) - assert params.p == 65537 + assert params.p == P_1536 assert params.g == 2 with pytest.raises(TypeError): - dh.DHParameterNumbers(None, 2) + dh.DHParameterNumbers(None, 2) # type: ignore[arg-type] with pytest.raises(TypeError): - dh.DHParameterNumbers(65537, None) + dh.DHParameterNumbers(P_1536, None) # type: ignore[arg-type] with pytest.raises(TypeError): - dh.DHParameterNumbers(None, None) + dh.DHParameterNumbers(None, None) # type: ignore[arg-type] + + with pytest.raises(ValueError): + dh.DHParameterNumbers(P_1536, 1) + # p too small with pytest.raises(ValueError): - dh.DHParameterNumbers(65537, 1) + dh.DHParameterNumbers(65537, 2) - params = dh.DHParameterNumbers(65537, 7, 1245) + params = dh.DHParameterNumbers(P_1536, 7, 1245) - assert params.p == 65537 + assert params.p == P_1536 assert params.g == 7 assert params.q == 1245 with pytest.raises(TypeError): - dh.DHParameterNumbers(65537, 2, "hello") + dh.DHParameterNumbers(P_1536, 2, "hello") # type: ignore[arg-type] def test_dh_numbers(): - params = dh.DHParameterNumbers(65537, 2) + params = dh.DHParameterNumbers(P_1536, 2) public = dh.DHPublicNumbers(1, params) @@ -68,10 +79,10 @@ def test_dh_numbers(): assert public.y == 1 with pytest.raises(TypeError): - dh.DHPublicNumbers(1, None) + dh.DHPublicNumbers(1, None) # type: ignore[arg-type] with pytest.raises(TypeError): - dh.DHPublicNumbers(None, params) + dh.DHPublicNumbers(None, params) # type:ignore[arg-type] private = dh.DHPrivateNumbers(1, public) @@ -79,27 +90,29 @@ def test_dh_numbers(): assert private.x == 1 with pytest.raises(TypeError): - dh.DHPrivateNumbers(1, None) + dh.DHPrivateNumbers(1, None) # type: ignore[arg-type] with pytest.raises(TypeError): - dh.DHPrivateNumbers(None, public) + dh.DHPrivateNumbers(None, public) # type:ignore[arg-type] def test_dh_parameter_numbers_equality(): - assert dh.DHParameterNumbers(65537, 2) == dh.DHParameterNumbers(65537, 2) - assert dh.DHParameterNumbers(65537, 7, 12345) == dh.DHParameterNumbers( - 65537, 7, 12345 + assert dh.DHParameterNumbers(P_1536, 2) == dh.DHParameterNumbers(P_1536, 2) + assert dh.DHParameterNumbers(P_1536, 7, 12345) == dh.DHParameterNumbers( + P_1536, 7, 12345 ) - assert dh.DHParameterNumbers(6, 2) != dh.DHParameterNumbers(65537, 2) - assert dh.DHParameterNumbers(65537, 2, 123) != dh.DHParameterNumbers( - 65537, 2, 456 + assert dh.DHParameterNumbers(P_1536 + 2, 2) != dh.DHParameterNumbers( + P_1536, 2 ) - assert dh.DHParameterNumbers(65537, 5) != dh.DHParameterNumbers(65537, 2) - assert dh.DHParameterNumbers(65537, 2) != object() + assert dh.DHParameterNumbers(P_1536, 2, 123) != dh.DHParameterNumbers( + P_1536, 2, 456 + ) + assert dh.DHParameterNumbers(P_1536, 5) != dh.DHParameterNumbers(P_1536, 2) + assert dh.DHParameterNumbers(P_1536, 2) != object() def test_dh_private_numbers_equality(): - params = dh.DHParameterNumbers(65537, 2) + params = dh.DHParameterNumbers(P_1536, 2) public = dh.DHPublicNumbers(1, params) private = dh.DHPrivateNumbers(2, public) @@ -107,23 +120,26 @@ def test_dh_private_numbers_equality(): assert private != dh.DHPrivateNumbers(0, public) assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params)) assert private != dh.DHPrivateNumbers( - 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) + 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(P_1536, 5)) ) assert private != object() def test_dh_public_numbers_equality(): - params = dh.DHParameterNumbers(65537, 2) + params = dh.DHParameterNumbers(P_1536, 2) public = dh.DHPublicNumbers(1, params) assert public == dh.DHPublicNumbers(1, params) assert public != dh.DHPublicNumbers(0, params) - assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) + assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(P_1536, 5)) assert public != object() -@pytest.mark.requires_backend_interface(interface=DHBackend) -class TestDH(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="DH not supported", +) +class TestDH: def test_small_key_generate_dh(self, backend): with pytest.raises(ValueError): dh.generate_parameters(2, 511, backend) @@ -132,6 +148,11 @@ def test_unsupported_generator_generate_dh(self, backend): with pytest.raises(ValueError): dh.generate_parameters(7, 512, backend) + def test_large_key_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(2, 1 << 30) + + @pytest.mark.skip_fips(reason="non-FIPS parameters") def test_dh_parameters_supported(self, backend): valid_p = int( b"907c7211ae61aaaba1825ff53b6cb71ac6df9f1a424c033f4a0a41ac42fad3a9" @@ -151,16 +172,35 @@ def test_dh_parameters_supported(self, backend): ), ) def test_dh_parameters_allows_rfc3526_groups(self, backend, vector): - p = int_from_bytes(binascii.unhexlify(vector["p"]), "big") + p = int.from_bytes(binascii.unhexlify(vector["p"]), "big") + if ( + backend._fips_enabled + and p.bit_length() < backend._fips_dh_min_modulus + ): + pytest.skip("modulus too small for FIPS mode") + params = dh.DHParameterNumbers(p, int(vector["g"])) param = params.parameters(backend) key = param.generate_private_key() - # This confirms that a key generated with this group - # will pass DH_check when we serialize and de-serialize it via - # the Numbers path. - roundtripped_key = key.private_numbers().private_key(backend) - assert key.private_numbers() == roundtripped_key.private_numbers() + # In OpenSSL 3.0.0 OpenSSL maps to known groups. This results in + # a scenario where loading a known group with p and g returns a + # re-serialized form that has q as well (the Sophie Germain prime of + # that group). This makes a naive comparison of the parameter numbers + # objects fail, so we have to be a bit smarter + serialized_params = ( + key.private_numbers().public_numbers.parameter_numbers + ) + if serialized_params.q is None: + # This is the path OpenSSL < 3.0 takes + assert serialized_params == params + else: + assert serialized_params.p == params.p + assert serialized_params.g == params.g + # p = 2q + 1 since it is a Sophie Germain prime, so we can compute + # what we expect OpenSSL to have done here. + assert serialized_params.q == (params.p - 1) // 2 + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( "vector", load_vectors_from_file( @@ -182,7 +222,7 @@ def test_convert_to_numbers(self, backend, with_q): )[0] p = int(vector["p"], 16) g = int(vector["g"], 16) - q = int(vector["q"], 16) + q: typing.Optional[int] = int(vector["q"], 16) else: parameters = backend.generate_dh_private_key_and_parameters(2, 512) @@ -200,21 +240,17 @@ def test_convert_to_numbers(self, backend, with_q): deserialized_public = public.public_key(backend) deserialized_private = private.private_key(backend) - assert isinstance( - deserialized_params, dh.DHParametersWithSerialization - ) - assert isinstance(deserialized_public, dh.DHPublicKeyWithSerialization) - assert isinstance( - deserialized_private, dh.DHPrivateKeyWithSerialization - ) + assert isinstance(deserialized_params, dh.DHParameters) + assert isinstance(deserialized_public, dh.DHPublicKey) + assert isinstance(deserialized_private, dh.DHPrivateKey) + @pytest.mark.skip_fips(reason="FIPS requires specific parameters") def test_numbers_unsupported_parameters(self, backend): - # p is set to 21 because when calling private_key we want it to - # fail the DH_check call OpenSSL does. Originally this was 23, but - # we are allowing p % 24 to == 23 with this PR (see #3768 for more) - # By setting it to 21 it fails later in DH_check in a primality check - # which triggers the code path we want to test - params = dh.DHParameterNumbers(21, 2) + # p is set to P_1536 + 1 because when calling private_key we want it to + # fail the DH_check call OpenSSL does, but we specifically want it to + # fail such that we don't get a DH_NOT_SUITABLE_GENERATOR. We can cause + # this by making sure p is not prime. + params = dh.DHParameterNumbers(P_1536 + 1, 2) public = dh.DHPublicNumbers(1, params) private = dh.DHPrivateNumbers(2, public) @@ -249,19 +285,25 @@ def test_generate_dh(self, backend, with_q): assert isinstance(public, dh.DHPublicKey) assert public.key_size == key_size - assert isinstance(parameters, dh.DHParametersWithSerialization) + assert isinstance(parameters, dh.DHParameters) parameter_numbers = parameters.parameter_numbers() assert isinstance(parameter_numbers, dh.DHParameterNumbers) assert parameter_numbers.p.bit_length() == key_size - assert isinstance(public, dh.DHPublicKeyWithSerialization) + assert isinstance(public, dh.DHPublicKey) assert isinstance(public.public_numbers(), dh.DHPublicNumbers) assert isinstance(public.parameters(), dh.DHParameters) - assert isinstance(key, dh.DHPrivateKeyWithSerialization) + assert isinstance(key, dh.DHPrivateKey) assert isinstance(key.private_numbers(), dh.DHPrivateNumbers) assert isinstance(key.parameters(), dh.DHParameters) + def test_exchange_wrong_type(self, backend): + parameters = FFDH3072_P.parameters(backend) + key1 = parameters.generate_private_key() + with pytest.raises(TypeError): + key1.exchange(b"invalidtype") # type: ignore[arg-type] + def test_exchange(self, backend): parameters = FFDH3072_P.parameters(backend) assert isinstance(parameters, dh.DHParameters) @@ -282,7 +324,7 @@ def test_exchange_algorithm(self, backend): key2 = parameters.generate_private_key() shared_key_bytes = key2.exchange(key1.public_key()) - symkey = int_from_bytes(shared_key_bytes, "big") + symkey = int.from_bytes(shared_key_bytes, "big") symkey_manual = pow( key1.public_key().public_numbers().y, @@ -352,16 +394,28 @@ def test_bad_exchange(self, backend, vector): key2 = private2.private_key(backend) pub_key2 = key2.public_key() - if pub_key2.public_numbers().y >= parameters1.p: - with pytest.raises(ValueError): - key1.exchange(pub_key2) - else: - symkey1 = key1.exchange(pub_key2) - assert symkey1 + with pytest.raises(ValueError): + key1.exchange(pub_key2) - symkey2 = key2.exchange(pub_key1) + with pytest.raises(ValueError): + key2.exchange(pub_key1) - assert symkey1 != symkey2 + @pytest.mark.skip_fips(reason="key_size too small for FIPS") + @pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER + ), + skip_message="256-bit DH keys are not supported in OpenSSL 3.0.0+", + ) + def test_load_256bit_key_from_pkcs8(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dh_key_256.pem"), + lambda pemfile: pemfile.read(), + mode="rb", + ) + key = serialization.load_pem_private_key(data, None, backend) + assert isinstance(key, dh.DHPrivateKey) + assert key.key_size == 256 @pytest.mark.parametrize( "vector", @@ -375,14 +429,19 @@ def test_dh_vectors(self, backend, vector): and int(vector["p"]) < backend._fips_dh_min_modulus ): pytest.skip("modulus too small for FIPS mode") + + if int(vector["p"]).bit_length() < 512: + pytest.skip("DH keys less than 512 bits are unsupported") + parameters = dh.DHParameterNumbers(int(vector["p"]), int(vector["g"])) public = dh.DHPublicNumbers(int(vector["y"]), parameters) private = dh.DHPrivateNumbers(int(vector["x"]), public) key = private.private_key(backend) symkey = key.exchange(public.public_key(backend)) - assert int_from_bytes(symkey, "big") == int(vector["k"], 16) + assert int.from_bytes(symkey, "big") == int(vector["k"], 16) + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( "vector", load_vectors_from_file( @@ -402,14 +461,40 @@ def test_dh_vectors_with_q(self, backend, vector): symkey1 = key1.exchange(public2.public_key(backend)) symkey2 = key2.exchange(public1.public_key(backend)) - assert int_from_bytes(symkey1, "big") == int(vector["z"], 16) - assert int_from_bytes(symkey2, "big") == int(vector["z"], 16) + assert int.from_bytes(symkey1, "big") == int(vector["z"], 16) + assert int.from_bytes(symkey2, "big") == int(vector["z"], 16) + + @pytest.mark.supported( + only_if=lambda backend: backend.dh_x942_serialization_supported(), + skip_message="DH X9.42 not supported", + ) + def test_public_key_equality(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhpub.pem"), + lambda pemfile: pemfile.read(), + mode="rb", + ) + key_bytes_2 = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), + lambda pemfile: pemfile.read(), + mode="rb", + ) + key1 = serialization.load_pem_public_key(key_bytes) + key2 = serialization.load_pem_public_key(key_bytes) + key3 = serialization.load_pem_public_key(key_bytes_2) + assert key1 == key2 + assert key1 != key3 + assert key1 != object() + + with pytest.raises(TypeError): + key1 < key2 # type: ignore[operator] -@pytest.mark.requires_backend_interface(interface=DHBackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) -class TestDHPrivateKeySerialization(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="DH not supported", +) +class TestDHPrivateKeySerialization: @pytest.mark.parametrize( ("encoding", "loader_func"), [ @@ -445,6 +530,7 @@ def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): with pytest.raises(ValueError): key.private_bytes(encoding, fmt, serialization.NoEncryption()) + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( ("key_path", "loader_func", "encoding", "is_dhx"), [ @@ -489,6 +575,7 @@ def test_private_bytes_match( ) assert serialized == key_bytes + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( ("key_path", "loader_func", "vec_path", "is_dhx"), [ @@ -558,7 +645,7 @@ def test_private_bytes_invalid_encoding(self, backend): key = parameters.generate_private_key() with pytest.raises(TypeError): key.private_bytes( - "notencoding", + "notencoding", # type:ignore[arg-type] serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) @@ -569,7 +656,7 @@ def test_private_bytes_invalid_format(self, backend): with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, - "invalidformat", + "invalidformat", # type:ignore[arg-type] serialization.NoEncryption(), ) @@ -580,7 +667,7 @@ def test_private_bytes_invalid_encryption_algorithm(self, backend): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, - "notanencalg", + "notanencalg", # type:ignore[arg-type] ) def test_private_bytes_unsupported_encryption_type(self, backend): @@ -594,10 +681,11 @@ def test_private_bytes_unsupported_encryption_type(self, backend): ) -@pytest.mark.requires_backend_interface(interface=DHBackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) -class TestDHPublicKeySerialization(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="DH not supported", +) +class TestDHPublicKeySerialization: @pytest.mark.parametrize( ("encoding", "loader_func"), [ @@ -616,6 +704,7 @@ def test_public_bytes(self, backend, encoding, loader_func): pub_num = key.public_numbers() assert loaded_pub_num == pub_num + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( ("key_path", "loader_func", "encoding", "is_dhx"), [ @@ -659,6 +748,7 @@ def test_public_bytes_match( ) assert serialized == key_bytes + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( ("key_path", "loader_func", "vec_path", "is_dhx"), [ @@ -711,7 +801,8 @@ def test_public_bytes_invalid_encoding(self, backend): key = parameters.generate_private_key().public_key() with pytest.raises(TypeError): key.public_bytes( - "notencoding", serialization.PublicFormat.SubjectPublicKeyInfo + "notencoding", # type:ignore[arg-type] + serialization.PublicFormat.SubjectPublicKeyInfo, ) def test_public_bytes_pkcs1_unsupported(self, backend): @@ -723,10 +814,11 @@ def test_public_bytes_pkcs1_unsupported(self, backend): ) -@pytest.mark.requires_backend_interface(interface=DHBackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) -class TestDHParameterSerialization(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="DH not supported", +) +class TestDHParameterSerialization: @pytest.mark.parametrize( ("encoding", "loader_func"), [ @@ -867,13 +959,17 @@ def test_parameter_bytes_invalid_encoding(self, backend): parameters = FFDH3072_P.parameters(backend) with pytest.raises(TypeError): parameters.parameter_bytes( - "notencoding", serialization.ParameterFormat.PKCS3 + "notencoding", # type:ignore[arg-type] + serialization.ParameterFormat.PKCS3, ) def test_parameter_bytes_invalid_format(self, backend): parameters = FFDH3072_P.parameters(backend) with pytest.raises(ValueError): - parameters.parameter_bytes(serialization.Encoding.PEM, "notformat") + parameters.parameter_bytes( + serialization.Encoding.PEM, + "notformat", # type: ignore[arg-type] + ) def test_parameter_bytes_openssh_unsupported(self, backend): parameters = FFDH3072_P.parameters(backend) diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index bda275064ea2..00920868fc65 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -2,53 +2,65 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import itertools import os +import typing import pytest -from cryptography.exceptions import AlreadyFinalized, InvalidSignature -from cryptography.hazmat.backends.interfaces import ( - DSABackend, - PEMSerializationBackend, -) +from cryptography import utils +from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric.utils import ( Prehashed, encode_dss_signature, ) -from cryptography.utils import CryptographyDeprecationWarning -from .fixtures_dsa import DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 -from .utils import skip_fips_traditional_openssl from ...doubles import DummyHashAlgorithm, DummyKeySerializationEncryption from ...utils import ( load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, load_vectors_from_file, ) +from .fixtures_dsa import DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 +from .utils import skip_fips_traditional_openssl - -def _skip_if_dsa_not_supported(backend, algorithm, p, q, g): - if not backend.dsa_parameters_supported( - p, q, g - ) or not backend.dsa_hash_supported(algorithm): +_ALGORITHMS_DICT: typing.Dict[str, hashes.HashAlgorithm] = { + "SHA1": hashes.SHA1(), + "SHA224": hashes.SHA224(), + "SHA256": hashes.SHA256(), + "SHA384": hashes.SHA384(), + "SHA512": hashes.SHA512(), +} + + +def _skip_if_dsa_not_supported( + backend: typing.Any, + algorithm: hashes.HashAlgorithm, + p: int, + q: int, + g: int, +) -> None: + if not backend.dsa_hash_supported(algorithm): pytest.skip( - "{} does not support the provided parameters".format(backend) + "{} does not support the provided args. p: {}, hash: {}".format( + backend, p.bit_length(), algorithm.name + ) ) -@pytest.mark.requires_backend_interface(interface=DSABackend) def test_skip_if_dsa_not_supported(backend): with pytest.raises(pytest.skip.Exception): _skip_if_dsa_not_supported(backend, DummyHashAlgorithm(), 1, 1, 1) -@pytest.mark.requires_backend_interface(interface=DSABackend) -class TestDSA(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", +) +class TestDSA: def test_generate_dsa_parameters(self, backend): parameters = dsa.generate_parameters(2048, backend) assert isinstance(parameters, dsa.DSAParameters) @@ -65,11 +77,6 @@ def test_generate_invalid_dsa_parameters(self, backend): ), ) def test_generate_dsa_keys(self, vector, backend): - if ( - backend._fips_enabled - and vector["p"] < backend._fips_dsa_min_modulus - ): - pytest.skip("Small modulus blocked in FIPS mode") parameters = dsa.DSAParameterNumbers( p=vector["p"], q=vector["q"], g=vector["g"] ).parameters(backend) @@ -106,38 +113,38 @@ def test_generate_dsa_private_key_and_parameters(self, backend): ("p", "q", "g"), [ ( - 2 ** 1000, + 2**1000, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, ), ( - 2 ** 2000, + 2**2000, DSA_KEY_2048.public_numbers.parameter_numbers.q, DSA_KEY_2048.public_numbers.parameter_numbers.g, ), ( - 2 ** 3000, + 2**3000, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, ), ( - 2 ** 3100, + 2**3100, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, - 2 ** 150, + 2**150, DSA_KEY_1024.public_numbers.parameter_numbers.g, ), ( DSA_KEY_2048.public_numbers.parameter_numbers.p, - 2 ** 250, + 2**250, DSA_KEY_2048.public_numbers.parameter_numbers.g, ), ( DSA_KEY_3072.public_numbers.parameter_numbers.p, - 2 ** 260, + 2**260, DSA_KEY_3072.public_numbers.parameter_numbers.g, ), ( @@ -153,7 +160,7 @@ def test_generate_dsa_private_key_and_parameters(self, backend): ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, - 2 ** 1200, + 2**1200, ), ], ) @@ -165,28 +172,28 @@ def test_invalid_parameters_values(self, p, q, g, backend): ("p", "q", "g", "y", "x"), [ ( - 2 ** 1000, + 2**1000, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), ( - 2 ** 2000, + 2**2000, DSA_KEY_2048.public_numbers.parameter_numbers.q, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, DSA_KEY_2048.x, ), ( - 2 ** 3000, + 2**3000, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, DSA_KEY_3072.x, ), ( - 2 ** 3100, + 2**3100, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, @@ -194,21 +201,21 @@ def test_invalid_parameters_values(self, p, q, g, backend): ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, - 2 ** 150, + 2**150, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), ( DSA_KEY_2048.public_numbers.parameter_numbers.p, - 2 ** 250, + 2**250, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, DSA_KEY_2048.x, ), ( DSA_KEY_3072.public_numbers.parameter_numbers.p, - 2 ** 260, + 2**260, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, DSA_KEY_3072.x, @@ -230,7 +237,7 @@ def test_invalid_parameters_values(self, p, q, g, backend): ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, - 2 ** 1200, + 2**1200, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), @@ -253,20 +260,20 @@ def test_invalid_parameters_values(self, p, q, g, backend): DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, - 2 ** 159, + 2**159, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, - 2 ** 200, + 2**200, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, - 2 ** 100, + 2**100, DSA_KEY_1024.x, ), ], @@ -285,44 +292,44 @@ def test_invalid_dsa_private_key_arguments(self, p, q, g, y, x, backend): ("p", "q", "g", "y"), [ ( - 2 ** 1000, + 2**1000, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, ), ( - 2 ** 2000, + 2**2000, DSA_KEY_2048.public_numbers.parameter_numbers.q, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, ), ( - 2 ** 3000, + 2**3000, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, ), ( - 2 ** 3100, + 2**3100, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, - 2 ** 150, + 2**150, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, ), ( DSA_KEY_2048.public_numbers.parameter_numbers.p, - 2 ** 250, + 2**250, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, ), ( DSA_KEY_3072.public_numbers.parameter_numbers.p, - 2 ** 260, + 2**260, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, ), @@ -341,7 +348,7 @@ def test_invalid_dsa_private_key_arguments(self, p, q, g, y, x, backend): ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, - 2 ** 1200, + 2**1200, DSA_KEY_1024.public_numbers.y, ), ], @@ -360,6 +367,7 @@ def test_large_p(self, backend): ), mode="rb", ) + assert isinstance(key, dsa.DSAPrivateKey) pn = key.private_numbers() assert pn.public_numbers.parameter_numbers.p.bit_length() == 4096 # Turn it back into a key to confirm that values this large pass @@ -376,70 +384,59 @@ def test_large_p(self, backend): x=pn.x, ).private_key(backend) + def test_public_key_equality(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: pemfile.read().encode(), + ) + key1 = serialization.load_pem_private_key(key_bytes, None).public_key() + key2 = serialization.load_pem_private_key(key_bytes, None).public_key() + key3 = DSA_KEY_2048.private_key().public_key() + assert key1 == key2 + assert key1 != key3 + assert key1 != object() + with pytest.raises(TypeError): + key1 < key2 # type: ignore[operator] -@pytest.mark.requires_backend_interface(interface=DSABackend) -class TestDSAVerification(object): - _algorithms_dict = { - "SHA1": hashes.SHA1, - "SHA224": hashes.SHA224, - "SHA256": hashes.SHA256, - "SHA384": hashes.SHA384, - "SHA512": hashes.SHA512, - } - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", +) +class TestDSAVerification: + def test_dsa_verification(self, backend, subtests): + vectors = load_vectors_from_file( os.path.join("asymmetric", "DSA", "FIPS_186-3", "SigVer.rsp"), load_fips_dsa_sig_vectors, - ), - ) - def test_dsa_verification(self, vector, backend): - digest_algorithm = vector["digest_algorithm"].replace("-", "") - algorithm = self._algorithms_dict[digest_algorithm] - - _skip_if_dsa_not_supported( - backend, algorithm, vector["p"], vector["q"], vector["g"] ) - - public_key = dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - vector["p"], vector["q"], vector["g"] - ), - y=vector["y"], - ).public_key(backend) - sig = encode_dss_signature(vector["r"], vector["s"]) - - if vector["result"] == "F": - with pytest.raises(InvalidSignature): - public_key.verify(sig, vector["msg"], algorithm()) - else: - public_key.verify(sig, vector["msg"], algorithm()) + for vector in vectors: + with subtests.test(): + digest_algorithm = vector["digest_algorithm"].replace("-", "") + algorithm = _ALGORITHMS_DICT[digest_algorithm] + + _skip_if_dsa_not_supported( + backend, algorithm, vector["p"], vector["q"], vector["g"] + ) + + public_key = dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + vector["p"], vector["q"], vector["g"] + ), + y=vector["y"], + ).public_key(backend) + sig = encode_dss_signature(vector["r"], vector["s"]) + + if vector["result"] == "F": + with pytest.raises(InvalidSignature): + public_key.verify(sig, vector["msg"], algorithm) + else: + public_key.verify(sig, vector["msg"], algorithm) def test_dsa_verify_invalid_asn1(self, backend): public_key = DSA_KEY_1024.public_numbers.public_key(backend) with pytest.raises(InvalidSignature): public_key.verify(b"fakesig", b"fakemsg", hashes.SHA1()) - def test_signature_not_bytes(self, backend): - public_key = DSA_KEY_1024.public_numbers.public_key(backend) - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - public_key.verifier(1234, hashes.SHA1()) - - def test_use_after_finalize(self, backend): - public_key = DSA_KEY_1024.public_numbers.public_key(backend) - with pytest.warns(CryptographyDeprecationWarning): - verifier = public_key.verifier(b"fakesig", hashes.SHA1()) - verifier.update(b"irrelevant") - with pytest.raises(InvalidSignature): - verifier.verify() - with pytest.raises(AlreadyFinalized): - verifier.verify() - with pytest.raises(AlreadyFinalized): - verifier.update(b"more data") - def test_verify(self, backend): message = b"one little message" algorithm = hashes.SHA1() @@ -470,70 +467,41 @@ def test_prehashed_digest_mismatch(self, backend): with pytest.raises(ValueError): public_key.verify(b"\x00" * 128, digest, prehashed_alg) - def test_prehashed_unsupported_in_signer_ctx(self, backend): - private_key = DSA_KEY_1024.private_key(backend) - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - private_key.signer(Prehashed(hashes.SHA1())) - - def test_prehashed_unsupported_in_verifier_ctx(self, backend): - public_key = DSA_KEY_1024.private_key(backend).public_key() - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - public_key.verifier(b"0" * 64, Prehashed(hashes.SHA1())) - - -@pytest.mark.requires_backend_interface(interface=DSABackend) -class TestDSASignature(object): - _algorithms_dict = { - "SHA1": hashes.SHA1, - "SHA224": hashes.SHA224, - "SHA256": hashes.SHA256, - "SHA384": hashes.SHA384, - "SHA512": hashes.SHA512, - } - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", +) +class TestDSASignature: + def test_dsa_signing(self, backend, subtests): + vectors = load_vectors_from_file( os.path.join("asymmetric", "DSA", "FIPS_186-3", "SigGen.txt"), load_fips_dsa_sig_vectors, - ), - ) - def test_dsa_signing(self, vector, backend): - digest_algorithm = vector["digest_algorithm"].replace("-", "") - algorithm = self._algorithms_dict[digest_algorithm] - - _skip_if_dsa_not_supported( - backend, algorithm, vector["p"], vector["q"], vector["g"] ) - - private_key = dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - vector["p"], vector["q"], vector["g"] - ), - y=vector["y"], - ), - x=vector["x"], - ).private_key(backend) - signature = private_key.sign(vector["msg"], algorithm()) - assert signature - - private_key.public_key().verify(signature, vector["msg"], algorithm()) - - def test_use_after_finalize(self, backend): - private_key = DSA_KEY_1024.private_key(backend) - with pytest.warns(CryptographyDeprecationWarning): - signer = private_key.signer(hashes.SHA1()) - signer.update(b"data") - signer.finalize() - with pytest.raises(AlreadyFinalized): - signer.finalize() - with pytest.raises(AlreadyFinalized): - signer.update(b"more data") + for vector in vectors: + with subtests.test(): + digest_algorithm = vector["digest_algorithm"].replace("-", "") + algorithm = _ALGORITHMS_DICT[digest_algorithm] + + _skip_if_dsa_not_supported( + backend, algorithm, vector["p"], vector["q"], vector["g"] + ) + + private_key = dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + vector["p"], vector["q"], vector["g"] + ), + y=vector["y"], + ), + x=vector["x"], + ).private_key(backend) + signature = private_key.sign(vector["msg"], algorithm) + assert signature + + private_key.public_key().verify( + signature, vector["msg"], algorithm + ) def test_sign(self, backend): private_key = DSA_KEY_1024.private_key(backend) @@ -565,7 +533,7 @@ def test_prehashed_digest_mismatch(self, backend): private_key.sign(digest, prehashed_alg) -class TestDSANumbers(object): +class TestDSANumbers: def test_dsa_parameter_numbers(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) assert parameter_numbers.p == 1 @@ -574,13 +542,13 @@ def test_dsa_parameter_numbers(self): def test_dsa_parameter_numbers_invalid_types(self): with pytest.raises(TypeError): - dsa.DSAParameterNumbers(p=None, q=2, g=3) + dsa.DSAParameterNumbers(p=None, q=2, g=3) # type: ignore[arg-type] with pytest.raises(TypeError): - dsa.DSAParameterNumbers(p=1, q=None, g=3) + dsa.DSAParameterNumbers(p=1, q=None, g=3) # type: ignore[arg-type] with pytest.raises(TypeError): - dsa.DSAParameterNumbers(p=1, q=2, g=None) + dsa.DSAParameterNumbers(p=1, q=2, g=None) # type: ignore[arg-type] def test_dsa_public_numbers(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) @@ -592,11 +560,16 @@ def test_dsa_public_numbers(self): def test_dsa_public_numbers_invalid_types(self): with pytest.raises(TypeError): - dsa.DSAPublicNumbers(y=4, parameter_numbers=None) + dsa.DSAPublicNumbers( + y=4, parameter_numbers=None # type: ignore[arg-type] + ) with pytest.raises(TypeError): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) - dsa.DSAPublicNumbers(y=None, parameter_numbers=parameter_numbers) + dsa.DSAPublicNumbers( + y=None, # type: ignore[arg-type] + parameter_numbers=parameter_numbers, + ) def test_dsa_private_numbers(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) @@ -615,10 +588,15 @@ def test_dsa_private_numbers_invalid_types(self): y=4, parameter_numbers=parameter_numbers ) with pytest.raises(TypeError): - dsa.DSAPrivateNumbers(x=4, public_numbers=None) + dsa.DSAPrivateNumbers( + x=4, + public_numbers=None, # type: ignore[arg-type] + ) with pytest.raises(TypeError): - dsa.DSAPrivateNumbers(x=None, public_numbers=public_numbers) + dsa.DSAPrivateNumbers( + x=None, public_numbers=public_numbers # type: ignore[arg-type] + ) def test_repr(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) @@ -635,7 +613,7 @@ def test_repr(self): ) -class TestDSANumberEquality(object): +class TestDSANumberEquality: def test_parameter_numbers_eq(self): param = dsa.DSAParameterNumbers(1, 2, 3) assert param == dsa.DSAParameterNumbers(1, 2, 3) @@ -687,9 +665,11 @@ def test_private_numbers_ne(self): assert priv != object() -@pytest.mark.requires_backend_interface(interface=DSABackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -class TestDSASerialization(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", +) +class TestDSASerialization: @pytest.mark.parametrize( ("fmt", "password"), itertools.product( @@ -712,6 +692,7 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): lambda pemfile: pemfile.read().encode(), ) key = serialization.load_pem_private_key(key_bytes, None, backend) + assert isinstance(key, dsa.DSAPrivateKey) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -720,6 +701,7 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): loaded_key = serialization.load_pem_private_key( serialized, password, backend ) + assert isinstance(loaded_key, dsa.DSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -731,6 +713,10 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ( + serialization.Encoding.SMIME, + serialization.PrivateFormat.TraditionalOpenSSL, + ), ], ) def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): @@ -753,6 +739,7 @@ def test_private_bytes_encrypted_der(self, backend, fmt, password): lambda pemfile: pemfile.read().encode(), ) key = serialization.load_pem_private_key(key_bytes, None, backend) + assert isinstance(key, dsa.DSAPrivateKey) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -761,6 +748,7 @@ def test_private_bytes_encrypted_der(self, backend, fmt, password): loaded_key = serialization.load_der_private_key( serialized, password, backend ) + assert isinstance(loaded_key, dsa.DSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -858,7 +846,7 @@ def test_private_bytes_invalid_encoding(self, backend): ) with pytest.raises(TypeError): key.private_bytes( - "notencoding", + "notencoding", # type: ignore[arg-type] serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) @@ -873,7 +861,7 @@ def test_private_bytes_invalid_format(self, backend): with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, - "invalidformat", + "invalidformat", # type: ignore[arg-type] serialization.NoEncryption(), ) @@ -888,7 +876,7 @@ def test_private_bytes_invalid_encryption_algorithm(self, backend): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - "notanencalg", + "notanencalg", # type: ignore[arg-type] ) def test_private_bytes_unsupported_encryption_type(self, backend): @@ -906,9 +894,11 @@ def test_private_bytes_unsupported_encryption_type(self, backend): ) -@pytest.mark.requires_backend_interface(interface=DSABackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -class TestDSAPEMPublicKeySerialization(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", +) +class TestDSAPEMPublicKeySerialization: @pytest.mark.parametrize( ("key_path", "loader_func", "encoding"), [ @@ -949,9 +939,11 @@ def test_public_bytes_openssh(self, backend): ) key = serialization.load_pem_public_key(key_bytes, backend) - ssh_bytes = key.public_bytes( - serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH - ) + with pytest.warns(utils.DeprecatedIn40): + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.OpenSSH, + ) assert ssh_bytes == ( b"ssh-dss AAAAB3NzaC1kc3MAAACBAKoJMMwUWCUiHK/6KKwolBlqJ4M95ewhJweR" b"aJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5StOTzAik1K" @@ -969,13 +961,17 @@ def test_public_bytes_invalid_encoding(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() with pytest.raises(TypeError): key.public_bytes( - "notencoding", serialization.PublicFormat.SubjectPublicKeyInfo + "notencoding", # type: ignore[arg-type] + serialization.PublicFormat.SubjectPublicKeyInfo, ) def test_public_bytes_invalid_format(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() with pytest.raises(TypeError): - key.public_bytes(serialization.Encoding.PEM, "invalidformat") + key.public_bytes( + serialization.Encoding.PEM, + "invalidformat", # type: ignore[arg-type] + ) def test_public_bytes_pkcs1_unsupported(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 8361306f719b..601edcc48bd4 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -2,30 +2,24 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import itertools import os +import textwrap +import typing from binascii import hexlify import pytest -from cryptography import exceptions, utils, x509 -from cryptography.hazmat.backends.interfaces import ( - EllipticCurveBackend, - PEMSerializationBackend, -) +from cryptography import exceptions, x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( Prehashed, encode_dss_signature, ) -from cryptography.utils import CryptographyDeprecationWarning -from .fixtures_ec import EC_KEY_SECP384R1 -from .utils import skip_fips_traditional_openssl from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_fips_ecdsa_key_pair_vectors, @@ -35,8 +29,10 @@ load_vectors_from_file, raises_unsupported_algorithm, ) +from .fixtures_ec import EC_KEY_SECP384R1 +from .utils import skip_fips_traditional_openssl -_HASH_TYPES = { +_HASH_TYPES: typing.Dict[str, typing.Type[hashes.HashAlgorithm]] = { "SHA-1": hashes.SHA1, "SHA-224": hashes.SHA224, "SHA-256": hashes.SHA256, @@ -82,36 +78,30 @@ def test_get_curve_for_oid(): ec.get_curve_for_oid(x509.ObjectIdentifier("1.1.1.1")) -@utils.register_interface(ec.EllipticCurve) -class DummyCurve(object): +class DummyCurve(ec.EllipticCurve): name = "dummy-curve" key_size = 1 -@utils.register_interface(ec.EllipticCurveSignatureAlgorithm) -class DummySignatureAlgorithm(object): - algorithm = None +class DummySignatureAlgorithm(ec.EllipticCurveSignatureAlgorithm): + algorithm = hashes.SHA256() -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_skip_curve_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_curve_unsupported(backend, DummyCurve()) -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_skip_exchange_algorithm_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), DummyCurve()) -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_skip_ecdsa_vector(backend): with pytest.raises(pytest.skip.Exception): _skip_ecdsa_vector(backend, DummyCurve, hashes.SHA256) -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_derive_private_key_success(backend): curve = ec.SECP256K1() _skip_curve_unsupported(backend, curve) @@ -125,21 +115,29 @@ def test_derive_private_key_success(backend): assert private_numbers == derived_key.private_numbers() -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_derive_private_key_errors(backend): curve = ec.SECP256K1() _skip_curve_unsupported(backend, curve) with pytest.raises(TypeError): - ec.derive_private_key("one", curve, backend) + ec.derive_private_key("one", curve, backend) # type: ignore[arg-type] with pytest.raises(TypeError): - ec.derive_private_key(10, "five", backend) + ec.derive_private_key(10, "five", backend) # type: ignore[arg-type] with pytest.raises(ValueError): ec.derive_private_key(-7, curve, backend) +def test_derive_point_at_infinity(backend): + curve = ec.SECP256R1() + _skip_curve_unsupported(backend, curve) + # order of the curve + q = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + with pytest.raises(ValueError, match="Unable to derive"): + ec.derive_private_key(q, ec.SECP256R1()) + + def test_ec_numbers(): numbers = ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) @@ -169,74 +167,7 @@ def test_invalid_ec_numbers_args(private_value, x, y, curve): def test_invalid_private_numbers_public_numbers(): with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers(1, None) - - -def test_encode_point(): - # secp256r1 point - x = int( - "233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec", 16 - ) - y = int( - "3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e", 16 - ) - pn = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()) - with pytest.warns(utils.PersistentlyDeprecated2019): - data = pn.encode_point() - assert data == binascii.unhexlify( - "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" - "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" - ) - - -def test_from_encoded_point(): - # secp256r1 point - data = binascii.unhexlify( - "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" - "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" - ) - with pytest.warns(CryptographyDeprecationWarning): - pn = ec.EllipticCurvePublicNumbers.from_encoded_point( - ec.SECP256R1(), data - ) - assert pn.x == int( - "233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec", 16 - ) - assert pn.y == int( - "3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e", 16 - ) - - -def test_from_encoded_point_invalid_length(): - bad_data = binascii.unhexlify( - "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" - "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460" - ) - with pytest.raises(ValueError): - with pytest.warns(CryptographyDeprecationWarning): - ec.EllipticCurvePublicNumbers.from_encoded_point( - ec.SECP384R1(), bad_data - ) - - -def test_from_encoded_point_unsupported_point_no_backend(): - # set to point type 2. - unsupported_type = binascii.unhexlify( - "02233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22a" - ) - with pytest.raises(ValueError): - with pytest.warns(CryptographyDeprecationWarning): - ec.EllipticCurvePublicNumbers.from_encoded_point( - ec.SECP256R1(), unsupported_type - ) - - -def test_from_encoded_point_not_a_curve(): - with pytest.raises(TypeError): - with pytest.warns(CryptographyDeprecationWarning): - ec.EllipticCurvePublicNumbers.from_encoded_point( - "notacurve", b"\x04data" - ) + ec.EllipticCurvePrivateNumbers(1, None) # type: ignore[arg-type] def test_ec_public_numbers_repr(): @@ -268,7 +199,6 @@ def test_ec_private_numbers_hash(): assert hash(numbers1) != hash(numbers3) -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_ec_key_key_size(backend): curve = ec.SECP256R1() _skip_curve_unsupported(backend, curve) @@ -277,83 +207,75 @@ def test_ec_key_key_size(backend): assert key.public_key().key_size == 256 -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -class TestECWithNumbers(object): - @pytest.mark.parametrize( - ("vector", "hash_type"), - list( - itertools.product( - load_vectors_from_file( - os.path.join( - "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp" - ), - load_fips_ecdsa_key_pair_vectors, +class TestECWithNumbers: + def test_with_numbers(self, backend, subtests): + vectors = itertools.product( + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp" ), - _HASH_TYPES.values(), - ) - ), - ) - def test_with_numbers(self, backend, vector, hash_type): - curve_type = ec._CURVE_TYPES[vector["curve"]] - - _skip_ecdsa_vector(backend, curve_type, hash_type) - - key = ec.EllipticCurvePrivateNumbers( - vector["d"], - ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() + load_fips_ecdsa_key_pair_vectors, ), - ).private_key(backend) - assert key + _HASH_TYPES.values(), + ) + for vector, hash_type in vectors: + with subtests.test(): + curve_type: typing.Type[ec.EllipticCurve] = ec._CURVE_TYPES[ + vector["curve"] + ] + + _skip_ecdsa_vector(backend, curve_type, hash_type) + + key = ec.EllipticCurvePrivateNumbers( + vector["d"], + ec.EllipticCurvePublicNumbers( + vector["x"], vector["y"], curve_type() + ), + ).private_key(backend) + assert key - priv_num = key.private_numbers() - assert priv_num.private_value == vector["d"] - assert priv_num.public_numbers.x == vector["x"] - assert priv_num.public_numbers.y == vector["y"] - assert curve_type().name == priv_num.public_numbers.curve.name + priv_num = key.private_numbers() + assert priv_num.private_value == vector["d"] + assert priv_num.public_numbers.x == vector["x"] + assert priv_num.public_numbers.y == vector["y"] + assert curve_type().name == priv_num.public_numbers.curve.name -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -class TestECDSAVectors(object): - @pytest.mark.parametrize( - ("vector", "hash_type"), - list( - itertools.product( - load_vectors_from_file( - os.path.join( - "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp" - ), - load_fips_ecdsa_key_pair_vectors, +class TestECDSAVectors: + def test_signing_with_example_keys(self, backend, subtests): + vectors = itertools.product( + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp" ), - _HASH_TYPES.values(), - ) - ), - ) - def test_signing_with_example_keys(self, backend, vector, hash_type): - curve_type = ec._CURVE_TYPES[vector["curve"]] + load_fips_ecdsa_key_pair_vectors, + ), + _HASH_TYPES.values(), + ) + for vector, hash_type in vectors: + with subtests.test(): + curve_type = ec._CURVE_TYPES[vector["curve"]] - _skip_ecdsa_vector(backend, curve_type, hash_type) + _skip_ecdsa_vector(backend, curve_type, hash_type) - key = ec.EllipticCurvePrivateNumbers( - vector["d"], - ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() - ), - ).private_key(backend) - assert key + key = ec.EllipticCurvePrivateNumbers( + vector["d"], + ec.EllipticCurvePublicNumbers( + vector["x"], vector["y"], curve_type() + ), + ).private_key(backend) + assert key - pkey = key.public_key() - assert pkey + pkey = key.public_key() + assert pkey - with pytest.warns(CryptographyDeprecationWarning): - signer = key.signer(ec.ECDSA(hash_type())) - signer.update(b"YELLOW SUBMARINE") - signature = signer.finalize() + signature = key.sign( + b"YELLOW SUBMARINE", ec.ECDSA(hash_type()) + ) - with pytest.warns(CryptographyDeprecationWarning): - verifier = pkey.verifier(signature, ec.ECDSA(hash_type())) - verifier.update(b"YELLOW SUBMARINE") - verifier.verify() + pkey.verify( + signature, b"YELLOW SUBMARINE", ec.ECDSA(hash_type()) + ) @pytest.mark.parametrize("curve", ec._CURVE_TYPES.values()) def test_generate_vector_curves(self, backend, curve): @@ -387,21 +309,11 @@ def test_unknown_signature_algoritm(self, backend): key = ec.generate_private_key(ec.SECP192R1(), backend) - with raises_unsupported_algorithm( - exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ), pytest.warns(CryptographyDeprecationWarning): - key.signer(DummySignatureAlgorithm()) - with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): key.sign(b"somedata", DummySignatureAlgorithm()) - with raises_unsupported_algorithm( - exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ), pytest.warns(CryptographyDeprecationWarning): - key.public_key().verifier(b"", DummySignatureAlgorithm()) - with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): @@ -493,9 +405,43 @@ def test_load_invalid_public_ec_key_from_numbers(self, backend): with pytest.raises(ValueError): numbers.public_key(backend) - @pytest.mark.parametrize( - "vector", - itertools.chain( + def test_load_invalid_ec_key_from_pem(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + # BoringSSL rejects infinity points before it ever gets to us, so it + # uses a more generic error message. + match = ( + r"infinity|invalid form" + if not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + else None + ) + with pytest.raises(ValueError, match=match): + serialization.load_pem_public_key( + textwrap.dedent( + """ + -----BEGIN PUBLIC KEY----- + MBkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDAgAA + -----END PUBLIC KEY----- + """ + ).encode(), + backend=backend, + ) + with pytest.raises(ValueError, match=match): + serialization.load_pem_private_key( + textwrap.dedent( + """ + -----BEGIN PRIVATE KEY----- + MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCD/////AAAAAP////// + ////vOb6racXnoTzucrC/GMlUQ== + -----END PRIVATE KEY----- + """ + ).encode(), + password=None, + backend=backend, + ) + + def test_signatures(self, backend, subtests): + vectors = itertools.chain( load_vectors_from_file( os.path.join( "asymmetric", "ECDSA", "FIPS_186-3", "SigGen.txt" @@ -506,46 +452,51 @@ def test_load_invalid_public_ec_key_from_numbers(self, backend): os.path.join("asymmetric", "ECDSA", "SECP256K1", "SigGen.txt"), load_fips_ecdsa_signing_vectors, ), - ), - ) - def test_signatures(self, backend, vector): - hash_type = _HASH_TYPES[vector["digest_algorithm"]] - curve_type = ec._CURVE_TYPES[vector["curve"]] + ) + for vector in vectors: + with subtests.test(): + hash_type = _HASH_TYPES[vector["digest_algorithm"]] + curve_type: typing.Type[ec.EllipticCurve] = ec._CURVE_TYPES[ + vector["curve"] + ] - _skip_ecdsa_vector(backend, curve_type, hash_type) + _skip_ecdsa_vector(backend, curve_type, hash_type) - key = ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() - ).public_key(backend) + key = ec.EllipticCurvePublicNumbers( + vector["x"], vector["y"], curve_type() + ).public_key(backend) - signature = encode_dss_signature(vector["r"], vector["s"]) + signature = encode_dss_signature(vector["r"], vector["s"]) - key.verify(signature, vector["message"], ec.ECDSA(hash_type())) + key.verify(signature, vector["message"], ec.ECDSA(hash_type())) - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( + def test_signature_failures(self, backend, subtests): + vectors = load_vectors_from_file( os.path.join("asymmetric", "ECDSA", "FIPS_186-3", "SigVer.rsp"), load_fips_ecdsa_signing_vectors, - ), - ) - def test_signature_failures(self, backend, vector): - hash_type = _HASH_TYPES[vector["digest_algorithm"]] - curve_type = ec._CURVE_TYPES[vector["curve"]] + ) + for vector in vectors: + with subtests.test(): + hash_type = _HASH_TYPES[vector["digest_algorithm"]] + curve_type = ec._CURVE_TYPES[vector["curve"]] - _skip_ecdsa_vector(backend, curve_type, hash_type) + _skip_ecdsa_vector(backend, curve_type, hash_type) - key = ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() - ).public_key(backend) + key = ec.EllipticCurvePublicNumbers( + vector["x"], vector["y"], curve_type() + ).public_key(backend) - signature = encode_dss_signature(vector["r"], vector["s"]) + signature = encode_dss_signature(vector["r"], vector["s"]) - if vector["fail"] is True: - with pytest.raises(exceptions.InvalidSignature): - key.verify(signature, vector["message"], ec.ECDSA(hash_type())) - else: - key.verify(signature, vector["message"], ec.ECDSA(hash_type())) + if vector["fail"] is True: + with pytest.raises(exceptions.InvalidSignature): + key.verify( + signature, vector["message"], ec.ECDSA(hash_type()) + ) + else: + key.verify( + signature, vector["message"], ec.ECDSA(hash_type()) + ) def test_sign(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) @@ -613,25 +564,8 @@ def test_verify_prehashed_digest_mismatch(self, backend): b"\x00" * 32, data, ec.ECDSA(Prehashed(hashes.SHA256())) ) - def test_prehashed_unsupported_in_signer_ctx(self, backend): - _skip_curve_unsupported(backend, ec.SECP256R1()) - private_key = ec.generate_private_key(ec.SECP256R1(), backend) - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - private_key.signer(ec.ECDSA(Prehashed(hashes.SHA1()))) - - def test_prehashed_unsupported_in_verifier_ctx(self, backend): - _skip_curve_unsupported(backend, ec.SECP256R1()) - private_key = ec.generate_private_key(ec.SECP256R1(), backend) - public_key = private_key.public_key() - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - public_key.verifier(b"0" * 64, ec.ECDSA(Prehashed(hashes.SHA1()))) - -class TestECNumbersEquality(object): +class TestECEquality: def test_public_numbers_eq(self): pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) assert pub == ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) @@ -667,10 +601,21 @@ def test_private_numbers_ne(self): ) assert priv != object() + def test_public_key_equality(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode(), + ) + key1 = serialization.load_pem_private_key(key_bytes, None).public_key() + key2 = serialization.load_pem_private_key(key_bytes, None).public_key() + key3 = ec.generate_private_key(ec.SECP256R1()).public_key() + assert key1 == key2 + assert key1 != key3 + assert key1 != object() -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -class TestECSerialization(object): + +class TestECSerialization: @pytest.mark.parametrize( ("fmt", "password"), itertools.product( @@ -694,6 +639,7 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): lambda pemfile: pemfile.read().encode(), ) key = serialization.load_pem_private_key(key_bytes, None, backend) + assert isinstance(key, ec.EllipticCurvePrivateKey) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -702,6 +648,7 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): loaded_key = serialization.load_pem_private_key( serialized, password, backend ) + assert isinstance(loaded_key, ec.EllipticCurvePrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -737,6 +684,7 @@ def test_private_bytes_encrypted_der(self, backend, fmt, password): lambda pemfile: pemfile.read().encode(), ) key = serialization.load_pem_private_key(key_bytes, None, backend) + assert isinstance(key, ec.EllipticCurvePrivateKey) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -745,6 +693,7 @@ def test_private_bytes_encrypted_der(self, backend, fmt, password): loaded_key = serialization.load_der_private_key( serialized, password, backend ) + assert isinstance(loaded_key, ec.EllipticCurvePrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -783,10 +732,12 @@ def test_private_bytes_unencrypted( lambda pemfile: pemfile.read().encode(), ) key = serialization.load_pem_private_key(key_bytes, None, backend) + assert isinstance(key, ec.EllipticCurvePrivateKey) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) loaded_key = loader_func(serialized, None, backend) + assert isinstance(loaded_key, ec.EllipticCurvePrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -853,7 +804,7 @@ def test_private_bytes_invalid_encoding(self, backend): ) with pytest.raises(TypeError): key.private_bytes( - "notencoding", + "notencoding", # type: ignore[arg-type] serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) @@ -869,7 +820,7 @@ def test_private_bytes_invalid_format(self, backend): with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, - "invalidformat", + "invalidformat", # type: ignore[arg-type] serialization.NoEncryption(), ) @@ -885,7 +836,7 @@ def test_private_bytes_invalid_encryption_algorithm(self, backend): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - "notanencalg", + "notanencalg", # type: ignore[arg-type] ) def test_private_bytes_unsupported_encryption_type(self, backend): @@ -920,9 +871,7 @@ def test_public_bytes_from_derived_public_key(self, backend): assert parsed_public -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -class TestEllipticCurvePEMPublicKeySerialization(object): +class TestEllipticCurvePEMPublicKeySerialization: @pytest.mark.parametrize( ("key_path", "loader_func", "encoding"), [ @@ -997,7 +946,8 @@ def test_public_bytes_invalid_encoding(self, backend): ) with pytest.raises(TypeError): key.public_bytes( - "notencoding", serialization.PublicFormat.SubjectPublicKeyInfo + "notencoding", # type: ignore[arg-type] + serialization.PublicFormat.SubjectPublicKeyInfo, ) @pytest.mark.parametrize( @@ -1042,7 +992,10 @@ def test_public_bytes_invalid_format(self, backend): ), ) with pytest.raises(TypeError): - key.public_bytes(serialization.Encoding.PEM, "invalidformat") + key.public_bytes( + serialization.Encoding.PEM, + "invalidformat", # type: ignore[arg-type] + ) def test_public_bytes_pkcs1_unsupported(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) @@ -1124,7 +1077,7 @@ def test_from_encoded_point_empty_byte_string(self): def test_from_encoded_point_not_a_curve(self): with pytest.raises(TypeError): ec.EllipticCurvePublicKey.from_encoded_point( - "notacurve", b"\x04data" + "notacurve", b"\x04data" # type: ignore[arg-type] ) def test_from_encoded_point_unsupported_encoding(self): @@ -1174,79 +1127,65 @@ def test_serialize_point(self, vector, backend): ) -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -class TestECDSAVerification(object): - def test_signature_not_bytes(self, backend): - _skip_curve_unsupported(backend, ec.SECP256R1()) - key = ec.generate_private_key(ec.SECP256R1(), backend) - public_key = key.public_key() - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - public_key.verifier(1234, ec.ECDSA(hashes.SHA256())) - - -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -class TestECDH(object): - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( +class TestECDH: + def test_key_exchange_with_vectors(self, backend, subtests): + vectors = load_vectors_from_file( os.path.join( "asymmetric", "ECDH", "KASValidityTest_ECCStaticUnified_NOKC_ZZOnly_init.fax", ), load_kasvs_ecdh_vectors, - ), - ) - def test_key_exchange_with_vectors(self, backend, vector): - _skip_exchange_algorithm_unsupported( - backend, ec.ECDH(), ec._CURVE_TYPES[vector["curve"]] - ) - - key_numbers = vector["IUT"] - private_numbers = ec.EllipticCurvePrivateNumbers( - key_numbers["d"], - ec.EllipticCurvePublicNumbers( - key_numbers["x"], - key_numbers["y"], - ec._CURVE_TYPES[vector["curve"]](), - ), ) - # Errno 5-7 indicates a bad public or private key, this doesn't test - # the ECDH code at all - if vector["fail"] and vector["errno"] in [5, 6, 7]: - with pytest.raises(ValueError): - private_numbers.private_key(backend) - return - else: - private_key = private_numbers.private_key(backend) - - peer_numbers = vector["CAVS"] - public_numbers = ec.EllipticCurvePublicNumbers( - peer_numbers["x"], - peer_numbers["y"], - ec._CURVE_TYPES[vector["curve"]](), - ) - # Errno 1 and 2 indicates a bad public key, this doesn't test the ECDH - # code at all - if vector["fail"] and vector["errno"] in [1, 2]: - with pytest.raises(ValueError): - public_numbers.public_key(backend) - return - else: - peer_pubkey = public_numbers.public_key(backend) - - z = private_key.exchange(ec.ECDH(), peer_pubkey) - z = int(hexlify(z).decode("ascii"), 16) - # At this point fail indicates that one of the underlying keys was - # changed. This results in a non-matching derived key. - if vector["fail"]: - # Errno 8 indicates Z should be changed. - assert vector["errno"] == 8 - assert z != vector["Z"] - else: - assert z == vector["Z"] + for vector in vectors: + with subtests.test(): + _skip_exchange_algorithm_unsupported( + backend, ec.ECDH(), ec._CURVE_TYPES[vector["curve"]]() + ) + + key_numbers = vector["IUT"] + private_numbers = ec.EllipticCurvePrivateNumbers( + key_numbers["d"], + ec.EllipticCurvePublicNumbers( + key_numbers["x"], + key_numbers["y"], + ec._CURVE_TYPES[vector["curve"]](), + ), + ) + # Errno 5-7 indicates a bad public or private key, this + # doesn't test the ECDH code at all + if vector["fail"] and vector["errno"] in [5, 6, 7]: + with pytest.raises(ValueError): + private_numbers.private_key(backend) + continue + else: + private_key = private_numbers.private_key(backend) + + peer_numbers = vector["CAVS"] + public_numbers = ec.EllipticCurvePublicNumbers( + peer_numbers["x"], + peer_numbers["y"], + ec._CURVE_TYPES[vector["curve"]](), + ) + # Errno 1 and 2 indicates a bad public key, this doesn't test + # the ECDH code at all + if vector["fail"] and vector["errno"] in [1, 2]: + with pytest.raises(ValueError): + public_numbers.public_key(backend) + continue + else: + peer_pubkey = public_numbers.public_key(backend) + + z = private_key.exchange(ec.ECDH(), peer_pubkey) + zz = int(hexlify(z).decode("ascii"), 16) + # At this point fail indicates that one of the underlying keys + # was changed. This results in a non-matching derived key. + if vector["fail"]: + # Errno 8 indicates Z should be changed. + assert vector["errno"] == 8 + assert zz != vector["Z"] + else: + assert zz == vector["Z"] @pytest.mark.parametrize( "vector", @@ -1256,18 +1195,18 @@ def test_key_exchange_with_vectors(self, backend, vector): ), ) def test_brainpool_kex(self, backend, vector): - curve = ec._CURVE_TYPES[vector["curve"].decode("ascii")] + curve = ec._CURVE_TYPES[vector["curve"].decode("ascii")]() _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) key = ec.EllipticCurvePrivateNumbers( int(vector["da"], 16), ec.EllipticCurvePublicNumbers( - int(vector["x_qa"], 16), int(vector["y_qa"], 16), curve() + int(vector["x_qa"], 16), int(vector["y_qa"], 16), curve ), ).private_key(backend) peer = ec.EllipticCurvePrivateNumbers( int(vector["db"], 16), ec.EllipticCurvePublicNumbers( - int(vector["x_qb"], 16), int(vector["y_qb"], 16), curve() + int(vector["x_qb"], 16), int(vector["y_qb"], 16), curve ), ).private_key(backend) shared_secret = key.exchange(ec.ECDH(), peer.public_key()) @@ -1284,11 +1223,12 @@ def test_exchange_unsupported_algorithm(self, backend): pemfile.read().encode(), None, backend ), ) + assert isinstance(key, ec.EllipticCurvePrivateKey) with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM ): - key.exchange(None, key.public_key()) + key.exchange(None, key.public_key()) # type: ignore[arg-type] def test_exchange_non_matching_curve(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) @@ -1300,6 +1240,7 @@ def test_exchange_non_matching_curve(self, backend): pemfile.read().encode(), None, backend ), ) + assert isinstance(key, ec.EllipticCurvePrivateKey) public_key = EC_KEY_SECP384R1.public_numbers.public_key(backend) with pytest.raises(ValueError): diff --git a/tests/hazmat/primitives/test_ed25519.py b/tests/hazmat/primitives/test_ed25519.py index 5b003d1e411e..2501f1cf1bb1 100644 --- a/tests/hazmat/primitives/test_ed25519.py +++ b/tests/hazmat/primitives/test_ed25519.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os @@ -16,6 +15,7 @@ Ed25519PublicKey, ) +from ...doubles import DummyKeySerializationEncryption from ...utils import load_vectors_from_file, raises_unsupported_algorithm @@ -68,30 +68,44 @@ def test_ed25519_unsupported(backend): only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) -class TestEd25519Signing(object): - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( +class TestEd25519Signing: + def test_sign_verify_input(self, backend, subtests): + vectors = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "sign.input"), + load_ed25519_vectors, + ) + for vector in vectors: + with subtests.test(): + sk = binascii.unhexlify(vector["secret_key"]) + pk = binascii.unhexlify(vector["public_key"]) + message = binascii.unhexlify(vector["message"]) + signature = binascii.unhexlify(vector["signature"]) + private_key = Ed25519PrivateKey.from_private_bytes(sk) + computed_sig = private_key.sign(message) + assert computed_sig == signature + public_key = private_key.public_key() + assert ( + public_key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.Raw, + ) + == pk + ) + public_key.verify(signature, message) + + def test_pub_priv_bytes_raw(self, backend, subtests): + vectors = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "sign.input"), load_ed25519_vectors, - ), - ) - def test_sign_verify_input(self, vector, backend): - sk = binascii.unhexlify(vector["secret_key"]) - pk = binascii.unhexlify(vector["public_key"]) - message = binascii.unhexlify(vector["message"]) - signature = binascii.unhexlify(vector["signature"]) - private_key = Ed25519PrivateKey.from_private_bytes(sk) - computed_sig = private_key.sign(message) - assert computed_sig == signature - public_key = private_key.public_key() - assert ( - public_key.public_bytes( - serialization.Encoding.Raw, serialization.PublicFormat.Raw - ) - == pk ) - public_key.verify(signature, message) + for vector in vectors: + with subtests.test(): + sk = binascii.unhexlify(vector["secret_key"]) + pk = binascii.unhexlify(vector["public_key"]) + private_key = Ed25519PrivateKey.from_private_bytes(sk) + assert private_key.private_bytes_raw() == sk + public_key = Ed25519PublicKey.from_public_bytes(pk) + assert public_key.public_bytes_raw() == pk def test_invalid_signature(self, backend): key = Ed25519PrivateKey.generate() @@ -119,11 +133,15 @@ def test_load_public_bytes(self, backend): def test_invalid_type_public_bytes(self, backend): with pytest.raises(TypeError): - Ed25519PublicKey.from_public_bytes(object()) + Ed25519PublicKey.from_public_bytes( + object() # type: ignore[arg-type] + ) def test_invalid_type_private_bytes(self, backend): with pytest.raises(TypeError): - Ed25519PrivateKey.from_private_bytes(object()) + Ed25519PrivateKey.from_private_bytes( + object() # type: ignore[arg-type] + ) def test_invalid_length_from_public_bytes(self, backend): with pytest.raises(ValueError): @@ -139,18 +157,24 @@ def test_invalid_length_from_private_bytes(self, backend): def test_invalid_private_bytes(self, backend): key = Ed25519PrivateKey.generate() + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None, # type: ignore[arg-type] + ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, - None, + DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8, - None, + DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): @@ -160,6 +184,13 @@ def test_invalid_private_bytes(self, backend): serialization.NoEncryption(), ) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.OpenSSH, + serialization.NoEncryption(), + ) + def test_invalid_public_bytes(self, backend): key = Ed25519PrivateKey.generate().public_key() with pytest.raises(ValueError): @@ -178,6 +209,11 @@ def test_invalid_public_bytes(self, backend): serialization.Encoding.PEM, serialization.PublicFormat.Raw ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.DER, serialization.PublicFormat.OpenSSH + ) + @pytest.mark.parametrize( ("encoding", "fmt", "encryption", "passwd", "load_func"), [ @@ -209,6 +245,13 @@ def test_invalid_public_bytes(self, backend): None, serialization.load_der_private_key, ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"\x00"), + b"\x00", + serialization.load_der_private_key, + ), ], ) def test_round_trip_private_serialization( @@ -230,3 +273,24 @@ def test_buffer_protocol(self, backend): ) == private_bytes ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support", +) +def test_public_key_equality(backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb", + ) + key1 = serialization.load_der_private_key(key_bytes, None).public_key() + key2 = serialization.load_der_private_key(key_bytes, None).public_key() + key3 = Ed25519PrivateKey.generate().public_key() + assert key1 == key2 + assert key1 != key3 + assert key1 != object() + + with pytest.raises(TypeError): + key1 < key2 # type: ignore[operator] diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py index 9a1f9056993c..650cdda7997c 100644 --- a/tests/hazmat/primitives/test_ed448.py +++ b/tests/hazmat/primitives/test_ed448.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os @@ -16,6 +15,7 @@ Ed448PublicKey, ) +from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_nist_vectors, load_vectors_from_file, @@ -48,7 +48,7 @@ def test_ed448_unsupported(backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) -class TestEd448Signing(object): +class TestEd448Signing: @pytest.mark.parametrize( "vector", load_vectors_from_file( @@ -109,12 +109,14 @@ def test_pub_priv_bytes_raw(self, vector, backend): ) == sk ) + assert private_key.private_bytes_raw() == sk assert ( private_key.public_key().public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) == pk ) + assert private_key.public_key().public_bytes_raw() == pk public_key = Ed448PublicKey.from_public_bytes(pk) assert ( public_key.public_bytes( @@ -122,6 +124,7 @@ def test_pub_priv_bytes_raw(self, vector, backend): ) == pk ) + assert public_key.public_bytes_raw() == pk @pytest.mark.parametrize( ("encoding", "fmt", "encryption", "passwd", "load_func"), @@ -166,11 +169,15 @@ def test_round_trip_private_serialization( def test_invalid_type_public_bytes(self, backend): with pytest.raises(TypeError): - Ed448PublicKey.from_public_bytes(object()) + Ed448PublicKey.from_public_bytes( + object() # type: ignore[arg-type] + ) def test_invalid_type_private_bytes(self, backend): with pytest.raises(TypeError): - Ed448PrivateKey.from_private_bytes(object()) + Ed448PrivateKey.from_private_bytes( + object() # type: ignore[arg-type] + ) def test_invalid_length_from_public_bytes(self, backend): with pytest.raises(ValueError): @@ -186,18 +193,24 @@ def test_invalid_length_from_private_bytes(self, backend): def test_invalid_private_bytes(self, backend): key = Ed448PrivateKey.generate() + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None, # type: ignore[arg-type] + ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, - None, + DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8, - None, + DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): @@ -254,3 +267,24 @@ def test_malleability(self, backend): key = Ed448PublicKey.from_public_bytes(public_bytes) with pytest.raises(InvalidSignature): key.verify(signature, b"8") + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support", +) +def test_public_key_equality(backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb", + ) + key1 = serialization.load_der_private_key(key_bytes, None).public_key() + key2 = serialization.load_der_private_key(key_bytes, None).public_key() + key3 = Ed448PrivateKey.generate().public_key() + assert key1 == key2 + assert key1 != key3 + assert key1 != object() + + with pytest.raises(TypeError): + key1 < key2 # type: ignore[operator] diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py index 9301b6217101..bde811186268 100644 --- a/tests/hazmat/primitives/test_hash_vectors.py +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -2,26 +2,23 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import hashes -from .utils import _load_all_params, generate_hash_test from ...utils import load_hash_vectors, load_nist_vectors +from .utils import _load_all_params, generate_hash_test @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA1(object): +class TestSHA1: test_sha1 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA1"), @@ -34,8 +31,7 @@ class TestSHA1(object): only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA224(object): +class TestSHA224: test_sha224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), @@ -48,8 +44,7 @@ class TestSHA224(object): only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA256(object): +class TestSHA256: test_sha256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), @@ -62,8 +57,7 @@ class TestSHA256(object): only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA384(object): +class TestSHA384: test_sha384 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), @@ -76,8 +70,7 @@ class TestSHA384(object): only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA512(object): +class TestSHA512: test_sha512 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), @@ -90,8 +83,7 @@ class TestSHA512(object): only_if=lambda backend: backend.hash_supported(hashes.SHA512_224()), skip_message="Does not support SHA512/224", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA512224(object): +class TestSHA512224: test_sha512_224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), @@ -104,8 +96,7 @@ class TestSHA512224(object): only_if=lambda backend: backend.hash_supported(hashes.SHA512_256()), skip_message="Does not support SHA512/256", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA512256(object): +class TestSHA512256: test_sha512_256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), @@ -118,8 +109,7 @@ class TestSHA512256(object): only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestMD5(object): +class TestMD5: test_md5 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "MD5"), @@ -134,8 +124,7 @@ class TestMD5(object): ), skip_message="Does not support BLAKE2b", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestBLAKE2b(object): +class TestBLAKE2b: test_b2b = generate_hash_test( load_hash_vectors, os.path.join("hashes", "blake2"), @@ -150,8 +139,7 @@ class TestBLAKE2b(object): ), skip_message="Does not support BLAKE2s", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestBLAKE2s256(object): +class TestBLAKE2s256: test_b2s = generate_hash_test( load_hash_vectors, os.path.join("hashes", "blake2"), @@ -164,8 +152,7 @@ class TestBLAKE2s256(object): only_if=lambda backend: backend.hash_supported(hashes.SHA3_224()), skip_message="Does not support SHA3_224", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA3224(object): +class TestSHA3224: test_sha3_224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), @@ -178,8 +165,7 @@ class TestSHA3224(object): only_if=lambda backend: backend.hash_supported(hashes.SHA3_256()), skip_message="Does not support SHA3_256", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA3256(object): +class TestSHA3256: test_sha3_256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), @@ -192,8 +178,7 @@ class TestSHA3256(object): only_if=lambda backend: backend.hash_supported(hashes.SHA3_384()), skip_message="Does not support SHA3_384", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA3384(object): +class TestSHA3384: test_sha3_384 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), @@ -206,8 +191,7 @@ class TestSHA3384(object): only_if=lambda backend: backend.hash_supported(hashes.SHA3_512()), skip_message="Does not support SHA3_512", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA3512(object): +class TestSHA3512: test_sha3_512 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), @@ -222,8 +206,7 @@ class TestSHA3512(object): ), skip_message="Does not support SHAKE128", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHAKE128(object): +class TestSHAKE128: test_shake128 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHAKE"), @@ -231,21 +214,20 @@ class TestSHAKE128(object): hashes.SHAKE128(digest_size=16), ) - @pytest.mark.parametrize( - "vector", - _load_all_params( + def test_shake128_variable(self, backend, subtests): + vectors = _load_all_params( os.path.join("hashes", "SHAKE"), ["SHAKE128VariableOut.rsp"], load_nist_vectors, - ), - ) - def test_shake128_variable(self, vector, backend): - output_length = int(vector["outputlen"]) // 8 - msg = binascii.unhexlify(vector["msg"]) - shake = hashes.SHAKE128(digest_size=output_length) - m = hashes.Hash(shake, backend=backend) - m.update(msg) - assert m.finalize() == binascii.unhexlify(vector["output"]) + ) + for vector in vectors: + with subtests.test(): + output_length = int(vector["outputlen"]) // 8 + msg = binascii.unhexlify(vector["msg"]) + shake = hashes.SHAKE128(digest_size=output_length) + m = hashes.Hash(shake, backend=backend) + m.update(msg) + assert m.finalize() == binascii.unhexlify(vector["output"]) @pytest.mark.supported( @@ -254,8 +236,7 @@ def test_shake128_variable(self, vector, backend): ), skip_message="Does not support SHAKE256", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHAKE256(object): +class TestSHAKE256: test_shake256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHAKE"), @@ -263,18 +244,30 @@ class TestSHAKE256(object): hashes.SHAKE256(digest_size=32), ) - @pytest.mark.parametrize( - "vector", - _load_all_params( + def test_shake256_variable(self, backend, subtests): + vectors = _load_all_params( os.path.join("hashes", "SHAKE"), ["SHAKE256VariableOut.rsp"], load_nist_vectors, - ), + ) + for vector in vectors: + with subtests.test(): + output_length = int(vector["outputlen"]) // 8 + msg = binascii.unhexlify(vector["msg"]) + shake = hashes.SHAKE256(digest_size=output_length) + m = hashes.Hash(shake, backend=backend) + m.update(msg) + assert m.finalize() == binascii.unhexlify(vector["output"]) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SM3()), + skip_message="Does not support SM3", +) +class TestSM3: + test_sm3 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SM3"), + ["oscca.txt"], + hashes.SM3(), ) - def test_shake256_variable(self, vector, backend): - output_length = int(vector["outputlen"]) // 8 - msg = binascii.unhexlify(vector["msg"]) - shake = hashes.SHAKE256(digest_size=output_length) - m = hashes.Hash(shake, backend=backend) - m.update(msg) - assert m.finalize() == binascii.unhexlify(vector["output"]) diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index eadd0febf25f..1d096772aed0 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -2,31 +2,28 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import AlreadyFinalized, _Reasons -from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import hashes -from .utils import generate_base_hash_test from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm +from .utils import generate_base_hash_test -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestHashContext(object): +class TestHashContext: def test_hash_reject_unicode(self, backend): m = hashes.Hash(hashes.SHA1(), backend=backend) with pytest.raises(TypeError): - m.update(u"\u00FC") + m.update("\u00FC") # type: ignore[arg-type] def test_hash_algorithm_instance(self, backend): with pytest.raises(TypeError): - hashes.Hash(hashes.SHA1, backend=backend) + hashes.Hash(hashes.SHA1, backend=backend) # type: ignore[arg-type] def test_raises_after_finalize(self, backend): h = hashes.Hash(hashes.SHA1(), backend=backend) @@ -50,8 +47,7 @@ def test_unsupported_hash(self, backend): only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA1(object): +class TestSHA1: test_sha1 = generate_base_hash_test( hashes.SHA1(), digest_size=20, @@ -62,8 +58,7 @@ class TestSHA1(object): only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA224(object): +class TestSHA224: test_sha224 = generate_base_hash_test( hashes.SHA224(), digest_size=28, @@ -74,8 +69,7 @@ class TestSHA224(object): only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA256(object): +class TestSHA256: test_sha256 = generate_base_hash_test( hashes.SHA256(), digest_size=32, @@ -86,8 +80,7 @@ class TestSHA256(object): only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA384(object): +class TestSHA384: test_sha384 = generate_base_hash_test( hashes.SHA384(), digest_size=48, @@ -98,8 +91,7 @@ class TestSHA384(object): only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestSHA512(object): +class TestSHA512: test_sha512 = generate_base_hash_test( hashes.SHA512(), digest_size=64, @@ -110,8 +102,7 @@ class TestSHA512(object): only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestMD5(object): +class TestMD5: test_md5 = generate_base_hash_test( hashes.MD5(), digest_size=16, @@ -124,8 +115,7 @@ class TestMD5(object): ), skip_message="Does not support BLAKE2b", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestBLAKE2b(object): +class TestBLAKE2b: test_blake2b = generate_base_hash_test( hashes.BLAKE2b(digest_size=64), digest_size=64, @@ -148,8 +138,7 @@ def test_invalid_digest_size(self, backend): ), skip_message="Does not support BLAKE2s", ) -@pytest.mark.requires_backend_interface(interface=HashBackend) -class TestBLAKE2s(object): +class TestBLAKE2s: test_blake2s = generate_base_hash_test( hashes.BLAKE2s(digest_size=32), digest_size=32, @@ -166,14 +155,6 @@ def test_invalid_digest_size(self, backend): hashes.BLAKE2s(digest_size=-1) -def test_invalid_backend(): - pretend_backend = object() - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - hashes.Hash(hashes.SHA1(), pretend_backend) - - -@pytest.mark.requires_backend_interface(interface=HashBackend) def test_buffer_protocol_hash(backend): data = binascii.unhexlify(b"b4190e") h = hashes.Hash(hashes.SHA256(), backend) @@ -183,7 +164,7 @@ def test_buffer_protocol_hash(backend): ) -class TestSHAKE(object): +class TestSHAKE: @pytest.mark.parametrize("xof", [hashes.SHAKE128, hashes.SHAKE256]) def test_invalid_digest_type(self, xof): with pytest.raises(TypeError): @@ -196,3 +177,14 @@ def test_invalid_digest_size(self, xof): with pytest.raises(ValueError): xof(digest_size=0) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SM3()), + skip_message="Does not support SM3", +) +class TestSM3: + test_sm3 = generate_base_hash_test( + hashes.SM3(), + digest_size=32, + ) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 1d7de6c472ee..0bd5c97c48d0 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -2,27 +2,20 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.exceptions import AlreadyFinalized, InvalidKey, _Reasons -from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand -from ...utils import ( - load_nist_vectors, - load_vectors_from_file, - raises_unsupported_algorithm, -) +from ...utils import load_nist_vectors, load_vectors_from_file -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHKDF(object): +class TestHKDF: def test_length_limit(self, backend): big_length = 255 * hashes.SHA256().digest_size + 1 @@ -65,31 +58,43 @@ def test_verify_invalid(self, backend): def test_unicode_typeerror(self, backend): with pytest.raises(TypeError): - HKDF(hashes.SHA256(), 16, salt=u"foo", info=None, backend=backend) + HKDF( + hashes.SHA256(), + 16, + salt="foo", # type: ignore[arg-type] + info=None, + backend=backend, + ) with pytest.raises(TypeError): - HKDF(hashes.SHA256(), 16, salt=None, info=u"foo", backend=backend) + HKDF( + hashes.SHA256(), + 16, + salt=None, + info="foo", # type: ignore[arg-type] + backend=backend, + ) with pytest.raises(TypeError): hkdf = HKDF( hashes.SHA256(), 16, salt=None, info=None, backend=backend ) - hkdf.derive(u"foo") + hkdf.derive("foo") # type: ignore[arg-type] with pytest.raises(TypeError): hkdf = HKDF( hashes.SHA256(), 16, salt=None, info=None, backend=backend ) - hkdf.verify(u"foo", b"bar") + hkdf.verify("foo", b"bar") # type: ignore[arg-type] with pytest.raises(TypeError): hkdf = HKDF( hashes.SHA256(), 16, salt=None, info=None, backend=backend ) - hkdf.verify(b"foo", u"bar") + hkdf.verify(b"foo", "bar") # type: ignore[arg-type] def test_derive_short_output(self, backend): hkdf = HKDF(hashes.SHA256(), 4, salt=None, info=None, backend=backend) @@ -127,8 +132,7 @@ def test_buffer_protocol(self, backend): assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"]) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHKDFExpand(object): +class TestHKDFExpand: def test_derive(self, backend): prk = binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" @@ -175,7 +179,7 @@ def test_verify(self, backend): info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) - assert hkdf.verify(prk, binascii.unhexlify(okm)) is None + hkdf.verify(prk, binascii.unhexlify(okm)) def test_invalid_verify(self, backend): prk = binascii.unhexlify( @@ -202,14 +206,4 @@ def test_unicode_error(self, backend): hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) with pytest.raises(TypeError): - hkdf.derive(u"first") - - -def test_invalid_backend(): - pretend_backend = object() - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - HKDF(hashes.SHA256(), 16, None, None, pretend_backend) - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - HKDFExpand(hashes.SHA256(), 16, None, pretend_backend) + hkdf.derive("first") # type: ignore[arg-type] diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index 97385e203c19..080aa1b5b557 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -2,25 +2,22 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import os import pytest -from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes -from .utils import generate_hkdf_test from ...utils import load_nist_vectors +from .utils import generate_hkdf_test @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1.", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHKDFSHA1(object): +class TestHKDFSHA1: test_hkdfsha1 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), @@ -33,8 +30,7 @@ class TestHKDFSHA1(object): only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), skip_message="Does not support SHA256.", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHKDFSHA256(object): +class TestHKDFSHA256: test_hkdfsha256 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 7ea931aca4db..78bb26254d9b 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii @@ -13,35 +12,34 @@ InvalidSignature, _Reasons, ) -from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes, hmac -from .utils import generate_base_hmac_test from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm +from .utils import generate_base_hmac_test @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACCopy(object): +class TestHMACCopy: test_copy = generate_base_hmac_test( hashes.MD5(), ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMAC(object): +class TestHMAC: def test_hmac_reject_unicode(self, backend): h = hmac.HMAC(b"mykey", hashes.SHA1(), backend=backend) with pytest.raises(TypeError): - h.update(u"\u00FC") + h.update("\u00FC") # type: ignore[arg-type] def test_hmac_algorithm_instance(self, backend): with pytest.raises(TypeError): - hmac.HMAC(b"key", hashes.SHA1, backend=backend) + hmac.HMAC( + b"key", hashes.SHA1, backend=backend # type: ignore[arg-type] + ) def test_raises_after_finalize(self, backend): h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend) @@ -77,7 +75,7 @@ def test_invalid_verify(self, backend): def test_verify_reject_unicode(self, backend): h = hmac.HMAC(b"", hashes.SHA1(), backend=backend) with pytest.raises(TypeError): - h.verify(u"") + h.verify("") # type: ignore[arg-type] def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): @@ -91,9 +89,7 @@ def test_buffer_protocol(self, backend): b"a1bf7169c56a501c6585190ff4f07cad6e492a3ee187c0372614fb444b9fc3f0" ) - -def test_invalid_backend(): - pretend_backend = object() - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - hmac.HMAC(b"key", hashes.SHA1(), pretend_backend) + def test_algorithm(self): + alg = hashes.SHA256() + h = hmac.HMAC(b"123456", alg) + assert h.algorithm is alg diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py index b39df1a75e7c..790993a34ae4 100644 --- a/tests/hazmat/primitives/test_hmac_vectors.py +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -2,25 +2,22 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest -from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes, hmac -from .utils import generate_hmac_test from ...utils import load_hash_vectors +from .utils import generate_hmac_test @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACMD5(object): +class TestHMACMD5: test_hmac_md5 = generate_hmac_test( load_hash_vectors, "HMAC", @@ -33,8 +30,7 @@ class TestHMACMD5(object): only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACSHA1(object): +class TestHMACSHA1: test_hmac_sha1 = generate_hmac_test( load_hash_vectors, "HMAC", @@ -47,8 +43,7 @@ class TestHMACSHA1(object): only_if=lambda backend: backend.hmac_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACSHA224(object): +class TestHMACSHA224: test_hmac_sha224 = generate_hmac_test( load_hash_vectors, "HMAC", @@ -61,8 +56,7 @@ class TestHMACSHA224(object): only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACSHA256(object): +class TestHMACSHA256: test_hmac_sha256 = generate_hmac_test( load_hash_vectors, "HMAC", @@ -75,8 +69,7 @@ class TestHMACSHA256(object): only_if=lambda backend: backend.hmac_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACSHA384(object): +class TestHMACSHA384: test_hmac_sha384 = generate_hmac_test( load_hash_vectors, "HMAC", @@ -89,8 +82,7 @@ class TestHMACSHA384(object): only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACSHA512(object): +class TestHMACSHA512: test_hmac_sha512 = generate_hmac_test( load_hash_vectors, "HMAC", @@ -105,8 +97,7 @@ class TestHMACSHA512(object): ), skip_message="Does not support BLAKE2", ) -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACBLAKE2(object): +class TestHMACBLAKE2: def test_blake2b(self, backend): h = hmac.HMAC(b"0" * 64, hashes.BLAKE2b(digest_size=64), backend) h.update(b"test") diff --git a/tests/hazmat/primitives/test_idea.py b/tests/hazmat/primitives/test_idea.py index 1f766def082a..6631a93f91cc 100644 --- a/tests/hazmat/primitives/test_idea.py +++ b/tests/hazmat/primitives/test_idea.py @@ -2,83 +2,85 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA(b"\x00" * 16), modes.ECB() + algorithms._IDEAInternal(b"\x00" * 16), modes.ECB() ), skip_message="Does not support IDEA ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestIDEAModeECB(object): +class TestIDEAModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ecb.txt"], - lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._IDEAInternal( + binascii.unhexlify(key) + ), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA(b"\x00" * 16), modes.CBC(b"\x00" * 8) + algorithms._IDEAInternal(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support IDEA CBC", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestIDEAModeCBC(object): +class TestIDEAModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cbc.txt"], - lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._IDEAInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA(b"\x00" * 16), modes.OFB(b"\x00" * 8) + algorithms._IDEAInternal(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support IDEA OFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestIDEAModeOFB(object): +class TestIDEAModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ofb.txt"], - lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._IDEAInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA(b"\x00" * 16), modes.CFB(b"\x00" * 8) + algorithms._IDEAInternal(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support IDEA CFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestIDEAModeCFB(object): +class TestIDEAModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cfb.txt"], - lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._IDEAInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) diff --git a/tests/hazmat/primitives/test_kbkdf.py b/tests/hazmat/primitives/test_kbkdf.py index 5ff5d74ea871..4329e3df60cd 100644 --- a/tests/hazmat/primitives/test_kbkdf.py +++ b/tests/hazmat/primitives/test_kbkdf.py @@ -2,25 +2,30 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function + +import re import pytest from cryptography.exceptions import AlreadyFinalized, InvalidKey, _Reasons -from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.kdf.kbkdf import ( - CounterLocation, + KBKDFCMAC, KBKDFHMAC, + CounterLocation, Mode, ) -from ...doubles import DummyHashAlgorithm +from ...doubles import ( + DummyBlockCipherAlgorithm, + DummyCipherAlgorithm, + DummyHashAlgorithm, +) from ...utils import raises_unsupported_algorithm -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestKBKDFHMAC(object): +class TestKBKDFHMAC: def test_invalid_key(self, backend): kdf = KBKDFHMAC( hashes.SHA256(), @@ -145,7 +150,7 @@ def test_r_type(self, backend): hashes.SHA1(), Mode.CounterMode, 32, - b"r", + b"r", # type: ignore[arg-type] 4, CounterLocation.BeforeFixed, b"label", @@ -161,7 +166,7 @@ def test_l_type(self, backend): Mode.CounterMode, 32, 4, - b"l", + b"l", # type: ignore[arg-type] CounterLocation.BeforeFixed, b"label", b"context", @@ -188,7 +193,7 @@ def test_unsupported_mode(self, backend): with pytest.raises(TypeError): KBKDFHMAC( hashes.SHA256(), - None, + None, # type: ignore[arg-type] 32, 4, 4, @@ -207,7 +212,7 @@ def test_unsupported_location(self, backend): 32, 4, 4, - None, + None, # type: ignore[arg-type] b"label", b"context", None, @@ -229,10 +234,122 @@ def test_unsupported_parameters(self, backend): backend=backend, ) - def test_unsupported_hash(self, backend): - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + def test_missing_break_location(self, backend): + with pytest.raises( + ValueError, match=re.escape("Please specify a break_location") + ): + KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + ) + + with pytest.raises( + ValueError, match=re.escape("Please specify a break_location") + ): + KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location=None, + ) + + def test_keyword_only_break_location(self, backend): + with pytest.raises( + TypeError, match=r"\d+ positional arguments but \d+ were given\Z" + ): + KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend, + 0, # break_location + ) # type: ignore + + def test_invalid_break_location(self, backend): + with pytest.raises( + TypeError, match=re.escape("break_location must be an integer") + ): + KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location="0", # type: ignore[arg-type] + ) + + with pytest.raises( + ValueError, + match=re.escape("break_location must be a positive integer"), + ): + KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location=-1, + ) + + with pytest.raises( + ValueError, match=re.escape("break_location offset > len(fixed)") + ): + kdf = KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location=18, + ) + kdf.derive(b"input key") + + def test_ignored_break_location_before(self, backend): + with pytest.raises( + ValueError, + match=re.escape( + "break_location is ignored when location is not" + " CounterLocation.MiddleFixed" + ), + ): KBKDFHMAC( - object(), + hashes.SHA256(), Mode.CounterMode, 32, 4, @@ -242,12 +359,35 @@ def test_unsupported_hash(self, backend): b"context", None, backend=backend, + break_location=0, ) - def test_unsupported_algorithm(self, backend): + def test_ignored_break_location_after(self, backend): + with pytest.raises( + ValueError, + match=re.escape( + "break_location is ignored when location is not" + " CounterLocation.MiddleFixed" + ), + ): + KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.AfterFixed, + b"label", + b"context", + None, + backend=backend, + break_location=0, + ) + + def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): KBKDFHMAC( - DummyHashAlgorithm(), + object(), # type: ignore[arg-type] Mode.CounterMode, 32, 4, @@ -259,10 +399,10 @@ def test_unsupported_algorithm(self, backend): backend=backend, ) - def test_invalid_backend(self, backend): - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + def test_unsupported_algorithm(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): KBKDFHMAC( - hashes.SHA256(), + DummyHashAlgorithm(), Mode.CounterMode, 32, 4, @@ -271,7 +411,7 @@ def test_invalid_backend(self, backend): b"label", b"context", None, - backend=object(), + backend=backend, ) def test_unicode_error_label(self, backend): @@ -283,8 +423,9 @@ def test_unicode_error_label(self, backend): 4, 4, CounterLocation.BeforeFixed, - u"label", + "label", # type: ignore[arg-type] b"context", + None, backend=backend, ) @@ -298,7 +439,7 @@ def test_unicode_error_context(self, backend): 4, CounterLocation.BeforeFixed, b"label", - u"context", + "context", # type: ignore[arg-type] None, backend=backend, ) @@ -317,7 +458,7 @@ def test_unicode_error_key_material(self, backend): None, backend=backend, ) - kdf.derive(u"material") + kdf.derive("material") # type: ignore[arg-type] def test_buffer_protocol(self, backend): kdf = KBKDFHMAC( @@ -335,3 +476,504 @@ def test_buffer_protocol(self, backend): key = kdf.derive(bytearray(b"material")) assert key == b"\xb7\x01\x05\x98\xf5\x1a\x12L\xc7." + + +class TestKBKDFCMAC: + _KEY_MATERIAL = bytes(32) + _KEY_MATERIAL2 = _KEY_MATERIAL.replace(b"\x00", b"\x01", 1) + + def test_invalid_key(self, backend): + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + key = kdf.derive(self._KEY_MATERIAL) + + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + with pytest.raises(InvalidKey): + kdf.verify(self._KEY_MATERIAL2, key) + + def test_already_finalized(self, backend): + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + kdf.derive(self._KEY_MATERIAL) + + with pytest.raises(AlreadyFinalized): + kdf.derive(self._KEY_MATERIAL2) + + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + key = kdf.derive(self._KEY_MATERIAL) + + with pytest.raises(AlreadyFinalized): + kdf.verify(self._KEY_MATERIAL, key) + + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + kdf.verify(self._KEY_MATERIAL, key) + + with pytest.raises(AlreadyFinalized): + kdf.verify(self._KEY_MATERIAL, key) + + def test_key_length(self, backend): + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 85899345920, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + with pytest.raises(ValueError): + kdf.derive(self._KEY_MATERIAL) + + def test_rlen(self, backend): + with pytest.raises(ValueError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 5, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + def test_r_type(self, backend): + with pytest.raises(TypeError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + b"r", # type: ignore[arg-type] + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + def test_l_type(self, backend): + with pytest.raises(TypeError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + b"l", # type: ignore[arg-type] + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + def test_l(self, backend): + with pytest.raises(ValueError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + None, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + def test_unsupported_mode(self, backend): + with pytest.raises(TypeError): + KBKDFCMAC( + algorithms.AES, + None, # type: ignore[arg-type] + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + def test_unsupported_location(self, backend): + with pytest.raises(TypeError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + None, # type: ignore[arg-type] + b"label", + b"context", + None, + backend=backend, + ) + + def test_unsupported_parameters(self, backend): + with pytest.raises(ValueError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + b"fixed", + backend=backend, + ) + + def test_missing_break_location(self, backend): + with pytest.raises( + ValueError, match=re.escape("Please specify a break_location") + ): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + ) + + with pytest.raises( + ValueError, match=re.escape("Please specify a break_location") + ): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location=None, + ) + + def test_keyword_only_break_location(self, backend): + with pytest.raises( + TypeError, match=r"\d+ positional arguments but \d+ were given\Z" + ): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend, + 0, # break_location + ) # type: ignore + + def test_invalid_break_location(self, backend): + with pytest.raises( + TypeError, match=re.escape("break_location must be an integer") + ): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location="0", # type: ignore[arg-type] + ) + + with pytest.raises( + ValueError, + match=re.escape("break_location must be a positive integer"), + ): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location=-1, + ) + + with pytest.raises( + ValueError, match=re.escape("break_location offset > len(fixed)") + ): + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.MiddleFixed, + b"label", + b"context", + None, + backend=backend, + break_location=18, + ) + kdf.derive(b"32 bytes long input key material") + + def test_ignored_break_location_before(self, backend): + with pytest.raises( + ValueError, + match=re.escape( + "break_location is ignored when location is not" + " CounterLocation.MiddleFixed" + ), + ): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + break_location=0, + ) + + def test_ignored_break_location_after(self, backend): + with pytest.raises( + ValueError, + match=re.escape( + "break_location is ignored when location is not" + " CounterLocation.MiddleFixed" + ), + ): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.AfterFixed, + b"label", + b"context", + None, + backend=backend, + break_location=0, + ) + + def test_unsupported_algorithm(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + KBKDFCMAC( + object, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + KBKDFCMAC( + DummyCipherAlgorithm, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + KBKDFCMAC( + algorithms.ARC4, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + def test_unicode_error_label(self, backend): + with pytest.raises(TypeError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + "label", # type: ignore[arg-type] + b"context", + None, + backend=backend, + ) + + def test_unicode_error_context(self, backend): + with pytest.raises(TypeError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + "context", # type: ignore[arg-type] + None, + backend=backend, + ) + + def test_unsupported_cipher(self, backend): + kdf = KBKDFCMAC( + DummyBlockCipherAlgorithm, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + kdf.derive(self._KEY_MATERIAL) + + def test_unicode_error_key_material(self, backend): + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + with pytest.raises(TypeError): + kdf.derive("material") # type: ignore[arg-type] + + def test_wrong_key_material_length(self, backend): + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + with pytest.raises(ValueError): + kdf.derive(b"material") + + def test_buffer_protocol(self, backend): + kdf = KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 10, + 4, + 4, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + + key = kdf.derive(bytearray(self._KEY_MATERIAL)) + assert key == b"\x19\xcd\xbe\x17Lb\x115<\xd0" diff --git a/tests/hazmat/primitives/test_kbkdf_vectors.py b/tests/hazmat/primitives/test_kbkdf_vectors.py index 462e04ec5a87..cab817bf4e98 100644 --- a/tests/hazmat/primitives/test_kbkdf_vectors.py +++ b/tests/hazmat/primitives/test_kbkdf_vectors.py @@ -2,20 +2,14 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import os -import pytest - -from cryptography.hazmat.backends.interfaces import HMACBackend - -from .utils import generate_kbkdf_counter_mode_test from ...utils import load_nist_kbkdf_vectors +from .utils import generate_kbkdf_counter_mode_test -@pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestCounterKDFCounterMode(object): +class TestCounterKDFCounterMode: test_kbkdfctr = generate_kbkdf_counter_mode_test( load_nist_kbkdf_vectors, os.path.join("KDF"), diff --git a/tests/hazmat/primitives/test_keywrap.py b/tests/hazmat/primitives/test_keywrap.py index 9b91ccf36b33..7dfb80901871 100644 --- a/tests/hazmat/primitives/test_keywrap.py +++ b/tests/hazmat/primitives/test_keywrap.py @@ -2,31 +2,20 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives import keywrap from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import _load_all_params from ...utils import load_nist_vectors +from .utils import _load_all_params -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESKeyWrap(object): - @pytest.mark.parametrize( - "params", - _load_all_params( - os.path.join("keywrap", "kwtestvectors"), - ["KW_AE_128.txt", "KW_AE_192.txt", "KW_AE_256.txt"], - load_nist_vectors, - ), - ) +class TestAESKeyWrap: @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() @@ -34,20 +23,21 @@ class TestAESKeyWrap(object): skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" " is unsupported", ) - def test_wrap(self, backend, params): - wrapping_key = binascii.unhexlify(params["k"]) - key_to_wrap = binascii.unhexlify(params["p"]) - wrapped_key = keywrap.aes_key_wrap(wrapping_key, key_to_wrap, backend) - assert params["c"] == binascii.hexlify(wrapped_key) - - @pytest.mark.parametrize( - "params", - _load_all_params( + def test_wrap(self, backend, subtests): + params = _load_all_params( os.path.join("keywrap", "kwtestvectors"), - ["KW_AD_128.txt", "KW_AD_192.txt", "KW_AD_256.txt"], + ["KW_AE_128.txt", "KW_AE_192.txt", "KW_AE_256.txt"], load_nist_vectors, - ), - ) + ) + for param in params: + with subtests.test(): + wrapping_key = binascii.unhexlify(param["k"]) + key_to_wrap = binascii.unhexlify(param["p"]) + wrapped_key = keywrap.aes_key_wrap( + wrapping_key, key_to_wrap, backend + ) + assert param["c"] == binascii.hexlify(wrapped_key) + @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() @@ -55,17 +45,26 @@ def test_wrap(self, backend, params): skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" " is unsupported", ) - def test_unwrap(self, backend, params): - wrapping_key = binascii.unhexlify(params["k"]) - wrapped_key = binascii.unhexlify(params["c"]) - if params.get("fail") is True: - with pytest.raises(keywrap.InvalidUnwrap): - keywrap.aes_key_unwrap(wrapping_key, wrapped_key, backend) - else: - unwrapped_key = keywrap.aes_key_unwrap( - wrapping_key, wrapped_key, backend - ) - assert params["p"] == binascii.hexlify(unwrapped_key) + def test_unwrap(self, backend, subtests): + params = _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KW_AD_128.txt", "KW_AD_192.txt", "KW_AD_256.txt"], + load_nist_vectors, + ) + for param in params: + with subtests.test(): + wrapping_key = binascii.unhexlify(param["k"]) + wrapped_key = binascii.unhexlify(param["c"]) + if param.get("fail") is True: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap( + wrapping_key, wrapped_key, backend + ) + else: + unwrapped_key = keywrap.aes_key_unwrap( + wrapping_key, wrapped_key, backend + ) + assert param["p"] == binascii.hexlify(unwrapped_key) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( @@ -123,69 +122,68 @@ def test_unwrap_invalid_wrapped_key_length(self, backend): skip_message="Does not support AES key wrap (RFC 5649) because AES-ECB" " is unsupported", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestAESKeyWrapWithPadding(object): - @pytest.mark.parametrize( - "params", - _load_all_params( +class TestAESKeyWrapWithPadding: + def test_wrap(self, backend, subtests): + params = _load_all_params( os.path.join("keywrap", "kwtestvectors"), ["KWP_AE_128.txt", "KWP_AE_192.txt", "KWP_AE_256.txt"], load_nist_vectors, - ), - ) - def test_wrap(self, backend, params): - wrapping_key = binascii.unhexlify(params["k"]) - key_to_wrap = binascii.unhexlify(params["p"]) - wrapped_key = keywrap.aes_key_wrap_with_padding( - wrapping_key, key_to_wrap, backend ) - assert params["c"] == binascii.hexlify(wrapped_key) + for param in params: + with subtests.test(): + wrapping_key = binascii.unhexlify(param["k"]) + key_to_wrap = binascii.unhexlify(param["p"]) + wrapped_key = keywrap.aes_key_wrap_with_padding( + wrapping_key, key_to_wrap, backend + ) + assert param["c"] == binascii.hexlify(wrapped_key) - @pytest.mark.parametrize( - "params", - _load_all_params("keywrap", ["kwp_botan.txt"], load_nist_vectors), - ) - def test_wrap_additional_vectors(self, backend, params): - wrapping_key = binascii.unhexlify(params["key"]) - key_to_wrap = binascii.unhexlify(params["input"]) - wrapped_key = keywrap.aes_key_wrap_with_padding( - wrapping_key, key_to_wrap, backend + def test_wrap_additional_vectors(self, backend, subtests): + params = _load_all_params( + "keywrap", ["kwp_botan.txt"], load_nist_vectors ) - assert wrapped_key == binascii.unhexlify(params["output"]) + for param in params: + with subtests.test(): + wrapping_key = binascii.unhexlify(param["key"]) + key_to_wrap = binascii.unhexlify(param["input"]) + wrapped_key = keywrap.aes_key_wrap_with_padding( + wrapping_key, key_to_wrap, backend + ) + assert wrapped_key == binascii.unhexlify(param["output"]) - @pytest.mark.parametrize( - "params", - _load_all_params( + def test_unwrap(self, backend, subtests): + params = _load_all_params( os.path.join("keywrap", "kwtestvectors"), ["KWP_AD_128.txt", "KWP_AD_192.txt", "KWP_AD_256.txt"], load_nist_vectors, - ), - ) - def test_unwrap(self, backend, params): - wrapping_key = binascii.unhexlify(params["k"]) - wrapped_key = binascii.unhexlify(params["c"]) - if params.get("fail") is True: - with pytest.raises(keywrap.InvalidUnwrap): - keywrap.aes_key_unwrap_with_padding( + ) + for param in params: + with subtests.test(): + wrapping_key = binascii.unhexlify(param["k"]) + wrapped_key = binascii.unhexlify(param["c"]) + if param.get("fail") is True: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + else: + unwrapped_key = keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + assert param["p"] == binascii.hexlify(unwrapped_key) + + def test_unwrap_additional_vectors(self, backend, subtests): + params = _load_all_params( + "keywrap", ["kwp_botan.txt"], load_nist_vectors + ) + for param in params: + with subtests.test(): + wrapping_key = binascii.unhexlify(param["key"]) + wrapped_key = binascii.unhexlify(param["output"]) + unwrapped_key = keywrap.aes_key_unwrap_with_padding( wrapping_key, wrapped_key, backend ) - else: - unwrapped_key = keywrap.aes_key_unwrap_with_padding( - wrapping_key, wrapped_key, backend - ) - assert params["p"] == binascii.hexlify(unwrapped_key) - - @pytest.mark.parametrize( - "params", - _load_all_params("keywrap", ["kwp_botan.txt"], load_nist_vectors), - ) - def test_unwrap_additional_vectors(self, backend, params): - wrapping_key = binascii.unhexlify(params["key"]) - wrapped_key = binascii.unhexlify(params["output"]) - unwrapped_key = keywrap.aes_key_unwrap_with_padding( - wrapping_key, wrapped_key, backend - ) - assert unwrapped_key == binascii.unhexlify(params["input"]) + assert unwrapped_key == binascii.unhexlify(param["input"]) def test_unwrap_invalid_wrapped_key_length(self, backend): # Keys to unwrap must be at least 16 bytes diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py index bf5379730131..1a9a01f6cf15 100644 --- a/tests/hazmat/primitives/test_padding.py +++ b/tests/hazmat/primitives/test_padding.py @@ -2,17 +2,14 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import pytest -import six - from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import padding -class TestPKCS7(object): +class TestPKCS7: @pytest.mark.parametrize("size", [127, 4096, -2]) def test_invalid_block_size(self, size): with pytest.raises(ValueError): @@ -38,10 +35,22 @@ def test_invalid_padding(self, size, padded): def test_non_bytes(self): padder = padding.PKCS7(128).padder() with pytest.raises(TypeError): - padder.update(u"abc") + padder.update("abc") # type: ignore[arg-type] unpadder = padding.PKCS7(128).unpadder() with pytest.raises(TypeError): - unpadder.update(u"abc") + unpadder.update("abc") # type: ignore[arg-type] + + def test_zany_py2_bytes_subclass(self): + class mybytes(bytes): # noqa: N801 + def __str__(self): + return "broken" + + str(mybytes()) + padder = padding.PKCS7(128).padder() + padder.update(mybytes(b"abc")) + unpadder = padding.PKCS7(128).unpadder() + unpadder.update(mybytes(padder.finalize())) + assert unpadder.finalize() == b"abc" @pytest.mark.parametrize( ("size", "unpadded", "padded"), @@ -100,7 +109,7 @@ def test_large_padding(self): padded_data = padder.update(b"") padded_data += padder.finalize() - for i in six.iterbytes(padded_data): + for i in padded_data: assert i == 255 unpadder = padding.PKCS7(2040).unpadder() @@ -122,7 +131,7 @@ def test_bytearray(self): assert final == unpadded + unpadded -class TestANSIX923(object): +class TestANSIX923: @pytest.mark.parametrize("size", [127, 4096, -2]) def test_invalid_block_size(self, size): with pytest.raises(ValueError): @@ -149,10 +158,22 @@ def test_invalid_padding(self, size, padded): def test_non_bytes(self): padder = padding.ANSIX923(128).padder() with pytest.raises(TypeError): - padder.update(u"abc") + padder.update("abc") # type: ignore[arg-type] unpadder = padding.ANSIX923(128).unpadder() with pytest.raises(TypeError): - unpadder.update(u"abc") + unpadder.update("abc") # type: ignore[arg-type] + + def test_zany_py2_bytes_subclass(self): + class mybytes(bytes): # noqa: N801 + def __str__(self): + return "broken" + + str(mybytes()) + padder = padding.ANSIX923(128).padder() + padder.update(mybytes(b"abc")) + unpadder = padding.ANSIX923(128).unpadder() + unpadder.update(mybytes(padder.finalize())) + assert unpadder.finalize() == b"abc" @pytest.mark.parametrize( ("size", "unpadded", "padded"), diff --git a/tests/hazmat/primitives/test_pbkdf2hmac.py b/tests/hazmat/primitives/test_pbkdf2hmac.py index 34fd25cf47e5..2be47ea003e3 100644 --- a/tests/hazmat/primitives/test_pbkdf2hmac.py +++ b/tests/hazmat/primitives/test_pbkdf2hmac.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import pytest @@ -14,7 +13,7 @@ from ...utils import raises_unsupported_algorithm -class TestPBKDF2HMAC(object): +class TestPBKDF2HMAC: def test_already_finalized(self, backend): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) kdf.derive(b"password") @@ -45,21 +44,20 @@ def test_invalid_key(self, backend): def test_unicode_error_with_salt(self, backend): with pytest.raises(TypeError): - PBKDF2HMAC(hashes.SHA1(), 20, u"salt", 10, backend) + PBKDF2HMAC( + hashes.SHA1(), + 20, + "salt", # type: ignore[arg-type] + 10, + backend, + ) def test_unicode_error_with_key_material(self, backend): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) with pytest.raises(TypeError): - kdf.derive(u"unicode here") + kdf.derive("unicode here") # type: ignore[arg-type] def test_buffer_protocol(self, backend): kdf = PBKDF2HMAC(hashes.SHA1(), 10, b"salt", 10, backend) data = bytearray(b"data") assert kdf.derive(data) == b"\xe9n\xaa\x81\xbbt\xa4\xf6\x08\xce" - - -def test_invalid_backend(): - pretend_backend = object() - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, pretend_backend) diff --git a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py index 4b97b0d13a97..db44114e3194 100644 --- a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py +++ b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py @@ -2,26 +2,36 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +import binascii +import os import pytest -from cryptography.hazmat.backends.interfaces import PBKDF2HMACBackend from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from .utils import generate_pbkdf2_test -from ...utils import load_nist_vectors +from ...utils import load_nist_vectors, load_vectors_from_file @pytest.mark.supported( only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1 for PBKDF2HMAC", ) -@pytest.mark.requires_backend_interface(interface=PBKDF2HMACBackend) -class TestPBKDF2HMACSHA1(object): - test_pbkdf2_sha1 = generate_pbkdf2_test( +def test_pbkdf2_hmacsha1_vectors(subtests, backend): + params = load_vectors_from_file( + os.path.join("KDF", "rfc-6070-PBKDF2-SHA1.txt"), load_nist_vectors, - "KDF", - ["rfc-6070-PBKDF2-SHA1.txt"], - hashes.SHA1(), ) + for param in params: + with subtests.test(): + iterations = int(param["iterations"]) + if iterations > 1_000_000: + pytest.skip("Skipping test due to iteration count") + kdf = PBKDF2HMAC( + hashes.SHA1(), + int(param["length"]), + param["salt"], + iterations, + ) + derived_key = kdf.derive(param["password"]) + assert binascii.hexlify(derived_key) == param["derived_key"] diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index 297483e2f99d..f44fdd115af3 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -2,28 +2,54 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import os +from datetime import datetime import pytest from cryptography import x509 -from cryptography.hazmat.backends.interfaces import DERSerializationBackend +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.openssl.backend import _RC2 -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.serialization import load_pem_private_key +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed448, + ed25519, + rsa, +) +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PublicFormat, + load_pem_private_key, +) from cryptography.hazmat.primitives.serialization.pkcs12 import ( + PBES, + PKCS12Certificate, + PKCS12KeyAndCertificates, load_key_and_certificates, + load_pkcs12, serialize_key_and_certificates, ) -from .utils import load_vectors_from_file from ...doubles import DummyKeySerializationEncryption +from ...utils import load_vectors_from_file + + +def _skip_curve_unsupported(backend, curve): + if not backend.elliptic_curve_supported(curve): + pytest.skip( + "Curve {} is not supported by this backend {}".format( + curve.name, backend + ) + ) -@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) -class TestPKCS12Loading(object): +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) +class TestPKCS12Loading: def _test_load_pkcs12_ec_keys(self, filename, password, backend): cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "ca.pem"), @@ -39,6 +65,7 @@ def _test_load_pkcs12_ec_keys(self, filename, password, backend): ), mode="rb", ) + assert isinstance(key, ec.EllipticCurvePrivateKey) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", filename), lambda derfile: load_key_and_certificates( @@ -46,6 +73,7 @@ def _test_load_pkcs12_ec_keys(self, filename, password, backend): ), mode="rb", ) + assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_cert == cert assert parsed_key.private_numbers() == key.private_numbers() assert parsed_more_certs == [] @@ -71,7 +99,6 @@ def test_load_pkcs12_ec_keys(self, filename, password, backend): only_if=lambda backend: backend.cipher_supported(_RC2(), None), skip_message="Does not support RC2", ) - @pytest.mark.skip_fips(reason="Unsupported algorithm in FIPS mode") def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): self._test_load_pkcs12_ec_keys(filename, password, backend) @@ -102,6 +129,7 @@ def test_load_pkcs12_key_only(self, backend): ), mode="rb", ) + assert isinstance(key, ec.EllipticCurvePrivateKey) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"), lambda data: load_key_and_certificates( @@ -109,13 +137,16 @@ def test_load_pkcs12_key_only(self, backend): ), mode="rb", ) + assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_key.private_numbers() == key.private_numbers() assert parsed_cert is None assert parsed_more_certs == [] def test_non_bytes(self, backend): with pytest.raises(TypeError): - load_key_and_certificates(b"irrelevant", object(), backend) + load_key_and_certificates( + b"irrelevant", object(), backend # type: ignore[arg-type] + ) def test_not_a_pkcs12(self, backend): with pytest.raises(ValueError): @@ -145,6 +176,108 @@ def test_buffer_protocol(self, backend): assert parsed_cert is not None assert parsed_more_certs == [] + @pytest.mark.parametrize( + ("name", "name2", "name3", "filename", "password"), + [ + (None, None, None, "no-name-no-pwd.p12", None), + (b"name", b"name2", b"name3", "name-all-no-pwd.p12", None), + (b"name", None, None, "name-1-no-pwd.p12", None), + (None, b"name2", b"name3", "name-2-3-no-pwd.p12", None), + (None, b"name2", None, "name-2-no-pwd.p12", None), + (None, None, b"name3", "name-3-no-pwd.p12", None), + ( + "☺".encode(), + "ä".encode(), + "ç".encode(), + "name-unicode-no-pwd.p12", + None, + ), + (None, None, None, "no-name-pwd.p12", b"password"), + (b"name", b"name2", b"name3", "name-all-pwd.p12", b"password"), + (b"name", None, None, "name-1-pwd.p12", b"password"), + (None, b"name2", b"name3", "name-2-3-pwd.p12", b"password"), + (None, b"name2", None, "name-2-pwd.p12", b"password"), + (None, None, b"name3", "name-3-pwd.p12", b"password"), + ( + "☺".encode(), + "ä".encode(), + "ç".encode(), + "name-unicode-pwd.p12", + b"password", + ), + ], + ) + def test_load_object( + self, filename, name, name2, name3, password, backend + ): + cert, key = _load_ca(backend) + cert2 = _load_cert( + backend, os.path.join("x509", "cryptography.io.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + + pkcs12 = load_vectors_from_file( + os.path.join("pkcs12", filename), + lambda derfile: load_pkcs12(derfile.read(), password, backend), + mode="rb", + ) + assert pkcs12.cert is not None + assert pkcs12.cert.certificate == cert + assert pkcs12.cert.friendly_name == name + assert isinstance(pkcs12.key, ec.EllipticCurvePrivateKey) + assert pkcs12.key.private_numbers() == key.private_numbers() + assert len(pkcs12.additional_certs) == 2 + assert pkcs12.additional_certs[0].certificate == cert2 + assert pkcs12.additional_certs[0].friendly_name == name2 + assert pkcs12.additional_certs[1].certificate == cert3 + assert pkcs12.additional_certs[1].friendly_name == name3 + + @pytest.mark.parametrize( + ("name2", "name3", "filename", "password"), + [ + (None, None, "no-cert-no-name-no-pwd.p12", None), + (b"name2", b"name3", "no-cert-name-all-no-pwd.p12", None), + (b"name2", None, "no-cert-name-2-no-pwd.p12", None), + (None, b"name3", "no-cert-name-3-no-pwd.p12", None), + ( + "☹".encode(), + "ï".encode(), + "no-cert-name-unicode-no-pwd.p12", + None, + ), + (None, None, "no-cert-no-name-pwd.p12", b"password"), + (b"name2", b"name3", "no-cert-name-all-pwd.p12", b"password"), + (b"name2", None, "no-cert-name-2-pwd.p12", b"password"), + (None, b"name3", "no-cert-name-3-pwd.p12", b"password"), + ( + "☹".encode(), + "ï".encode(), + "no-cert-name-unicode-pwd.p12", + b"password", + ), + ], + ) + def test_load_object_no_cert_key( + self, filename, name2, name3, password, backend + ): + cert2 = _load_cert( + backend, os.path.join("x509", "cryptography.io.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + + pkcs12 = load_vectors_from_file( + os.path.join("pkcs12", filename), + lambda derfile: load_pkcs12(derfile.read(), password, backend), + mode="rb", + ) + assert pkcs12.cert is None + assert pkcs12.key is None + assert len(pkcs12.additional_certs) == 2 + assert pkcs12.additional_certs[0].certificate == cert2 + assert pkcs12.additional_certs[0].friendly_name == name2 + assert pkcs12.additional_certs[1].certificate == cert3 + assert pkcs12.additional_certs[1].friendly_name == name3 + def _load_cert(backend, path): return load_vectors_from_file( @@ -166,27 +299,89 @@ def _load_ca(backend): return cert, key -class TestPKCS12Creation(object): +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) +class TestPKCS12Creation: + @pytest.mark.parametrize( + ( + "kgenerator", + "ktype", + "kparam", + ), + [ + pytest.param( + ed448.Ed448PrivateKey.generate, + ed448.Ed448PrivateKey, + [], + marks=pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support", + ), + ), + pytest.param( + ed25519.Ed25519PrivateKey.generate, + ed25519.Ed25519PrivateKey, + [], + marks=pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support", + ), + ), + (rsa.generate_private_key, rsa.RSAPrivateKey, [65537, 1024]), + (dsa.generate_private_key, dsa.DSAPrivateKey, [1024]), + ] + + [ + pytest.param( + ec.generate_private_key, ec.EllipticCurvePrivateKey, [curve] + ) + for curve in ec._CURVE_TYPES.values() + ], + ) @pytest.mark.parametrize("name", [None, b"name"]) @pytest.mark.parametrize( - ("encryption_algorithm", "password"), + ("algorithm", "password"), [ (serialization.BestAvailableEncryption(b"password"), b"password"), (serialization.NoEncryption(), None), ], ) - def test_generate(self, backend, name, encryption_algorithm, password): - cert, key = _load_ca(backend) + def test_generate_each_supported_keytype( + self, backend, kgenerator, ktype, kparam, name, algorithm, password + ): + if ktype == ec.EllipticCurvePrivateKey: + _skip_curve_unsupported(backend, *kparam) + + key = kgenerator(*kparam) + + assert isinstance(key, ktype) + cacert, cakey = _load_ca(backend) + now = datetime.utcnow() + cert = ( + x509.CertificateBuilder() + .subject_name(cacert.subject) + .issuer_name(cacert.subject) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now) + .not_valid_after(now) + .sign(cakey, hashes.SHA256()) + ) + assert isinstance(cert, x509.Certificate) p12 = serialize_key_and_certificates( - name, key, cert, None, encryption_algorithm + name, key, cert, [cacert], algorithm ) - parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, password, backend ) assert parsed_cert == cert - assert parsed_key.private_numbers() == key.private_numbers() - assert parsed_more_certs == [] + assert isinstance(parsed_key, ktype) + assert parsed_key.public_key().public_bytes( + Encoding.PEM, PublicFormat.SubjectPublicKeyInfo + ) == key.public_key().public_bytes( + Encoding.PEM, PublicFormat.SubjectPublicKeyInfo + ) + assert parsed_more_certs == [cacert] def test_generate_with_cert_key_ca(self, backend): cert, key = _load_ca(backend) @@ -203,9 +398,33 @@ def test_generate_with_cert_key_ca(self, backend): p12, None, backend ) assert parsed_cert == cert + assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_key.private_numbers() == key.private_numbers() assert parsed_more_certs == [cert2, cert3] + def test_generate_cas_friendly_names(self, backend): + cert, key = _load_ca(backend) + cert2 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + encryption = serialization.NoEncryption() + p12 = serialize_key_and_certificates( + b"test", + key, + cert, + [ + PKCS12Certificate(cert2, b"cert2"), + PKCS12Certificate(cert3, None), + ], + encryption, + ) + + p12_cert = load_pkcs12(p12, None, backend) + cas = p12_cert.additional_certs + assert cas[0].friendly_name == b"cert2" + assert cas[1].friendly_name is None + def test_generate_wrong_types(self, backend): cert, key = _load_ca(backend) cert2 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) @@ -214,14 +433,13 @@ def test_generate_wrong_types(self, backend): serialize_key_and_certificates( b"name", cert, cert, None, encryption ) - assert ( - str(exc.value) - == "Key must be RSA, DSA, or EllipticCurve private key." + assert str(exc.value) == ( + "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" + " private key, or None." ) - with pytest.raises(TypeError) as exc: serialize_key_and_certificates(b"name", key, key, None, encryption) - assert str(exc.value) == "cert must be a certificate" + assert str(exc.value) == "cert must be a certificate or None" with pytest.raises(TypeError) as exc: serialize_key_and_certificates(b"name", key, cert, None, key) @@ -246,9 +464,53 @@ def test_generate_no_cert(self, backend): p12, None, backend ) assert parsed_cert is None + assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_key.private_numbers() == key.private_numbers() assert parsed_more_certs == [] + @pytest.mark.parametrize( + ("encryption_algorithm", "password"), + [ + (serialization.BestAvailableEncryption(b"password"), b"password"), + (serialization.NoEncryption(), None), + ], + ) + def test_generate_cas_only(self, encryption_algorithm, password, backend): + cert, _ = _load_ca(backend) + p12 = serialize_key_and_certificates( + None, None, None, [cert], encryption_algorithm + ) + parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( + p12, password, backend + ) + assert parsed_cert is None + assert parsed_key is None + assert parsed_more_certs == [cert] + + @pytest.mark.parametrize( + ("encryption_algorithm", "password"), + [ + (serialization.BestAvailableEncryption(b"password"), b"password"), + (serialization.NoEncryption(), None), + ], + ) + def test_generate_cert_only(self, encryption_algorithm, password, backend): + # This test is a bit weird, but when passing *just* a cert + # with no corresponding key it will be encoded in the cas + # list. We have external consumers relying on this behavior + # (and the underlying structure makes no real distinction + # anyway) so this test ensures we don't break them. + cert, _ = _load_ca(backend) + p12 = serialize_key_and_certificates( + None, None, cert, [], encryption_algorithm + ) + parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( + p12, password, backend + ) + assert parsed_cert is None + assert parsed_key is None + assert parsed_more_certs == [cert] + def test_must_supply_something(self): with pytest.raises(ValueError) as exc: serialize_key_and_certificates( @@ -269,3 +531,426 @@ def test_generate_unsupported_encryption_type(self, backend): DummyKeySerializationEncryption(), ) assert str(exc.value) == "Unsupported key encryption type" + + @pytest.mark.parametrize( + ("enc_alg", "enc_alg_der"), + [ + ( + PBES.PBESv2SHA256AndAES256CBC, + [ + b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x05\x0d", # PBESv2 + b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x2a", # AES + ], + ), + ( + PBES.PBESv1SHA1And3KeyTripleDESCBC, + [b"\x06\x0a\x2a\x86\x48\x86\xf7\x0d\x01\x0c\x01\x03"], + ), + ( + None, + [], + ), + ], + ) + @pytest.mark.parametrize( + ("mac_alg", "mac_alg_der"), + [ + (hashes.SHA1(), b"\x06\x05\x2b\x0e\x03\x02\x1a"), + (hashes.SHA256(), b"\x06\t`\x86H\x01e\x03\x04\x02\x01"), + (None, None), + ], + ) + @pytest.mark.parametrize( + ("iters", "iter_der"), + [ + (420, b"\x02\x02\x01\xa4"), + (22222, b"\x02\x02\x56\xce"), + (None, None), + ], + ) + def test_key_serialization_encryption( + self, + backend, + enc_alg, + enc_alg_der, + mac_alg, + mac_alg_der, + iters, + iter_der, + ): + if ( + enc_alg is PBES.PBESv2SHA256AndAES256CBC + ) and not backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + pytest.skip("PBESv2 is not supported on OpenSSL < 3.0") + + if ( + mac_alg is not None + and not backend._lib.Cryptography_HAS_PKCS12_SET_MAC + ): + pytest.skip("PKCS12_set_mac is not supported (boring)") + + builder = serialization.PrivateFormat.PKCS12.encryption_builder() + if enc_alg is not None: + builder = builder.key_cert_algorithm(enc_alg) + if mac_alg is not None: + builder = builder.hmac_hash(mac_alg) + if iters is not None: + builder = builder.kdf_rounds(iters) + + encryption = builder.build(b"password") + key = ec.generate_private_key(ec.SECP256R1()) + cacert, cakey = _load_ca(backend) + now = datetime.utcnow() + cert = ( + x509.CertificateBuilder() + .subject_name(cacert.subject) + .issuer_name(cacert.subject) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now) + .not_valid_after(now) + .sign(cakey, hashes.SHA256()) + ) + assert isinstance(cert, x509.Certificate) + p12 = serialize_key_and_certificates( + b"name", key, cert, [cacert], encryption + ) + # We want to know if we've serialized something that has the parameters + # we expect, so we match on specific byte strings of OIDs & DER values. + for der in enc_alg_der: + assert der in p12 + if mac_alg_der is not None: + assert mac_alg_der in p12 + if iter_der is not None: + assert iter_der in p12 + parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( + p12, b"password", backend + ) + assert parsed_cert == cert + assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) + assert parsed_key.public_key().public_bytes( + Encoding.PEM, PublicFormat.SubjectPublicKeyInfo + ) == key.public_key().public_bytes( + Encoding.PEM, PublicFormat.SubjectPublicKeyInfo + ) + assert parsed_more_certs == [cacert] + + @pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER + ), + skip_message="Requires OpenSSL < 3.0.0 (or Libre/Boring)", + ) + @pytest.mark.parametrize( + ("algorithm"), + [ + serialization.PrivateFormat.PKCS12.encryption_builder() + .key_cert_algorithm(PBES.PBESv2SHA256AndAES256CBC) + .build(b"password"), + ], + ) + def test_key_serialization_encryption_unsupported( + self, algorithm, backend + ): + cacert, cakey = _load_ca(backend) + with pytest.raises(UnsupportedAlgorithm): + serialize_key_and_certificates( + b"name", cakey, cacert, [], algorithm + ) + + @pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.Cryptography_HAS_PKCS12_SET_MAC + ), + skip_message="Requires OpenSSL without PKCS12_set_mac (boring only)", + ) + @pytest.mark.parametrize( + "algorithm", + [ + serialization.PrivateFormat.PKCS12.encryption_builder() + .key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) + .hmac_hash(hashes.SHA256()) + .build(b"password"), + ], + ) + def test_key_serialization_encryption_set_mac_unsupported( + self, algorithm, backend + ): + cacert, cakey = _load_ca(backend) + with pytest.raises(UnsupportedAlgorithm): + serialize_key_and_certificates( + b"name", cakey, cacert, [], algorithm + ) + + +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) +def test_pkcs12_ordering(): + """ + In OpenSSL < 3.0.0 PKCS12 parsing reverses the order. However, we + accidentally thought it was **encoding** that did it, leading to bug + https://github.com/pyca/cryptography/issues/5872 + This test ensures our ordering is correct going forward. + """ + + def make_cert(name): + key = ec.generate_private_key(ec.SECP256R1()) + subject = x509.Name( + [ + x509.NameAttribute(x509.NameOID.COMMON_NAME, name), + ] + ) + now = datetime.utcnow() + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(subject) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now) + .not_valid_after(now) + .sign(key, hashes.SHA256()) + ) + return (key, cert) + + # Make some certificates with distinct names. + a_name = "A" * 20 + b_name = "B" * 20 + c_name = "C" * 20 + a_key, a_cert = make_cert(a_name) + _, b_cert = make_cert(b_name) + _, c_cert = make_cert(c_name) + + # Bundle them in a PKCS#12 file in order A, B, C. + p12 = serialize_key_and_certificates( + b"p12", a_key, a_cert, [b_cert, c_cert], serialization.NoEncryption() + ) + + # Parse them out. The API should report them in the same order. + (key, cert, certs) = load_key_and_certificates(p12, None) + assert cert == a_cert + assert certs == [b_cert, c_cert] + + # The ordering in the PKCS#12 file itself should also match. + a_idx = p12.index(a_name.encode("utf-8")) + b_idx = p12.index(b_name.encode("utf-8")) + c_idx = p12.index(c_name.encode("utf-8")) + + assert a_idx < b_idx < c_idx + + +class TestPKCS12Objects: + def test_certificate_constructor(self, backend): + with pytest.raises(TypeError): + PKCS12Certificate(None, None) # type:ignore[arg-type] + with pytest.raises(TypeError): + PKCS12Certificate("hello", None) # type:ignore[arg-type] + cert = _load_cert(backend, os.path.join("x509", "cryptography.io.pem")) + with pytest.raises(TypeError): + PKCS12Certificate(cert, "hello") # type:ignore[arg-type] + with pytest.raises(TypeError): + PKCS12Certificate(cert, 42) # type:ignore[arg-type] + + def test_certificate_equality(self, backend): + cert2 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + + c2n = PKCS12Certificate(cert2, None) + c2a = PKCS12Certificate(cert2, b"a") + c2b = PKCS12Certificate(cert2, b"b") + c3n = PKCS12Certificate(cert3, None) + c3a = PKCS12Certificate(cert3, b"a") + + assert c2n == c2n + assert c2a == c2a + assert c2n != c2a + assert c2n != c3n + assert c2a != c2b + assert c2a != c3a + + assert c2n != "test" + + def test_certificate_hash(self, backend): + cert2 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + + c2n = PKCS12Certificate(cert2, None) + c2a = PKCS12Certificate(cert2, b"a") + c2b = PKCS12Certificate(cert2, b"b") + c3n = PKCS12Certificate(cert3, None) + c3a = PKCS12Certificate(cert3, b"a") + + assert hash(c2n) == hash(c2n) + assert hash(c2a) == hash(c2a) + assert hash(c2n) != hash(c2a) + assert hash(c2n) != hash(c3n) + assert hash(c2a) != hash(c2b) + assert hash(c2a) != hash(c3a) + + def test_certificate_repr(self, backend): + cert = _load_cert(backend, os.path.join("x509", "cryptography.io.pem")) + assert ( + repr(PKCS12Certificate(cert, None)) + == f"" + ) + assert ( + repr(PKCS12Certificate(cert, b"a")) + == f"" + ) + + def test_key_and_certificates_constructor(self, backend): + with pytest.raises(TypeError): + PKCS12KeyAndCertificates( + "hello", None, [] # type:ignore[arg-type] + ) + with pytest.raises(TypeError): + PKCS12KeyAndCertificates( + None, "hello", [] # type:ignore[arg-type] + ) + with pytest.raises(TypeError): + PKCS12KeyAndCertificates( + None, None, ["hello"] # type:ignore[list-item] + ) + + def test_key_and_certificates_equality(self, backend): + cert, key = _load_ca(backend) + cert2 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + + p12a = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12b = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert, b"name"), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12c = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert2, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12d = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert3, None), PKCS12Certificate(cert2, None)], + ) + p12e = PKCS12KeyAndCertificates( + None, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12f = PKCS12KeyAndCertificates( + None, + PKCS12Certificate(cert2, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12g = PKCS12KeyAndCertificates( + key, + None, + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12h = PKCS12KeyAndCertificates(None, None, []) + + assert p12a == p12a + assert p12h == p12h + + assert p12a != p12b + assert p12a != p12c + assert p12a != p12d + assert p12a != p12e + assert p12a != p12g + assert p12a != p12h + assert p12e != p12f + assert p12e != p12g + assert p12e != p12h + + assert p12e != "test" + + def test_key_and_certificates_hash(self, backend): + cert, key = _load_ca(backend) + cert2 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + + p12a = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12b = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert, b"name"), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12c = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert2, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12d = PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert3, None), PKCS12Certificate(cert2, None)], + ) + p12e = PKCS12KeyAndCertificates( + None, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12f = PKCS12KeyAndCertificates( + None, + PKCS12Certificate(cert2, None), + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12g = PKCS12KeyAndCertificates( + key, + None, + [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], + ) + p12h = PKCS12KeyAndCertificates(None, None, []) + + assert hash(p12a) == hash(p12a) + assert hash(p12h) == hash(p12h) + + assert hash(p12a) != hash(p12b) + assert hash(p12a) != hash(p12c) + assert hash(p12a) != hash(p12d) + assert hash(p12a) != hash(p12e) + assert hash(p12a) != hash(p12g) + assert hash(p12a) != hash(p12h) + assert hash(p12e) != hash(p12f) + assert hash(p12e) != hash(p12g) + assert hash(p12e) != hash(p12h) + + def test_key_and_certificates_repr(self, backend): + cert, key = _load_ca(backend) + cert2 = _load_cert( + backend, os.path.join("x509", "cryptography.io.pem") + ) + assert ( + repr( + PKCS12KeyAndCertificates( + key, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert2, b"name2")], + ) + ) + == ", additional_certs=[])>".format( + key, + cert, + cert2, + ) + ) diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 8b93cb6334ba..172cf40bd6e4 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -2,40 +2,44 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +import email.parser import os +import typing import pytest from cryptography import x509 from cryptography.exceptions import _Reasons from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.hazmat.primitives.asymmetric import ed25519, rsa from cryptography.hazmat.primitives.serialization import pkcs7 -from .utils import load_vectors_from_file -from ...utils import raises_unsupported_algorithm +from ...utils import load_vectors_from_file, raises_unsupported_algorithm -class TestPKCS7Loading(object): - def test_load_invalid_der_pkcs7(self): +@pytest.mark.supported( + only_if=lambda backend: backend.pkcs7_supported(), + skip_message="Requires OpenSSL with PKCS7 support", +) +class TestPKCS7Loading: + def test_load_invalid_der_pkcs7(self, backend): with pytest.raises(ValueError): pkcs7.load_der_pkcs7_certificates(b"nonsense") - def test_load_invalid_pem_pkcs7(self): + def test_load_invalid_pem_pkcs7(self, backend): with pytest.raises(ValueError): pkcs7.load_pem_pkcs7_certificates(b"nonsense") - def test_not_bytes_der(self): + def test_not_bytes_der(self, backend): with pytest.raises(TypeError): - pkcs7.load_der_pkcs7_certificates(38) + pkcs7.load_der_pkcs7_certificates(38) # type: ignore[arg-type] - def test_not_bytes_pem(self): + def test_not_bytes_pem(self, backend): with pytest.raises(TypeError): - pkcs7.load_pem_pkcs7_certificates(38) + pkcs7.load_pem_pkcs7_certificates(38) # type: ignore[arg-type] - def test_load_pkcs7_pem(self): + def test_load_pkcs7_pem(self, backend): certs = load_vectors_from_file( os.path.join("pkcs7", "isrg.pem"), lambda pemfile: pkcs7.load_pem_pkcs7_certificates(pemfile.read()), @@ -44,13 +48,18 @@ def test_load_pkcs7_pem(self): assert len(certs) == 1 assert certs[0].subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME - ) == [ - x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, u"ISRG Root X1") - ] + ) == [x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "ISRG Root X1")] - def test_load_pkcs7_der(self): - certs = load_vectors_from_file( + @pytest.mark.parametrize( + "filepath", + [ + os.path.join("pkcs7", "amazon-roots.der"), os.path.join("pkcs7", "amazon-roots.p7b"), + ], + ) + def test_load_pkcs7_der(self, filepath, backend): + certs = load_vectors_from_file( + filepath, lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), mode="rb", ) @@ -59,18 +68,18 @@ def test_load_pkcs7_der(self): x509.oid.NameOID.COMMON_NAME ) == [ x509.NameAttribute( - x509.oid.NameOID.COMMON_NAME, u"Amazon Root CA 3" + x509.oid.NameOID.COMMON_NAME, "Amazon Root CA 3" ) ] assert certs[1].subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME ) == [ x509.NameAttribute( - x509.oid.NameOID.COMMON_NAME, u"Amazon Root CA 2" + x509.oid.NameOID.COMMON_NAME, "Amazon Root CA 2" ) ] - def test_load_pkcs7_unsupported_type(self): + def test_load_pkcs7_unsupported_type(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): load_vectors_from_file( os.path.join("pkcs7", "enveloped.pem"), @@ -106,8 +115,12 @@ def _pkcs7_verify(encoding, sig, msg, certs, options, backend): store = backend._lib.X509_STORE_new() backend.openssl_assert(store != backend._ffi.NULL) store = backend._ffi.gc(store, backend._lib.X509_STORE_free) + # This list is to keep the x509 values alive until end of function + ossl_certs = [] for cert in certs: - res = backend._lib.X509_STORE_add_cert(store, cert._x509) + ossl_cert = backend._cert2ossl(cert) + ossl_certs.append(ossl_cert) + res = backend._lib.X509_STORE_add_cert(store, ossl_cert) backend.openssl_assert(res == 1) if msg is None: res = backend._lib.PKCS7_verify( @@ -120,17 +133,24 @@ def _pkcs7_verify(encoding, sig, msg, certs, options, backend): ) else: msg_bio = backend._bytes_to_bio(msg) + # libressl 3.7.0 has a bug when NULL is passed as an `out_bio`. Work + # around it for now. + out_bio = backend._create_mem_bio_gc() res = backend._lib.PKCS7_verify( - p7, backend._ffi.NULL, store, msg_bio.bio, backend._ffi.NULL, flags + p7, backend._ffi.NULL, store, msg_bio.bio, out_bio, flags ) backend.openssl_assert(res == 1) + # OpenSSL 3.0 leaves a random bio error on the stack: + # https://github.com/openssl/openssl/issues/16681 + if backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + backend._consume_errors() def _load_cert_key(): key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "ca_key.pem"), lambda pemfile: serialization.load_pem_private_key( - pemfile.read(), None + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -142,23 +162,27 @@ def _load_cert_key(): return cert, key -class TestPKCS7Builder(object): - def test_invalid_data(self): +@pytest.mark.supported( + only_if=lambda backend: backend.pkcs7_supported(), + skip_message="Requires OpenSSL with PKCS7 support", +) +class TestPKCS7Builder: + def test_invalid_data(self, backend): builder = pkcs7.PKCS7SignatureBuilder() with pytest.raises(TypeError): - builder.set_data(u"not bytes") + builder.set_data("not bytes") # type: ignore[arg-type] - def test_set_data_twice(self): + def test_set_data_twice(self, backend): builder = pkcs7.PKCS7SignatureBuilder().set_data(b"test") with pytest.raises(ValueError): builder.set_data(b"test") - def test_sign_no_signer(self): + def test_sign_no_signer(self, backend): builder = pkcs7.PKCS7SignatureBuilder().set_data(b"test") with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, []) - def test_sign_no_data(self): + def test_sign_no_data(self, backend): cert, key = _load_cert_key() builder = pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, hashes.SHA256() @@ -166,18 +190,18 @@ def test_sign_no_data(self): with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, []) - def test_unsupported_hash_alg(self): + def test_unsupported_hash_alg(self, backend): cert, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( - cert, key, hashes.SHA512_256() + cert, key, hashes.SHA512_256() # type: ignore[arg-type] ) - def test_not_a_cert(self): + def test_not_a_cert(self, backend): cert, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( - b"notacert", key, hashes.SHA256() + b"notacert", key, hashes.SHA256() # type: ignore[arg-type] ) @pytest.mark.supported( @@ -189,10 +213,10 @@ def test_unsupported_key_type(self, backend): key = ed25519.Ed25519PrivateKey.generate() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( - cert, key, hashes.SHA256() + cert, key, hashes.SHA256() # type: ignore[arg-type] ) - def test_sign_invalid_options(self): + def test_sign_invalid_options(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() @@ -200,9 +224,12 @@ def test_sign_invalid_options(self): .add_signer(cert, key, hashes.SHA256()) ) with pytest.raises(ValueError): - builder.sign(serialization.Encoding.SMIME, [b"invalid"]) + builder.sign( + serialization.Encoding.SMIME, + [b"invalid"], # type: ignore[list-item] + ) - def test_sign_invalid_encoding(self): + def test_sign_invalid_encoding(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() @@ -212,7 +239,7 @@ def test_sign_invalid_encoding(self): with pytest.raises(ValueError): builder.sign(serialization.Encoding.Raw, []) - def test_sign_invalid_options_text_no_detached(self): + def test_sign_invalid_options_text_no_detached(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() @@ -223,7 +250,7 @@ def test_sign_invalid_options_text_no_detached(self): with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, options) - def test_sign_invalid_options_text_der_encoding(self): + def test_sign_invalid_options_text_der_encoding(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() @@ -237,7 +264,7 @@ def test_sign_invalid_options_text_der_encoding(self): with pytest.raises(ValueError): builder.sign(serialization.Encoding.DER, options) - def test_sign_invalid_options_no_attrs_and_no_caps(self): + def test_sign_invalid_options_no_attrs_and_no_caps(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() @@ -263,6 +290,7 @@ def test_smime_sign_detached(self, backend): sig = builder.sign(serialization.Encoding.SMIME, options) sig_binary = builder.sign(serialization.Encoding.DER, options) + assert b"text/plain" not in sig # We don't have a generic ASN.1 parser available to us so we instead # will assert on specific byte sequences being present based on the # parameters chosen above. @@ -272,8 +300,17 @@ def test_smime_sign_detached(self, backend): # as a separate section before the PKCS7 data. So we should expect to # have data in sig but not in sig_binary assert data in sig + # Parse the message to get the signed data, which is the + # first payload in the message + message = email.parser.BytesParser().parsebytes(sig) + signed_data = message.get_payload()[0].get_payload().encode() _pkcs7_verify( - serialization.Encoding.SMIME, sig, data, [cert], options, backend + serialization.Encoding.SMIME, + sig, + signed_data, + [cert], + options, + backend, ) assert data not in sig_binary _pkcs7_verify( @@ -285,7 +322,7 @@ def test_smime_sign_detached(self, backend): backend, ) - def test_sign_byteslike(self): + def test_sign_byteslike(self, backend): data = bytearray(b"hello world") cert, key = _load_cert_key() options = [pkcs7.PKCS7Options.DetachedSignature] @@ -297,11 +334,36 @@ def test_sign_byteslike(self): sig = builder.sign(serialization.Encoding.SMIME, options) assert bytes(data) in sig + _pkcs7_verify( + serialization.Encoding.SMIME, + sig, + data, + [cert], + options, + backend, + ) + + data = bytearray(b"") + builder = ( + pkcs7.PKCS7SignatureBuilder() + .set_data(data) + .add_signer(cert, key, hashes.SHA256()) + ) + + sig = builder.sign(serialization.Encoding.SMIME, options) + _pkcs7_verify( + serialization.Encoding.SMIME, + sig, + data, + [cert], + options, + backend, + ) def test_sign_pem(self, backend): data = b"hello world" cert, key = _load_cert_key() - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) @@ -321,7 +383,6 @@ def test_sign_pem(self, backend): @pytest.mark.parametrize( ("hash_alg", "expected_value"), [ - (hashes.SHA1(), b"\x06\x05+\x0e\x03\x02\x1a"), (hashes.SHA256(), b"\x06\t`\x86H\x01e\x03\x04\x02\x01"), (hashes.SHA384(), b"\x06\t`\x86H\x01e\x03\x04\x02\x02"), (hashes.SHA512(), b"\x06\t`\x86H\x01e\x03\x04\x02\x03"), @@ -337,7 +398,7 @@ def test_sign_alternate_digests_der( .set_data(data) .add_signer(cert, key, hash_alg) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert expected_value in sig _pkcs7_verify( @@ -347,13 +408,14 @@ def test_sign_alternate_digests_der( @pytest.mark.parametrize( ("hash_alg", "expected_value"), [ - (hashes.SHA1(), b"sha1"), (hashes.SHA256(), b"sha-256"), (hashes.SHA384(), b"sha-384"), (hashes.SHA512(), b"sha-512"), ], ) - def test_sign_alternate_digests_detached(self, hash_alg, expected_value): + def test_sign_alternate_digests_detached( + self, hash_alg, expected_value, backend + ): data = b"hello world" cert, key = _load_cert_key() builder = ( @@ -370,7 +432,7 @@ def test_sign_alternate_digests_detached(self, hash_alg, expected_value): def test_sign_attached(self, backend): data = b"hello world" cert, key = _load_cert_key() - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) @@ -398,7 +460,7 @@ def test_sign_binary(self, backend): .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig_no_binary = builder.sign(serialization.Encoding.DER, options) sig_binary = builder.sign( serialization.Encoding.DER, [pkcs7.PKCS7Options.Binary] @@ -434,7 +496,7 @@ def test_sign_smime_canonicalization(self, backend): .add_signer(cert, key, hashes.SHA256()) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig_binary = builder.sign(serialization.Encoding.DER, options) # LF gets converted to CR+LF (SMIME canonical form) # so data should not be present in the sig @@ -466,10 +528,14 @@ def test_sign_text(self, backend): # The text option adds text/plain headers to the S/MIME message # These headers are only relevant in SMIME mode, not binary, which is # just the PKCS7 structure itself. - assert b"text/plain" in sig_pem - # When passing the Text option the header is prepended so the actual - # signed data is this. - signed_data = b"Content-Type: text/plain\r\n\r\nhello world" + assert sig_pem.count(b"text/plain") == 1 + assert b"Content-Type: text/plain\r\n\r\nhello world\r\n" in sig_pem + # Parse the message to get the signed data, which is the + # first payload in the message + message = email.parser.BytesParser().parsebytes(sig_pem) + signed_data = message.get_payload()[0].as_bytes( + policy=message.policy.clone(linesep="\r\n") + ) _pkcs7_verify( serialization.Encoding.SMIME, sig_pem, @@ -544,7 +610,7 @@ def test_sign_no_certs(self, backend): .add_signer(cert, key, hashes.SHA256()) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert sig.count(cert.public_bytes(serialization.Encoding.DER)) == 1 @@ -558,10 +624,11 @@ def test_multiple_signers(self, backend): rsa_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( - pemfile.read(), None + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) + assert isinstance(rsa_key, rsa.RSAPrivateKey) rsa_cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate( @@ -575,7 +642,7 @@ def test_multiple_signers(self, backend): .add_signer(cert, key, hashes.SHA512()) .add_signer(rsa_cert, rsa_key, hashes.SHA512()) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) # There should be three SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 3 @@ -594,7 +661,7 @@ def test_multiple_signers_different_hash_algs(self, backend): rsa_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( - pemfile.read(), None + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -605,13 +672,14 @@ def test_multiple_signers_different_hash_algs(self, backend): ), mode="rb", ) + assert isinstance(rsa_key, rsa.RSAPrivateKey) builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA384()) .add_signer(rsa_cert, rsa_key, hashes.SHA512()) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) # There should be two SHA384 and two SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x02") == 2 @@ -627,7 +695,9 @@ def test_multiple_signers_different_hash_algs(self, backend): def test_add_additional_cert_not_a_cert(self, backend): with pytest.raises(TypeError): - pkcs7.PKCS7SignatureBuilder().add_certificate(b"notacert") + pkcs7.PKCS7SignatureBuilder().add_certificate( + b"notacert" # type: ignore[arg-type] + ) def test_add_additional_cert(self, backend): data = b"hello world" @@ -645,7 +715,7 @@ def test_add_additional_cert(self, backend): .add_signer(cert, key, hashes.SHA384()) .add_certificate(rsa_cert) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert ( sig.count(rsa_cert.public_bytes(serialization.Encoding.DER)) == 1 @@ -668,8 +738,83 @@ def test_add_multiple_additional_certs(self, backend): .add_certificate(rsa_cert) .add_certificate(rsa_cert) ) - options = [] + options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert ( sig.count(rsa_cert.public_bytes(serialization.Encoding.DER)) == 2 ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.pkcs7_supported(), + skip_message="Requires OpenSSL with PKCS7 support", +) +class TestPKCS7SerializeCerts: + @pytest.mark.parametrize( + ("encoding", "loader"), + [ + (serialization.Encoding.PEM, pkcs7.load_pem_pkcs7_certificates), + (serialization.Encoding.DER, pkcs7.load_der_pkcs7_certificates), + ], + ) + def test_roundtrip(self, encoding, loader, backend): + certs = load_vectors_from_file( + os.path.join("pkcs7", "amazon-roots.der"), + lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), + mode="rb", + ) + p7 = pkcs7.serialize_certificates(certs, encoding) + certs2 = loader(p7) + assert certs == certs2 + + def test_ordering(self, backend): + certs = load_vectors_from_file( + os.path.join("pkcs7", "amazon-roots.der"), + lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), + mode="rb", + ) + p7 = pkcs7.serialize_certificates( + list(reversed(certs)), serialization.Encoding.DER + ) + certs2 = pkcs7.load_der_pkcs7_certificates(p7) + assert certs == certs2 + + def test_pem_matches_vector(self, backend): + p7_pem = load_vectors_from_file( + os.path.join("pkcs7", "isrg.pem"), + lambda p: p.read(), + mode="rb", + ) + certs = pkcs7.load_pem_pkcs7_certificates(p7_pem) + p7 = pkcs7.serialize_certificates(certs, serialization.Encoding.PEM) + assert p7 == p7_pem + + def test_der_matches_vector(self, backend): + p7_der = load_vectors_from_file( + os.path.join("pkcs7", "amazon-roots.der"), + lambda p: p.read(), + mode="rb", + ) + certs = pkcs7.load_der_pkcs7_certificates(p7_der) + p7 = pkcs7.serialize_certificates(certs, serialization.Encoding.DER) + assert p7 == p7_der + + def test_invalid_types(self): + certs = load_vectors_from_file( + os.path.join("pkcs7", "amazon-roots.der"), + lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), + mode="rb", + ) + with pytest.raises(TypeError): + pkcs7.serialize_certificates( + object(), # type: ignore[arg-type] + serialization.Encoding.PEM, + ) + + with pytest.raises(TypeError): + pkcs7.serialize_certificates([], serialization.Encoding.PEM) + + with pytest.raises(TypeError): + pkcs7.serialize_certificates( + certs, "not an encoding" # type: ignore[arg-type] + ) diff --git a/tests/hazmat/primitives/test_poly1305.py b/tests/hazmat/primitives/test_poly1305.py index 8779484ac9aa..1a579bbd34bc 100644 --- a/tests/hazmat/primitives/test_poly1305.py +++ b/tests/hazmat/primitives/test_poly1305.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os @@ -36,7 +35,7 @@ def test_poly1305_unsupported(backend): only_if=lambda backend: backend.poly1305_supported(), skip_message="Requires OpenSSL with poly1305 support", ) -class TestPoly1305(object): +class TestPoly1305: @pytest.mark.parametrize( "vector", load_vectors_from_file( @@ -71,10 +70,10 @@ def test_raises_after_finalize(self, backend): def test_reject_unicode(self, backend): poly = Poly1305(b"0" * 32) with pytest.raises(TypeError): - poly.update(u"") + poly.update("") # type:ignore[arg-type] with pytest.raises(TypeError): - Poly1305.generate_tag(b"0" * 32, u"") + Poly1305.generate_tag(b"0" * 32, "") # type:ignore[arg-type] def test_verify(self, backend): poly = Poly1305(b"0" * 32) @@ -107,17 +106,17 @@ def test_invalid_verify(self, backend): def test_verify_reject_unicode(self, backend): poly = Poly1305(b"0" * 32) with pytest.raises(TypeError): - poly.verify(u"") + poly.verify("") # type:ignore[arg-type] with pytest.raises(TypeError): - Poly1305.verify_tag(b"0" * 32, b"msg", u"") + Poly1305.verify_tag(b"0" * 32, b"msg", "") # type:ignore[arg-type] def test_invalid_key_type(self, backend): with pytest.raises(TypeError): - Poly1305(object()) + Poly1305(object()) # type:ignore[arg-type] with pytest.raises(TypeError): - Poly1305.generate_tag(object(), b"msg") + Poly1305.generate_tag(object(), b"msg") # type:ignore[arg-type] def test_invalid_key_length(self, backend): with pytest.raises(ValueError): diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index d7fa7744f493..017e02d424b2 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import itertools @@ -11,27 +10,35 @@ import pytest from cryptography.exceptions import ( - AlreadyFinalized, InvalidSignature, + UnsupportedAlgorithm, _Reasons, ) -from cryptography.hazmat.backends.interfaces import ( - PEMSerializationBackend, - RSABackend, -) from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ( - padding, - rsa, - utils as asym_utils, -) +from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers, ) -from cryptography.utils import CryptographyDeprecationWarning +from ...doubles import ( + DummyAsymmetricPadding, + DummyHashAlgorithm, + DummyKeySerializationEncryption, +) +from ...utils import ( + load_nist_vectors, + load_pkcs1_vectors, + load_rsa_nist_vectors, + load_vectors_from_file, + raises_unsupported_algorithm, +) from .fixtures_rsa import ( + RSA_KEY_512, + RSA_KEY_522, + RSA_KEY_599, + RSA_KEY_745, RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, @@ -43,12 +50,6 @@ RSA_KEY_1536, RSA_KEY_2048, RSA_KEY_2048_ALT, - RSA_KEY_512, - RSA_KEY_512_ALT, - RSA_KEY_522, - RSA_KEY_599, - RSA_KEY_745, - RSA_KEY_768, RSA_KEY_CORRUPTED, ) from .utils import ( @@ -56,26 +57,33 @@ generate_rsa_verification_test, skip_fips_traditional_openssl, ) -from ...doubles import ( - DummyAsymmetricPadding, - DummyHashAlgorithm, - DummyKeySerializationEncryption, -) -from ...utils import ( - load_nist_vectors, - load_pkcs1_vectors, - load_rsa_nist_vectors, - load_vectors_from_file, - raises_unsupported_algorithm, -) -class DummyMGF(object): +@pytest.fixture(scope="session") +def rsa_key_512() -> rsa.RSAPrivateKey: + return RSA_KEY_512.private_key(unsafe_skip_rsa_key_validation=True) + + +@pytest.fixture(scope="session") +def rsa_key_2048() -> rsa.RSAPrivateKey: + return RSA_KEY_2048.private_key(unsafe_skip_rsa_key_validation=True) + + +class DummyMGF(padding.MGF): _salt_length = 0 + _algorithm = hashes.SHA1() + + +def _check_fips_key_length(backend, private_key): + if ( + backend._fips_enabled + and private_key.key_size < backend._fips_rsa_min_key_size + ): + pytest.skip(f"Key size not FIPS compliant: {private_key.key_size}") def _check_rsa_private_numbers_if_serializable(key): - if isinstance(key, rsa.RSAPrivateKeyWithSerialization): + if isinstance(key, rsa.RSAPrivateKey): _check_rsa_private_numbers(key.private_numbers()) @@ -115,7 +123,7 @@ def _build_oaep_sha2_vectors(): load_vectors_from_file( os.path.join( base_path, - "oaep-{}-{}.txt".format(mgf1alg.name, oaepalg.name), + f"oaep-{mgf1alg.name}-{oaepalg.name}.txt", ), load_pkcs1_vectors, ) @@ -134,12 +142,9 @@ def _skip_pss_hash_algorithm_unsupported(backend, hash_alg): mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH ) ): - pytest.skip( - "Does not support {} in MGF1 using PSS.".format(hash_alg.name) - ) + pytest.skip(f"Does not support {hash_alg.name} in MGF1 using PSS.") -@pytest.mark.requires_backend_interface(interface=RSABackend) def test_skip_pss_hash_algorithm_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_pss_hash_algorithm_unsupported(backend, DummyHashAlgorithm()) @@ -169,23 +174,20 @@ def test_modular_inverse(): ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSA(object): +class TestRSA: @pytest.mark.parametrize( ("public_exponent", "key_size"), itertools.product( (3, 65537), - (1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1536, 2048), + (1024, 1536, 2048), ), ) def test_generate_rsa_keys(self, backend, public_exponent, key_size): if backend._fips_enabled: if key_size < backend._fips_rsa_min_key_size: - pytest.skip("Key size not FIPS compliant: {}".format(key_size)) + pytest.skip(f"Key size not FIPS compliant: {key_size}") if public_exponent < backend._fips_rsa_min_public_exponent: - pytest.skip( - "Exponent not FIPS compliant: {}".format(public_exponent) - ) + pytest.skip(f"Exponent not FIPS compliant: {public_exponent}") skey = rsa.generate_private_key(public_exponent, key_size, backend) assert skey.key_size == key_size @@ -256,6 +258,91 @@ def test_load_pss_vect_example_keys(self, pkcs1_example): assert public_num.n == public_num2.n assert public_num.e == public_num2.e + @pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + ), + skip_message="Does not support RSA PSS loading", + ) + @pytest.mark.parametrize( + "path", + [ + os.path.join("asymmetric", "PKCS8", "rsa_pss_2048.pem"), + os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_hash.pem"), + os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_hash_mask.pem"), + os.path.join( + "asymmetric", "PKCS8", "rsa_pss_2048_hash_mask_diff.pem" + ), + os.path.join( + "asymmetric", "PKCS8", "rsa_pss_2048_hash_mask_salt.pem" + ), + ], + ) + def test_load_pss_keys_strips_constraints(self, path, backend): + key = load_vectors_from_file( + filename=path, + loader=lambda p: serialization.load_pem_private_key( + p.read(), password=None, unsafe_skip_rsa_key_validation=True + ), + mode="rb", + ) + # These keys have constraints that prohibit PKCS1v15 signing, + # but for now we load them without the constraint and test that + # it's truly removed by performing a disallowed signature. + assert isinstance(key, rsa.RSAPrivateKey) + signature = key.sign(b"whatever", padding.PKCS1v15(), hashes.SHA224()) + key.public_key().verify( + signature, b"whatever", padding.PKCS1v15(), hashes.SHA224() + ) + + @pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + ), + skip_message="Does not support RSA PSS loading", + ) + def test_load_pss_pub_keys_strips_constraints(self, backend): + key = load_vectors_from_file( + filename=os.path.join( + "asymmetric", "PKCS8", "rsa_pss_2048_pub.der" + ), + loader=lambda p: serialization.load_der_public_key( + p.read(), + ), + mode="rb", + ) + assert isinstance(key, rsa.RSAPublicKey) + with pytest.raises(InvalidSignature): + key.verify( + b"badsig", b"whatever", padding.PKCS1v15(), hashes.SHA256() + ) + + @pytest.mark.supported( + only_if=lambda backend: ( + backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + or backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + or backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + ), + skip_message="Test requires a backend without RSA-PSS key support", + ) + def test_load_pss_unsupported(self, backend): + # Key loading errors unfortunately have multiple paths so + # we need to allow ValueError and UnsupportedAlgorithm + with pytest.raises((UnsupportedAlgorithm, ValueError)): + load_vectors_from_file( + filename=os.path.join( + "asymmetric", "PKCS8", "rsa_pss_2048.pem" + ), + loader=lambda p: serialization.load_pem_private_key( + p.read(), password=None + ), + mode="rb", + ) + @pytest.mark.parametrize( "vector", load_vectors_from_file( @@ -275,8 +362,12 @@ def test_load_pss_vect_example_keys(self, pkcs1_example): ) def test_oaep_label_decrypt(self, vector, backend): private_key = serialization.load_der_private_key( - binascii.unhexlify(vector["key"]), None, backend + binascii.unhexlify(vector["key"]), + None, + backend, + unsafe_skip_rsa_key_validation=True, ) + assert isinstance(private_key, rsa.RSAPrivateKey) assert vector["oaepdigest"] == b"SHA512" decrypted = private_key.decrypt( binascii.unhexlify(vector["input"]), @@ -305,8 +396,8 @@ def test_oaep_label_decrypt(self, vector, backend): ), skip_message="Does not support RSA OAEP labels", ) - def test_oaep_label_roundtrip(self, msg, label, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_oaep_label_roundtrip(self, rsa_key_2048, msg, label, backend): + private_key = rsa_key_2048 ct = private_key.public_key().encrypt( msg, padding.OAEP( @@ -339,8 +430,8 @@ def test_oaep_label_roundtrip(self, msg, label, backend): ), skip_message="Does not support RSA OAEP labels", ) - def test_oaep_wrong_label(self, enclabel, declabel, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_oaep_wrong_label(self, rsa_key_2048, enclabel, declabel, backend): + private_key = rsa_key_2048 msg = b"test" ct = private_key.public_key().encrypt( msg, @@ -361,71 +452,84 @@ def test_oaep_wrong_label(self, enclabel, declabel, backend): ) @pytest.mark.supported( - only_if=lambda backend: not backend.rsa_padding_supported( - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA256(), - label=b"label", - ) + only_if=lambda backend: backend.rsa_encryption_supported( + padding.PKCS1v15() ), - skip_message="Requires backend without RSA OAEP label support", + skip_message="Does not support PKCS1v1.5.", ) - def test_unsupported_oaep_label_decrypt(self, backend): - private_key = RSA_KEY_512.private_key(backend) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt( - b"0" * 64, - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=b"label", - ), - ) - - -def test_rsa_generate_invalid_backend(): - pretend_backend = object() - - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - rsa.generate_private_key(65537, 2048, pretend_backend) + def test_lazy_blinding(self, backend): + # We don't want to reuse the rsa_key_2048 fixture here because lazy + # blinding mutates the object to add the blinding factor on + # the first call to decrypt/sign. Since we reuse rsa_key_2048 in + # many tests we can't properly test blinding, which will (likely) + # already be set on the fixture. + private_key = RSA_KEY_2048.private_key( + unsafe_skip_rsa_key_validation=True + ) + public_key = private_key.public_key() + msg = b"encrypt me!" + ct = public_key.encrypt( + msg, + padding.PKCS1v15(), + ) + assert private_key._blinded is False # type: ignore[attr-defined] + pt = private_key.decrypt( + ct, + padding.PKCS1v15(), + ) + assert private_key._blinded is True # type: ignore[attr-defined] + # Call a second time to cover the branch where blinding + # has already occurred and we don't want to do it again. + pt2 = private_key.decrypt( + ct, + padding.PKCS1v15(), + ) + assert pt == pt2 + assert private_key._blinded is True + # Private method call to cover the racy branch within the lock + private_key._non_threadsafe_enable_blinding() + assert private_key._blinded is True -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSASignature(object): +class TestRSASignature: @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) - @pytest.mark.parametrize( - "pkcs1_example", - _flatten_pkcs1_examples( + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + def test_pkcs1v15_signing(self, backend, subtests): + vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), load_pkcs1_vectors, ) - ), - ) - def test_pkcs1v15_signing(self, pkcs1_example, backend): - private, public, example = pkcs1_example - private_key = rsa.RSAPrivateNumbers( - p=private["p"], - q=private["q"], - d=private["private_exponent"], - dmp1=private["dmp1"], - dmq1=private["dmq1"], - iqmp=private["iqmp"], - public_numbers=rsa.RSAPublicNumbers( - e=private["public_exponent"], n=private["modulus"] - ), - ).private_key(backend) - signature = private_key.sign( - binascii.unhexlify(example["message"]), - padding.PKCS1v15(), - hashes.SHA1(), ) - assert binascii.hexlify(signature) == example["signature"] + for private, public, example in vectors: + with subtests.test(): + private_key = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], n=private["modulus"] + ), + ).private_key(backend, unsafe_skip_rsa_key_validation=True) + signature = private_key.sign( + binascii.unhexlify(example["message"]), + padding.PKCS1v15(), + hashes.SHA1(), + ) + assert binascii.hexlify(signature) == example["signature"] @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -436,62 +540,65 @@ def test_pkcs1v15_signing(self, pkcs1_example, backend): ), skip_message="Does not support PSS.", ) - @pytest.mark.parametrize( - "pkcs1_example", - _flatten_pkcs1_examples( + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + def test_pss_signing(self, subtests, backend): + for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt" ), load_pkcs1_vectors, ) - ), - ) - def test_pss_signing(self, pkcs1_example, backend): - private, public, example = pkcs1_example - private_key = rsa.RSAPrivateNumbers( - p=private["p"], - q=private["q"], - d=private["private_exponent"], - dmp1=private["dmp1"], - dmq1=private["dmq1"], - iqmp=private["iqmp"], - public_numbers=rsa.RSAPublicNumbers( - e=private["public_exponent"], n=private["modulus"] - ), - ).private_key(backend) - public_key = rsa.RSAPublicNumbers( - e=public["public_exponent"], n=public["modulus"] - ).public_key(backend) - signature = private_key.sign( - binascii.unhexlify(example["message"]), - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH, - ), - hashes.SHA1(), - ) - assert len(signature) == (private_key.key_size + 7) // 8 - # PSS signatures contain randomness so we can't do an exact - # signature check. Instead we'll verify that the signature created - # successfully verifies. - public_key.verify( - signature, - binascii.unhexlify(example["message"]), - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH, - ), - hashes.SHA1(), - ) + ): + with subtests.test(): + private_key = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], n=private["modulus"] + ), + ).private_key(backend, unsafe_skip_rsa_key_validation=True) + public_key = rsa.RSAPublicNumbers( + e=public["public_exponent"], n=public["modulus"] + ).public_key(backend) + signature = private_key.sign( + binascii.unhexlify(example["message"]), + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH, + ), + hashes.SHA1(), + ) + assert len(signature) == (private_key.key_size + 7) // 8 + # PSS signatures contain randomness so we can't do an exact + # signature check. Instead we'll verify that the signature + # created successfully verifies. + public_key.verify( + signature, + binascii.unhexlify(example["message"]), + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH, + ), + hashes.SHA1(), + ) @pytest.mark.parametrize( "hash_alg", [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()], ) - def test_pss_signing_sha2(self, hash_alg, backend): + def test_pss_signing_sha2(self, rsa_key_2048, hash_alg, backend): _skip_pss_hash_algorithm_unsupported(backend, hash_alg) - private_key = RSA_KEY_768.private_key(backend) + private_key = rsa_key_2048 public_key = private_key.public_key() pss = padding.PSS( mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH @@ -500,6 +607,45 @@ def test_pss_signing_sha2(self, hash_alg, backend): signature = private_key.sign(msg, pss, hash_alg) public_key.verify(signature, msg, pss, hash_alg) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ) + ), + skip_message="Does not support PSS.", + ) + def test_pss_digest_length(self, rsa_key_2048, backend): + private_key = rsa_key_2048 + signature = private_key.sign( + b"some data", + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ), + hashes.SHA256(), + ) + public = private_key.public_key() + public.verify( + signature, + b"some data", + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ), + hashes.SHA256(), + ) + public.verify( + signature, + b"some data", + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=32, + ), + hashes.SHA256(), + ) + @pytest.mark.supported( only_if=lambda backend: ( backend.hash_supported(hashes.SHA512()) @@ -512,8 +658,11 @@ def test_pss_signing_sha2(self, hash_alg, backend): ), skip_message="Does not support SHA512.", ) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pss_minimum_key_size_for_digest(self, backend): - private_key = RSA_KEY_522.private_key(backend) + private_key = RSA_KEY_522.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) private_key.sign( b"no failure", padding.PSS( @@ -536,8 +685,11 @@ def test_pss_minimum_key_size_for_digest(self, backend): only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512.", ) - def test_pss_signing_digest_too_large_for_key_size(self, backend): - private_key = RSA_KEY_512.private_key(backend) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") + def test_pss_signing_digest_too_large_for_key_size( + self, rsa_key_512: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_512 with pytest.raises(ValueError): private_key.sign( b"msg", @@ -557,43 +709,36 @@ def test_pss_signing_digest_too_large_for_key_size(self, backend): ), skip_message="Does not support PSS.", ) - def test_pss_signing_salt_length_too_long(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_pss_signing_salt_length_too_long( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 with pytest.raises(ValueError): private_key.sign( b"failure coming", padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=1000000 ), - hashes.SHA1(), + hashes.SHA256(), ) - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.PKCS1v15() - ), - skip_message="Does not support PKCS1v1.5.", - ) - def test_use_after_finalize(self, backend): - private_key = RSA_KEY_512.private_key(backend) - with pytest.warns(CryptographyDeprecationWarning): - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(b"sign me") - signer.finalize() - with pytest.raises(AlreadyFinalized): - signer.finalize() - with pytest.raises(AlreadyFinalized): - signer.update(b"more data") - - def test_unsupported_padding(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_padding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.sign(b"msg", DummyAsymmetricPadding(), hashes.SHA1()) - def test_padding_incorrect_type(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_padding_incorrect_type( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 with pytest.raises(TypeError): - private_key.sign(b"msg", "notpadding", hashes.SHA1()) + private_key.sign( + b"msg", + "notpadding", # type: ignore[arg-type] + hashes.SHA256(), + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -601,25 +746,54 @@ def test_padding_incorrect_type(self, backend): ), skip_message="Does not support PSS.", ) - def test_unsupported_pss_mgf(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_pss_mgf( + self, rsa_key_512: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_512 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.sign( b"msg", padding.PSS( - mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH + mgf=DummyMGF(), + salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA1(), ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.AUTO, + ) + ), + skip_message="Does not support PSS.", + ) + def test_pss_sign_unsupported_auto( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 + with pytest.raises(ValueError): + private_key.sign( + b"some data", + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.AUTO, + ), + hashes.SHA256(), + ) + @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pkcs1_digest_too_large_for_key_size(self, backend): - private_key = RSA_KEY_599.private_key(backend) + private_key = RSA_KEY_599.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) with pytest.raises(ValueError): private_key.sign( b"failure coming", padding.PKCS1v15(), hashes.SHA512() @@ -631,15 +805,18 @@ def test_pkcs1_digest_too_large_for_key_size(self, backend): ), skip_message="Does not support PKCS1v1.5.", ) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pkcs1_minimum_key_size(self, backend): - private_key = RSA_KEY_745.private_key(backend) + private_key = RSA_KEY_745.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) private_key.sign(b"no failure", padding.PKCS1v15(), hashes.SHA512()) - def test_sign(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_sign(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 message = b"one little message" pkcs = padding.PKCS1v15() - algorithm = hashes.SHA1() + algorithm = hashes.SHA256() signature = private_key.sign(message, pkcs, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, pkcs, algorithm) @@ -650,17 +827,43 @@ def test_sign(self, backend): ), skip_message="Does not support PSS.", ) - def test_prehashed_sign(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_prehashed_sign(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 message = b"one little message" - h = hashes.Hash(hashes.SHA1(), backend) + h = hashes.Hash(hashes.SHA256(), backend) h.update(message) digest = h.finalize() pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) - prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) signature = private_key.sign(digest, pss, prehashed_alg) public_key = private_key.public_key() - public_key.verify(signature, message, pss, hashes.SHA1()) + public_key.verify(signature, message, pss, hashes.SHA256()) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ) + ), + skip_message="Does not support PSS.", + ) + def test_prehashed_digest_length( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 + message = b"one little message" + h = hashes.Hash(hashes.SHA256(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ) + prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) + signature = private_key.sign(digest, pss, prehashed_alg) + public_key = private_key.public_key() + public_key.verify(signature, message, pss, hashes.SHA256()) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( @@ -674,8 +877,8 @@ def test_prehashed_sign(self, backend): ), skip_message="Does not support PSS.", ) - def test_unsupported_hash(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_hash(self, rsa_key_512: rsa.RSAPrivateKey, backend): + private_key = rsa_key_512 message = b"one little message" pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): @@ -687,8 +890,10 @@ def test_unsupported_hash(self, backend): ), skip_message="Does not support PSS.", ) - def test_prehashed_digest_mismatch(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_prehashed_digest_mismatch( + self, rsa_key_512: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_512 message = b"one little message" h = hashes.Hash(hashes.SHA512(), backend) h.update(message) @@ -698,36 +903,20 @@ def test_prehashed_digest_mismatch(self, backend): with pytest.raises(ValueError): private_key.sign(digest, pss, prehashed_alg) - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.PKCS1v15() - ), - skip_message="Does not support PKCS1v1.5.", - ) - def test_prehashed_unsupported_in_signer_ctx(self, backend): - private_key = RSA_KEY_512.private_key(backend) - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - private_key.signer( - padding.PKCS1v15(), asym_utils.Prehashed(hashes.SHA1()) - ) - - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.PKCS1v15() - ), - skip_message="Does not support PKCS1v1.5.", - ) - def test_prehashed_unsupported_in_verifier_ctx(self, backend): - public_key = RSA_KEY_512.private_key(backend).public_key() - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - public_key.verifier( - b"0" * 64, + def test_prehashed_unsupported_in_signature_recover( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 + public_key = private_key.public_key() + signature = private_key.sign( + b"sign me", padding.PKCS1v15(), hashes.SHA256() + ) + prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) + with pytest.raises(TypeError): + public_key.recover_data_from_signature( + signature, padding.PKCS1v15(), - asym_utils.Prehashed(hashes.SHA1()), + prehashed_alg, # type: ignore[arg-type] ) def test_corrupted_private_key(self, backend): @@ -737,34 +926,53 @@ def test_corrupted_private_key(self, backend): ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSAVerification(object): +class TestRSAVerification: @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) - @pytest.mark.parametrize( - "pkcs1_example", - _flatten_pkcs1_examples( + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + def test_pkcs1v15_verification(self, backend, subtests): + vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), load_pkcs1_vectors, ) - ), - ) - def test_pkcs1v15_verification(self, pkcs1_example, backend): - private, public, example = pkcs1_example - public_key = rsa.RSAPublicNumbers( - e=public["public_exponent"], n=public["modulus"] - ).public_key(backend) - public_key.verify( - binascii.unhexlify(example["signature"]), - binascii.unhexlify(example["message"]), - padding.PKCS1v15(), - hashes.SHA1(), ) + for private, public, example in vectors: + with subtests.test(): + public_key = rsa.RSAPublicNumbers( + e=public["public_exponent"], n=public["modulus"] + ).public_key(backend) + signature = binascii.unhexlify(example["signature"]) + message = binascii.unhexlify(example["message"]) + public_key.verify( + signature, message, padding.PKCS1v15(), hashes.SHA1() + ) + + # Test digest recovery by providing hash + digest = hashes.Hash(hashes.SHA1()) + digest.update(message) + msg_digest = digest.finalize() + rec_msg_digest = public_key.recover_data_from_signature( + signature, padding.PKCS1v15(), hashes.SHA1() + ) + assert msg_digest == rec_msg_digest + + # Test recovery of all data (full DigestInfo) with hash alg. as + # None + rec_sig_data = public_key.recover_data_from_signature( + signature, padding.PKCS1v15(), None + ) + assert len(rec_sig_data) > len(msg_digest) + assert msg_digest == rec_sig_data[-len(msg_digest) :] @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -772,15 +980,33 @@ def test_pkcs1v15_verification(self, pkcs1_example, backend): ), skip_message="Does not support PKCS1v1.5.", ) - def test_invalid_pkcs1v15_signature_wrong_data(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_invalid_pkcs1v15_signature_wrong_data( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() signature = private_key.sign( - b"sign me", padding.PKCS1v15(), hashes.SHA1() + b"sign me", padding.PKCS1v15(), hashes.SHA256() ) with pytest.raises(InvalidSignature): public_key.verify( - signature, b"incorrect data", padding.PKCS1v15(), hashes.SHA1() + signature, + b"incorrect data", + padding.PKCS1v15(), + hashes.SHA256(), + ) + + def test_invalid_pkcs1v15_signature_recover_wrong_hash_alg( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 + public_key = private_key.public_key() + signature = private_key.sign( + b"sign me", padding.PKCS1v15(), hashes.SHA256() + ) + with pytest.raises(InvalidSignature): + public_key.recover_data_from_signature( + signature, padding.PKCS1v15(), hashes.SHA512() ) def test_invalid_signature_sequence_removed(self, backend): @@ -810,6 +1036,7 @@ def test_invalid_signature_sequence_removed(self, backend): b"bda3b33946490057b9a3003d3fd9daf7c4778b43fd46144d945d815f12628ff4" ) public_key = serialization.load_der_public_key(key_der, backend) + assert isinstance(public_key, rsa.RSAPublicKey) with pytest.raises(InvalidSignature): public_key.verify( sig, @@ -824,15 +1051,19 @@ def test_invalid_signature_sequence_removed(self, backend): ), skip_message="Does not support PKCS1v1.5.", ) - def test_invalid_pkcs1v15_signature_wrong_key(self, backend): - private_key = RSA_KEY_512.private_key(backend) - private_key2 = RSA_KEY_512_ALT.private_key(backend) + def test_invalid_pkcs1v15_signature_wrong_key( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 + private_key2 = RSA_KEY_2048_ALT.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) public_key = private_key2.public_key() msg = b"sign me" - signature = private_key.sign(msg, padding.PKCS1v15(), hashes.SHA1()) + signature = private_key.sign(msg, padding.PKCS1v15(), hashes.SHA256()) with pytest.raises(InvalidSignature): public_key.verify( - signature, msg, padding.PKCS1v15(), hashes.SHA1() + signature, msg, padding.PKCS1v15(), hashes.SHA256() ) @pytest.mark.supported( @@ -841,29 +1072,64 @@ def test_invalid_pkcs1v15_signature_wrong_key(self, backend): ), skip_message="Does not support PSS.", ) - @pytest.mark.parametrize( - "pkcs1_example", - _flatten_pkcs1_examples( + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + def test_pss_verification(self, subtests, backend): + for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt" ), load_pkcs1_vectors, ) + ): + with subtests.test(): + public_key = rsa.RSAPublicNumbers( + e=public["public_exponent"], n=public["modulus"] + ).public_key(backend) + public_key.verify( + binascii.unhexlify(example["signature"]), + binascii.unhexlify(example["message"]), + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=20, + ), + hashes.SHA1(), + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.AUTO, + ) ), + skip_message="Does not support PSS.", ) - def test_pss_verification(self, pkcs1_example, backend): - private, public, example = pkcs1_example - public_key = rsa.RSAPublicNumbers( - e=public["public_exponent"], n=public["modulus"] - ).public_key(backend) - public_key.verify( - binascii.unhexlify(example["signature"]), - binascii.unhexlify(example["message"]), + def test_pss_verify_auto_salt_length( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 + signature = private_key.sign( + b"some data", padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=20 + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH, ), - hashes.SHA1(), + hashes.SHA256(), + ) + private_key.public_key().verify( + signature, + b"some data", + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.AUTO, + ), + hashes.SHA256(), ) @pytest.mark.supported( @@ -875,6 +1141,13 @@ def test_pss_verification(self, pkcs1_example, backend): ), skip_message="Does not support PSS.", ) + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_wrong_data(self, backend): public_key = rsa.RSAPublicNumbers( n=int( @@ -909,6 +1182,13 @@ def test_invalid_pss_signature_wrong_data(self, backend): ), skip_message="Does not support PSS.", ) + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_wrong_key(self, backend): signature = binascii.unhexlify( b"3a1880165014ba6eb53cc1449d13e5132ebcc0cfd9ade6d7a2494a0503bd0826" @@ -945,6 +1225,13 @@ def test_invalid_pss_signature_wrong_key(self, backend): ), skip_message="Does not support PSS.", ) + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): # 2048 bit PSS signature signature = binascii.unhexlify( @@ -958,7 +1245,9 @@ def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): b"ac462c50a488dca486031a3dc8c4cdbbc53e9f71d64732e1533a5d1249b833ce" ) # 1024 bit key - public_key = RSA_KEY_1024.private_key(backend).public_key() + public_key = RSA_KEY_1024.private_key( + unsafe_skip_rsa_key_validation=True + ).public_key() with pytest.raises(InvalidSignature): public_key.verify( signature, @@ -971,57 +1260,56 @@ def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): ) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.PKCS1v15() + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() ), - skip_message="Does not support PKCS1v1.5.", + skip_message="Does not support SHA1 signature.", ) - def test_use_after_finalize(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_invalid_pss_signature_recover( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() - signature = private_key.sign( - b"sign me", padding.PKCS1v15(), hashes.SHA1() + pss_padding = padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH, ) + signature = private_key.sign(b"sign me", pss_padding, hashes.SHA256()) - with pytest.warns(CryptographyDeprecationWarning): - verifier = public_key.verifier( - signature, padding.PKCS1v15(), hashes.SHA1() + # Hash algorithm cannot be absent for PSS padding + with pytest.raises(TypeError): + public_key.recover_data_from_signature( + signature, pss_padding, None + ) + + # Signature data recovery not supported with PSS + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + public_key.recover_data_from_signature( + signature, pss_padding, hashes.SHA256() ) - verifier.update(b"sign me") - verifier.verify() - with pytest.raises(AlreadyFinalized): - verifier.verify() - with pytest.raises(AlreadyFinalized): - verifier.update(b"more data") - def test_unsupported_padding(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_padding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): public_key.verify( - b"sig", b"msg", DummyAsymmetricPadding(), hashes.SHA1() + b"sig", b"msg", DummyAsymmetricPadding(), hashes.SHA256() ) - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.PKCS1v15() - ), - skip_message="Does not support PKCS1v1.5.", - ) - def test_signature_not_bytes(self, backend): - public_key = RSA_KEY_512.public_numbers.public_key(backend) - signature = 1234 - - with pytest.raises(TypeError), pytest.warns( - CryptographyDeprecationWarning - ): - public_key.verifier(signature, padding.PKCS1v15(), hashes.SHA1()) - - def test_padding_incorrect_type(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_padding_incorrect_type( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() with pytest.raises(TypeError): - public_key.verify(b"sig", b"msg", "notpadding", hashes.SHA1()) + public_key.verify( + b"sig", + b"msg", + "notpadding", # type: ignore[arg-type] + hashes.SHA256(), + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -1029,8 +1317,10 @@ def test_padding_incorrect_type(self, backend): ), skip_message="Does not support PSS.", ) - def test_unsupported_pss_mgf(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_pss_mgf( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): public_key.verify( @@ -1039,7 +1329,7 @@ def test_unsupported_pss_mgf(self, backend): padding.PSS( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH ), - hashes.SHA1(), + hashes.SHA256(), ) @pytest.mark.supported( @@ -1055,8 +1345,11 @@ def test_unsupported_pss_mgf(self, backend): only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512.", ) - def test_pss_verify_digest_too_large_for_key_size(self, backend): - private_key = RSA_KEY_512.private_key(backend) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") + def test_pss_verify_digest_too_large_for_key_size( + self, rsa_key_512: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_512 signature = binascii.unhexlify( b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" b"534c050ef6b19b1bdc6eb4da422e89161106a6f5b5cc16135b11eb6439b646bd" @@ -1082,6 +1375,13 @@ def test_pss_verify_digest_too_large_for_key_size(self, backend): ), skip_message="Does not support PSS.", ) + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA1 signature.", + ) + @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pss_verify_salt_length_too_long(self, backend): signature = binascii.unhexlify( b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" @@ -1109,31 +1409,33 @@ def test_pss_verify_salt_length_too_long(self, backend): hashes.SHA1(), ) - def test_verify(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_verify(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 message = b"one little message" pkcs = padding.PKCS1v15() - algorithm = hashes.SHA1() + algorithm = hashes.SHA256() signature = private_key.sign(message, pkcs, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, pkcs, algorithm) - def test_prehashed_verify(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_prehashed_verify(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 message = b"one little message" - h = hashes.Hash(hashes.SHA1(), backend) + h = hashes.Hash(hashes.SHA256(), backend) h.update(message) digest = h.finalize() - prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) pkcs = padding.PKCS1v15() - signature = private_key.sign(message, pkcs, hashes.SHA1()) + signature = private_key.sign(message, pkcs, hashes.SHA256()) public_key = private_key.public_key() public_key.verify(signature, digest, pkcs, prehashed_alg) - def test_prehashed_digest_mismatch(self, backend): - public_key = RSA_KEY_512.private_key(backend).public_key() + def test_prehashed_digest_mismatch( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + public_key = rsa_key_2048.public_key() message = b"one little message" - h = hashes.Hash(hashes.SHA1(), backend) + h = hashes.Hash(hashes.SHA256(), backend) h.update(message) data = h.finalize() prehashed_alg = asym_utils.Prehashed(hashes.SHA512()) @@ -1142,16 +1444,18 @@ def test_prehashed_digest_mismatch(self, backend): public_key.verify(b"\x00" * 64, data, pkcs, prehashed_alg) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSAPSSMGF1Verification(object): +class TestRSAPSSMGF1Verification: test_rsa_pss_mgf1_sha1 = pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH, ) + ) + and backend.signature_hash_supported(hashes.SHA1()), + skip_message=( + "Does not support PSS using MGF1 with SHA1 or SHA1 signature." ), - skip_message="Does not support PSS using MGF1 with SHA1.", )( generate_rsa_verification_test( load_rsa_nist_vectors, @@ -1280,11 +1584,10 @@ class TestRSAPSSMGF1Verification(object): ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSAPKCS1Verification(object): +class TestRSAPKCS1Verification: test_rsa_pkcs1v15_verify_sha1 = pytest.mark.supported( only_if=lambda backend: ( - backend.hash_supported(hashes.SHA1()) + backend.signature_hash_supported(hashes.SHA1()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA1 and PKCS1v1.5.", @@ -1304,7 +1607,7 @@ class TestRSAPKCS1Verification(object): test_rsa_pkcs1v15_verify_sha224 = pytest.mark.supported( only_if=lambda backend: ( - backend.hash_supported(hashes.SHA224()) + backend.signature_hash_supported(hashes.SHA224()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA224 and PKCS1v1.5.", @@ -1324,7 +1627,7 @@ class TestRSAPKCS1Verification(object): test_rsa_pkcs1v15_verify_sha256 = pytest.mark.supported( only_if=lambda backend: ( - backend.hash_supported(hashes.SHA256()) + backend.signature_hash_supported(hashes.SHA256()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA256 and PKCS1v1.5.", @@ -1344,7 +1647,7 @@ class TestRSAPKCS1Verification(object): test_rsa_pkcs1v15_verify_sha384 = pytest.mark.supported( only_if=lambda backend: ( - backend.hash_supported(hashes.SHA384()) + backend.signature_hash_supported(hashes.SHA384()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA384 and PKCS1v1.5.", @@ -1364,7 +1667,7 @@ class TestRSAPKCS1Verification(object): test_rsa_pkcs1v15_verify_sha512 = pytest.mark.supported( only_if=lambda backend: ( - backend.hash_supported(hashes.SHA512()) + backend.signature_hash_supported(hashes.SHA512()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA512 and PKCS1v1.5.", @@ -1383,15 +1686,18 @@ class TestRSAPKCS1Verification(object): ) -class TestPSS(object): +class TestPSS: def test_calculate_max_pss_salt_length(self): with pytest.raises(TypeError): - padding.calculate_max_pss_salt_length(object(), hashes.SHA256()) + padding.calculate_max_pss_salt_length( + object(), hashes.SHA256() # type:ignore[arg-type] + ) def test_invalid_salt_length_not_integer(self): with pytest.raises(TypeError): padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), salt_length=b"not_a_length" + mgf=padding.MGF1(hashes.SHA1()), + salt_length=b"not_a_length", # type:ignore[arg-type] ) def test_invalid_salt_length_negative_integer(self): @@ -1414,10 +1720,10 @@ def test_valid_pss_parameters_maximum(self): assert pss._salt_length == padding.PSS.MAX_LENGTH -class TestMGF1(object): +class TestMGF1: def test_invalid_hash_algorithm(self): with pytest.raises(TypeError): - padding.MGF1(b"not_a_hash") + padding.MGF1(b"not_a_hash") # type:ignore[arg-type] def test_valid_mgf1_parameters(self): algorithm = hashes.SHA1() @@ -1425,83 +1731,91 @@ def test_valid_mgf1_parameters(self): assert mgf._algorithm == algorithm -class TestOAEP(object): +class TestOAEP: def test_invalid_algorithm(self): mgf = padding.MGF1(hashes.SHA1()) with pytest.raises(TypeError): - padding.OAEP(mgf=mgf, algorithm=b"", label=None) + padding.OAEP( + mgf=mgf, algorithm=b"", label=None # type:ignore[arg-type] + ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSADecryption(object): +class TestRSADecryption: @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) - @pytest.mark.parametrize( - "vector", - _flatten_pkcs1_examples( + def test_decrypt_pkcs1v15_vectors(self, backend, subtests): + vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"), load_pkcs1_vectors, ) - ), - ) - def test_decrypt_pkcs1v15_vectors(self, vector, backend): - private, public, example = vector - skey = rsa.RSAPrivateNumbers( - p=private["p"], - q=private["q"], - d=private["private_exponent"], - dmp1=private["dmp1"], - dmq1=private["dmq1"], - iqmp=private["iqmp"], - public_numbers=rsa.RSAPublicNumbers( - e=private["public_exponent"], n=private["modulus"] - ), - ).private_key(backend) - ciphertext = binascii.unhexlify(example["encryption"]) - assert len(ciphertext) == (skey.key_size + 7) // 8 - message = skey.decrypt(ciphertext, padding.PKCS1v15()) - assert message == binascii.unhexlify(example["message"]) - - def test_unsupported_padding(self, backend): - private_key = RSA_KEY_512.private_key(backend) + ) + for private, public, example in vectors: + with subtests.test(): + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], n=private["modulus"] + ), + ).private_key(backend, unsafe_skip_rsa_key_validation=True) + ciphertext = binascii.unhexlify(example["encryption"]) + assert len(ciphertext) == (skey.key_size + 7) // 8 + message = skey.decrypt(ciphertext, padding.PKCS1v15()) + assert message == binascii.unhexlify(example["message"]) + + def test_unsupported_padding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt(b"0" * 64, DummyAsymmetricPadding()) + private_key.decrypt(b"0" * 256, DummyAsymmetricPadding()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.PKCS1v15() + only_if=lambda backend: ( + backend.rsa_encryption_supported(padding.PKCS1v15()) + and not backend._lib.Cryptography_HAS_IMPLICIT_RSA_REJECTION ), skip_message="Does not support PKCS1v1.5.", ) - def test_decrypt_invalid_decrypt(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_decrypt_invalid_decrypt( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 with pytest.raises(ValueError): - private_key.decrypt(b"\x00" * 64, padding.PKCS1v15()) + private_key.decrypt(b"\x00" * 256, padding.PKCS1v15()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) - def test_decrypt_ciphertext_too_large(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_decrypt_ciphertext_too_large( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 with pytest.raises(ValueError): - private_key.decrypt(b"\x00" * 65, padding.PKCS1v15()) + private_key.decrypt(b"\x00" * 257, padding.PKCS1v15()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) - def test_decrypt_ciphertext_too_small(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_decrypt_ciphertext_too_small( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 ct = binascii.unhexlify( b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1" b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0" @@ -1510,7 +1824,7 @@ def test_decrypt_ciphertext_too_small(self, backend): private_key.decrypt(ct, padding.PKCS1v15()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1519,78 +1833,70 @@ def test_decrypt_ciphertext_too_small(self, backend): ), skip_message="Does not support OAEP.", ) - @pytest.mark.parametrize( - "vector", - _flatten_pkcs1_examples( + def test_decrypt_oaep_sha1_vectors(self, subtests, backend): + for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt" ), load_pkcs1_vectors, ) - ), - ) - def test_decrypt_oaep_vectors(self, vector, backend): - private, public, example = vector - skey = rsa.RSAPrivateNumbers( - p=private["p"], - q=private["q"], - d=private["private_exponent"], - dmp1=private["dmp1"], - dmq1=private["dmq1"], - iqmp=private["iqmp"], - public_numbers=rsa.RSAPublicNumbers( - e=private["public_exponent"], n=private["modulus"] - ), - ).private_key(backend) - message = skey.decrypt( - binascii.unhexlify(example["encryption"]), - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=None, - ), - ) - assert message == binascii.unhexlify(example["message"]) - - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA224()), - algorithm=hashes.SHA224(), - label=None, - ) - ), - skip_message=( - "Does not support OAEP using SHA224 MGF1 and SHA224 hash." - ), - ) - @pytest.mark.parametrize("vector", _build_oaep_sha2_vectors()) - def test_decrypt_oaep_sha2_vectors(self, vector, backend): - private, public, example, mgf1_alg, hash_alg = vector - skey = rsa.RSAPrivateNumbers( - p=private["p"], - q=private["q"], - d=private["private_exponent"], - dmp1=private["dmp1"], - dmq1=private["dmq1"], - iqmp=private["iqmp"], - public_numbers=rsa.RSAPublicNumbers( - e=private["public_exponent"], n=private["modulus"] - ), - ).private_key(backend) - message = skey.decrypt( - binascii.unhexlify(example["encryption"]), - padding.OAEP( - mgf=padding.MGF1(algorithm=mgf1_alg), - algorithm=hash_alg, - label=None, - ), - ) - assert message == binascii.unhexlify(example["message"]) + ): + with subtests.test(): + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], n=private["modulus"] + ), + ).private_key(backend, unsafe_skip_rsa_key_validation=True) + message = skey.decrypt( + binascii.unhexlify(example["encryption"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None, + ), + ) + assert message == binascii.unhexlify(example["message"]) + + def test_decrypt_oaep_sha2_vectors(self, backend, subtests): + vectors = _build_oaep_sha2_vectors() + for private, public, example, mgf1_alg, hash_alg in vectors: + with subtests.test(): + pad = padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1_alg), + algorithm=hash_alg, + label=None, + ) + if not backend.rsa_encryption_supported(pad): + pytest.skip( + f"Does not support OAEP using {mgf1_alg.name} MGF1 " + f"or {hash_alg.name} hash." + ) + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], n=private["modulus"] + ), + ).private_key(backend, unsafe_skip_rsa_key_validation=True) + message = skey.decrypt( + binascii.unhexlify(example["encryption"]), + pad, + ) + assert message == binascii.unhexlify(example["message"]) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1599,11 +1905,13 @@ def test_decrypt_oaep_sha2_vectors(self, vector, backend): ), skip_message="Does not support OAEP.", ) - def test_invalid_oaep_decryption(self, backend): + def test_invalid_oaep_decryption( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): # More recent versions of OpenSSL may raise different errors. # This test triggers a failure and confirms that we properly handle # it. - private_key = RSA_KEY_512.private_key(backend) + private_key = rsa_key_2048 ciphertext = private_key.public_key().encrypt( b"secure data", @@ -1614,7 +1922,9 @@ def test_invalid_oaep_decryption(self, backend): ), ) - private_key_alt = RSA_KEY_512_ALT.private_key(backend) + private_key_alt = RSA_KEY_2048_ALT.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) with pytest.raises(ValueError): private_key_alt.decrypt( @@ -1627,7 +1937,7 @@ def test_invalid_oaep_decryption(self, backend): ) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1637,7 +1947,9 @@ def test_invalid_oaep_decryption(self, backend): skip_message="Does not support OAEP.", ) def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): - key = RSA_KEY_2048_ALT.private_key(backend) + key = RSA_KEY_2048_ALT.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) ciphertext = ( b"\xb1ph\xc0\x0b\x1a|\xe6\xda\xea\xb5\xd7%\x94\x07\xf96\xfb\x96" @@ -1664,21 +1976,24 @@ def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): ), ) - def test_unsupported_oaep_mgf(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_oaep_mgf( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.decrypt( - b"0" * 64, + b"0" * 256, padding.OAEP( - mgf=DummyMGF(), algorithm=hashes.SHA1(), label=None + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None, ), ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSAEncryption(object): +class TestRSAEncryption: @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1712,7 +2027,8 @@ class TestRSAEncryption(object): ), ) def test_rsa_encrypt_oaep(self, key_data, pad, backend): - private_key = key_data.private_key(backend) + private_key = key_data.private_key(unsafe_skip_rsa_key_validation=True) + _check_fips_key_length(backend, private_key) pt = b"encrypt me!" public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) @@ -1721,18 +2037,6 @@ def test_rsa_encrypt_oaep(self, key_data, pad, backend): recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA512(), - label=None, - ) - ), - skip_message=( - "Does not support OAEP using SHA256 MGF1 and SHA512 hash." - ), - ) @pytest.mark.parametrize( ("mgf1hash", "oaephash"), itertools.product( @@ -1752,13 +2056,20 @@ def test_rsa_encrypt_oaep(self, key_data, pad, backend): ], ), ) - def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): + def test_rsa_encrypt_oaep_sha2( + self, rsa_key_2048: rsa.RSAPrivateKey, mgf1hash, oaephash, backend + ): pad = padding.OAEP( mgf=padding.MGF1(algorithm=mgf1hash), algorithm=oaephash, label=None, ) - private_key = RSA_KEY_2048.private_key(backend) + if not backend.rsa_encryption_supported(pad): + pytest.skip( + f"Does not support OAEP using {mgf1hash.name} MGF1 " + f"or {oaephash.name} hash." + ) + private_key = rsa_key_2048 pt = b"encrypt me using sha2 hashes!" public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) @@ -1768,7 +2079,7 @@ def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): assert recovered_pt == pt @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", @@ -1792,7 +2103,8 @@ def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): ), ) def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): - private_key = key_data.private_key(backend) + private_key = key_data.private_key(unsafe_skip_rsa_key_validation=True) + _check_fips_key_length(backend, private_key) pt = b"encrypt me!" public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) @@ -1818,8 +2130,8 @@ def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): ), ( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), padding.PKCS1v15(), @@ -1827,7 +2139,10 @@ def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): ), ) def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): - private_key = key_data.private_key(backend) + private_key = key_data.private_key(unsafe_skip_rsa_key_validation=True) + if not backend.rsa_encryption_supported(pad): + pytest.skip("PKCS1v15 padding not allowed in FIPS") + _check_fips_key_length(backend, private_key) public_key = private_key.public_key() # Slightly smaller than the key size but not enough for padding. with pytest.raises(ValueError): @@ -1837,30 +2152,45 @@ def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): with pytest.raises(ValueError): public_key.encrypt(b"\x00" * (private_key.key_size // 8 + 5), pad) - def test_unsupported_padding(self, backend): - private_key = RSA_KEY_512.private_key(backend) + @pytest.mark.supported( + only_if=lambda backend: backend._fips_enabled, + skip_message="Requires FIPS", + ) + def test_rsa_fips_small_key(self, rsa_key_512: rsa.RSAPrivateKey, backend): + with pytest.raises(ValueError): + rsa_key_512.sign(b"somedata", padding.PKCS1v15(), hashes.SHA512()) + + def test_unsupported_padding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): public_key.encrypt(b"somedata", DummyAsymmetricPadding()) with pytest.raises(TypeError): - public_key.encrypt(b"somedata", padding=object()) + public_key.encrypt( + b"somedata", padding=object() # type: ignore[arg-type] + ) - def test_unsupported_oaep_mgf(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_oaep_mgf( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): public_key.encrypt( b"ciphertext", padding.OAEP( - mgf=DummyMGF(), algorithm=hashes.SHA1(), label=None + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None, ), ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSANumbers(object): +class TestRSANumbers: def test_rsa_public_numbers(self): public_numbers = rsa.RSAPublicNumbers(e=1, n=15) assert public_numbers.e == 1 @@ -1887,19 +2217,24 @@ def test_rsa_private_numbers(self): assert private_numbers.public_numbers == public_numbers def test_rsa_private_numbers_create_key(self, backend): - private_key = RSA_KEY_1024.private_key(backend) + private_key = RSA_KEY_1024.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) assert private_key def test_rsa_public_numbers_create_key(self, backend): public_key = RSA_KEY_1024.public_numbers.public_key(backend) assert public_key + public_key = rsa.RSAPublicNumbers(n=10, e=3).public_key(backend) + assert public_key + def test_public_numbers_invalid_types(self): with pytest.raises(TypeError): - rsa.RSAPublicNumbers(e=None, n=15) + rsa.RSAPublicNumbers(e=None, n=15) # type: ignore[arg-type] with pytest.raises(TypeError): - rsa.RSAPublicNumbers(e=1, n=None) + rsa.RSAPublicNumbers(e=1, n=None) # type: ignore[arg-type] @pytest.mark.parametrize( ("p", "q", "d", "dmp1", "dmq1", "iqmp", "public_numbers"), @@ -1984,7 +2319,7 @@ def test_public_number_repr(self): assert repr(num) == "" -class TestRSANumbersEquality(object): +class TestRSANumbersEquality: def test_public_numbers_eq(self): num = RSAPublicNumbers(1, 2) num2 = RSAPublicNumbers(1, 2) @@ -2049,38 +2384,40 @@ def test_private_numbers_hash(self): assert hash(priv1) != hash(priv3) -class TestRSAPrimeFactorRecovery(object): - @pytest.mark.parametrize( - "vector", - _flatten_pkcs1_examples( - load_vectors_from_file( - os.path.join("asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"), - load_pkcs1_vectors, - ) - ), - ) - def test_recover_prime_factors(self, vector): - private, public, example = vector - p, q = rsa.rsa_recover_prime_factors( - private["modulus"], - private["public_exponent"], - private["private_exponent"], - ) - # Unfortunately there is no convention on which prime should be p - # and which one q. The function we use always makes p > q, but the - # NIST vectors are not so consistent. Accordingly, we verify we've - # recovered the proper (p, q) by sorting them and asserting on that. - assert sorted([p, q]) == sorted([private["p"], private["q"]]) - assert p > q +class TestRSAPrimeFactorRecovery: + def test_recover_prime_factors(self, subtests): + for key in [ + RSA_KEY_1024, + RSA_KEY_1025, + RSA_KEY_1026, + RSA_KEY_1027, + RSA_KEY_1028, + RSA_KEY_1029, + RSA_KEY_1030, + RSA_KEY_1031, + RSA_KEY_1536, + RSA_KEY_2048, + ]: + with subtests.test(): + p, q = rsa.rsa_recover_prime_factors( + key.public_numbers.n, + key.public_numbers.e, + key.d, + ) + # Unfortunately there is no convention on which prime should be + # p and which one q. The function we use always makes p > q, + # but the NIST vectors are not so consistent. Accordingly, we + # verify we've recovered the proper (p, q) by sorting them and + # asserting on that. + assert sorted([p, q]) == sorted([key.p, key.q]) + assert p > q def test_invalid_recover_prime_factors(self): with pytest.raises(ValueError): rsa.rsa_recover_prime_factors(34, 3, 7) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -class TestRSAPrivateKeySerialization(object): +class TestRSAPrivateKeySerialization: @pytest.mark.parametrize( ("fmt", "password"), itertools.product( @@ -2096,17 +2433,20 @@ class TestRSAPrivateKeySerialization(object): ], ), ) - def test_private_bytes_encrypted_pem(self, backend, fmt, password): + def test_private_bytes_encrypted_pem( + self, rsa_key_2048: rsa.RSAPrivateKey, backend, fmt, password + ): skip_fips_traditional_openssl(backend, fmt) - key = RSA_KEY_2048.private_key(backend) + key = rsa_key_2048 serialized = key.private_bytes( serialization.Encoding.PEM, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_pem_private_key( - serialized, password, backend + serialized, password, backend, unsafe_skip_rsa_key_validation=True ) + assert isinstance(loaded_key, rsa.RSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -2120,8 +2460,10 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ], ) - def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): - key = RSA_KEY_2048.private_key(backend) + def test_private_bytes_rejects_invalid( + self, rsa_key_2048: rsa.RSAPrivateKey, encoding, fmt, backend + ): + key = rsa_key_2048 with pytest.raises(ValueError): key.private_bytes(encoding, fmt, serialization.NoEncryption()) @@ -2134,16 +2476,19 @@ def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): [serialization.PrivateFormat.PKCS8, b"\x01" * 1000], ], ) - def test_private_bytes_encrypted_der(self, backend, fmt, password): - key = RSA_KEY_2048.private_key(backend) + def test_private_bytes_encrypted_der( + self, rsa_key_2048: rsa.RSAPrivateKey, backend, fmt, password + ): + key = rsa_key_2048 serialized = key.private_bytes( serialization.Encoding.DER, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_der_private_key( - serialized, password, backend + serialized, password, backend, unsafe_skip_rsa_key_validation=True ) + assert isinstance(loaded_key, rsa.RSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -2174,13 +2519,20 @@ def test_private_bytes_encrypted_der(self, backend, fmt, password): ], ) def test_private_bytes_unencrypted( - self, backend, encoding, fmt, loader_func + self, + rsa_key_2048: rsa.RSAPrivateKey, + backend, + encoding, + fmt, + loader_func, ): - key = RSA_KEY_2048.private_key(backend) + key = rsa_key_2048 serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) - loaded_key = loader_func(serialized, None, backend) + loaded_key = loader_func( + serialized, None, backend, unsafe_skip_rsa_key_validation=True + ) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @@ -2213,7 +2565,9 @@ def test_private_bytes_traditional_openssl_unencrypted( key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) - key = loader_func(key_bytes, None, backend) + key = loader_func( + key_bytes, None, backend, unsafe_skip_rsa_key_validation=True + ) serialized = key.private_bytes( encoding, serialization.PrivateFormat.TraditionalOpenSSL, @@ -2221,8 +2575,10 @@ def test_private_bytes_traditional_openssl_unencrypted( ) assert serialized == key_bytes - def test_private_bytes_traditional_der_encrypted_invalid(self, backend): - key = RSA_KEY_2048.private_key(backend) + def test_private_bytes_traditional_der_encrypted_invalid( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048 with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, @@ -2230,35 +2586,43 @@ def test_private_bytes_traditional_der_encrypted_invalid(self, backend): serialization.BestAvailableEncryption(b"password"), ) - def test_private_bytes_invalid_encoding(self, backend): - key = RSA_KEY_2048.private_key(backend) + def test_private_bytes_invalid_encoding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048 with pytest.raises(TypeError): key.private_bytes( - "notencoding", + "notencoding", # type: ignore[arg-type] serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) - def test_private_bytes_invalid_format(self, backend): - key = RSA_KEY_2048.private_key(backend) + def test_private_bytes_invalid_format( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048 with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, - "invalidformat", + "invalidformat", # type: ignore[arg-type] serialization.NoEncryption(), ) - def test_private_bytes_invalid_encryption_algorithm(self, backend): - key = RSA_KEY_2048.private_key(backend) + def test_private_bytes_invalid_encryption_algorithm( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048 with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - "notanencalg", + "notanencalg", # type: ignore[arg-type] ) - def test_private_bytes_unsupported_encryption_type(self, backend): - key = RSA_KEY_2048.private_key(backend) + def test_private_bytes_unsupported_encryption_type( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048 with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, @@ -2267,9 +2631,7 @@ def test_private_bytes_unsupported_encryption_type(self, backend): ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -class TestRSAPEMPublicKeySerialization(object): +class TestRSAPEMPublicKeySerialization: @pytest.mark.parametrize( ("key_path", "loader_func", "encoding", "format"), [ @@ -2350,15 +2712,25 @@ def test_public_bytes_openssh(self, backend): serialization.PublicFormat.SubjectPublicKeyInfo, ) - def test_public_bytes_invalid_encoding(self, backend): - key = RSA_KEY_2048.private_key(backend).public_key() + def test_public_bytes_invalid_encoding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048.public_key() with pytest.raises(TypeError): - key.public_bytes("notencoding", serialization.PublicFormat.PKCS1) + key.public_bytes( + "notencoding", # type: ignore[arg-type] + serialization.PublicFormat.PKCS1, + ) - def test_public_bytes_invalid_format(self, backend): - key = RSA_KEY_2048.private_key(backend).public_key() + def test_public_bytes_invalid_format( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048.public_key() with pytest.raises(TypeError): - key.public_bytes(serialization.Encoding.PEM, "invalidformat") + key.public_bytes( + serialization.Encoding.PEM, + "invalidformat", # type: ignore[arg-type] + ) @pytest.mark.parametrize( ("encoding", "fmt"), @@ -2385,7 +2757,21 @@ def test_public_bytes_invalid_format(self, backend): ) ), ) - def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): - key = RSA_KEY_2048.private_key(backend).public_key() + def test_public_bytes_rejects_invalid( + self, rsa_key_2048: rsa.RSAPrivateKey, encoding, fmt, backend + ): + key = rsa_key_2048.public_key() with pytest.raises(ValueError): key.public_bytes(encoding, fmt) + + def test_public_key_equality(self, rsa_key_2048: rsa.RSAPrivateKey): + key1 = rsa_key_2048.public_key() + key2 = RSA_KEY_2048.private_key( + unsafe_skip_rsa_key_validation=True + ).public_key() + key3 = RSA_KEY_2048_ALT.private_key( + unsafe_skip_rsa_key_validation=True + ).public_key() + assert key1 == key2 + assert key1 != key3 + assert key1 != object() diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py index 52e7e153802a..4b4641854755 100644 --- a/tests/hazmat/primitives/test_scrypt.py +++ b/tests/hazmat/primitives/test_scrypt.py @@ -2,22 +2,19 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidKey, - UnsupportedAlgorithm, +from cryptography.exceptions import AlreadyFinalized, InvalidKey +from cryptography.hazmat.primitives.kdf.scrypt import _MEM_LIMIT, Scrypt +from tests.utils import ( + load_nist_vectors, + load_vectors_from_file, + raises_unsupported_algorithm, ) -from cryptography.hazmat.backends.interfaces import ScryptBackend -from cryptography.hazmat.primitives.kdf.scrypt import Scrypt, _MEM_LIMIT - -from tests.utils import load_nist_vectors, load_vectors_from_file vectors = load_vectors_from_file( os.path.join("KDF", "scrypt.txt"), load_nist_vectors @@ -42,11 +39,25 @@ def test_memory_limit_skip(): with pytest.raises(pytest.skip.Exception): _skip_if_memory_limited(1000, {"p": 16, "r": 64, "n": 1024}) - _skip_if_memory_limited(2 ** 31, {"p": 16, "r": 64, "n": 1024}) + _skip_if_memory_limited(2**31, {"p": 16, "r": 64, "n": 1024}) + +@pytest.mark.supported( + only_if=lambda backend: not backend.scrypt_supported(), + skip_message="Supports scrypt so can't test unsupported path", +) +def test_unsupported_backend(backend): + # This test is currently exercised by LibreSSL, which does + # not support scrypt + with raises_unsupported_algorithm(None): + Scrypt(b"NaCl", 64, 1024, 8, 16) -@pytest.mark.requires_backend_interface(interface=ScryptBackend) -class TestScrypt(object): + +@pytest.mark.supported( + only_if=lambda backend: backend.scrypt_supported(), + skip_message="Does not support Scrypt", +) +class TestScrypt: @pytest.mark.parametrize("params", vectors) def test_derive(self, backend, params): _skip_if_memory_limited(_MEM_LIMIT, params) @@ -68,24 +79,6 @@ def test_derive(self, backend, params): ) assert binascii.hexlify(scrypt.derive(password)) == derived_key - def test_unsupported_backend(self): - work_factor = 1024 - block_size = 8 - parallelization_factor = 16 - length = 64 - salt = b"NaCl" - backend = object() - - with pytest.raises(UnsupportedAlgorithm): - Scrypt( - salt, - length, - work_factor, - block_size, - parallelization_factor, - backend, - ) - def test_salt_not_bytes(self, backend): work_factor = 1024 block_size = 8 @@ -95,7 +88,7 @@ def test_salt_not_bytes(self, backend): with pytest.raises(TypeError): Scrypt( - salt, + salt, # type: ignore[arg-type] length, work_factor, block_size, @@ -105,7 +98,7 @@ def test_salt_not_bytes(self, backend): def test_scrypt_malloc_failure(self, backend): password = b"NaCl" - work_factor = 1024 ** 3 + work_factor = 1024**3 block_size = 589824 parallelization_factor = 16 length = 64 @@ -141,7 +134,7 @@ def test_password_not_bytes(self, backend): ) with pytest.raises(TypeError): - scrypt.derive(password) + scrypt.derive(password) # type: ignore[arg-type] def test_buffer_protocol(self, backend): password = bytearray(b"password") @@ -181,7 +174,7 @@ def test_verify(self, backend, params): parallelization_factor, backend, ) - assert scrypt.verify(password, binascii.unhexlify(derived_key)) is None + scrypt.verify(password, binascii.unhexlify(derived_key)) def test_invalid_verify(self, backend): password = b"password" diff --git a/tests/hazmat/primitives/test_seed.py b/tests/hazmat/primitives/test_seed.py index 66da97836a0a..f36ce1e4ecea 100644 --- a/tests/hazmat/primitives/test_seed.py +++ b/tests/hazmat/primitives/test_seed.py @@ -2,83 +2,85 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import os import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED(b"\x00" * 16), modes.ECB() + algorithms._SEEDInternal(b"\x00" * 16), modes.ECB() ), skip_message="Does not support SEED ECB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestSEEDModeECB(object): +class TestSEEDModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4269.txt"], - lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._SEEDInternal( + binascii.unhexlify(key) + ), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED(b"\x00" * 16), modes.CBC(b"\x00" * 16) + algorithms._SEEDInternal(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support SEED CBC", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestSEEDModeCBC(object): +class TestSEEDModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4196.txt"], - lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._SEEDInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED(b"\x00" * 16), modes.OFB(b"\x00" * 16) + algorithms._SEEDInternal(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support SEED OFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestSEEDModeOFB(object): +class TestSEEDModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-ofb.txt"], - lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._SEEDInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED(b"\x00" * 16), modes.CFB(b"\x00" * 16) + algorithms._SEEDInternal(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support SEED CFB", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestSEEDModeCFB(object): +class TestSEEDModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-cfb.txt"], - lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda key, **kwargs: algorithms._SEEDInternal( + binascii.unhexlify(key) + ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 2f56711d5dab..58693a4912d2 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import base64 import itertools @@ -11,25 +10,16 @@ import pytest -import six - -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.backends.interfaces import ( - DERSerializationBackend, - DSABackend, - EllipticCurveBackend, - PEMSerializationBackend, - RSABackend, -) from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, - ed25519, ed448, + ed25519, rsa, - x25519, x448, + x25519, ) +from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.hazmat.primitives.serialization import ( BestAvailableEncryption, Encoding, @@ -42,32 +32,39 @@ load_pem_parameters, load_pem_private_key, load_pem_public_key, - load_ssh_private_key, - load_ssh_public_key, - ssh, ) +from cryptography.hazmat.primitives.serialization.pkcs12 import PBES - +from ...utils import load_vectors_from_file from .test_ec import _skip_curve_unsupported -from .utils import ( - _check_dsa_private_numbers, - _check_rsa_private_numbers, - load_vectors_from_file, -) -from ...doubles import DummyKeySerializationEncryption -from ...utils import raises_unsupported_algorithm +from .test_rsa import rsa_key_2048 +from .utils import _check_dsa_private_numbers, _check_rsa_private_numbers + +# Make ruff happy since we're importing fixtures that pytest patches in as +# func args +__all__ = ["rsa_key_2048"] def _skip_fips_format(key_path, password, backend): if backend._fips_enabled: if key_path[0] == "Traditional_OpenSSL_Serialization": pytest.skip("Traditional OpenSSL format blocked in FIPS mode") - if key_path[0] == "PEM_Serialization" and password is not None: - pytest.skip("Encrypted PEM_Serialization blocked in FIPS mode") + if ( + key_path[0] in ("PEM_Serialization", "PKCS8") + and password is not None + ): + pytest.skip( + "The encrypted PEM vectors currently have encryption " + "that is not FIPS approved in the 3.0 provider" + ) + if key_path[0] == "DER_Serialization" and password is not None: + pytest.skip( + "The encrypted PKCS8 DER vectors currently have encryption " + "that is not FIPS approved in the 3.0 provider" + ) -class TestBufferProtocolSerialization(object): - @pytest.mark.requires_backend_interface(interface=RSABackend) +class TestBufferProtocolSerialization: @pytest.mark.parametrize( ("key_path", "password"), [ @@ -78,17 +75,19 @@ class TestBufferProtocolSerialization(object): ], ) def test_load_der_rsa_private_key(self, key_path, password, backend): + _skip_fips_format(key_path, password, backend) data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: derfile.read(), mode="rb", ) - key = load_der_private_key(bytearray(data), password, backend) + key = load_der_private_key( + bytearray(data), password, unsafe_skip_rsa_key_validation=True + ) assert key assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) - @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.parametrize( ("key_path", "password"), [ @@ -112,15 +111,15 @@ def test_load_pem_rsa_private_key(self, key_path, password, backend): lambda pemfile: pemfile.read(), mode="rb", ) - key = load_pem_private_key(bytearray(data), password, backend) + key = load_pem_private_key( + bytearray(data), password, unsafe_skip_rsa_key_validation=True + ) assert key assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) -@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) -class TestDERSerialization(object): - @pytest.mark.requires_backend_interface(interface=RSABackend) +class TestDERSerialization: @pytest.mark.parametrize( ("key_path", "password"), [ @@ -131,10 +130,11 @@ class TestDERSerialization(object): ], ) def test_load_der_rsa_private_key(self, key_path, password, backend): + _skip_fips_format(key_path, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: load_der_private_key( - derfile.read(), password, backend + derfile.read(), password, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -142,7 +142,10 @@ def test_load_der_rsa_private_key(self, key_path, password, backend): assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) - @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) @pytest.mark.parametrize( ("key_path", "password"), [ @@ -167,16 +170,17 @@ def test_load_der_dsa_private_key(self, key_path, password, backend): @pytest.mark.parametrize( "key_path", [["DER_Serialization", "enc-rsa-pkcs8.der"]] ) - @pytest.mark.requires_backend_interface(interface=RSABackend) def test_password_not_bytes(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) - password = u"this password is not bytes" + password = "this password is not bytes" with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda derfile: load_der_private_key( - derfile.read(), password, backend + derfile.read(), + password, # type:ignore[arg-type] + backend, ), mode="rb", ) @@ -188,7 +192,6 @@ def test_password_not_bytes(self, key_path, backend): (["DER_Serialization", "ec_private_key_encrypted.der"], b"123456"), ], ) - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_load_der_ec_private_key(self, key_path, password, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -207,7 +210,6 @@ def test_load_der_ec_private_key(self, key_path, password, backend): @pytest.mark.parametrize( "key_path", [["DER_Serialization", "enc-rsa-pkcs8.der"]] ) - @pytest.mark.requires_backend_interface(interface=RSABackend) def test_wrong_password(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = b"this password is wrong" @@ -224,7 +226,6 @@ def test_wrong_password(self, key_path, backend): @pytest.mark.parametrize( "key_path", [["DER_Serialization", "unenc-rsa-pkcs8.der"]] ) - @pytest.mark.requires_backend_interface(interface=RSABackend) def test_unused_password(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = b"this password will not be used" @@ -244,7 +245,6 @@ def test_unused_password(self, key_path, backend): [["DER_Serialization", "enc-rsa-pkcs8.der"]], [b"", None] ), ) - @pytest.mark.requires_backend_interface(interface=RSABackend) def test_missing_password(self, key_path, password, backend): key_file = os.path.join("asymmetric", *key_path) @@ -268,6 +268,17 @@ def test_wrong_format(self, backend): key_data, b"this password will not be used", backend ) + def test_invalid_rsa_even_q(self, backend): + data = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "rsa-bad-1025-q-is-2.pem" + ), + lambda pemfile: pemfile.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, None) + def test_corrupt_der_pkcs8(self, backend): # unenc-rsa-pkcs8 with a bunch of data missing. key_data = textwrap.dedent( @@ -331,7 +342,6 @@ def test_corrupt_traditional_format_der(self, backend): os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.der"), ], ) - @pytest.mark.requires_backend_interface(interface=RSABackend) def test_load_der_rsa_public_key(self, key_file, backend): key = load_vectors_from_file( key_file, @@ -347,6 +357,10 @@ def test_load_der_invalid_public_key(self, backend): with pytest.raises(ValueError): load_der_public_key(b"invalid data", backend) + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) @pytest.mark.parametrize( "key_file", [ @@ -358,7 +372,6 @@ def test_load_der_invalid_public_key(self, backend): ), ], ) - @pytest.mark.requires_backend_interface(interface=DSABackend) def test_load_der_dsa_public_key(self, key_file, backend): key = load_vectors_from_file( key_file, @@ -368,7 +381,6 @@ def test_load_der_dsa_public_key(self, key_file, backend): assert key assert isinstance(key, dsa.DSAPublicKey) - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_load_ec_public_key(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -383,6 +395,10 @@ def test_load_ec_public_key(self, backend): assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 + @pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="DH not supported", + ) def test_wrong_parameters_format(self, backend): param_data = b"---- NOT A KEY ----\n" @@ -390,8 +406,7 @@ def test_wrong_parameters_format(self, backend): load_der_parameters(param_data, backend) -@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) -class TestPEMSerialization(object): +class TestPEMSerialization: @pytest.mark.parametrize( ("key_file", "password"), [ @@ -423,7 +438,9 @@ def test_load_pem_rsa_private_key(self, key_file, password, backend): key = load_vectors_from_file( os.path.join("asymmetric", *key_file), lambda pemfile: load_pem_private_key( - pemfile.read().encode(), password, backend + pemfile.read().encode(), + password, + unsafe_skip_rsa_key_validation=True, ), ) @@ -431,6 +448,10 @@ def test_load_pem_rsa_private_key(self, key_file, password, backend): assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) @pytest.mark.parametrize( ("key_path", "password"), [ @@ -462,7 +483,6 @@ def test_load_dsa_private_key(self, key_path, password, backend): (["PEM_Serialization", "ec_private_key_encrypted.pem"], b"123456"), ], ) - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_load_pem_ec_private_key(self, key_path, password, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) _skip_fips_format(key_path, password, backend) @@ -500,6 +520,26 @@ def test_load_pem_rsa_public_key(self, key_file, backend): numbers = key.public_numbers() assert numbers.e == 65537 + def test_load_priv_key_with_public_key_api_fails( + self, rsa_key_2048, backend + ): + # In OpenSSL 3.0.x the PEM_read_bio_PUBKEY function will invoke + # the default password callback if you pass an encrypted private + # key. This is very, very, very bad as the default callback can + # trigger an interactive console prompt, which will hang the + # Python process. This test makes sure we don't do that. + priv_key_serialized = rsa_key_2048.private_bytes( + Encoding.PEM, + PrivateFormat.PKCS8, + BestAvailableEncryption(b"password"), + ) + with pytest.raises(ValueError): + load_pem_public_key(priv_key_serialized) + + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) @pytest.mark.parametrize( ("key_file"), [ @@ -519,7 +559,6 @@ def test_load_pem_dsa_public_key(self, key_file, backend): assert key assert isinstance(key, dsa.DSAPublicKey) - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_load_ec_public_key(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -544,10 +583,12 @@ def test_rsa_traditional_encrypted_values(self, backend): "asymmetric", "Traditional_OpenSSL_Serialization", "key1.pem" ), lambda pemfile: load_pem_private_key( - pemfile.read().encode(), b"123456", backend + pemfile.read().encode(), + b"123456", + unsafe_skip_rsa_key_validation=True, ), ) - assert pkey + assert isinstance(pkey, rsa.RSAPrivateKey) numbers = pkey.private_numbers() assert numbers.p == int( @@ -608,7 +649,7 @@ def test_invalid_encoding_with_traditional(self, backend): key = load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( - pemfile.read(), None, backend + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -628,13 +669,15 @@ def test_invalid_encoding_with_traditional(self, backend): ) def test_password_not_bytes(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) - password = u"this password is not bytes" + password = "this password is not bytes" with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( - pemfile.read().encode(), password, backend + pemfile.read().encode(), + password, # type:ignore[arg-type] + backend, ), ) @@ -695,6 +738,10 @@ def test_wrong_public_format(self, backend): with pytest.raises(ValueError): load_pem_public_key(key_data, backend) + @pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="DH not supported", + ) def test_wrong_parameters_format(self, backend): param_data = b"---- NOT A KEY ----\n" @@ -771,7 +818,7 @@ def test_unsupported_key_encryption(self, backend): password = b"password" - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + with pytest.raises(ValueError): load_pem_private_key(key_data, password, backend) def test_corrupt_pkcs8_format(self, backend): @@ -836,14 +883,17 @@ def test_pks8_encrypted_corrupt_format(self, backend): with pytest.raises(ValueError): load_pem_private_key(key_data, password, backend) + @pytest.mark.skip_fips(reason="non-FIPS parameters") def test_rsa_pkcs8_encrypted_values(self, backend): pkey = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "enc-rsa-pkcs8.pem"), lambda pemfile: load_pem_private_key( - pemfile.read().encode(), b"foobar", backend + pemfile.read().encode(), + b"foobar", + unsafe_skip_rsa_key_validation=True, ), ) - assert pkey + assert isinstance(pkey, rsa.RSAPrivateKey) numbers = pkey.private_numbers() @@ -902,6 +952,10 @@ def test_rsa_pkcs8_encrypted_values(self, backend): 16, ) + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) def test_load_pem_dsa_private_key(self, backend): key = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), @@ -966,7 +1020,7 @@ def test_load_bad_oid_key(self, key_file, password, backend): ("key_file", "password"), [("bad-encryption-oid.pem", b"password")] ) def test_load_bad_encryption_oid_key(self, key_file, password, backend): - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + with pytest.raises(ValueError): load_vectors_from_file( os.path.join("asymmetric", "PKCS8", key_file), lambda pemfile: load_pem_private_key( @@ -975,416 +1029,10 @@ def test_load_bad_encryption_oid_key(self, key_file, password, backend): ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -class TestRSASSHSerialization(object): - def test_load_ssh_public_key_unsupported(self, backend): - ssh_key = b"ecdsa-sha2-junk AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY=" - - with pytest.raises(UnsupportedAlgorithm): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_bad_format(self, backend): - ssh_key = b"ssh-rsa not-a-real-key" - - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_rsa_too_short(self, backend): - ssh_key = b"ssh-rsa" - - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_truncated_int(self, backend): - ssh_key = b"ssh-rsa AAAAB3NzaC1yc2EAAAA=" - - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - ssh_key = b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAACKr+IHXo" - - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_rsa_comment_with_spaces(self, backend): - ssh_key = ( - b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" - b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" - b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" - b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" - b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" - b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" - # Extra section appended - b"2MzHvnbv testkey@localhost extra" - ) - - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_rsa_extra_data_after_modulo(self, backend): - ssh_key = ( - b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" - b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" - b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" - b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" - b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" - b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" - b"2MzHvnbvAQ== testkey@localhost" - ) - - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_rsa_different_string(self, backend): - ssh_key = ( - # "AAAAB3NzA" the final A is capitalized here to cause the string - # ssh-rsa inside the base64 encoded blob to be incorrect. It should - # be a lower case 'a'. - b"ssh-rsa AAAAB3NzAC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" - b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" - b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" - b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" - b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" - b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" - b"2MzHvnbvAQ== testkey@localhost" - ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_rsa(self, backend): - ssh_key = ( - b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" - b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" - b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" - b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" - b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" - b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" - b"2MzHvnbv testkey@localhost" - ) - - key = load_ssh_public_key(ssh_key, backend) - - assert key is not None - assert isinstance(key, rsa.RSAPublicKey) - - numbers = key.public_numbers() - - expected_e = 0x10001 - expected_n = int( - "00C3BBF5D13F59322BA0A0B77EA0B6CF570241628AE24B5BA454D" - "23DCA295652B3523B67752653DFFD69587FAD9578DD6406F23691" - "EA491C3F8B2D391D0312D9653C303B651067ADF887A5241843CEF" - "8019680A088E092FEC305FB04EA070340BB9BD0F1635B2AD84142" - "61B4E2D010ABD8FC6D2FB768912F78EE6B05A60857532B75B75EF" - "C007601A4EF58BA947B7E75E38F3443CDD87E7C138A1DAD9D9FB3" - "19FF69DA43A9F6F6B0CD243F042CD1A5AFAEB286BD46AEB2D922B" - "D01385D6892167074A0907F94A2BF08A54ABB2FFFFC89920861D0" - "46F8706AB88DDADBD9E8204D48B87789081E074024C8996783B31" - "7076A98ABF0A2D8550EAF2097D8CCC7BE76EF", - 16, - ) - - expected = rsa.RSAPublicNumbers(expected_e, expected_n) - - assert numbers == expected - - -@pytest.mark.requires_backend_interface(interface=DSABackend) -class TestDSSSSHSerialization(object): - def test_load_ssh_public_key_dss_too_short(self, backend): - ssh_key = b"ssh-dss" - - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_dss_comment_with_spaces(self, backend): - ssh_key = ( - b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" - b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" - b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" - b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" - b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" - b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" - b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" - b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" - b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" - b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost extra" - ) - - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_dss_extra_data_after_modulo(self, backend): - ssh_key = ( - b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" - b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" - b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" - b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" - b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" - b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" - b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" - b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" - b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" - b"z53N7tPF/IhHTjBHb1Ol7IFu9p9AAwMD== testkey@localhost" - ) - - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_dss_different_string(self, backend): - ssh_key = ( - # "AAAAB3NzA" the final A is capitalized here to cause the string - # ssh-dss inside the base64 encoded blob to be incorrect. It should - # be a lower case 'a'. - b"ssh-dss AAAAB3NzAC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" - b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" - b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" - b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" - b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" - b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" - b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" - b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" - b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" - b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost" - ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_dss(self, backend): - ssh_key = ( - b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" - b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" - b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" - b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" - b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" - b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" - b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" - b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" - b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" - b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost" - ) - - key = load_ssh_public_key(ssh_key, backend) - - assert key is not None - assert isinstance(key, dsa.DSAPublicKey) - - numbers = key.public_numbers() - - expected_y = int( - "d143cf92901f936fa258e9a11422460c8f8f1597884eef8cb1252a3e2ff0aae" - "96a7032c01cdd8485b5cbfb73a46bb04708f98a18bc88d4c7812b284da8f900" - "6e473e89897f9bc9125c69bbfd8ef691c0e76c1c34e6c843b8fe240e6e5aeb3" - "13486e5fa917ab1288ff1a6ebcf9dcdeed3c5fc88474e30476f53a5ec816ef6" - "9f4", - 16, - ) - expected_p = int( - "b9b052d7f07630148d4d838b17790ef4f43437238ebebd5032ea483fd7b7902" - "5ec3dc65ebd563ab586a633b4344f6acd10af31353bcf29111fa5e3b8d5c1e8" - "7befe3c65f9b8be69c740716698c8366c8ef925b9cec1dcd69e73d926b554e2" - "b4b6ddd1453eab39ba0f846e1555adcc33c5a8637128c9ed61104a45505a748" - "f6db", - 16, - ) - expected_q = 1230879958723280233885494314531920096931919647917 - expected_g = int( - "7f6c9170b2cfb67e78267c6fcb8b93b22fb03d895a0676451a15ac44511393a" - "7bc249b6cf8f5f5c5022afefd4df5bf9d13bbdf182df5af2a5c5d1dc7604185" - "7d5b0e4b22b856c300f850a3b00bac394b728755b8b7a56522eefc491573967" - "debb5982fc94d6a8c291f758feae63ad769a5621947221522a2dc31d18ede6f" - "b656", - 16, - ) - expected = dsa.DSAPublicNumbers( - expected_y, - dsa.DSAParameterNumbers(expected_p, expected_q, expected_g), - ) - - assert numbers == expected - - -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -class TestECDSASSHSerialization(object): - def test_load_ssh_public_key_ecdsa_nist_p256(self, backend): - _skip_curve_unsupported(backend, ec.SECP256R1()) - - ssh_key = ( - b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" - b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" - b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" - ) - key = load_ssh_public_key(ssh_key, backend) - assert isinstance(key, ec.EllipticCurvePublicKey) - - expected_x = int( - "44196257377740326295529888716212621920056478823906609851236662550" - "785814128027", - 10, - ) - expected_y = int( - "12257763433170736656417248739355923610241609728032203358057767672" - "925775019611", - 10, - ) - - assert key.public_numbers() == ec.EllipticCurvePublicNumbers( - expected_x, expected_y, ec.SECP256R1() - ) - - def test_load_ssh_public_key_byteslike(self, backend): - _skip_curve_unsupported(backend, ec.SECP256R1()) - - ssh_key = ( - b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" - b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" - b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" - ) - assert load_ssh_public_key(bytearray(ssh_key), backend) - if six.PY3: - assert load_ssh_public_key(memoryview(ssh_key), backend) - assert load_ssh_public_key(memoryview(bytearray(ssh_key)), backend) - - def test_load_ssh_public_key_ecdsa_nist_p384(self, backend): - _skip_curve_unsupported(backend, ec.SECP384R1()) - ssh_key = ( - b"ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAz" - b"ODQAAABhBMzucOm9wbwg4iMr5QL0ya0XNQGXpw4wM5f12E3tWhdcrzyGHyel71t1" - b"4bvF9JZ2/WIuSxUr33XDl8jYo+lMQ5N7Vanc7f7i3AR1YydatL3wQfZStQ1I3rBa" - b"qQtRSEU8Tg== root@cloud-server-01" - ) - key = load_ssh_public_key(ssh_key, backend) - - expected_x = int( - "31541830871345183397582554827482786756220448716666815789487537666" - "592636882822352575507883817901562613492450642523901", - 10, - ) - expected_y = int( - "15111413269431823234030344298767984698884955023183354737123929430" - "995703524272335782455051101616329050844273733614670", - 10, - ) - - assert key.public_numbers() == ec.EllipticCurvePublicNumbers( - expected_x, expected_y, ec.SECP384R1() - ) - - def test_load_ssh_public_key_ecdsa_nist_p521(self, backend): - _skip_curve_unsupported(backend, ec.SECP521R1()) - ssh_key = ( - b"ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1" - b"MjEAAACFBAGTrRhMSEgF6Ni+PXNz+5fjS4lw3ypUILVVQ0Av+0hQxOx+MyozELon" - b"I8NKbrbBjijEs1GuImsmkTmWsMXS1j2A7wB4Kseh7W9KA9IZJ1+TMrzWUEwvOOXi" - b"wT23pbaWWXG4NaM7vssWfZBnvz3S174TCXnJ+DSccvWBFnKP0KchzLKxbg== " - b"root@cloud-server-01" - ) - key = load_ssh_public_key(ssh_key, backend) - - expected_x = int( - "54124123120178189598842622575230904027376313369742467279346415219" - "77809037378785192537810367028427387173980786968395921877911964629" - "142163122798974160187785455", - 10, - ) - expected_y = int( - "16111775122845033200938694062381820957441843014849125660011303579" - "15284560361402515564433711416776946492019498546572162801954089916" - "006665939539407104638103918", - 10, - ) - - assert key.public_numbers() == ec.EllipticCurvePublicNumbers( - expected_x, expected_y, ec.SECP521R1() - ) - - def test_load_ssh_public_key_ecdsa_nist_p256_trailing_data(self, backend): - ssh_key = ( - b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" - b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" - b"teIg1TO03/FD9hbpBFgBeix3NrCFPltB= root@cloud-server-01" - ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_ecdsa_nist_p256_missing_data(self, backend): - ssh_key = ( - b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" - b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" - b"teIg1TO03/FD9hbpBFgBeix3NrCF= root@cloud-server-01" - ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_ecdsa_nist_p256_compressed(self, backend): - # If we ever implement compressed points, note that this is not a valid - # one, it just has the compressed marker in the right place. - ssh_key = ( - b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" - b"NTYAAABBAWG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" - b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" - ) - with pytest.raises(NotImplementedError): - load_ssh_public_key(ssh_key, backend) - - def test_load_ssh_public_key_ecdsa_nist_p256_bad_curve_name(self, backend): - ssh_key = ( - # The curve name in here is changed to be "nistp255". - b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" - b"NTUAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" - b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" - ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) - - -@pytest.mark.supported( - only_if=lambda backend: backend.ed25519_supported(), - skip_message="Requires OpenSSL with Ed25519 support", -) -class TestEd25519SSHSerialization(object): - def test_load_ssh_public_key(self, backend): - ssh_key = ( - b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4" - b"GR+xWvBmvxjxrB1vG user@chiron.local" - ) - key = load_ssh_public_key(ssh_key, backend) - assert isinstance(key, ed25519.Ed25519PublicKey) - assert key.public_bytes(Encoding.Raw, PublicFormat.Raw) == ( - b"m\x9f\x82\x99\xa9`\xee\xb5\xa9\xe01\x19\xdd0\x81\x16\x8d\xfc" - b"N\x06G\xecV\xbc\x19\xaf\xc6= 16] + for params in all_params: + with subtests.test(): + aead_test(backend, cipher_factory, mode_factory, params) return test_aead @@ -96,23 +102,26 @@ def aead_test(backend, cipher_factory, mode_factory, params): # hex encoded. pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") + tag = binascii.unhexlify(params["tag"]) + mode = mode_factory( + binascii.unhexlify(params["iv"]), + tag, + len(tag), + ) + assert isinstance(mode, GCM) if params.get("pt") is not None: - plaintext = params["pt"] - ciphertext = params["ct"] - aad = params["aad"] + plaintext = binascii.unhexlify(params["pt"]) + ciphertext = binascii.unhexlify(params["ct"]) + aad = binascii.unhexlify(params["aad"]) if params.get("fail") is True: cipher = Cipher( cipher_factory(binascii.unhexlify(params["key"])), - mode_factory( - binascii.unhexlify(params["iv"]), - binascii.unhexlify(params["tag"]), - len(binascii.unhexlify(params["tag"])), - ), + mode, backend, ) decryptor = cipher.decryptor() - decryptor.authenticate_additional_data(binascii.unhexlify(aad)) - actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + decryptor.authenticate_additional_data(aad) + actual_plaintext = decryptor.update(ciphertext) with pytest.raises(InvalidTag): decryptor.finalize() else: @@ -122,35 +131,33 @@ def aead_test(backend, cipher_factory, mode_factory, params): backend, ) encryptor = cipher.encryptor() - encryptor.authenticate_additional_data(binascii.unhexlify(aad)) - actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + encryptor.authenticate_additional_data(aad) + actual_ciphertext = encryptor.update(plaintext) actual_ciphertext += encryptor.finalize() - tag_len = len(binascii.unhexlify(params["tag"])) - assert binascii.hexlify(encryptor.tag[:tag_len]) == params["tag"] + assert encryptor.tag[: len(tag)] == tag cipher = Cipher( cipher_factory(binascii.unhexlify(params["key"])), mode_factory( binascii.unhexlify(params["iv"]), - binascii.unhexlify(params["tag"]), - min_tag_length=tag_len, + tag, + min_tag_length=len(tag), ), backend, ) decryptor = cipher.decryptor() - decryptor.authenticate_additional_data(binascii.unhexlify(aad)) - actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + decryptor.authenticate_additional_data(aad) + actual_plaintext = decryptor.update(ciphertext) actual_plaintext += decryptor.finalize() - assert actual_plaintext == binascii.unhexlify(plaintext) + assert actual_plaintext == plaintext def generate_stream_encryption_test( param_loader, path, file_names, cipher_factory ): - all_params = _load_all_params(path, file_names, param_loader) - - @pytest.mark.parametrize("params", all_params) - def test_stream_encryption(self, backend, params): - stream_encryption_test(backend, cipher_factory, params) + def test_stream_encryption(self, backend, subtests): + for params in _load_all_params(path, file_names, param_loader): + with subtests.test(): + stream_encryption_test(backend, cipher_factory, params) return test_stream_encryption @@ -174,11 +181,10 @@ def stream_encryption_test(backend, cipher_factory, params): def generate_hash_test(param_loader, path, file_names, hash_cls): - all_params = _load_all_params(path, file_names, param_loader) - - @pytest.mark.parametrize("params", all_params) - def test_hash(self, backend, params): - hash_test(backend, hash_cls, params) + def test_hash(self, backend, subtests): + for params in _load_all_params(path, file_names, param_loader): + with subtests.test(): + hash_test(backend, hash_cls, params) return test_hash @@ -203,7 +209,6 @@ def base_hash_test(backend, algorithm, digest_size): assert m.algorithm.digest_size == digest_size m_copy = m.copy() assert m != m_copy - assert m._ctx != m_copy._ctx m.update(b"abc") copy = m.copy() @@ -224,15 +229,13 @@ def base_hmac_test(backend, algorithm): h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) h_copy = h.copy() assert h != h_copy - assert h._ctx != h_copy._ctx def generate_hmac_test(param_loader, path, file_names, algorithm): - all_params = _load_all_params(path, file_names, param_loader) - - @pytest.mark.parametrize("params", all_params) - def test_hmac(self, backend, params): - hmac_test(backend, algorithm, params) + def test_hmac(self, backend, subtests): + for params in _load_all_params(path, file_names, param_loader): + with subtests.test(): + hmac_test(backend, algorithm, params) return test_hmac @@ -244,31 +247,6 @@ def hmac_test(backend, algorithm, params): assert h.finalize() == binascii.unhexlify(md.encode("ascii")) -def generate_pbkdf2_test(param_loader, path, file_names, algorithm): - all_params = _load_all_params(path, file_names, param_loader) - - @pytest.mark.parametrize("params", all_params) - def test_pbkdf2(self, backend, params): - pbkdf2_test(backend, algorithm, params) - - return test_pbkdf2 - - -def pbkdf2_test(backend, algorithm, params): - # Password and salt can contain \0, which should be loaded as a null char. - # The NIST loader loads them as literal strings so we replace with the - # proper value. - kdf = PBKDF2HMAC( - algorithm, - int(params["length"]), - params["salt"], - int(params["iterations"]), - backend, - ) - derived_key = kdf.derive(params["password"]) - assert binascii.hexlify(derived_key) == params["derived_key"] - - def generate_aead_exception_test(cipher_factory, mode_factory): def test_aead_exception(self, backend): aead_exception_test(backend, cipher_factory, mode_factory) @@ -277,9 +255,11 @@ def test_aead_exception(self, backend): def aead_exception_test(backend, cipher_factory, mode_factory): + mode = mode_factory(binascii.unhexlify(b"0" * 24)) + assert isinstance(mode, GCM) cipher = Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), - mode_factory(binascii.unhexlify(b"0" * 24)), + mode, backend, ) encryptor = cipher.encryptor() @@ -295,15 +275,18 @@ def aead_exception_test(backend, cipher_factory, mode_factory): encryptor.update(b"b" * 16) with pytest.raises(AlreadyFinalized): encryptor.finalize() + + mode2 = mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16) + assert isinstance(mode2, GCM) cipher = Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), - mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), + mode2, backend, ) decryptor = cipher.decryptor() decryptor.update(b"a" * 16) with pytest.raises(AttributeError): - decryptor.tag + decryptor.tag # type: ignore[attr-defined] def generate_aead_tag_exception_test(cipher_factory, mode_factory): @@ -323,6 +306,13 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): with pytest.raises(ValueError): mode_factory(binascii.unhexlify(b"0" * 24), b"000") + with pytest.raises(ValueError): + Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24), b"toolong" * 12), + backend, + ) + with pytest.raises(ValueError): mode_factory(binascii.unhexlify(b"0" * 24), b"000000", 2) @@ -377,31 +367,31 @@ def hkdf_expand_test(backend, algorithm, params): def generate_hkdf_test(param_loader, path, file_names, algorithm): - all_params = _load_all_params(path, file_names, param_loader) - - all_tests = [hkdf_extract_test, hkdf_expand_test, hkdf_derive_test] - - @pytest.mark.parametrize( - ("params", "hkdf_test"), itertools.product(all_params, all_tests) - ) - def test_hkdf(self, backend, params, hkdf_test): - hkdf_test(backend, algorithm, params) + def test_hkdf(self, backend, subtests): + for params in _load_all_params(path, file_names, param_loader): + with subtests.test(): + hkdf_extract_test(backend, algorithm, params) + with subtests.test(): + hkdf_expand_test(backend, algorithm, params) + with subtests.test(): + hkdf_derive_test(backend, algorithm, params) return test_hkdf def generate_kbkdf_counter_mode_test(param_loader, path, file_names): - all_params = _load_all_params(path, file_names, param_loader) - - @pytest.mark.parametrize("params", all_params) - def test_kbkdf(self, backend, params): - kbkdf_counter_mode_test(backend, params) + def test_kbkdf(self, backend, subtests): + for params in _load_all_params(path, file_names, param_loader): + with subtests.test(): + kbkdf_counter_mode_test(backend, params) return test_kbkdf -def kbkdf_counter_mode_test(backend, params): - supported_algorithms = { +def _kbkdf_hmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params): + supported_hash_algorithms: typing.Dict[ + str, typing.Type[hashes.HashAlgorithm] + ] = { "hmac_sha1": hashes.SHA1, "hmac_sha224": hashes.SHA224, "hmac_sha256": hashes.SHA256, @@ -409,27 +399,48 @@ def kbkdf_counter_mode_test(backend, params): "hmac_sha512": hashes.SHA512, } - supported_counter_locations = { - "before_fixed": CounterLocation.BeforeFixed, - "after_fixed": CounterLocation.AfterFixed, + algorithm = supported_hash_algorithms.get(prf) + assert algorithm is not None + assert backend.hmac_supported(algorithm()) + + ctrkdf = KBKDFHMAC( + algorithm(), + Mode.CounterMode, + params["l"] // 8, + params["rlen"] // 8, + None, + ctr_loc, + None, + None, + binascii.unhexlify(params["fixedinputdata"]), + backend=backend, + break_location=brk_loc, + ) + + ko = ctrkdf.derive(binascii.unhexlify(params["ki"])) + assert binascii.hexlify(ko) == params["ko"] + + +def _kbkdf_cmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params): + supported_cipher_algorithms: typing.Dict[ + str, typing.Type[BlockCipherAlgorithm] + ] = { + "cmac_aes128": algorithms.AES, + "cmac_aes192": algorithms.AES, + "cmac_aes256": algorithms.AES, + "cmac_tdes2": algorithms.TripleDES, + "cmac_tdes3": algorithms.TripleDES, } - algorithm = supported_algorithms.get(params.get("prf")) - if algorithm is None or not backend.hmac_supported(algorithm()): - pytest.skip( - "KBKDF does not support algorithm: {}".format(params.get("prf")) - ) + algorithm = supported_cipher_algorithms.get(prf) + assert algorithm is not None - ctr_loc = supported_counter_locations.get(params.get("ctrlocation")) - if ctr_loc is None or not isinstance(ctr_loc, CounterLocation): - pytest.skip( - "Does not support counter location: {}".format( - params.get("ctrlocation") - ) - ) + # TripleDES is disallowed in FIPS mode. + if backend._fips_enabled and algorithm is algorithms.TripleDES: + pytest.skip("TripleDES is not supported in FIPS mode.") - ctrkdf = KBKDFHMAC( - algorithm(), + ctrkdf = KBKDFCMAC( + algorithm, Mode.CounterMode, params["l"] // 8, params["rlen"] // 8, @@ -439,23 +450,54 @@ def kbkdf_counter_mode_test(backend, params): None, binascii.unhexlify(params["fixedinputdata"]), backend=backend, + break_location=brk_loc, ) ko = ctrkdf.derive(binascii.unhexlify(params["ki"])) assert binascii.hexlify(ko) == params["ko"] +def kbkdf_counter_mode_test(backend, params): + supported_counter_locations = { + "before_fixed": CounterLocation.BeforeFixed, + "after_fixed": CounterLocation.AfterFixed, + "middle_fixed": CounterLocation.MiddleFixed, + } + + ctr_loc = supported_counter_locations[params.pop("ctrlocation")] + brk_loc = None + + if ctr_loc == CounterLocation.MiddleFixed: + assert "fixedinputdata" not in params + params["fixedinputdata"] = params.pop( + "databeforectrdata" + ) + params.pop("dataafterctrdata") + + brk_loc = params.pop("databeforectrlen") + assert isinstance(brk_loc, int) + + prf = params.get("prf") + assert prf is not None + assert isinstance(prf, str) + del params["prf"] + if prf.startswith("hmac"): + _kbkdf_hmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params) + else: + assert prf.startswith("cmac") + _kbkdf_cmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params) + + def generate_rsa_verification_test( param_loader, path, file_names, hash_alg, pad_factory ): - all_params = _load_all_params(path, file_names, param_loader) - all_params = [ - i for i in all_params if i["algorithm"] == hash_alg.name.upper() - ] - - @pytest.mark.parametrize("params", all_params) - def test_rsa_verification(self, backend, params): - rsa_verification_test(backend, params, hash_alg, pad_factory) + def test_rsa_verification(self, backend, subtests): + all_params = _load_all_params(path, file_names, param_loader) + all_params = [ + i for i in all_params if i["algorithm"] == hash_alg.name.upper() + ] + for params in all_params: + with subtests.test(): + rsa_verification_test(backend, params, hash_alg, pad_factory) return test_rsa_verification diff --git a/tests/hazmat/test_der.py b/tests/hazmat/test_der.py deleted file mode 100644 index ef2052f50026..000000000000 --- a/tests/hazmat/test_der.py +++ /dev/null @@ -1,232 +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 absolute_import, division, print_function - -import pytest - -from cryptography.hazmat._der import ( - DERReader, - INTEGER, - NULL, - OCTET_STRING, - SEQUENCE, - encode_der, - encode_der_integer, -) - - -def test_der_reader_basic(): - reader = DERReader(b"123456789") - assert reader.read_byte() == ord(b"1") - assert reader.read_bytes(1).tobytes() == b"2" - assert reader.read_bytes(4).tobytes() == b"3456" - - with pytest.raises(ValueError): - reader.read_bytes(4) - - assert reader.read_bytes(3).tobytes() == b"789" - - # The input is now empty. - with pytest.raises(ValueError): - reader.read_bytes(1) - with pytest.raises(ValueError): - reader.read_byte() - - -def test_der(): - # This input is the following structure, using - # https://github.com/google/der-ascii - # - # SEQUENCE { - # SEQUENCE { - # NULL {} - # INTEGER { 42 } - # OCTET_STRING { "hello" } - # } - # } - der = b"\x30\x0e\x30\x0c\x05\x00\x02\x01\x2a\x04\x05\x68\x65\x6c\x6c\x6f" - reader = DERReader(der) - with pytest.raises(ValueError): - reader.check_empty() - - with pytest.raises(ValueError): - with reader: - pass - - with pytest.raises(ZeroDivisionError): - with DERReader(der): - raise ZeroDivisionError - - # Parse the outer element. - outer = reader.read_element(SEQUENCE) - reader.check_empty() - assert outer.data.tobytes() == der[2:] - - # Parse the outer element with read_any_element. - reader = DERReader(der) - tag, outer2 = reader.read_any_element() - reader.check_empty() - assert tag == SEQUENCE - assert outer2.data.tobytes() == der[2:] - - # Parse the outer element with read_single_element. - outer3 = DERReader(der).read_single_element(SEQUENCE) - assert outer3.data.tobytes() == der[2:] - - # read_single_element rejects trailing data. - with pytest.raises(ValueError): - DERReader(der + der).read_single_element(SEQUENCE) - - # Continue parsing the structure. - inner = outer.read_element(SEQUENCE) - outer.check_empty() - - # Parsing a missing optional element should work. - assert inner.read_optional_element(INTEGER) is None - - null = inner.read_element(NULL) - null.check_empty() - - # Parsing a present optional element should work. - integer = inner.read_optional_element(INTEGER) - assert integer.as_integer() == 42 - - octet_string = inner.read_element(OCTET_STRING) - assert octet_string.data.tobytes() == b"hello" - - # Parsing a missing optional element should work when the input is empty. - inner.check_empty() - assert inner.read_optional_element(INTEGER) is None - - # Re-encode the same structure. - der2 = encode_der( - SEQUENCE, - encode_der( - SEQUENCE, - encode_der(NULL), - encode_der(INTEGER, encode_der_integer(42)), - encode_der(OCTET_STRING, b"hello"), - ), - ) - assert der2 == der - - -@pytest.mark.parametrize( - "length,header", - [ - # Single-byte lengths. - (0, b"\x04\x00"), - (1, b"\x04\x01"), - (2, b"\x04\x02"), - (127, b"\x04\x7f"), - # Long-form lengths. - (128, b"\x04\x81\x80"), - (129, b"\x04\x81\x81"), - (255, b"\x04\x81\xff"), - (0x100, b"\x04\x82\x01\x00"), - (0x101, b"\x04\x82\x01\x01"), - (0xFFFF, b"\x04\x82\xff\xff"), - (0x10000, b"\x04\x83\x01\x00\x00"), - ], -) -def test_der_lengths(length, header): - body = length * b"a" - der = header + body - - reader = DERReader(der) - element = reader.read_element(OCTET_STRING) - reader.check_empty() - assert element.data.tobytes() == body - - assert encode_der(OCTET_STRING, body) == der - - -@pytest.mark.parametrize( - "bad_input", - [ - # The input ended before the tag. - b"", - # The input ended before the length. - b"\x30", - # The input ended before the second byte of the length. - b"\x30\x81", - # The input ended before the body. - b"\x30\x01", - # The length used long form when it should be short form. - b"\x30\x81\x01\x00", - # The length was not minimally-encoded. - b"\x30\x82\x00\x80" + (0x80 * b"a"), - # Indefinite-length encoding is not valid DER. - b"\x30\x80\x00\x00" - # Tag number (the bottom 5 bits) 31 indicates long form tags, which we - # do not support. - b"\x1f\x00", - b"\x9f\x00", - b"\xbf\x00", - b"\xff\x00", - ], -) -def test_der_reader_bad_input(bad_input): - reader = DERReader(bad_input) - with pytest.raises(ValueError): - reader.read_any_element() - - -def test_der_reader_wrong_tag(): - reader = DERReader(b"\x04\x00") - with pytest.raises(ValueError): - reader.read_element(SEQUENCE) - - -@pytest.mark.parametrize( - "value,der", - [ - (0, b"\x00"), - (1, b"\x01"), - (2, b"\x02"), - (3, b"\x03"), - (127, b"\x7f"), - (128, b"\x00\x80"), - ( - 0x112233445566778899AABBCCDDEEFF, - b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", - ), - ], -) -def test_integer(value, der): - assert encode_der_integer(value) == der - assert DERReader(der).as_integer() == value - - -@pytest.mark.parametrize( - "bad_input", - [ - # Zero is encoded as b"\x00", not the empty string. - b"", - # Too many leading zeros. - b"\x00\x00", - b"\x00\x7f", - # Too many leading ones. - b"\xff\xff", - b"\xff\x80", - # Negative integers are not supported. - b"\x80", - b"\x81", - b"\x80\x00\x00", - b"\xff", - ], -) -def test_invalid_integer(bad_input): - reader = DERReader(bad_input) - with pytest.raises(ValueError): - reader.as_integer() - - -def test_invalid_integer_encode(): - with pytest.raises(ValueError): - encode_der_integer(-1) - - with pytest.raises(ValueError): - encode_der_integer("not an integer") diff --git a/tests/hazmat/test_oid.py b/tests/hazmat/test_oid.py index 5589ed976c0c..f537abcd517a 100644 --- a/tests/hazmat/test_oid.py +++ b/tests/hazmat/test_oid.py @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +import copy import pytest @@ -13,6 +13,15 @@ def test_basic_oid(): assert ObjectIdentifier("1.2.3.4").dotted_string == "1.2.3.4" +def test_oid_equal(): + assert ObjectIdentifier("1.2.3.4") == ObjectIdentifier("1.2.3.4") + + +def test_oid_deepcopy(): + oid = ObjectIdentifier("1.2.3.4") + assert oid == copy.deepcopy(oid) + + def test_oid_constraint(): # Too short with pytest.raises(ValueError): diff --git a/tests/hypothesis/test_fernet.py b/tests/hypothesis/test_fernet.py deleted file mode 100644 index 75195f5304a5..000000000000 --- a/tests/hypothesis/test_fernet.py +++ /dev/null @@ -1,16 +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 hypothesis import HealthCheck, given, settings -from hypothesis.strategies import binary - -from cryptography.fernet import Fernet - - -@settings(suppress_health_check=[HealthCheck.too_slow], deadline=None) -@given(binary()) -def test_fernet(data): - f = Fernet(Fernet.generate_key()) - ct = f.encrypt(data) - assert f.decrypt(ct) == data diff --git a/tests/hypothesis/test_padding.py b/tests/hypothesis/test_padding.py deleted file mode 100644 index 74a58eb8c2c5..000000000000 --- a/tests/hypothesis/test_padding.py +++ /dev/null @@ -1,34 +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 hypothesis import HealthCheck, given, settings -from hypothesis.strategies import binary, integers - -from cryptography.hazmat.primitives.padding import ANSIX923, PKCS7 - - -@settings(suppress_health_check=[HealthCheck.too_slow], deadline=None) -@given(integers(min_value=1, max_value=255), binary()) -def test_pkcs7(block_size, data): - # Generate in [1, 31] so we can easily get block_size in bits by - # multiplying by 8. - p = PKCS7(block_size=block_size * 8) - padder = p.padder() - unpadder = p.unpadder() - - padded = padder.update(data) + padder.finalize() - - assert unpadder.update(padded) + unpadder.finalize() == data - - -@settings(suppress_health_check=[HealthCheck.too_slow]) -@given(integers(min_value=1, max_value=255), binary()) -def test_ansix923(block_size, data): - a = ANSIX923(block_size=block_size * 8) - padder = a.padder() - unpadder = a.unpadder() - - padded = padder.update(data) + padder.finalize() - - assert unpadder.update(padded) + unpadder.finalize() == data diff --git a/tests/test_cryptography_utils.py b/tests/test_cryptography_utils.py index 0158ef351fe0..98fd6165afc1 100644 --- a/tests/test_cryptography_utils.py +++ b/tests/test_cryptography_utils.py @@ -2,29 +2,24 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +import enum +import typing import pytest from cryptography import utils -def test_int_from_bytes_bytearray(): - assert utils.int_from_bytes(bytearray(b"\x02\x10"), "big") == 528 - with pytest.raises(TypeError): - utils.int_from_bytes(["list", "is", "not", "bytes"], "big") - - -class TestCachedProperty(object): +class TestCachedProperty: def test_simple(self): - accesses = [] - - class T(object): + class T: @utils.cached_property def t(self): accesses.append(None) return 14 + accesses: typing.List[typing.Optional[T]] = [] + assert T.t t = T() assert t.t == 14 @@ -39,14 +34,13 @@ def t(self): assert len(accesses) == 2 def test_set(self): - accesses = [] - - class T(object): + class T: @utils.cached_property def t(self): accesses.append(None) return 14 + accesses: typing.List[typing.Optional[T]] = [] t = T() with pytest.raises(AttributeError): t.t = None @@ -58,3 +52,13 @@ def t(self): assert len(accesses) == 1 assert t.t == 14 assert len(accesses) == 1 + + +def test_enum(): + class TestEnum(utils.Enum): + something = "something" + + assert issubclass(TestEnum, enum.Enum) + assert isinstance(TestEnum.something, enum.Enum) + assert repr(TestEnum.something) == "" + assert str(TestEnum.something) == "TestEnum.something" diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 38409b03e888..89908e2793b8 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -2,27 +2,20 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import base64 -import calendar +import datetime import json import os import time -import iso8601 - +import pretend import pytest -import six - +import cryptography_vectors from cryptography.fernet import Fernet, InvalidToken, MultiFernet -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.backends.interfaces import CipherBackend, HMACBackend from cryptography.hazmat.primitives.ciphers import algorithms, modes -import cryptography_vectors - def json_parametrize(keys, filename): vector_file = cryptography_vectors.open_vector_file( @@ -31,24 +24,19 @@ def json_parametrize(keys, filename): with vector_file: data = json.load(vector_file) return pytest.mark.parametrize( - keys, [tuple([entry[k] for k in keys]) for entry in data] + keys, + [tuple([entry[k] for k in keys]) for entry in data], + ids=[f"{filename}[{i}]" for i in range(len(data))], ) -def test_default_backend(): - f = Fernet(Fernet.generate_key()) - assert f._backend is default_backend() - - -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.requires_backend_interface(interface=HMACBackend) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) -class TestFernet(object): +class TestFernet: @json_parametrize( ("secret", "now", "iv", "src", "token"), "generate.json", @@ -57,8 +45,8 @@ def test_generate(self, secret, now, iv, src, token, backend): f = Fernet(secret.encode("ascii"), backend=backend) actual_token = f._encrypt_from_parts( src.encode("ascii"), - calendar.timegm(iso8601.parse_date(now).utctimetuple()), - b"".join(map(six.int2byte, iv)), + int(datetime.datetime.fromisoformat(now).timestamp()), + bytes(iv), ) assert actual_token == token.encode("ascii") @@ -69,22 +57,35 @@ def test_generate(self, secret, now, iv, src, token, backend): def test_verify( self, secret, now, src, ttl_sec, token, backend, monkeypatch ): + # secret & token are both str f = Fernet(secret.encode("ascii"), backend=backend) - current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + current_time = int(datetime.datetime.fromisoformat(now).timestamp()) + payload = f.decrypt_at_time( + token, # str + ttl=ttl_sec, + current_time=current_time, + ) + assert payload == src.encode("ascii") + payload = f.decrypt_at_time( - token.encode("ascii"), + token.encode("ascii"), # bytes ttl=ttl_sec, current_time=current_time, ) assert payload == src.encode("ascii") + monkeypatch.setattr(time, "time", lambda: current_time) - payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) + + payload = f.decrypt(token, ttl=ttl_sec) # str + assert payload == src.encode("ascii") + + payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) # bytes assert payload == src.encode("ascii") @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch): f = Fernet(secret.encode("ascii"), backend=backend) - current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + current_time = int(datetime.datetime.fromisoformat(now).timestamp()) with pytest.raises(InvalidToken): f.decrypt_at_time( token.encode("ascii"), @@ -109,21 +110,21 @@ def test_non_base64_token(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(InvalidToken): f.decrypt(b"\x00") + with pytest.raises(InvalidToken): + f.decrypt("nonsensetoken") - def test_unicode(self, backend): + def test_invalid_types(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(TypeError): - f.encrypt(u"") + f.encrypt("") # type: ignore[arg-type] with pytest.raises(TypeError): - f.decrypt(u"") + f.decrypt(12345) # type: ignore[arg-type] def test_timestamp_ignored_no_ttl(self, monkeypatch, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) pt = b"encrypt me" token = f.encrypt(pt) - ts = "1985-10-26T01:20:01-07:00" - current_time = calendar.timegm(iso8601.parse_date(ts).utctimetuple()) - monkeypatch.setattr(time, "time", lambda: current_time) + monkeypatch.setattr(time, "time", pretend.raiser(ValueError)) assert f.decrypt(token, ttl=None) == pt def test_ttl_required_in_decrypt_at_time(self, monkeypatch, backend): @@ -131,35 +132,39 @@ def test_ttl_required_in_decrypt_at_time(self, monkeypatch, backend): pt = b"encrypt me" token = f.encrypt(pt) with pytest.raises(ValueError): - f.decrypt_at_time(token, ttl=None, current_time=int(time.time())) + f.decrypt_at_time( + token, + ttl=None, # type: ignore[arg-type] + current_time=int(time.time()), + ) @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) def test_roundtrips(self, message, backend): f = Fernet(Fernet.generate_key(), backend=backend) assert f.decrypt(f.encrypt(message)) == message - def test_bad_key(self, backend): + @pytest.mark.parametrize("key", [base64.urlsafe_b64encode(b"abc"), b"abc"]) + def test_bad_key(self, backend, key): with pytest.raises(ValueError): - Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend) + Fernet(key, backend=backend) def test_extract_timestamp(self, monkeypatch, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) current_time = 1526138327 token = f.encrypt_at_time(b"encrypt me", current_time) assert f.extract_timestamp(token) == current_time + assert f.extract_timestamp(token.decode("ascii")) == current_time with pytest.raises(InvalidToken): f.extract_timestamp(b"nonsensetoken") -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.requires_backend_interface(interface=HMACBackend) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) -class TestMultiFernet(object): +class TestMultiFernet: def test_encrypt(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) @@ -172,9 +177,14 @@ def test_decrypt(self, backend): f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) f = MultiFernet([f1, f2]) + # token as bytes assert f.decrypt(f1.encrypt(b"abc")) == b"abc" assert f.decrypt(f2.encrypt(b"abc")) == b"abc" + # token as str + assert f.decrypt(f1.encrypt(b"abc").decode("ascii")) == b"abc" + assert f.decrypt(f2.encrypt(b"abc").decode("ascii")) == b"abc" + with pytest.raises(InvalidToken): f.decrypt(b"\x00" * 16) @@ -187,7 +197,9 @@ def test_decrypt_at_time(self, backend): with pytest.raises(InvalidToken): f.decrypt_at_time(token, ttl=1, current_time=102) with pytest.raises(ValueError): - f.decrypt_at_time(token, ttl=None, current_time=100) + f.decrypt_at_time( + token, ttl=None, current_time=100 # type: ignore[arg-type] + ) def test_no_fernets(self, backend): with pytest.raises(ValueError): @@ -195,9 +207,9 @@ def test_no_fernets(self, backend): def test_non_iterable_argument(self, backend): with pytest.raises(TypeError): - MultiFernet(None) + MultiFernet(None) # type: ignore[arg-type] - def test_rotate(self, backend): + def test_rotate_bytes(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) @@ -217,6 +229,25 @@ def test_rotate(self, backend): with pytest.raises(InvalidToken): mf1.decrypt(rotated) + def test_rotate_str(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) + + mf1 = MultiFernet([f1]) + mf2 = MultiFernet([f2, f1]) + + plaintext = b"abc" + mf1_ciphertext = mf1.encrypt(plaintext).decode("ascii") + + assert mf2.decrypt(mf1_ciphertext) == plaintext + rotated = mf2.rotate(mf1_ciphertext).decode("ascii") + + assert rotated != mf1_ciphertext + assert mf2.decrypt(rotated) == plaintext + + with pytest.raises(InvalidToken): + mf1.decrypt(rotated) + def test_rotate_preserves_timestamp(self, backend, monkeypatch): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py deleted file mode 100644 index 042245f97706..000000000000 --- a/tests/test_interfaces.py +++ /dev/null @@ -1,86 +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. - -import abc - -import pytest - -import six - -from cryptography.utils import ( - InterfaceNotImplemented, - register_interface_if, - verify_interface, -) - - -def test_register_interface_if_true(): - @six.add_metaclass(abc.ABCMeta) - class SimpleInterface(object): - pass - - @register_interface_if(1 == 1, SimpleInterface) - class SimpleClass(object): - pass - - assert issubclass(SimpleClass, SimpleInterface) is True - - -def test_register_interface_if_false(): - @six.add_metaclass(abc.ABCMeta) - class SimpleInterface(object): - pass - - @register_interface_if(1 == 2, SimpleInterface) - class SimpleClass(object): - pass - - assert issubclass(SimpleClass, SimpleInterface) is False - - -class TestVerifyInterface(object): - def test_verify_missing_method(self): - @six.add_metaclass(abc.ABCMeta) - class SimpleInterface(object): - @abc.abstractmethod - def method(self): - """A simple method""" - - class NonImplementer(object): - pass - - with pytest.raises(InterfaceNotImplemented): - verify_interface(SimpleInterface, NonImplementer) - - def test_different_arguments(self): - @six.add_metaclass(abc.ABCMeta) - class SimpleInterface(object): - @abc.abstractmethod - def method(self, a): - """Method with one argument""" - - class NonImplementer(object): - def method(self): - """Method with no arguments""" - - # Invoke this to ensure the line is covered - NonImplementer().method() - with pytest.raises(InterfaceNotImplemented): - verify_interface(SimpleInterface, NonImplementer) - - def test_handles_abstract_property(self): - @six.add_metaclass(abc.ABCMeta) - class SimpleInterface(object): - @abc.abstractproperty - def property(self): - """An abstract property""" - - class NonImplementer(object): - @property - def property(self): - """A concrete property""" - - # Invoke this to ensure the line is covered - NonImplementer().property - verify_interface(SimpleInterface, NonImplementer) diff --git a/tests/test_meta.py b/tests/test_meta.py new file mode 100644 index 000000000000..9d7cde8e722e --- /dev/null +++ b/tests/test_meta.py @@ -0,0 +1,38 @@ +# 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 pkgutil +import subprocess +import sys +import typing + +import cryptography + + +def find_all_modules() -> typing.List[str]: + return sorted( + mod + for _, mod, _ in pkgutil.walk_packages( + cryptography.__path__, + prefix=cryptography.__name__ + ".", + ) + ) + + +def test_no_circular_imports(subtests): + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join(sys.path) + + # When using pytest-cov it attempts to instrument subprocesses. This + # causes the memleak tests to raise exceptions. + # we don't need coverage so we remove the env vars. + env.pop("COV_CORE_CONFIG", None) + env.pop("COV_CORE_DATAFILE", None) + env.pop("COV_CORE_SOURCE", None) + + for module in find_all_modules(): + with subtests.test(): + argv = [sys.executable, "-c", f"__import__({module!r})"] + subprocess.check_call(argv, env=env) diff --git a/tests/test_rust_utils.py b/tests/test_rust_utils.py new file mode 100644 index 000000000000..1ee68541e7fc --- /dev/null +++ b/tests/test_rust_utils.py @@ -0,0 +1,63 @@ +# 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 gc +import threading + +from cryptography.hazmat.bindings._rust import FixedPool + + +class TestFixedPool: + def test_basic(self): + c = 0 + events = [] + + def create(): + nonlocal c + c += 1 + events.append(("create", c)) + return c + + pool = FixedPool(create) + assert events == [("create", 1)] + with pool.acquire() as c: + assert c == 1 + assert events == [("create", 1)] + + with pool.acquire() as c: + assert c == 2 + assert events == [("create", 1), ("create", 2)] + + assert events == [("create", 1), ("create", 2)] + + assert events == [("create", 1), ("create", 2)] + + del pool + gc.collect() + gc.collect() + gc.collect() + + assert events == [ + ("create", 1), + ("create", 2), + ] + + def test_thread_stress(self): + def create(): + return None + + pool = FixedPool(create) + + def thread_fn(): + with pool.acquire(): + pass + + threads = [] + for i in range(1024): + t = threading.Thread(target=thread_fn) + t.start() + threads.append(t) + + for t in threads: + t.join() diff --git a/tests/test_utils.py b/tests/test_utils.py index d6afa3b34902..9f6e271500cc 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,21 +2,21 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii +import inspect import os import textwrap import pretend - import pytest import cryptography -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons - +import cryptography.utils import cryptography_vectors +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from . import deprecated_module from .utils import ( check_backend_support, load_cryptrec_vectors, @@ -4104,15 +4104,21 @@ def test_load_x963_vectors(): "hash": "SHA-512", "count": 0, "shared_secret_length": 521, - "Z": "00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb369348b\ -81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d", + "Z": ( + "00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb3693" + "48b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e7" + "53f55ef05a2d" + ), "sharedinfo_length": 128, "sharedinfo": "e3b5b4c1b0d5cf1d2b3a2f9937895d31", "key_data_length": 1024, - "key_data": "4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7f\ -a733633d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee208002267089db\ -f351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1a59b4106cf2dbbc0ab2\ -aa8e2efa7b17902d34276951ceccab87f9661c3e8816", + "key_data": ( + "4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733" + "633d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee" + "208002267089dbf351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda" + "33ca08bd56dd1a59b4106cf2dbbc0ab2aa8e2efa7b17902d34276951cecc" + "ab87f9661c3e8816" + ), }, ] @@ -4180,11 +4186,17 @@ def test_load_kbkdf_vectors(): "l": 128, "ki": b"00a39bd547fb88b2d98727cf64c195c61e1cad6c", "fixedinputdatabytelen": b"60", - "fixedinputdata": b"98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc\ -30056f6876f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242", + "fixedinputdata": ( + b"98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f6876f" + b"59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a682" + b"42" + ), "binary rep of i": b"01", - "instring": b"0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc3005\ -6f6876f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242", + "instring": ( + b"0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f687" + b"6f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a6" + b"8242" + ), "ko": b"0611e1903609b47ad7a5fc2c82e47702", }, { @@ -4194,11 +4206,17 @@ def test_load_kbkdf_vectors(): "l": 128, "ki": b"a39bdf744ed7e33fdec060c8736e9725179885a8", "fixedinputdatabytelen": b"60", - "fixedinputdata": b"af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9\ -c591e18235019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832", + "fixedinputdata": ( + b"af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182350" + b"19f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e8" + b"32" + ), "binary rep of i": b"01", - "instring": b"01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591\ -e18235019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832", + "instring": ( + b"01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e1823" + b"5019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045" + b"e832" + ), "ko": b"51dc4668947e3685099bc3b5f8527468", }, { @@ -4208,11 +4226,17 @@ def test_load_kbkdf_vectors(): "l": 128, "ki": b"ab56556b107a3a79fe084df0f1bb3ad049a6cc1490f20da4b3df282c", "fixedinputdatabytelen": b"60", - "fixedinputdata": b"7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43\ -aa1b91eeb5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a7", + "fixedinputdata": ( + b"7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91eeb57" + b"30d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4" + b"a7" + ), "binary rep of i": b"01", - "instring": b"7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91\ -eeb5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a701", + "instring": ( + b"7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91eeb57" + b"30d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4" + b"a701" + ), "ko": b"b8894c6133a46701909b5c8a84322dec", }, ] @@ -4445,3 +4469,20 @@ def test_raises_unsupported_algorithm(): "An error.", _Reasons.BACKEND_MISSING_INTERFACE ) assert exc_info.type is UnsupportedAlgorithm + + +class TestDeprecated: + def test_getattr(self): + with pytest.warns(DeprecationWarning): + assert deprecated_module.DEPRECATED == 3 + + assert deprecated_module.NOT_DEPRECATED == 12 + + def test_inspect_deprecated_module(self): + # Check if inspection is supported by _ModuleWithDeprecations. + assert isinstance( + deprecated_module, cryptography.utils._ModuleWithDeprecations + ) + source_file = inspect.getsourcefile(deprecated_module) + assert isinstance(source_file, str) + assert source_file.endswith("deprecated_module.py") diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 073c699bc084..412ff6323100 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -2,10 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import sys import types +import typing import warnings import pytest @@ -13,11 +13,13 @@ from cryptography.utils import deprecated -class TestDeprecated(object): +class TestDeprecated: + @typing.no_type_check def test_deprecated(self, monkeypatch): mod = types.ModuleType("TestDeprecated/test_deprecated") monkeypatch.setitem(sys.modules, mod.__name__, mod) - mod.X = deprecated( + deprecated( + name="X", value=1, module_name=mod.__name__, message="deprecated message text", @@ -48,10 +50,12 @@ def test_deprecated(self, monkeypatch): assert "Y" in dir(mod) + @typing.no_type_check def test_deleting_deprecated_members(self, monkeypatch): mod = types.ModuleType("TestDeprecated/test_deprecated") monkeypatch.setitem(sys.modules, mod.__name__, mod) - mod.X = deprecated( + deprecated( + name="X", value=1, module_name=mod.__name__, message="deprecated message text", diff --git a/tests/utils.py b/tests/utils.py index 5d98af00e337..bad0f87da164 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,23 +2,19 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import collections import json import os import re +import typing from contextlib import contextmanager import pytest -import six - -from cryptography.exceptions import UnsupportedAlgorithm - import cryptography_vectors - +from cryptography.exceptions import UnsupportedAlgorithm HashVector = collections.namedtuple("HashVector", ["message", "digest"]) KeyedHashVector = collections.namedtuple( @@ -37,16 +33,21 @@ def raises_unsupported_algorithm(reason): with pytest.raises(UnsupportedAlgorithm) as exc_info: yield exc_info - assert exc_info.value._reason is reason + assert exc_info.value._reason == reason + +T = typing.TypeVar("T") -def load_vectors_from_file(filename, loader, mode="r"): + +def load_vectors_from_file( + filename, loader: typing.Callable[..., T], mode="r" +) -> T: with cryptography_vectors.open_vector_file(filename, mode) as vector_file: return loader(vector_file) def load_nist_vectors(vector_data): - test_data = None + test_data = {} data = [] for line in vector_data: @@ -65,7 +66,7 @@ def load_nist_vectors(vector_data): continue # Build our data using a simple Key = Value format - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) # Some tests (PBKDF2) contain \0, which should be interpreted as a # null character rather than literal. @@ -106,12 +107,12 @@ def load_cryptrec_vectors(vector_data): {"key": key, "plaintext": pt, "ciphertext": ct} ) else: - raise ValueError("Invalid line in file '{}'".format(line)) + raise ValueError(f"Invalid line in file '{line}'") return cryptrec_list def load_hash_vectors(vector_data): - vectors = [] + vectors: typing.List[typing.Union[KeyedHashVector, HashVector]] = [] key = None msg = None md = None @@ -153,11 +154,11 @@ def load_pkcs1_vectors(vector_data): """ Loads data out of RSA PKCS #1 vector files. """ - private_key_vector = None - public_key_vector = None + private_key_vector: typing.Optional[typing.Dict[str, typing.Any]] = None + public_key_vector: typing.Optional[typing.Dict[str, typing.Any]] = None attr = None - key = None - example_vector = None + key: typing.Any = None + example_vector: typing.Optional[typing.Dict[str, typing.Any]] = None examples = [] vectors = [] for line in vector_data: @@ -167,9 +168,9 @@ def load_pkcs1_vectors(vector_data): or line.startswith("# PKCS#1 v1.5") ): if example_vector: - for key, value in six.iteritems(example_vector): - hex_str = "".join(value).replace(" ", "").encode("ascii") - example_vector[key] = hex_str + for key, value in example_vector.items(): + hex_bytes = "".join(value).replace(" ", "").encode("ascii") + example_vector[key] = hex_bytes examples.append(example_vector) attr = None @@ -193,9 +194,9 @@ def load_pkcs1_vectors(vector_data): elif example_vector and line.startswith( "# =============================================" ): - for key, value in six.iteritems(example_vector): - hex_str = "".join(value).replace(" ", "").encode("ascii") - example_vector[key] = hex_str + for key, value in example_vector.items(): + hex_bytes = "".join(value).replace(" ", "").encode("ascii") + example_vector[key] = hex_bytes examples.append(example_vector) example_vector = None attr = None @@ -213,11 +214,11 @@ def load_pkcs1_vectors(vector_data): assert private_key_vector assert public_key_vector - for key, value in six.iteritems(public_key_vector): + for key, value in public_key_vector.items(): hex_str = "".join(value).replace(" ", "") public_key_vector[key] = int(hex_str, 16) - for key, value in six.iteritems(private_key_vector): + for key, value in private_key_vector.items(): hex_str = "".join(value).replace(" ", "") private_key_vector[key] = int(hex_str, 16) @@ -242,9 +243,6 @@ def load_pkcs1_vectors(vector_data): attr = None if private_key_vector is None or public_key_vector is None: - # Random garbage to defeat CPython's peephole optimizer so that - # coverage records correctly: https://bugs.python.org/issue2506 - 1 + 1 continue if line.startswith("# Private key"): @@ -280,7 +278,7 @@ def load_pkcs1_vectors(vector_data): def load_rsa_nist_vectors(vector_data): - test_data = None + test_data: typing.Dict[str, typing.Any] = {} p = None salt_length = None data = [] @@ -299,7 +297,7 @@ def load_rsa_nist_vectors(vector_data): continue # Build our data using a simple Key = Value format - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) if name == "n": n = int(value, 16) @@ -371,14 +369,16 @@ def load_fips_dsa_key_pair_vectors(vector_data): return vectors +FIPS_SHA_REGEX = re.compile( + r"\[mod = L=...., N=..., SHA-(?P1|224|256|384|512)\]" +) + + def load_fips_dsa_sig_vectors(vector_data): """ Loads data out of the FIPS DSA SigVer vector files. """ vectors = [] - sha_regex = re.compile( - r"\[mod = L=...., N=..., SHA-(?P1|224|256|384|512)\]" - ) for line in vector_data: line = line.strip() @@ -386,14 +386,14 @@ def load_fips_dsa_sig_vectors(vector_data): if not line or line.startswith("#"): continue - sha_match = sha_regex.match(line) + sha_match = FIPS_SHA_REGEX.match(line) if sha_match: digest_algorithm = "SHA-{}".format(sha_match.group("sha")) if line.startswith("[mod"): continue - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) if name == "P": vectors.append( @@ -485,21 +485,22 @@ def load_fips_ecdsa_key_pair_vectors(vector_data): return vectors +CURVE_REGEX = re.compile( + r"\[(?P[PKB]-[0-9]{3}),SHA-(?P1|224|256|384|512)\]" +) + + def load_fips_ecdsa_signing_vectors(vector_data): """ Loads data out of the FIPS ECDSA SigGen vector files. """ vectors = [] - curve_rx = re.compile( - r"\[(?P[PKB]-[0-9]{3}),SHA-(?P1|224|256|384|512)\]" - ) - - data = None + data: typing.Optional[typing.Dict[str, object]] = None for line in vector_data: line = line.strip() - curve_match = curve_rx.match(line) + curve_match = CURVE_REGEX.match(line) if curve_match: curve_name = _ECDSA_CURVE_NAMES[curve_match.group("curve")] digest_name = "SHA-{}".format(curve_match.group("sha")) @@ -535,15 +536,16 @@ def load_fips_ecdsa_signing_vectors(vector_data): return vectors +KASVS_RESULT_REGEX = re.compile(r"([FP]) \(([0-9]+) -") + + def load_kasvs_dh_vectors(vector_data): """ Loads data out of the KASVS key exchange vector data """ - result_rx = re.compile(r"([FP]) \(([0-9]+) -") - vectors = [] - data = {"fail_z": False, "fail_agree": False} + data: typing.Dict[str, typing.Any] = {"fail_z": False, "fail_agree": False} for line in vector_data: line = line.strip() @@ -570,7 +572,8 @@ def load_kasvs_dh_vectors(vector_data): data["y2"] = int(line.split("=")[1], 16) elif line.startswith("Result = "): result_str = line.split("=")[1].strip() - match = result_rx.match(result_str) + match = KASVS_RESULT_REGEX.match(result_str) + assert match is not None if match.group(1) == "F": if int(match.group(2)) in (5, 10): @@ -604,8 +607,6 @@ def load_kasvs_ecdh_vectors(vector_data): "P-521": "secp521r1", } - result_rx = re.compile(r"([FP]) \(([0-9]+) -") - tags = [] sets = {} vectors = [] @@ -644,7 +645,7 @@ def load_kasvs_ecdh_vectors(vector_data): break # Data - data = { + data: typing.Dict[str, typing.Any] = { "CAVS": {}, "IUT": {}, } @@ -679,7 +680,8 @@ def load_kasvs_ecdh_vectors(vector_data): data["DKM"] = int(line.split("=")[1], 16) elif line.startswith("Result = "): result_str = line.split("=")[1].strip() - match = result_rx.match(result_str) + match = KASVS_RESULT_REGEX.match(result_str) + assert match is not None if match.group(1) == "F": data["fail"] = True @@ -732,14 +734,17 @@ def load_x963_vectors(vector_data): vector["key_data_length"] = key_data_len elif line.startswith("Z"): vector["Z"] = line.split("=")[1].strip() + assert vector["Z"] is not None assert ((shared_secret_len + 7) // 8) * 2 == len(vector["Z"]) elif line.startswith("SharedInfo"): if shared_info_len != 0: vector["sharedinfo"] = line.split("=")[1].strip() + assert vector["sharedinfo"] is not None silen = len(vector["sharedinfo"]) assert ((shared_info_len + 7) // 8) * 2 == silen elif line.startswith("key_data"): vector["key_data"] = line.split("=")[1].strip() + assert vector["key_data"] is not None assert ((key_data_len + 7) // 8) * 2 == len(vector["key_data"]) vectors.append(vector) vector = {} @@ -763,7 +768,7 @@ def load_nist_kbkdf_vectors(vector_data): if line.startswith("[") and line.endswith("]"): tag_data = line[1:-1] - name, value = [c.strip() for c in tag_data.split("=")] + name, value = (c.strip() for c in tag_data.split("=")) if value.endswith("_BITS"): value = int(value.split("_")[0]) tag.update({name.lower(): value}) @@ -774,11 +779,11 @@ def load_nist_kbkdf_vectors(vector_data): test_data = {} test_data.update(tag) vectors.append(test_data) - elif line.startswith("L"): - name, value = [c.strip() for c in line.split("=")] + elif line.startswith(("L", "DataBeforeCtrLen", "DataAfterCtrLen")): + name, value = (c.strip() for c in line.split("=")) test_data[name.lower()] = int(value) else: - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) test_data[name.lower()] = value.encode("ascii") return vectors @@ -804,7 +809,7 @@ def load_ed25519_vectors(vector_data): def load_nist_ccm_vectors(vector_data): - test_data = None + test_data = {} section_data = None global_data = {} new_section = False @@ -820,7 +825,7 @@ def load_nist_ccm_vectors(vector_data): # Some of the CCM vectors have global values for this. They are always # at the top before the first section header (see: VADT, VNT, VPT) if line.startswith(("Alen", "Plen", "Nlen", "Tlen")): - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) global_data[name.lower()] = int(value) continue @@ -831,11 +836,11 @@ def load_nist_ccm_vectors(vector_data): section = line[1:-1] items = [c.strip() for c in section.split(",")] for item in items: - name, value = [c.strip() for c in item.split("=")] + name, value = (c.strip() for c in item.split("=")) section_data[name.lower()] = int(value) continue - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) if name.lower() in ("key", "nonce") and new_section: section_data[name.lower()] = value.encode("ascii") @@ -876,7 +881,7 @@ def load_nist_ccm_vectors(vector_data): return data -class WycheproofTest(object): +class WycheproofTest: def __init__(self, testfiledata, testgroup, testcase): self.testfiledata = testfiledata self.testgroup = testgroup @@ -891,26 +896,26 @@ def __repr__(self): ) @property - def valid(self): + def valid(self) -> bool: return self.testcase["result"] == "valid" @property - def acceptable(self): + def acceptable(self) -> bool: return self.testcase["result"] == "acceptable" @property - def invalid(self): + def invalid(self) -> bool: return self.testcase["result"] == "invalid" - def has_flag(self, flag): + def has_flag(self, flag: str) -> bool: return flag in self.testcase["flags"] - -def skip_if_wycheproof_none(wycheproof): - # This is factored into its own function so we can easily test both - # branches - if wycheproof is None: - pytest.skip("--wycheproof-root not provided") + def cache_value_to_group(self, cache_key: str, func): + cache_val = self.testgroup.get(cache_key) + if cache_val is not None: + return cache_val + self.testgroup[cache_key] = cache_val = func() + return cache_val def load_wycheproof_tests(wycheproof, test_file): diff --git a/tests/wycheproof/test_aes.py b/tests/wycheproof/test_aes.py index e33c01e99f54..ce83fe3c0fa2 100644 --- a/tests/wycheproof/test_aes.py +++ b/tests/wycheproof/test_aes.py @@ -2,23 +2,21 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import InvalidTag -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESGCM from ..hazmat.primitives.test_aead import _aead_supported +from .utils import wycheproof_tests -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.wycheproof_tests("aes_cbc_pkcs5_test.json") +@wycheproof_tests("aes_cbc_pkcs5_test.json") def test_aes_cbc_pkcs5(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) @@ -45,8 +43,7 @@ def test_aes_cbc_pkcs5(backend, wycheproof): unpadder.update(padded_msg) + unpadder.finalize() -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.wycheproof_tests("aes_gcm_test.json") +@wycheproof_tests("aes_gcm_test.json") def test_aes_gcm(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) @@ -54,6 +51,11 @@ def test_aes_gcm(backend, wycheproof): msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) + if len(iv) < 8 or len(iv) > 128: + pytest.skip( + "Less than 64-bit IVs (and greater than 1024-bit) are no longer " + "supported" + ) if backend._fips_enabled and len(iv) != 12: # Red Hat disables non-96-bit IV support as part of its FIPS # patches. @@ -73,9 +75,6 @@ def test_aes_gcm(backend, wycheproof): dec.authenticate_additional_data(aad) computed_msg = dec.update(ct) + dec.finalize() assert computed_msg == msg - elif len(iv) == 0: - with pytest.raises(ValueError): - Cipher(algorithms.AES(key), modes.GCM(iv), backend) else: dec = Cipher( algorithms.AES(key), @@ -88,8 +87,7 @@ def test_aes_gcm(backend, wycheproof): dec.finalize() -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.wycheproof_tests("aes_gcm_test.json") +@wycheproof_tests("aes_gcm_test.json") def test_aes_gcm_aead_api(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) @@ -97,6 +95,12 @@ def test_aes_gcm_aead_api(backend, wycheproof): msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) + if len(iv) < 8 or len(iv) > 128: + pytest.skip( + "Less than 64-bit IVs (and greater than 1024-bit) are no longer " + "supported" + ) + if backend._fips_enabled and len(iv) != 12: # Red Hat disables non-96-bit IV support as part of its FIPS # patches. @@ -107,9 +111,6 @@ def test_aes_gcm_aead_api(backend, wycheproof): assert computed_ct == ct + tag computed_msg = aesgcm.decrypt(iv, ct + tag, aad) assert computed_msg == msg - elif len(iv) == 0: - with pytest.raises(ValueError): - aesgcm.encrypt(iv, msg, aad) else: with pytest.raises(InvalidTag): aesgcm.decrypt(iv, ct + tag, aad) @@ -119,8 +120,7 @@ def test_aes_gcm_aead_api(backend, wycheproof): not _aead_supported(AESCCM), reason="Requires OpenSSL with AES-CCM support", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.wycheproof_tests("aes_ccm_test.json") +@wycheproof_tests("aes_ccm_test.json") def test_aes_ccm_aead_api(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) diff --git a/tests/wycheproof/test_chacha20poly1305.py b/tests/wycheproof/test_chacha20poly1305.py index 48023ca63d70..06d6fc76a092 100644 --- a/tests/wycheproof/test_chacha20poly1305.py +++ b/tests/wycheproof/test_chacha20poly1305.py @@ -2,26 +2,24 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import InvalidTag -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 from ..hazmat.primitives.test_aead import _aead_supported +from .utils import wycheproof_tests @pytest.mark.skipif( not _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL with ChaCha20Poly1305 support", ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.wycheproof_tests("chacha20_poly1305_test.json") -def test_chacha2poly1305(wycheproof): +@wycheproof_tests("chacha20_poly1305_test.json") +def test_chacha20poly1305(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) aad = binascii.unhexlify(wycheproof.testcase["aad"]) diff --git a/tests/wycheproof/test_cmac.py b/tests/wycheproof/test_cmac.py index bef858395c9c..bca84805d7b9 100644 --- a/tests/wycheproof/test_cmac.py +++ b/tests/wycheproof/test_cmac.py @@ -2,20 +2,19 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.backends.interfaces import CMACBackend from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.cmac import CMAC +from .utils import wycheproof_tests -@pytest.mark.requires_backend_interface(interface=CMACBackend) -@pytest.mark.wycheproof_tests("aes_cmac_test.json") + +@wycheproof_tests("aes_cmac_test.json") def test_aes_cmac(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) msg = binascii.unhexlify(wycheproof.testcase["msg"]) diff --git a/tests/wycheproof/test_dsa.py b/tests/wycheproof/test_dsa.py index 9185b3e2f4e0..fd76a938bfd3 100644 --- a/tests/wycheproof/test_dsa.py +++ b/tests/wycheproof/test_dsa.py @@ -2,16 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.backends.interfaces import DSABackend from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import dsa +from .utils import wycheproof_tests _DIGESTS = { "SHA-1": hashes.SHA1(), @@ -20,8 +20,11 @@ } -@pytest.mark.requires_backend_interface(interface=DSABackend) -@pytest.mark.wycheproof_tests( +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Requires OpenSSL with DSA support", +) +@wycheproof_tests( "dsa_test.json", "dsa_2048_224_sha224_test.json", "dsa_2048_224_sha256_test.json", @@ -32,6 +35,7 @@ def test_dsa_signature(backend, wycheproof): key = serialization.load_der_public_key( binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend ) + assert isinstance(key, dsa.DSAPublicKey) digest = _DIGESTS[wycheproof.testgroup["sha"]] if wycheproof.valid or ( diff --git a/tests/wycheproof/test_ecdh.py b/tests/wycheproof/test_ecdh.py index b89dc68ce7fc..e2624a45a53c 100644 --- a/tests/wycheproof/test_ecdh.py +++ b/tests/wycheproof/test_ecdh.py @@ -2,19 +2,17 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends.interfaces import EllipticCurveBackend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from ..hazmat.primitives.test_ec import _skip_exchange_algorithm_unsupported - +from .utils import wycheproof_tests _CURVES = { "secp224r1": ec.SECP224R1(), @@ -23,6 +21,12 @@ "secp521r1": ec.SECP521R1(), "secp224k1": None, "secp256k1": ec.SECP256K1(), + "sect283r1": ec.SECT283R1(), + "sect409r1": ec.SECT409R1(), + "sect571r1": ec.SECT571R1(), + "sect283k1": ec.SECT283K1(), + "sect409k1": ec.SECT409K1(), + "sect571k1": ec.SECT571K1(), "brainpoolP224r1": None, "brainpoolP256r1": ec.BrainpoolP256R1(), "brainpoolP320r1": None, @@ -33,11 +37,11 @@ "brainpoolP320t1": None, "brainpoolP384t1": None, "brainpoolP512t1": None, + "FRP256v1": None, } -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "ecdh_test.json", "ecdh_brainpoolP224r1_test.json", "ecdh_brainpoolP256r1_test.json", @@ -49,6 +53,12 @@ "ecdh_secp256r1_test.json", "ecdh_secp384r1_test.json", "ecdh_secp521r1_test.json", + "ecdh_sect283k1_test.json", + "ecdh_sect283r1_test.json", + "ecdh_sect409k1_test.json", + "ecdh_sect409r1_test.json", + "ecdh_sect571k1_test.json", + "ecdh_sect571r1_test.json", ) def test_ecdh(backend, wycheproof): curve = _CURVES[wycheproof.testgroup["curve"]] @@ -57,18 +67,19 @@ def test_ecdh(backend, wycheproof): "Unsupported curve ({})".format(wycheproof.testgroup["curve"]) ) _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) - - private_key = ec.derive_private_key( - int(wycheproof.testcase["private"], 16), curve, backend + private_key = wycheproof.cache_value_to_group( + f"private_key_{wycheproof.testcase['private']}", + lambda: ec.derive_private_key( + int(wycheproof.testcase["private"], 16), curve + ), ) try: + # caching these values shows no performance improvement public_key = serialization.load_der_public_key( binascii.unhexlify(wycheproof.testcase["public"]), backend ) - except NotImplementedError: - assert wycheproof.has_flag("UnnamedCurve") - return + assert isinstance(public_key, ec.EllipticCurvePublicKey) except ValueError: assert wycheproof.invalid or wycheproof.acceptable return @@ -84,8 +95,7 @@ def test_ecdh(backend, wycheproof): private_key.exchange(ec.ECDH(), public_key) -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "ecdh_secp224r1_ecpoint_test.json", "ecdh_secp256r1_ecpoint_test.json", "ecdh_secp384r1_ecpoint_test.json", @@ -93,10 +103,14 @@ def test_ecdh(backend, wycheproof): ) def test_ecdh_ecpoint(backend, wycheproof): curve = _CURVES[wycheproof.testgroup["curve"]] + assert isinstance(curve, ec.EllipticCurve) _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) - private_key = ec.derive_private_key( - int(wycheproof.testcase["private"], 16), curve, backend + private_key = wycheproof.cache_value_to_group( + f"private_key_{wycheproof.testcase['private']}", + lambda: ec.derive_private_key( + int(wycheproof.testcase["private"], 16), curve + ), ) if wycheproof.invalid: @@ -107,6 +121,7 @@ def test_ecdh_ecpoint(backend, wycheproof): return assert wycheproof.valid or wycheproof.acceptable + # caching these values shows no performance improvement public_key = ec.EllipticCurvePublicKey.from_encoded_point( curve, binascii.unhexlify(wycheproof.testcase["public"]) ) diff --git a/tests/wycheproof/test_ecdsa.py b/tests/wycheproof/test_ecdsa.py index 802bb9f00b3e..0b0308393511 100644 --- a/tests/wycheproof/test_ecdsa.py +++ b/tests/wycheproof/test_ecdsa.py @@ -2,17 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm -from cryptography.hazmat.backends.interfaces import EllipticCurveBackend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec +from .utils import wycheproof_tests _DIGESTS = { "SHA-1": hashes.SHA1(), @@ -27,8 +26,7 @@ } -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "ecdsa_test.json", "ecdsa_brainpoolP224r1_sha224_test.json", "ecdsa_brainpoolP256r1_sha256_test.json", @@ -55,16 +53,25 @@ "ecdsa_secp384r1_sha3_512_test.json", "ecdsa_secp521r1_sha512_test.json", "ecdsa_secp521r1_sha3_512_test.json", + "ecdsa_secp160k1_sha256_test.json", + "ecdsa_secp160r1_sha256_test.json", + "ecdsa_secp160r2_sha256_test.json", + "ecdsa_secp192k1_sha256_test.json", + "ecdsa_secp192r1_sha256_test.json", ) def test_ecdsa_signature(backend, wycheproof): try: - key = serialization.load_der_public_key( - binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + key = wycheproof.cache_value_to_group( + "cache_key", + lambda: serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]) + ), ) + assert isinstance(key, ec.EllipticCurvePublicKey) except (UnsupportedAlgorithm, ValueError): - # In some OpenSSL 1.0.2s, some keys fail to load with ValueError, - # instead of Unsupported Algorithm. We can remove handling for that - # exception when we drop support. + # In some OpenSSL 1.1.1 versions (RHEL and Fedora), some keys fail to + # load with ValueError, instead of Unsupported Algorithm. We can + # remove handling for that exception when we drop support. pytest.skip( "unable to load key (curve {})".format( wycheproof.testgroup["key"]["curve"] @@ -73,7 +80,7 @@ def test_ecdsa_signature(backend, wycheproof): digest = _DIGESTS[wycheproof.testgroup["sha"]] if not backend.hash_supported(digest): - pytest.skip("Hash {} not supported".format(digest)) + pytest.skip(f"Hash {digest} not supported") if wycheproof.valid or ( wycheproof.acceptable and not wycheproof.has_flag("MissingZero") diff --git a/tests/wycheproof/test_eddsa.py b/tests/wycheproof/test_eddsa.py index 42c1498afff1..3b5dae37749f 100644 --- a/tests/wycheproof/test_eddsa.py +++ b/tests/wycheproof/test_eddsa.py @@ -2,22 +2,23 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + +from .utils import wycheproof_tests @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) -@pytest.mark.wycheproof_tests("eddsa_test.json") +@wycheproof_tests("eddsa_test.json") def test_ed25519_signature(backend, wycheproof): # We want to fail if/when wycheproof adds more edwards curve tests # so we can add them as well. @@ -44,7 +45,7 @@ def test_ed25519_signature(backend, wycheproof): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) -@pytest.mark.wycheproof_tests("ed448_test.json") +@wycheproof_tests("ed448_test.json") def test_ed448_signature(backend, wycheproof): key = Ed448PublicKey.from_public_bytes( binascii.unhexlify(wycheproof.testgroup["key"]["pk"]) diff --git a/tests/wycheproof/test_hkdf.py b/tests/wycheproof/test_hkdf.py index 3e1687ea3105..3d54e44ffc6e 100644 --- a/tests/wycheproof/test_hkdf.py +++ b/tests/wycheproof/test_hkdf.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii @@ -11,6 +10,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from .utils import wycheproof_tests _HASH_ALGORITHMS = { "HKDF-SHA-1": hashes.SHA1(), @@ -20,7 +20,7 @@ } -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "hkdf_sha1_test.json", "hkdf_sha256_test.json", "hkdf_sha384_test.json", diff --git a/tests/wycheproof/test_hmac.py b/tests/wycheproof/test_hmac.py index 0cf908fe90c1..4a42dc1eda5f 100644 --- a/tests/wycheproof/test_hmac.py +++ b/tests/wycheproof/test_hmac.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii @@ -11,6 +10,7 @@ from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, hmac +from .utils import wycheproof_tests _HMAC_ALGORITHMS = { "HMACSHA1": hashes.SHA1(), @@ -25,7 +25,7 @@ } -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "hmac_sha1_test.json", "hmac_sha224_test.json", "hmac_sha256_test.json", @@ -40,8 +40,8 @@ def test_hmac(backend, wycheproof): hash_algo = _HMAC_ALGORITHMS[wycheproof.testfiledata["algorithm"]] if wycheproof.testgroup["tagSize"] // 8 != hash_algo.digest_size: pytest.skip("Truncated HMAC not supported") - if not backend.hash_supported(hash_algo): - pytest.skip("Hash {} not supported".format(hash_algo.name)) + if not backend.hmac_supported(hash_algo): + pytest.skip(f"Hash {hash_algo.name} not supported") h = hmac.HMAC( key=binascii.unhexlify(wycheproof.testcase["key"]), diff --git a/tests/wycheproof/test_keywrap.py b/tests/wycheproof/test_keywrap.py index 9c7d522e61e0..7aec26989b20 100644 --- a/tests/wycheproof/test_keywrap.py +++ b/tests/wycheproof/test_keywrap.py @@ -2,18 +2,17 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest -from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives import keywrap +from .utils import wycheproof_tests -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.wycheproof_tests("kwp_test.json") + +@wycheproof_tests("kwp_test.json") def test_keywrap_with_padding(backend, wycheproof): wrapping_key = binascii.unhexlify(wycheproof.testcase["key"]) key_to_wrap = binascii.unhexlify(wycheproof.testcase["msg"]) @@ -37,8 +36,7 @@ def test_keywrap_with_padding(backend, wycheproof): ) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -@pytest.mark.wycheproof_tests("kw_test.json") +@wycheproof_tests("kw_test.json") def test_keywrap(backend, wycheproof): wrapping_key = binascii.unhexlify(wycheproof.testcase["key"]) key_to_wrap = binascii.unhexlify(wycheproof.testcase["msg"]) diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index 1262b58853d3..48d20f316a1d 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -2,17 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import pytest from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.backends.interfaces import RSABackend from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import padding, rsa +from .utils import wycheproof_tests _DIGESTS = { "SHA-1": hashes.SHA1(), @@ -35,18 +34,12 @@ def should_verify(backend, wycheproof): return True if wycheproof.acceptable: - if ( - backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - or backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - ) and wycheproof.has_flag("MissingNull"): - return False - return True + return not wycheproof.has_flag("MissingNull") return False -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "rsa_signature_test.json", "rsa_signature_2048_sha224_test.json", "rsa_signature_2048_sha256_test.json", @@ -70,9 +63,13 @@ def should_verify(backend, wycheproof): "rsa_signature_4096_sha512_256_test.json", ) def test_rsa_pkcs1v15_signature(backend, wycheproof): - key = serialization.load_der_public_key( - binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + key = wycheproof.cache_value_to_group( + "cached_key", + lambda: serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), + ), ) + assert isinstance(key, rsa.RSAPublicKey) digest = _DIGESTS[wycheproof.testgroup["sha"]] if digest is None or not backend.hash_supported(digest): @@ -97,14 +94,29 @@ def test_rsa_pkcs1v15_signature(backend, wycheproof): ) -@pytest.mark.wycheproof_tests("rsa_sig_gen_misc_test.json") +@wycheproof_tests("rsa_sig_gen_misc_test.json") def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): - key = serialization.load_pem_private_key( - wycheproof.testgroup["privateKeyPem"].encode(), - password=None, - backend=backend, + key = wycheproof.cache_value_to_group( + "cached_key", + lambda: serialization.load_pem_private_key( + wycheproof.testgroup["privateKeyPem"].encode("ascii"), + password=None, + unsafe_skip_rsa_key_validation=True, + ), ) + assert isinstance(key, rsa.RSAPrivateKey) + digest = _DIGESTS[wycheproof.testgroup["sha"]] + assert digest is not None + if backend._fips_enabled: + if key.key_size < backend._fips_rsa_min_key_size or isinstance( + digest, hashes.SHA1 + ): + pytest.skip( + "Invalid params for FIPS. key: {} bits, digest: {}".format( + key.key_size, digest.name + ) + ) sig = key.sign( binascii.unhexlify(wycheproof.testcase["msg"]), @@ -114,8 +126,7 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): assert sig == binascii.unhexlify(wycheproof.testcase["sig"]) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "rsa_pss_2048_sha1_mgf1_20_test.json", "rsa_pss_2048_sha256_mgf1_0_test.json", "rsa_pss_2048_sha256_mgf1_32_test.json", @@ -127,10 +138,17 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): "rsa_pss_misc_test.json", ) def test_rsa_pss_signature(backend, wycheproof): - key = serialization.load_der_public_key( - binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend - ) digest = _DIGESTS[wycheproof.testgroup["sha"]] + if backend._fips_enabled and isinstance(digest, hashes.SHA1): + pytest.skip("Invalid params for FIPS. SHA1 is disallowed") + + key = wycheproof.cache_value_to_group( + "cached_key", + lambda: serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), + ), + ) + assert isinstance(key, rsa.RSAPublicKey) mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] if digest is None or mgf_digest is None: @@ -164,18 +182,7 @@ def test_rsa_pss_signature(backend, wycheproof): ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.supported( - only_if=lambda backend: ( - backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - or backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - ), - skip_message=( - "A handful of these tests fail on OpenSSL 1.0.2 and since upstream " - "isn't maintaining it, they'll never be fixed." - ), -) -@pytest.mark.wycheproof_tests( +@wycheproof_tests( "rsa_oaep_2048_sha1_mgf1sha1_test.json", "rsa_oaep_2048_sha224_mgf1sha1_test.json", "rsa_oaep_2048_sha224_mgf1sha224_test.json", @@ -196,28 +203,33 @@ def test_rsa_pss_signature(backend, wycheproof): "rsa_oaep_misc_test.json", ) def test_rsa_oaep_encryption(backend, wycheproof): - key = serialization.load_pem_private_key( - wycheproof.testgroup["privateKeyPem"].encode("ascii"), - password=None, - backend=backend, - ) digest = _DIGESTS[wycheproof.testgroup["sha"]] mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] - + assert digest is not None + assert mgf_digest is not None padding_algo = padding.OAEP( mgf=padding.MGF1(algorithm=mgf_digest), algorithm=digest, label=binascii.unhexlify(wycheproof.testcase["label"]), ) - - if not backend.rsa_padding_supported(padding_algo): + if not backend.rsa_encryption_supported(padding_algo): pytest.skip( - "OAEP with digest={} and MGF digest={} not supported".format( - wycheproof.testgroup["sha"], - wycheproof.testgroup["mgfSha"], - ) + f"Does not support OAEP using {mgf_digest.name} MGF1 " + f"or {digest.name} hash." ) + key = wycheproof.cache_value_to_group( + "cached_key", + lambda: serialization.load_pem_private_key( + wycheproof.testgroup["privateKeyPem"].encode("ascii"), + password=None, + unsafe_skip_rsa_key_validation=True, + ), + ) + assert isinstance(key, rsa.RSAPrivateKey) + if backend._fips_enabled and key.key_size < backend._fips_rsa_min_key_size: + pytest.skip("Invalid params for FIPS. <2048 bit keys are disallowed") + if wycheproof.valid or wycheproof.acceptable: pt = key.decrypt( binascii.unhexlify(wycheproof.testcase["ct"]), padding_algo @@ -230,17 +242,27 @@ def test_rsa_oaep_encryption(backend, wycheproof): ) -@pytest.mark.wycheproof_tests( +@pytest.mark.supported( + only_if=lambda backend: backend.rsa_encryption_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5 for encryption.", +) +@wycheproof_tests( "rsa_pkcs1_2048_test.json", "rsa_pkcs1_3072_test.json", "rsa_pkcs1_4096_test.json", ) def test_rsa_pkcs1_encryption(backend, wycheproof): - key = serialization.load_pem_private_key( - wycheproof.testgroup["privateKeyPem"].encode("ascii"), - password=None, - backend=backend, + key = wycheproof.cache_value_to_group( + "cached_key", + lambda: serialization.load_pem_private_key( + wycheproof.testgroup["privateKeyPem"].encode("ascii"), + password=None, + unsafe_skip_rsa_key_validation=True, + ), ) + assert isinstance(key, rsa.RSAPrivateKey) if wycheproof.valid: pt = key.decrypt( @@ -248,8 +270,18 @@ def test_rsa_pkcs1_encryption(backend, wycheproof): ) assert pt == binascii.unhexlify(wycheproof.testcase["msg"]) else: - with pytest.raises(ValueError): - key.decrypt( - binascii.unhexlify(wycheproof.testcase["ct"]), - padding.PKCS1v15(), - ) + if backend._lib.Cryptography_HAS_IMPLICIT_RSA_REJECTION: + try: + assert key.decrypt( + binascii.unhexlify(wycheproof.testcase["ct"]), + padding.PKCS1v15(), + ) != binascii.unhexlify(wycheproof.testcase["ct"]) + except ValueError: + # Some raise ValueError due to length mismatch. + pass + else: + with pytest.raises(ValueError): + key.decrypt( + binascii.unhexlify(wycheproof.testcase["ct"]), + padding.PKCS1v15(), + ) diff --git a/tests/wycheproof/test_utils.py b/tests/wycheproof/test_utils.py index 2cf3be08e97c..b0c36d4797d8 100644 --- a/tests/wycheproof/test_utils.py +++ b/tests/wycheproof/test_utils.py @@ -2,20 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function -import pytest - -from ..utils import WycheproofTest, skip_if_wycheproof_none +from ..utils import WycheproofTest def test_wycheproof_test_repr(): wycheproof = WycheproofTest({}, {}, {"tcId": 3}) assert repr(wycheproof) == "" - - -def test_skip_if_wycheproof_none(): - with pytest.raises(pytest.skip.Exception): - skip_if_wycheproof_none(None) - - skip_if_wycheproof_none("abc") diff --git a/tests/wycheproof/test_x25519.py b/tests/wycheproof/test_x25519.py index ce2a965e3a42..17aef36fe2e1 100644 --- a/tests/wycheproof/test_x25519.py +++ b/tests/wycheproof/test_x25519.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii @@ -13,12 +12,14 @@ X25519PublicKey, ) +from .utils import wycheproof_tests + @pytest.mark.supported( only_if=lambda backend: backend.x25519_supported(), skip_message="Requires OpenSSL with X25519 support", ) -@pytest.mark.wycheproof_tests("x25519_test.json") +@wycheproof_tests("x25519_test.json") def test_x25519(backend, wycheproof): assert set(wycheproof.testgroup.items()) == { ("curve", "curve25519"), diff --git a/tests/wycheproof/test_x448.py b/tests/wycheproof/test_x448.py index fcac80996f74..8e7b321484c3 100644 --- a/tests/wycheproof/test_x448.py +++ b/tests/wycheproof/test_x448.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii @@ -13,12 +12,14 @@ X448PublicKey, ) +from .utils import wycheproof_tests + @pytest.mark.supported( only_if=lambda backend: backend.x448_supported(), skip_message="Requires OpenSSL with X448 support", ) -@pytest.mark.wycheproof_tests("x448_test.json") +@wycheproof_tests("x448_test.json") def test_x448(backend, wycheproof): assert set(wycheproof.testgroup.items()) == { ("curve", "curve448"), diff --git a/tests/wycheproof/utils.py b/tests/wycheproof/utils.py new file mode 100644 index 000000000000..eebbe7ce3bf6 --- /dev/null +++ b/tests/wycheproof/utils.py @@ -0,0 +1,17 @@ +from ..utils import load_wycheproof_tests + + +def wycheproof_tests(*paths): + def wrapper(func): + def run_wycheproof(backend, subtests, pytestconfig): + wycheproof_root = pytestconfig.getoption( + "--wycheproof-root", skip=True + ) + for path in paths: + for test in load_wycheproof_tests(wycheproof_root, path): + with subtests.test(): + func(backend, test) + + return run_wycheproof + + return wrapper diff --git a/tests/x509/test_name.py b/tests/x509/test_name.py new file mode 100644 index 000000000000..4c9ccc3b791c --- /dev/null +++ b/tests/x509/test_name.py @@ -0,0 +1,204 @@ +# 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 pytest + +from cryptography.x509 import ( + Name, + NameAttribute, + NameOID, + RelativeDistinguishedName, +) + + +class TestRFC4514: + def test_invalid(self, subtests): + for value in [ + "C=US,CN=Joe , Smith,DC=example", + ",C=US,CN=Joe , Smith,DC=example", + "C=US,UNKNOWN=Joe , Smith,DC=example", + "C=US,CN,DC=example", + "C=US,FOOBAR=example", + ]: + with subtests.test(): + with pytest.raises(ValueError): + Name.from_rfc4514_string(value) + + def test_valid(self, subtests): + for value, expected in [ + ( + r"CN=James \"Jim\" Smith\, III", + Name( + [ + NameAttribute( + NameOID.COMMON_NAME, 'James "Jim" Smith, III' + ) + ] + ), + ), + ( + r"UID=\# escape\+\,\;\00this\ ", + Name([NameAttribute(NameOID.USER_ID, "# escape+,;\0this ")]), + ), + ( + r"2.5.4.3=James \"Jim\" Smith\, III", + Name( + [ + NameAttribute( + NameOID.COMMON_NAME, 'James "Jim" Smith, III' + ) + ] + ), + ), + ("ST=", Name([NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "")])), + ( + "OU=Sales+CN=J. Smith,DC=example,DC=net", + Name( + [ + RelativeDistinguishedName( + [NameAttribute(NameOID.DOMAIN_COMPONENT, "net")] + ), + RelativeDistinguishedName( + [ + NameAttribute( + NameOID.DOMAIN_COMPONENT, "example" + ) + ] + ), + RelativeDistinguishedName( + [ + NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, "Sales" + ), + NameAttribute( + NameOID.COMMON_NAME, "J. Smith" + ), + ] + ), + ] + ), + ), + ( + "CN=cryptography.io,O=PyCA,L=,ST=,C=US", + Name( + [ + NameAttribute(NameOID.COUNTRY_NAME, "US"), + NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, ""), + NameAttribute(NameOID.LOCALITY_NAME, ""), + NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), + NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), + ] + ), + ), + ( + r"C=US,CN=Joe \, Smith,DC=example", + Name( + [ + NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), + NameAttribute(NameOID.COMMON_NAME, "Joe , Smith"), + NameAttribute(NameOID.COUNTRY_NAME, "US"), + ] + ), + ), + ( + r"C=US,CN=Jane \"J\,S\" Smith,DC=example", + Name( + [ + NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), + NameAttribute(NameOID.COMMON_NAME, 'Jane "J,S" Smith'), + NameAttribute(NameOID.COUNTRY_NAME, "US"), + ] + ), + ), + ( + 'C=US,CN=\\"Jane J\\,S Smith\\",DC=example', + Name( + [ + NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), + NameAttribute(NameOID.COMMON_NAME, '"Jane J,S Smith"'), + NameAttribute(NameOID.COUNTRY_NAME, "US"), + ] + ), + ), + ( + 'C=US,CN=\\"Jane \\"J\\,S\\" Smith\\",DC=example', + Name( + [ + NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), + NameAttribute( + NameOID.COMMON_NAME, '"Jane "J,S" Smith"' + ), + NameAttribute(NameOID.COUNTRY_NAME, "US"), + ] + ), + ), + ( + r"C=US,CN=Jane=Smith,DC=example", + Name( + [ + NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), + NameAttribute(NameOID.COMMON_NAME, "Jane=Smith"), + NameAttribute(NameOID.COUNTRY_NAME, "US"), + ] + ), + ), + (r"CN=#616263", Name([NameAttribute(NameOID.COMMON_NAME, "abc")])), + (r"CN=👍", Name([NameAttribute(NameOID.COMMON_NAME, "👍")])), + ( + "CN=\\\\123", + Name([NameAttribute(NameOID.COMMON_NAME, "\\123")]), + ), + ("CN=\\\\\\;", Name([NameAttribute(NameOID.COMMON_NAME, "\\;")])), + ( + "CN=\\\\#123", + Name([NameAttribute(NameOID.COMMON_NAME, "\\#123")]), + ), + ( + "2.5.4.10=abc", + Name([NameAttribute(NameOID.ORGANIZATION_NAME, "abc")]), + ), + ]: + with subtests.test(): + result = Name.from_rfc4514_string(value) + assert result == expected + + def test_attr_name_override(self): + assert Name.from_rfc4514_string( + "CN=Santa Claus,E=santa@north.pole", {"E": NameOID.EMAIL_ADDRESS} + ) == Name( + [ + NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"), + NameAttribute(NameOID.COMMON_NAME, "Santa Claus"), + ] + ) + + assert Name.from_rfc4514_string( + "CN=Santa Claus", {"CN": NameOID.EMAIL_ADDRESS} + ) == Name( + [ + NameAttribute(NameOID.EMAIL_ADDRESS, "Santa Claus"), + ] + ) + + def test_generate_parse(self): + name_value = Name( + [ + NameAttribute(NameOID.COMMON_NAME, "Common Name 1"), + NameAttribute(NameOID.LOCALITY_NAME, "City for Name 1"), + NameAttribute( + NameOID.ORGANIZATION_NAME, "Name 1 Organization" + ), + ] + ) + + assert ( + Name.from_rfc4514_string(name_value.rfc4514_string()) == name_value + ) + + name_string = "O=Organization,L=City,CN=Common Name" + assert ( + Name.from_rfc4514_string(name_string).rfc4514_string() + == name_string + ) diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py index b64940242905..2c595db324f5 100644 --- a/tests/x509/test_ocsp.py +++ b/tests/x509/test_ocsp.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import base64 import datetime @@ -13,13 +12,13 @@ from cryptography import x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448 +from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.x509 import ocsp -from .test_x509 import _load_cert from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 -from ..utils import load_vectors_from_file +from ..utils import load_vectors_from_file, raises_unsupported_algorithm +from .test_x509 import DummyExtension, _load_cert def _load_data(filename, loader): @@ -29,17 +28,13 @@ def _load_data(filename, loader): def _cert_and_issuer(): - from cryptography.hazmat.backends.openssl.backend import backend - cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) issuer = _load_cert( os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), x509.load_pem_x509_certificate, - backend, ) return cert, issuer @@ -52,8 +47,8 @@ def _generate_root(private_key=None, algorithm=hashes.SHA256()): subject = x509.Name( [ - x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(x509.NameOID.COMMON_NAME, u"Cryptography CA"), + x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(x509.NameOID.COMMON_NAME, "Cryptography CA"), ] ) @@ -73,7 +68,7 @@ def _generate_root(private_key=None, algorithm=hashes.SHA256()): return cert, private_key -class TestOCSPRequest(object): +class TestOCSPRequest: def test_bad_request(self): with pytest.raises(ValueError): ocsp.load_der_ocsp_request(b"invalid") @@ -104,9 +99,42 @@ def test_load_request_with_extensions(self): ext = req.extensions[0] assert ext.critical is False assert ext.value == x509.OCSPNonce( - b"\x04\x10{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd" + b"{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd" + ) + + def test_load_request_with_acceptable_responses(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-acceptable-responses.der"), + ocsp.load_der_ocsp_request, + ) + assert len(req.extensions) == 1 + ext = req.extensions[0] + assert ext.critical is False + assert ext.value == x509.OCSPAcceptableResponses( + [x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")] + ) + + def test_load_request_with_unknown_extension(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-ext-unknown-oid.der"), + ocsp.load_der_ocsp_request, + ) + assert len(req.extensions) == 1 + ext = req.extensions[0] + assert ext.critical is False + assert ext.value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.2213"), + b"\x04\x10{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd", ) + def test_load_request_with_duplicate_extension(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-duplicate-ext.der"), + ocsp.load_der_ocsp_request, + ) + with pytest.raises(x509.DuplicateExtension): + req.extensions + def test_load_request_two_requests(self): with pytest.raises(NotImplementedError): _load_data( @@ -119,7 +147,7 @@ def test_invalid_hash_algorithm(self): os.path.join("x509", "ocsp", "req-invalid-hash-alg.der"), ocsp.load_der_ocsp_request, ) - with pytest.raises(UnsupportedAlgorithm): + with raises_unsupported_algorithm(None): req.hash_algorithm def test_serialize_request(self): @@ -142,13 +170,56 @@ def test_invalid_serialize_encoding(self): req.public_bytes(serialization.Encoding.PEM) -class TestOCSPRequestBuilder(object): - def test_add_two_certs(self): +class TestOCSPRequestBuilder: + def test_add_cert_twice(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + # Fails calling a second time with pytest.raises(ValueError): builder.add_certificate(cert, issuer, hashes.SHA1()) + # Fails calling a second time with add_certificate_by_hash + with pytest.raises(ValueError): + builder.add_certificate_by_hash( + b"0" * 20, b"0" * 20, 1, hashes.SHA1() + ) + + def test_add_cert_by_hash_twice(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate_by_hash( + b"0" * 20, b"0" * 20, 1, hashes.SHA1() + ) + # Fails calling a second time + with pytest.raises(ValueError): + builder.add_certificate_by_hash( + b"0" * 20, b"0" * 20, 1, hashes.SHA1() + ) + # Fails calling a second time with add_certificate + with pytest.raises(ValueError): + builder.add_certificate(cert, issuer, hashes.SHA1()) + + def test_add_cert_by_hash_bad_hash(self): + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(ValueError): + builder.add_certificate_by_hash( + b"0" * 20, b"0" * 20, 1, "notahash" # type:ignore[arg-type] + ) + with pytest.raises(ValueError): + builder.add_certificate_by_hash( + b"0" * 19, b"0" * 20, 1, hashes.SHA1() + ) + with pytest.raises(ValueError): + builder.add_certificate_by_hash( + b"0" * 20, b"0" * 21, 1, hashes.SHA1() + ) + with pytest.raises(TypeError): + builder.add_certificate_by_hash( + b"0" * 20, + b"0" * 20, + "notanint", # type:ignore[arg-type] + hashes.SHA1(), + ) def test_create_ocsp_request_no_req(self): builder = ocsp.OCSPRequestBuilder() @@ -170,16 +241,37 @@ def test_add_extension_twice(self): def test_add_invalid_extension(self): builder = ocsp.OCSPRequestBuilder() with pytest.raises(TypeError): - builder.add_extension("notanext", False) + builder.add_extension( + "notanext", # type:ignore[arg-type] + False, + ) + + def test_unsupported_extension(self): + cert, issuer = _cert_and_issuer() + builder = ( + ocsp.OCSPRequestBuilder() + .add_extension(DummyExtension(), critical=False) + .add_certificate(cert, issuer, hashes.SHA256()) + ) + with pytest.raises(NotImplementedError): + builder.build() def test_create_ocsp_request_invalid_cert(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() with pytest.raises(TypeError): - builder.add_certificate(b"notacert", issuer, hashes.SHA1()) + builder.add_certificate( + b"notacert", # type:ignore[arg-type] + issuer, + hashes.SHA1(), + ) with pytest.raises(TypeError): - builder.add_certificate(cert, b"notacert", hashes.SHA1()) + builder.add_certificate( + cert, + b"notacert", # type:ignore[arg-type] + hashes.SHA1(), + ) def test_create_ocsp_request(self): cert, issuer = _cert_and_issuer() @@ -211,8 +303,30 @@ def test_create_ocsp_request_with_extension(self, ext, critical): assert req.extensions[0].oid == ext.oid assert req.extensions[0].critical is critical + def test_add_cert_by_hash(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + h = hashes.Hash(hashes.SHA1()) + h.update(cert.issuer.public_bytes()) + issuer_name_hash = h.finalize() + # issuer_key_hash is a hash of the public key BitString DER, + # not the subjectPublicKeyInfo + issuer_key_hash = base64.b64decode(b"w5zz/NNGCDS7zkZ/oHxb8+IIy1k=") + builder = builder.add_certificate_by_hash( + issuer_name_hash, + issuer_key_hash, + cert.serial_number, + hashes.SHA1(), + ) + req = builder.build() + serialized = req.public_bytes(serialization.Encoding.DER) + assert serialized == base64.b64decode( + b"MEMwQTA/MD0wOzAJBgUrDgMCGgUABBRAC0Z68eay0wmDug1gfn5ZN0gkxAQUw5zz" + b"/NNGCDS7zkZ/oHxb8+IIy1kCAj8g" + ) + -class TestOCSPResponseBuilder(object): +class TestOCSPResponseBuilder: def test_add_response_twice(self): cert, issuer = _cert_and_issuer() time = datetime.datetime.now() @@ -246,7 +360,7 @@ def test_invalid_add_response(self): builder = ocsp.OCSPResponseBuilder() with pytest.raises(TypeError): builder.add_response( - "bad", + "bad", # type:ignore[arg-type] issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, @@ -258,7 +372,7 @@ def test_invalid_add_response(self): with pytest.raises(TypeError): builder.add_response( cert, - "bad", + "bad", # type:ignore[arg-type] hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, @@ -270,7 +384,7 @@ def test_invalid_add_response(self): builder.add_response( cert, issuer, - "notahash", + "notahash", # type:ignore[arg-type] ocsp.OCSPCertStatus.GOOD, time, time, @@ -283,7 +397,7 @@ def test_invalid_add_response(self): issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, - "bad", + "bad", # type:ignore[arg-type] time, None, None, @@ -295,14 +409,21 @@ def test_invalid_add_response(self): hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, - "bad", + "bad", # type:ignore[arg-type] None, None, ) with pytest.raises(TypeError): builder.add_response( - cert, issuer, hashes.SHA256(), 0, time, time, None, None + cert, + issuer, + hashes.SHA256(), + 0, # type:ignore[arg-type] + time, + time, + None, + None, ) with pytest.raises(ValueError): builder.add_response( @@ -346,7 +467,7 @@ def test_invalid_add_response(self): time, time, time, - 0, + 0, # type:ignore[arg-type] ) with pytest.raises(ValueError): builder.add_response( @@ -365,9 +486,9 @@ def test_invalid_certificates(self): with pytest.raises(ValueError): builder.certificates([]) with pytest.raises(TypeError): - builder.certificates(["notacert"]) + builder.certificates(["notacert"]) # type: ignore[list-item] with pytest.raises(TypeError): - builder.certificates("invalid") + builder.certificates("invalid") # type: ignore[arg-type] _, issuer = _cert_and_issuer() builder = builder.certificates([issuer]) @@ -378,9 +499,12 @@ def test_invalid_responder_id(self): builder = ocsp.OCSPResponseBuilder() cert, _ = _cert_and_issuer() with pytest.raises(TypeError): - builder.responder_id(ocsp.OCSPResponderEncoding.HASH, "invalid") + builder.responder_id( + ocsp.OCSPResponderEncoding.HASH, + "invalid", # type: ignore[arg-type] + ) with pytest.raises(TypeError): - builder.responder_id("notanenum", cert) + builder.responder_id("notanenum", cert) # type: ignore[arg-type] builder = builder.responder_id(ocsp.OCSPResponderEncoding.NAME, cert) with pytest.raises(ValueError): @@ -389,7 +513,34 @@ def test_invalid_responder_id(self): def test_invalid_extension(self): builder = ocsp.OCSPResponseBuilder() with pytest.raises(TypeError): - builder.add_extension("notanextension", True) + builder.add_extension( + "notanextension", True # type: ignore[arg-type] + ) + + def test_unsupported_extension(self): + root_cert, private_key = _generate_root() + cert, issuer = _cert_and_issuer() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + + builder = ( + ocsp.OCSPResponseBuilder() + .responder_id(ocsp.OCSPResponderEncoding.NAME, root_cert) + .add_response( + cert, + issuer, + hashes.SHA256(), + ocsp.OCSPCertStatus.GOOD, + this_update, + next_update, + None, + None, + ) + .add_extension(DummyExtension(), critical=False) + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256()) def test_sign_no_response(self): builder = ocsp.OCSPResponseBuilder() @@ -440,7 +591,7 @@ def test_sign_invalid_hash_algorithm(self): None, ) with pytest.raises(TypeError): - builder.sign(private_key, "notahash") + builder.sign(private_key, "notahash") # type: ignore[arg-type] def test_sign_good_cert(self): builder = ocsp.OCSPResponseBuilder() @@ -508,6 +659,33 @@ def test_sign_revoked_cert(self): resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) + def test_sign_unknown_cert(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, + issuer, + hashes.SHA1(), + ocsp.OCSPCertStatus.UNKNOWN, + this_update, + next_update, + None, + None, + ) + resp = builder.sign(private_key, hashes.SHA384()) + assert resp.certificate_status == ocsp.OCSPCertStatus.UNKNOWN + assert resp.this_update == this_update + assert resp.next_update == next_update + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA384()) + ) + def test_sign_with_appended_certs(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() @@ -690,7 +868,9 @@ def test_build_non_successful_statuses(self, status, der): def test_invalid_build_not_a_status(self): with pytest.raises(TypeError): - ocsp.OCSPResponseBuilder.build_unsuccessful("notastatus") + ocsp.OCSPResponseBuilder.build_unsuccessful( + "notastatus" # type: ignore[arg-type] + ) def test_invalid_build_successful_status(self): with pytest.raises(ValueError): @@ -698,21 +878,92 @@ def test_invalid_build_successful_status(self): ocsp.OCSPResponseStatus.SUCCESSFUL ) + def test_sign_unknown_private_key(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, _ = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, + issuer, + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + this_update, + next_update, + None, + None, + ) + with pytest.raises(TypeError): + builder.sign(object(), hashes.SHA256()) # type:ignore[arg-type] -class TestSignedCertificateTimestampsExtension(object): + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2b(digest_size=64) + ), + skip_message="Does not support BLAKE2b", + ) + def test_sign_unrecognized_hash_algorithm(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, + issuer, + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + this_update, + next_update, + None, + None, + ) + + with pytest.raises(UnsupportedAlgorithm): + builder.sign(private_key, hashes.BLAKE2b(digest_size=64)) + + def test_sign_none_hash_not_eddsa(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, + issuer, + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + this_update, + next_update, + None, + None, + ) + with pytest.raises(TypeError): + builder.sign(private_key, None) + + +class TestSignedCertificateTimestampsExtension: def test_init(self): with pytest.raises(TypeError): - x509.SignedCertificateTimestamps([object()]) + x509.SignedCertificateTimestamps( + [object()] # type: ignore[list-item] + ) def test_repr(self): assert repr(x509.SignedCertificateTimestamps([])) == ( "" ) - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_eq(self, backend): sct1 = ( _load_data( @@ -736,10 +987,6 @@ def test_eq(self, backend): ) assert sct1 == sct2 - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_ne(self, backend): sct1 = ( _load_data( @@ -755,10 +1002,6 @@ def test_ne(self, backend): assert sct1 != sct2 assert sct1 != object() - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_hash(self, backend): sct1 = ( _load_data( @@ -784,8 +1027,24 @@ def test_hash(self, backend): assert hash(sct1) == hash(sct2) assert hash(sct1) != hash(sct3) + def test_entry_type(self, backend): + [sct, _, _, _] = ( + _load_data( + os.path.join("x509", "ocsp", "resp-sct-extension.der"), + ocsp.load_der_ocsp_response, + ) + .single_extensions.get_extension_for_class( + x509.SignedCertificateTimestamps + ) + .value + ) + assert ( + sct.entry_type + == x509.certificate_transparency.LogEntryType.X509_CERTIFICATE + ) -class TestOCSPResponse(object): + +class TestOCSPResponse: def test_bad_response(self): with pytest.raises(ValueError): ocsp.load_der_ocsp_response(b"invalid") @@ -795,12 +1054,9 @@ def test_load_response(self): os.path.join("x509", "ocsp", "resp-sha256.der"), ocsp.load_der_ocsp_response, ) - from cryptography.hazmat.backends.openssl.backend import backend - issuer = _load_cert( os.path.join("x509", "letsencryptx3.pem"), x509.load_pem_x509_certificate, - backend, ) assert resp.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL assert ( @@ -823,7 +1079,9 @@ def test_load_response(self): b"mMEfd265tE5t6ZFZe/zqOyhAhIDHHh6fckClQB7xfIiCztSevCAABgPMjAxODA4" b"MzAxMTAwMDBaoBEYDzIwMTgwOTA2MTEwMDAwWg==" ) - issuer.public_key().verify( + public_key = issuer.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + public_key.verify( resp.signature, resp.tbs_response_bytes, PKCS1v15(), @@ -849,11 +1107,62 @@ def test_load_response(self): assert len(resp.extensions) == 0 def test_load_multi_valued_response(self): + resp = _load_data( + os.path.join("x509", "ocsp", "ocsp-army.deps.mil-resp.der"), + ocsp.load_der_ocsp_response, + ) + with pytest.raises(ValueError): - _load_data( - os.path.join("x509", "ocsp", "ocsp-army.deps.mil-resp.der"), - ocsp.load_der_ocsp_response, - ) + resp.serial_number + + assert len(list(resp.responses)) == 20 + + def test_multi_valued_responses(self): + req_valid = _load_data( + os.path.join("x509", "ocsp", "ocsp-army.valid-req.der"), + ocsp.load_der_ocsp_request, + ) + + req_revoked = _load_data( + os.path.join("x509", "ocsp", "ocsp-army.revoked-req.der"), + ocsp.load_der_ocsp_request, + ) + + req_irrelevant = _load_data( + os.path.join("x509", "ocsp", "ocsp-army.inapplicable-req.der"), + ocsp.load_der_ocsp_request, + ) + + resp = _load_data( + os.path.join("x509", "ocsp", "ocsp-army.deps.mil-resp.der"), + ocsp.load_der_ocsp_response, + ) + + for elem in resp.responses: + serial = elem.serial_number + + assert req_irrelevant.serial_number != serial + if req_valid.serial_number == serial: + assert elem.issuer_key_hash == req_valid.issuer_key_hash + assert elem.issuer_name_hash == req_valid.issuer_name_hash + assert ( + elem.hash_algorithm.name == req_valid.hash_algorithm.name + ) + + assert elem.certificate_status == ocsp.OCSPCertStatus.GOOD + + assert elem.this_update == datetime.datetime(2020, 2, 22, 0, 0) + assert elem.next_update == datetime.datetime(2020, 2, 29, 1, 0) + elif req_revoked.serial_number == serial: + assert elem.certificate_status == ocsp.OCSPCertStatus.REVOKED + + assert ( + elem.revocation_reason + == x509.ReasonFlags.cessation_of_operation + ) + assert elem.revocation_time == datetime.datetime( + 2018, 5, 30, 14, 1, 39 + ) def test_load_unauthorized(self): resp = _load_data( @@ -926,9 +1235,17 @@ def test_load_invalid_signature_oid(self): assert resp.signature_algorithm_oid == x509.ObjectIdentifier( "1.2.840.113549.1.1.2" ) - with pytest.raises(UnsupportedAlgorithm): + with raises_unsupported_algorithm(None): resp.signature_hash_algorithm + def test_unknown_hash_algorithm(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-unknown-hash-alg.der"), + ocsp.load_der_ocsp_response, + ) + with raises_unsupported_algorithm(None): + resp.hash_algorithm + def test_load_responder_key_hash(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-responder-key-hash.der"), @@ -963,7 +1280,20 @@ def test_response_extensions(self): ext = resp.extensions[0] assert ext.critical is False assert ext.value == x509.OCSPNonce( - b'\x04\x105\x957\x9fa\x03\x83\x87\x89rW\x8f\xae\x99\xf7"' + b'5\x957\x9fa\x03\x83\x87\x89rW\x8f\xae\x99\xf7"' + ) + + def test_response_unknown_extension(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-unknown-extension.der"), + ocsp.load_der_ocsp_response, + ) + assert len(resp.extensions) == 1 + ext = resp.extensions[0] + assert ext.critical is False + assert ext.value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.2.200"), + b'\x04\x105\x957\x9fa\x03\x83\x87\x89rW\x8f\xae\x99\xf7"', ) def test_serialize_reponse(self): @@ -985,10 +1315,6 @@ def test_invalid_serialize_encoding(self): with pytest.raises(ValueError): resp.public_bytes(serialization.Encoding.PEM) - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_single_extensions_sct(self, backend): resp = _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), @@ -1006,27 +1332,6 @@ def test_single_extensions_sct(self, backend): b"7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs=", ] - @pytest.mark.supported( - only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER - ), - skip_message="Requires OpenSSL < 1.1.0f", - ) - def test_skips_single_extensions_scts_if_unsupported(self, backend): - resp = _load_data( - os.path.join("x509", "ocsp", "resp-sct-extension.der"), - ocsp.load_der_ocsp_response, - ) - with pytest.raises(x509.ExtensionNotFound): - resp.single_extensions.get_extension_for_class( - x509.SignedCertificateTimestamps - ) - - ext = resp.single_extensions.get_extension_for_oid( - x509.ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS - ) - assert isinstance(ext.value, x509.UnrecognizedExtension) - def test_single_extensions(self, backend): resp = _load_data( os.path.join("x509", "ocsp", "resp-single-extension-reason.der"), @@ -1037,8 +1342,35 @@ def test_single_extensions(self, backend): assert ext.oid == x509.CRLReason.oid assert ext.value == x509.CRLReason(x509.ReasonFlags.unspecified) + def test_unknown_response_type(self): + with pytest.raises(ValueError): + _load_data( + os.path.join( + "x509", "ocsp", "resp-response-type-unknown-oid.der" + ), + ocsp.load_der_ocsp_response, + ) + + def test_response_bytes_absent(self): + with pytest.raises(ValueError): + _load_data( + os.path.join( + "x509", "ocsp", "resp-successful-no-response-bytes.der" + ), + ocsp.load_der_ocsp_response, + ) + + def test_unknown_response_status(self): + with pytest.raises(ValueError): + _load_data( + os.path.join( + "x509", "ocsp", "resp-unknown-response-status.der" + ), + ocsp.load_der_ocsp_response, + ) + -class TestOCSPEdDSA(object): +class TestOCSPEdDSA: @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support / OCSP", diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 11c80816cff7..0bac1c271cfb 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -1,52 +1,32 @@ -# -*- coding: utf-8 -*- # 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 absolute_import, division, print_function import binascii -import collections import copy import datetime import ipaddress import os +import typing import pytest -import pytz - -import six - from cryptography import utils, x509 -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat._der import ( - BIT_STRING, - CONSTRUCTED, - CONTEXT_SPECIFIC, - DERReader, - GENERALIZED_TIME, - INTEGER, - OBJECT_IDENTIFIER, - PRINTABLE_STRING, - SEQUENCE, - SET, - UTC_TIME, -) -from cryptography.hazmat.backends.interfaces import ( - DSABackend, - EllipticCurveBackend, - RSABackend, - X509Backend, -) +from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm +from cryptography.hazmat.bindings._rust import asn1 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( + dh, dsa, ec, - ed25519, ed448, + ed25519, padding, rsa, + types, + x448, + x25519, ) from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, @@ -61,93 +41,110 @@ SubjectInformationAccessOID, ) -from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 +from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048, DSA_KEY_3072 from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 -from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 +from ..hazmat.primitives.fixtures_rsa import ( + RSA_KEY_2048_ALT, +) from ..hazmat.primitives.test_ec import _skip_curve_unsupported -from ..utils import load_vectors_from_file +from ..hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 +from ..utils import ( + load_nist_vectors, + load_vectors_from_file, + raises_unsupported_algorithm, +) +# Make ruff happy since we're importing fixtures that pytest patches in as +# func args +__all__ = ["rsa_key_512", "rsa_key_2048"] -@utils.register_interface(x509.ExtensionType) -class DummyExtension(object): + +class DummyExtension(x509.ExtensionType): oid = x509.ObjectIdentifier("1.2.3.4") -@utils.register_interface(x509.GeneralName) -class FakeGeneralName(object): +class FakeGeneralName(x509.GeneralName): def __init__(self, value): self._value = value - value = utils.read_only_property("_value") + @property + def value(self): + return self._value + +T = typing.TypeVar("T") -def _load_cert(filename, loader, backend): - cert = load_vectors_from_file( + +def _load_cert(filename, loader: typing.Callable[..., T]) -> T: + return load_vectors_from_file( filename=filename, - loader=lambda pemfile: loader(pemfile.read(), backend), + loader=lambda pemfile: loader(pemfile.read()), mode="rb", ) - return cert -ParsedCertificate = collections.namedtuple( - "ParsedCertificate", - ["not_before_tag", "not_after_tag", "issuer", "subject"], -) +def _generate_ca_and_leaf( + issuer_private_key: types.CertificateIssuerPrivateKeyTypes, + subject_private_key: types.CertificateIssuerPrivateKeyTypes, +): + if isinstance( + issuer_private_key, + (ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey), + ): + hash_alg = None + else: + hash_alg = hashes.SHA256() + + builder = ( + x509.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) + ) + .public_key(issuer_private_key.public_key()) + .serial_number(1) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2030, 1, 1)) + ) + ca = builder.sign(issuer_private_key, hash_alg) + builder = ( + x509.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "leaf")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) + ) + .public_key(subject_private_key.public_key()) + .serial_number(100) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2025, 1, 1)) + ) + cert = builder.sign(issuer_private_key, hash_alg) + return ca, cert -def _parse_cert(der): - # See the Certificate structured, defined in RFC 5280. - with DERReader(der).read_single_element(SEQUENCE) as cert: - tbs_cert = cert.read_element(SEQUENCE) - # Skip outer signature algorithm - _ = cert.read_element(SEQUENCE) - # Skip signature - _ = cert.read_element(BIT_STRING) - - with tbs_cert: - # Skip version - _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 0) - # Skip serialNumber - _ = tbs_cert.read_element(INTEGER) - # Skip inner signature algorithm - _ = tbs_cert.read_element(SEQUENCE) - issuer = tbs_cert.read_element(SEQUENCE) - validity = tbs_cert.read_element(SEQUENCE) - subject = tbs_cert.read_element(SEQUENCE) - # Skip subjectPublicKeyInfo - _ = tbs_cert.read_element(SEQUENCE) - # Skip issuerUniqueID - _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 1) - # Skip subjectUniqueID - _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 2) - # Skip extensions - _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 3) - - with validity: - not_before_tag, _ = validity.read_any_element() - not_after_tag, _ = validity.read_any_element() - - return ParsedCertificate( - not_before_tag=not_before_tag, - not_after_tag=not_after_tag, - issuer=issuer, - subject=subject, - ) +def _break_cert_sig(cert: x509.Certificate) -> x509.Certificate: + cert_bad_sig = bytearray(cert.public_bytes(serialization.Encoding.PEM)) + # Break the sig by mutating 5 bytes. That's the base64 representation + # though so there's somewhere closer to 2**-32 probability of + # not breaking the sig. Spin that roulette wheel. + cert_bad_sig[-40:-35] = 90, 90, 90, 90, 90 + return x509.load_pem_x509_certificate(bytes(cert_bad_sig)) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCertificateRevocationList(object): +class TestCertificateRevocationList: def test_load_pem_crl(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert isinstance(crl, x509.CertificateRevocationList) fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) - assert fingerprint == b"3234b0cb4c0cedf6423724b736729dcfc9e441ef" + assert fingerprint == b"191b3428bf9d0dafa4edd42bc98603e182614c57" assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) assert ( crl.signature_algorithm_oid @@ -158,7 +155,6 @@ def test_load_der_crl(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) assert isinstance(crl, x509.CertificateRevocationList) @@ -166,73 +162,117 @@ def test_load_der_crl(self, backend): assert fingerprint == b"dd3db63c50f4c4a13e090f14053227cb1011a5ad" assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) + def test_load_large_crl(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_almost_10k.pem"), + x509.load_pem_x509_crl, + ) + assert len(crl) == 9999 + + def test_empty_crl_no_sequence(self, backend): + # The SEQUENCE for revoked certificates is optional so let's + # test that we handle it properly. + crl = _load_cert( + os.path.join("x509", "custom", "crl_empty_no_sequence.der"), + x509.load_der_x509_crl, + ) + assert len(crl) == 0 + + with pytest.raises(IndexError): + crl[0] + assert crl.get_revoked_certificate_by_serial_number(12) is None + assert list(iter(crl)) == [] + def test_invalid_pem(self, backend): with pytest.raises(ValueError): x509.load_pem_x509_crl(b"notacrl", backend) + pem_bytes = _load_cert( + os.path.join("x509", "custom", "valid_signature_cert.pem"), + lambda data: data, + ) + with pytest.raises(ValueError): + x509.load_pem_x509_crl(pem_bytes, backend) + def test_invalid_der(self, backend): with pytest.raises(ValueError): x509.load_der_x509_crl(b"notacrl", backend) + def test_invalid_time(self, backend): + with pytest.raises(ValueError, match="TBSCertList::this_update"): + _load_cert( + os.path.join("x509", "custom", "crl_invalid_time.der"), + x509.load_der_x509_crl, + ) + def test_unknown_signature_algorithm(self, backend): crl = _load_cert( os.path.join( "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" ), x509.load_pem_x509_crl, - backend, ) - with pytest.raises(UnsupportedAlgorithm): - crl.signature_hash_algorithm() + with raises_unsupported_algorithm(None): + crl.signature_hash_algorithm + + def test_invalid_version(self, backend): + with pytest.raises(x509.InvalidVersion): + _load_cert( + os.path.join("x509", "custom", "crl_bad_version.pem"), + x509.load_pem_x509_crl, + ) def test_issuer(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) assert isinstance(crl.issuer, x509.Name) assert list(crl.issuer) == [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.OID_COUNTRY_NAME, "US"), x509.NameAttribute( - x509.OID_ORGANIZATION_NAME, u"Test Certificates 2011" + x509.OID_ORGANIZATION_NAME, "Test Certificates 2011" ), - x509.NameAttribute(x509.OID_COMMON_NAME, u"Good CA"), + x509.NameAttribute(x509.OID_COMMON_NAME, "Good CA"), ] assert crl.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ - x509.NameAttribute(x509.OID_COMMON_NAME, u"Good CA") + x509.NameAttribute(x509.OID_COMMON_NAME, "Good CA") ] def test_equality(self, backend): crl1 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) crl2 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) crl3 = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert crl1 == crl2 assert crl1 != crl3 assert crl1 != object() + def test_comparison(self, backend): + crl1 = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + ) + with pytest.raises(TypeError): + crl1 < crl1 # type: ignore[operator] + def test_update_dates(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert isinstance(crl.next_update, datetime.datetime) @@ -241,11 +281,29 @@ def test_update_dates(self, backend): assert crl.next_update.isoformat() == "2016-01-01T00:00:00" assert crl.last_update.isoformat() == "2015-01-01T00:00:00" + def test_no_next_update(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_no_next_update.pem"), + x509.load_pem_x509_crl, + ) + assert crl.next_update is None + + def test_unrecognized_extension(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_unrecognized_extension.der"), + x509.load_der_x509_crl, + ) + unrecognized = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4.5"), + b"abcdef", + ) + ext = crl.extensions.get_extension_for_oid(unrecognized.oid) + assert ext.value == unrecognized + def test_revoked_cert_retrieval(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) for r in crl: @@ -253,6 +311,10 @@ def test_revoked_cert_retrieval(self, backend): # Check that len() works for CRLs. assert len(crl) == 12 + it = iter(crl) + assert len(typing.cast(typing.Sized, it)) == 12 + next(it) + assert len(typing.cast(typing.Sized, it)) == 11 def test_get_revoked_certificate_by_serial_number(self, backend): crl = _load_cert( @@ -260,10 +322,10 @@ def test_get_revoked_certificate_by_serial_number(self, backend): "x509", "PKITS_data", "crls", "LongSerialNumberCACRL.crl" ), x509.load_der_x509_crl, - backend, ) serial_number = 725064303890588110203033396814564464046290047507 revoked = crl.get_revoked_certificate_by_serial_number(serial_number) + assert isinstance(revoked, x509.RevokedCertificate) assert revoked.serial_number == serial_number assert crl.get_revoked_certificate_by_serial_number(500) is None @@ -276,7 +338,6 @@ def test_revoked_cert_retrieval_retain_only_revoked(self, backend): revoked = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, )[11] assert revoked.revocation_date == datetime.datetime(2015, 1, 1, 0, 0) assert revoked.serial_number == 11 @@ -285,7 +346,6 @@ def test_extensions(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_ian_aia_aki.pem"), x509.load_pem_x509_crl, - backend, ) crl_number = crl.extensions.get_extension_for_oid( @@ -311,19 +371,18 @@ def test_extensions(self, backend): [ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.DNSName(u"cryptography.io"), + x509.DNSName("cryptography.io"), ) ] ) assert ian.value == x509.IssuerAlternativeName( - [x509.UniformResourceIdentifier(u"https://cryptography.io")] + [x509.UniformResourceIdentifier("https://cryptography.io")] ) def test_delta_crl_indicator(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_delta_crl_indicator.pem"), x509.load_pem_x509_crl, - backend, ) dci = crl.extensions.get_extension_for_oid( @@ -336,7 +395,6 @@ def test_signature(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert crl.signature == binascii.unhexlify( @@ -355,16 +413,17 @@ def test_tbs_certlist_bytes(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) ca_cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) - ca_cert.public_key().verify( + public_key = ca_cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert crl.signature_hash_algorithm is not None + public_key.verify( crl.signature, crl.tbs_certlist_bytes, padding.PKCS1v15(), @@ -375,7 +434,6 @@ def test_public_bytes_pem(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, - backend, ) # Encode it to PEM and load it back. @@ -383,7 +441,6 @@ def test_public_bytes_pem(self, backend): crl.public_bytes( encoding=serialization.Encoding.PEM, ), - backend, ) assert len(crl) == 0 @@ -394,7 +451,6 @@ def test_public_bytes_der(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) # Encode it to DER and load it back. @@ -402,7 +458,6 @@ def test_public_bytes_der(self, backend): crl.public_bytes( encoding=serialization.Encoding.DER, ), - backend, ) assert len(crl) == 12 @@ -438,61 +493,65 @@ def test_public_bytes_invalid_encoding(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(TypeError): - crl.public_bytes("NotAnEncoding") + crl.public_bytes("NotAnEncoding") # type: ignore[arg-type] def test_verify_bad(self, backend): crl = _load_cert( - os.path.join("x509", "custom", "invalid_signature.pem"), + os.path.join("x509", "custom", "invalid_signature_crl.pem"), x509.load_pem_x509_crl, - backend, ) crt = _load_cert( - os.path.join("x509", "custom", "invalid_signature.pem"), + os.path.join("x509", "custom", "invalid_signature_cert.pem"), x509.load_pem_x509_certificate, - backend, ) - assert not crl.is_signature_valid(crt.public_key()) + public_key = crt.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert not crl.is_signature_valid(public_key) + + crl = _load_cert( + os.path.join("x509", "custom", "crl_inner_outer_mismatch.der"), + x509.load_der_x509_crl, + ) + assert not crl.is_signature_valid(public_key) def test_verify_good(self, backend): crl = _load_cert( - os.path.join("x509", "custom", "valid_signature.pem"), + os.path.join("x509", "custom", "valid_signature_crl.pem"), x509.load_pem_x509_crl, - backend, ) crt = _load_cert( - os.path.join("x509", "custom", "valid_signature.pem"), + os.path.join("x509", "custom", "valid_signature_cert.pem"), x509.load_pem_x509_certificate, - backend, ) - assert crl.is_signature_valid(crt.public_key()) + public_key = crt.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert crl.is_signature_valid(public_key) def test_verify_argument_must_be_a_public_key(self, backend): crl = _load_cert( - os.path.join("x509", "custom", "valid_signature.pem"), + os.path.join("x509", "custom", "valid_signature_crl.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(TypeError): - crl.is_signature_valid("not a public key") + crl.is_signature_valid( + "not a public key" # type: ignore[arg-type] + ) with pytest.raises(TypeError): - crl.is_signature_valid(object) + crl.is_signature_valid(object) # type: ignore[arg-type] -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRevokedCertificate(object): +class TestRevokedCertificate: def test_revoked_basics(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) for i, rev in enumerate(crl): @@ -508,16 +567,15 @@ def test_revoked_extensions(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) exp_issuer = [ x509.DirectoryName( x509.Name( [ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.OID_COUNTRY_NAME, "US"), x509.NameAttribute( - x509.OID_COMMON_NAME, u"cryptography.io" + x509.OID_COMMON_NAME, "cryptography.io" ), ] ) @@ -571,7 +629,6 @@ def test_no_revoked_certs(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, - backend, ) assert len(crl) == 0 @@ -579,7 +636,6 @@ def test_duplicate_entry_ext(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(x509.DuplicateExtension): @@ -591,19 +647,18 @@ def test_unsupported_crit_entry_ext(self, backend): "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" ), x509.load_pem_x509_crl, - backend, ) ext = crl[0].extensions.get_extension_for_oid( x509.ObjectIdentifier("1.2.3.4") ) + assert isinstance(ext.value, x509.UnrecognizedExtension) assert ext.value.value == b"\n\x01\x00" def test_unsupported_reason(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_unsupported_reason.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(ValueError): @@ -615,7 +670,6 @@ def test_invalid_cert_issuer_ext(self, backend): "x509", "custom", "crl_inval_cert_issuer_entry_ext.pem" ), x509.load_pem_x509_crl, - backend, ) with pytest.raises(ValueError): @@ -625,7 +679,6 @@ def test_indexing(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(IndexError): @@ -638,8 +691,10 @@ def test_indexing(self, backend): assert crl[2:4][0].serial_number == crl[2].serial_number assert crl[2:4][1].serial_number == crl[3].serial_number - def test_get_revoked_certificate_doesnt_reorder(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_get_revoked_certificate_doesnt_reorder( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( @@ -648,7 +703,7 @@ def test_get_revoked_certificate_doesnt_reorder(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -674,14 +729,108 @@ def test_get_revoked_certificate_doesnt_reorder(self, backend): assert crl[2].serial_number == 3 -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRSACertificate(object): +@pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + ), + skip_message="Does not support RSA PSS loading", +) +class TestRSAPSSCertificate: + def test_load_cert_pub_key(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "rsa_pss_cert.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + expected_pub_key = _load_cert( + os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_pub.der"), + serialization.load_der_public_key, + ) + assert isinstance(expected_pub_key, rsa.RSAPublicKey) + pub_key = cert.public_key() + assert isinstance(pub_key, rsa.RSAPublicKey) + assert pub_key == expected_pub_key + pss = cert.signature_algorithm_parameters + assert isinstance(pss, padding.PSS) + assert isinstance(pss._mgf, padding.MGF1) + assert isinstance(pss._mgf._algorithm, hashes.SHA256) + assert pss._salt_length == 222 + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + pub_key.verify( + cert.signature, + cert.tbs_certificate_bytes, + pss, + cert.signature_hash_algorithm, + ) + + def test_load_pss_cert_no_null(self, backend): + """ + This test verifies that PSS certs where the hash algorithm + identifiers have no trailing null still load properly. LibreSSL + generates certs like this. + """ + cert = _load_cert( + os.path.join("x509", "custom", "rsa_pss_sha256_no_null.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + pss = cert.signature_algorithm_parameters + assert isinstance(pss, padding.PSS) + assert isinstance(pss._mgf, padding.MGF1) + assert isinstance(pss._mgf._algorithm, hashes.SHA256) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + + def test_load_pss_sha1_mgf1_sha1(self, backend): + cert = _load_cert( + os.path.join("x509", "ee-pss-sha1-cert.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + pub_key = cert.public_key() + assert isinstance(pub_key, rsa.RSAPublicKey) + pss = cert.signature_algorithm_parameters + assert isinstance(pss, padding.PSS) + assert isinstance(pss._mgf, padding.MGF1) + assert isinstance(pss._mgf._algorithm, hashes.SHA1) + assert pss._salt_length == 20 + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) + + def test_invalid_mgf(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "rsa_pss_cert_invalid_mgf.der"), + x509.load_der_x509_certificate, + ) + with pytest.raises(ValueError): + cert.signature_algorithm_parameters + + def test_unsupported_mgf_hash(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "rsa_pss_cert_unsupported_mgf_hash.der" + ), + x509.load_der_x509_certificate, + ) + with pytest.raises(UnsupportedAlgorithm): + cert.signature_algorithm_parameters + + def test_no_sig_params(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "rsa_pss_cert_no_sig_params.der"), + x509.load_der_x509_certificate, + ) + with pytest.raises(ValueError): + cert.signature_algorithm_parameters + with pytest.raises(ValueError): + cert.signature_hash_algorithm + + +class TestRSACertificate: def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 11559813051657483483 @@ -691,20 +840,100 @@ def test_load_pem_cert(self, backend): assert ( cert.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1 ) + assert isinstance( + cert.signature_algorithm_parameters, padding.PKCS1v15 + ) + + def test_check_pkcs1_signature_algorithm_parameters(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "ca", "rsa_ca.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + assert isinstance( + cert.signature_algorithm_parameters, padding.PKCS1v15 + ) + pk = cert.public_key() + assert isinstance(pk, rsa.RSAPublicKey) + assert cert.signature_hash_algorithm is not None + pk.verify( + cert.signature, + cert.tbs_certificate_bytes, + cert.signature_algorithm_parameters, + cert.signature_hash_algorithm, + ) + + def test_load_legacy_pem_header(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.old_header.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + + def test_load_with_other_sections(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.with_garbage.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + + cert = _load_cert( + os.path.join("x509", "cryptography.io.with_headers.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + + def test_load_multiple_sections(self, backend): + # We match OpenSSL's behavior of loading the first cert + # if there are multiple. Arguably this would ideally be an + # error, but "load the first" is a common expectation. + cert = _load_cert( + os.path.join("x509", "cryptography.io.chain.pem"), + x509.load_pem_x509_certificate, + ) + cert2 = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + ) + assert cert == cert2 def test_negative_serial_number(self, backend): + # We load certificates with negative serial numbers but on load + # and on access of the attribute we raise a warning + with pytest.warns(utils.DeprecatedIn36): + cert = _load_cert( + os.path.join("x509", "custom", "negative_serial.pem"), + x509.load_pem_x509_certificate, + ) + + with pytest.warns(utils.DeprecatedIn36): + assert cert.serial_number == -18008675309 + + def test_country_jurisdiction_country_too_long(self, backend): cert = _load_cert( - os.path.join("x509", "custom", "negative_serial.pem"), + os.path.join("x509", "custom", "bad_country.pem"), x509.load_pem_x509_certificate, - backend, ) - assert cert.serial_number == -18008675309 + with pytest.warns(UserWarning): + assert ( + cert.subject.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME)[ + 0 + ].value + == "too long" + ) + + with pytest.warns(UserWarning): + assert ( + cert.subject.get_attributes_for_oid( + x509.NameOID.JURISDICTION_COUNTRY_NAME + )[0].value + == "also too long" + ) def test_alternate_rsa_with_sha1_oid(self, backend): cert = _load_cert( - os.path.join("x509", "alternate-rsa-sha1-oid.pem"), - x509.load_pem_x509_certificate, - backend, + os.path.join("x509", "custom", "alternate-rsa-sha1-oid.der"), + x509.load_der_x509_certificate, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) assert ( @@ -712,11 +941,23 @@ def test_alternate_rsa_with_sha1_oid(self, backend): == SignatureAlgorithmOID._RSA_WITH_SHA1 ) + def test_load_bmpstring_explicittext(self, backend): + cert = _load_cert( + os.path.join("x509", "accvraiz1.pem"), + x509.load_pem_x509_certificate, + ) + ext = cert.extensions.get_extension_for_class(x509.CertificatePolicies) + et = ext.value[0].policy_qualifiers[0].explicit_text + assert et == ( + "Autoridad de Certificación Raíz de la ACCV (Agencia " + "de Tecnología y Certificación Electrónica, CIF Q4601" + "156E). CPS en http://www.accv.es" + ) + def test_load_der_cert(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 2 @@ -728,7 +969,6 @@ def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.signature == binascii.unhexlify( b"8e0f72fcbebe4755abcaf76c8ce0bae17cde4db16291638e1b1ce04a93cdb4c" @@ -741,13 +981,20 @@ def test_signature(self, backend): b"53dc5e505e2a10fbba4f9e93a0d3b53b7fa34b05d7ba6eef869bfc34b8e514f" b"d5419f75" ) - assert len(cert.signature) == cert.public_key().key_size // 8 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert len(cert.signature) == public_key.key_size // 8 + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA-1 signature.", + ) def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"308202d8a003020102020900a06cb4b955f7f4db300d06092a864886f70d010" @@ -775,13 +1022,72 @@ def test_tbs_certificate_bytes(self, backend): b"03550403130848656c6c6f204341820900a06cb4b955f7f4db300c0603551d1" b"3040530030101ff" ) - cert.public_key().verify( + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert cert.signature_hash_algorithm is not None + public_key.verify( cert.signature, cert.tbs_certificate_bytes, padding.PKCS1v15(), cert.signature_hash_algorithm, ) + def test_tbs_precertificate_bytes_duplicate_extensions_raises( + self, backend + ): + cert = _load_cert( + os.path.join("x509", "custom", "two_basic_constraints.pem"), + x509.load_pem_x509_certificate, + ) + + with pytest.raises( + x509.DuplicateExtension, + match="Duplicate 2.5.29.19 extension found", + ): + cert.tbs_precertificate_bytes + + def test_tbs_precertificate_bytes_no_extensions_raises(self, backend): + cert = _load_cert( + os.path.join("x509", "v1_cert.pem"), + x509.load_pem_x509_certificate, + ) + + with pytest.raises( + ValueError, + match="Could not find any extensions in TBS certificate", + ): + cert.tbs_precertificate_bytes + + def test_tbs_precertificate_bytes_missing_extension_raises(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + ) + + # This cert doesn't have an SCT list extension, so it will throw a + # `ValueError` when we try to retrieve the property + with pytest.raises( + ValueError, + match="Could not find pre-certificate SCT list extension", + ): + cert.tbs_precertificate_bytes + + def test_tbs_precertificate_bytes_strips_scts(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography-scts.pem"), + x509.load_pem_x509_certificate, + ) + + expected_tbs_precertificate_bytes = load_vectors_from_file( + filename=os.path.join("x509", "cryptography-scts-tbs-precert.der"), + loader=lambda data: data.read(), + mode="rb", + ) + assert ( + expected_tbs_precertificate_bytes == cert.tbs_precertificate_bytes + ) + assert cert.tbs_precertificate_bytes != cert.tbs_certificate_bytes + def test_issuer(self, backend): cert = _load_cert( os.path.join( @@ -791,61 +1097,59 @@ def test_issuer(self, backend): "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, - backend, ) issuer = cert.issuer assert isinstance(issuer, x509.Name) assert list(issuer) == [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"Test Certificates 2011" + NameOID.ORGANIZATION_NAME, "Test Certificates 2011" ), - x509.NameAttribute(NameOID.COMMON_NAME, u"Good CA"), + x509.NameAttribute(NameOID.COMMON_NAME, "Good CA"), ] assert issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ - x509.NameAttribute(NameOID.COMMON_NAME, u"Good CA") + x509.NameAttribute(NameOID.COMMON_NAME, "Good CA") ] def test_all_issuer_name_types(self, backend): cert = _load_cert( os.path.join("x509", "custom", "all_supported_names.pem"), x509.load_pem_x509_certificate, - backend, ) issuer = cert.issuer assert isinstance(issuer, x509.Name) assert list(issuer) == [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(NameOID.COUNTRY_NAME, u"CA"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Illinois"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Chicago"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Zero, LLC"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"One, LLC"), - x509.NameAttribute(NameOID.COMMON_NAME, u"common name 0"), - x509.NameAttribute(NameOID.COMMON_NAME, u"common name 1"), - x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"OU 0"), - x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"OU 1"), - x509.NameAttribute(NameOID.DN_QUALIFIER, u"dnQualifier0"), - x509.NameAttribute(NameOID.DN_QUALIFIER, u"dnQualifier1"), - x509.NameAttribute(NameOID.SERIAL_NUMBER, u"123"), - x509.NameAttribute(NameOID.SERIAL_NUMBER, u"456"), - x509.NameAttribute(NameOID.TITLE, u"Title 0"), - x509.NameAttribute(NameOID.TITLE, u"Title 1"), - x509.NameAttribute(NameOID.SURNAME, u"Surname 0"), - x509.NameAttribute(NameOID.SURNAME, u"Surname 1"), - x509.NameAttribute(NameOID.GIVEN_NAME, u"Given Name 0"), - x509.NameAttribute(NameOID.GIVEN_NAME, u"Given Name 1"), - x509.NameAttribute(NameOID.PSEUDONYM, u"Incognito 0"), - x509.NameAttribute(NameOID.PSEUDONYM, u"Incognito 1"), - x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u"Last Gen"), - x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u"Next Gen"), - x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"dc0"), - x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"dc1"), - x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"test0@test.local"), - x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"test1@test.local"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "CA"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Illinois"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Chicago"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Zero, LLC"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "One, LLC"), + x509.NameAttribute(NameOID.COMMON_NAME, "common name 0"), + x509.NameAttribute(NameOID.COMMON_NAME, "common name 1"), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "OU 0"), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "OU 1"), + x509.NameAttribute(NameOID.DN_QUALIFIER, "dnQualifier0"), + x509.NameAttribute(NameOID.DN_QUALIFIER, "dnQualifier1"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "123"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "456"), + x509.NameAttribute(NameOID.TITLE, "Title 0"), + x509.NameAttribute(NameOID.TITLE, "Title 1"), + x509.NameAttribute(NameOID.SURNAME, "Surname 0"), + x509.NameAttribute(NameOID.SURNAME, "Surname 1"), + x509.NameAttribute(NameOID.GIVEN_NAME, "Given Name 0"), + x509.NameAttribute(NameOID.GIVEN_NAME, "Given Name 1"), + x509.NameAttribute(NameOID.PSEUDONYM, "Incognito 0"), + x509.NameAttribute(NameOID.PSEUDONYM, "Incognito 1"), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "Last Gen"), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "Next Gen"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc0"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc1"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test0@test.local"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test1@test.local"), ] def test_subject(self, backend): @@ -857,24 +1161,23 @@ def test_subject(self, backend): "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, - backend, ) subject = cert.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"Test Certificates 2011" + NameOID.ORGANIZATION_NAME, "Test Certificates 2011" ), x509.NameAttribute( NameOID.COMMON_NAME, - u"Valid pre2000 UTC notBefore Date EE Certificate Test3", + "Valid pre2000 UTC notBefore Date EE Certificate Test3", ), ] assert subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ x509.NameAttribute( NameOID.COMMON_NAME, - u"Valid pre2000 UTC notBefore Date EE Certificate Test3", + "Valid pre2000 UTC notBefore Date EE Certificate Test3", ) ] @@ -882,20 +1185,28 @@ def test_unicode_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "utf8_common_name.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ - x509.NameAttribute(NameOID.COMMON_NAME, u"We heart UTF8!\u2122") + x509.NameAttribute(NameOID.COMMON_NAME, "We heart UTF8!\u2122") ] assert cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ - x509.NameAttribute(NameOID.COMMON_NAME, u"We heart UTF8!\u2122") + x509.NameAttribute(NameOID.COMMON_NAME, "We heart UTF8!\u2122") ] + def test_invalid_unicode_name(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "invalid_utf8_common_name.pem"), + x509.load_pem_x509_certificate, + ) + with pytest.raises(ValueError, match="subject"): + cert.subject + with pytest.raises(ValueError, match="issuer"): + cert.issuer + def test_non_ascii_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "utf8-dnsname.pem"), x509.load_pem_x509_certificate, - backend, ) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName @@ -904,65 +1215,63 @@ def test_non_ascii_dns_name(self, backend): names = san.get_values_for_type(x509.DNSName) assert names == [ - u"partner.biztositas.hu", - u"biztositas.hu", - u"*.biztositas.hu", - u"biztos\xedt\xe1s.hu", - u"*.biztos\xedt\xe1s.hu", - u"xn--biztosts-fza2j.hu", - u"*.xn--biztosts-fza2j.hu", + "partner.biztositas.hu", + "biztositas.hu", + "*.biztositas.hu", + "biztos\xedt\xe1s.hu", + "*.biztos\xedt\xe1s.hu", + "xn--biztosts-fza2j.hu", + "*.xn--biztosts-fza2j.hu", ] def test_all_subject_name_types(self, backend): cert = _load_cert( os.path.join("x509", "custom", "all_supported_names.pem"), x509.load_pem_x509_certificate, - backend, ) subject = cert.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"AU"), - x509.NameAttribute(NameOID.COUNTRY_NAME, u"DE"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Ithaca"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Org Zero, LLC"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Org One, LLC"), - x509.NameAttribute(NameOID.COMMON_NAME, u"CN 0"), - x509.NameAttribute(NameOID.COMMON_NAME, u"CN 1"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "AU"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "New York"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Ithaca"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Org Zero, LLC"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Org One, LLC"), + x509.NameAttribute(NameOID.COMMON_NAME, "CN 0"), + x509.NameAttribute(NameOID.COMMON_NAME, "CN 1"), x509.NameAttribute( - NameOID.ORGANIZATIONAL_UNIT_NAME, u"Engineering 0" + NameOID.ORGANIZATIONAL_UNIT_NAME, "Engineering 0" ), x509.NameAttribute( - NameOID.ORGANIZATIONAL_UNIT_NAME, u"Engineering 1" + NameOID.ORGANIZATIONAL_UNIT_NAME, "Engineering 1" ), - x509.NameAttribute(NameOID.DN_QUALIFIER, u"qualified0"), - x509.NameAttribute(NameOID.DN_QUALIFIER, u"qualified1"), - x509.NameAttribute(NameOID.SERIAL_NUMBER, u"789"), - x509.NameAttribute(NameOID.SERIAL_NUMBER, u"012"), - x509.NameAttribute(NameOID.TITLE, u"Title IX"), - x509.NameAttribute(NameOID.TITLE, u"Title X"), - x509.NameAttribute(NameOID.SURNAME, u"Last 0"), - x509.NameAttribute(NameOID.SURNAME, u"Last 1"), - x509.NameAttribute(NameOID.GIVEN_NAME, u"First 0"), - x509.NameAttribute(NameOID.GIVEN_NAME, u"First 1"), - x509.NameAttribute(NameOID.PSEUDONYM, u"Guy Incognito 0"), - x509.NameAttribute(NameOID.PSEUDONYM, u"Guy Incognito 1"), - x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u"32X"), - x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u"Dreamcast"), - x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"dc2"), - x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"dc3"), - x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"test2@test.local"), - x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"test3@test.local"), + x509.NameAttribute(NameOID.DN_QUALIFIER, "qualified0"), + x509.NameAttribute(NameOID.DN_QUALIFIER, "qualified1"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "789"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "012"), + x509.NameAttribute(NameOID.TITLE, "Title IX"), + x509.NameAttribute(NameOID.TITLE, "Title X"), + x509.NameAttribute(NameOID.SURNAME, "Last 0"), + x509.NameAttribute(NameOID.SURNAME, "Last 1"), + x509.NameAttribute(NameOID.GIVEN_NAME, "First 0"), + x509.NameAttribute(NameOID.GIVEN_NAME, "First 1"), + x509.NameAttribute(NameOID.PSEUDONYM, "Guy Incognito 0"), + x509.NameAttribute(NameOID.PSEUDONYM, "Guy Incognito 1"), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "32X"), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "Dreamcast"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc2"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc3"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test2@test.local"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test3@test.local"), ] def test_load_good_ca_cert(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) @@ -983,7 +1292,6 @@ def test_utc_pre_2000_not_before_cert(self, backend): "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime(1950, 1, 1, 12, 1) @@ -997,7 +1305,6 @@ def test_pre_2000_utc_not_after_cert(self, backend): "Invalidpre2000UTCEEnotAfterDateTest7EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_after == datetime.datetime(1999, 1, 1, 12, 1) @@ -1006,7 +1313,6 @@ def test_post_2000_utc_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime( 2014, 11, 26, 21, 41, 20 @@ -1024,7 +1330,6 @@ def test_generalized_time_not_before_cert(self, backend): "ValidGeneralizedTimenotBeforeDateTest4EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime(2002, 1, 1, 12, 1) assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) @@ -1039,7 +1344,6 @@ def test_generalized_time_not_after_cert(self, backend): "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) assert cert.not_valid_after == datetime.datetime(2050, 1, 1, 12, 1) @@ -1050,21 +1354,37 @@ def test_invalid_version_cert(self, backend): _load_cert( os.path.join("x509", "custom", "invalid_version.pem"), x509.load_pem_x509_certificate, - backend, ) assert exc.value.parsed_version == 7 - def test_eq(self, backend): + def test_invalid_visiblestring_in_explicit_text(self, backend): cert = _load_cert( - os.path.join("x509", "custom", "post2000utctime.pem"), + os.path.join( + "x509", + "belgian-eid-invalid-visiblestring.pem", + ), + x509.load_pem_x509_certificate, + ) + with pytest.warns(utils.DeprecatedIn41): + cp = cert.extensions.get_extension_for_class( + x509.CertificatePolicies + ).value + assert isinstance(cp, x509.CertificatePolicies) + assert cp[0].policy_qualifiers[1].explicit_text == ( + "Gebruik onderworpen aan aansprakelijkheidsbeperkingen, zie CPS " + "- Usage soumis à des limitations de responsabilité, voir CPS - " + "Verwendung unterliegt Haftungsbeschränkungen, gemäss CPS" + ) + + def test_eq(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert == cert2 @@ -1072,7 +1392,6 @@ def test_ne(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert2 = _load_cert( os.path.join( @@ -1082,21 +1401,30 @@ def test_ne(self, backend): "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert != cert2 assert cert != object() + def test_ordering_unsupported(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + ) + cert2 = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + ) + with pytest.raises(TypeError, match="cannot be ordered"): + cert > cert2 # type: ignore[operator] + def test_hash(self, backend): cert1 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert3 = _load_cert( os.path.join( @@ -1106,7 +1434,6 @@ def test_hash(self, backend): "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert hash(cert1) == hash(cert2) @@ -1116,14 +1443,21 @@ def test_version_1_cert(self, backend): cert = _load_cert( os.path.join("x509", "v1_cert.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.version is x509.Version.v1 def test_invalid_pem(self, backend): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Unable to load"): x509.load_pem_x509_certificate(b"notacert", backend) + crl = load_vectors_from_file( + filename=os.path.join("x509", "custom", "crl_empty.pem"), + loader=lambda pemfile: pemfile.read(), + mode="rb", + ) + with pytest.raises(ValueError, match="Valid PEM but no"): + x509.load_pem_x509_certificate(crl, backend) + def test_invalid_der(self, backend): with pytest.raises(ValueError): x509.load_der_x509_certificate(b"notacert", backend) @@ -1132,9 +1466,8 @@ def test_unsupported_signature_hash_algorithm_cert(self, backend): cert = _load_cert( os.path.join("x509", "verisign_md2_root.pem"), x509.load_pem_x509_certificate, - backend, ) - with pytest.raises(UnsupportedAlgorithm): + with raises_unsupported_algorithm(None): cert.signature_hash_algorithm def test_public_bytes_pem(self, backend): @@ -1142,7 +1475,6 @@ def test_public_bytes_pem(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) # Encode it to PEM and load it back. @@ -1168,7 +1500,6 @@ def test_public_bytes_der(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) # Encode it to DER and load it back. @@ -1176,7 +1507,6 @@ def test_public_bytes_der(self, backend): cert.public_bytes( encoding=serialization.Encoding.DER, ), - backend, ) # We should recover what we had to start with. @@ -1193,11 +1523,10 @@ def test_public_bytes_invalid_encoding(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) with pytest.raises(TypeError): - cert.public_bytes("NotAnEncoding") + cert.public_bytes("NotAnEncoding") # type: ignore[arg-type] @pytest.mark.parametrize( ("cert_path", "loader_func", "encoding"), @@ -1220,7 +1549,7 @@ def test_public_bytes_match( cert_bytes = load_vectors_from_file( cert_path, lambda pemfile: pemfile.read(), mode="rb" ) - cert = loader_func(cert_bytes, backend) + cert = loader_func(cert_bytes) serialized = cert.public_bytes(encoding) assert serialized == cert_bytes @@ -1228,7 +1557,6 @@ def test_certificate_repr(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) assert repr(cert) == ( " csr2 # type: ignore[operator] + def test_hash(self, backend): request1 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) request2 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) request3 = _load_cert( os.path.join("x509", "requests", "san_rsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) assert hash(request1) == hash(request2) assert hash(request1) != hash(request3) - def test_build_cert(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) + @pytest.mark.parametrize( + ("hashalg", "hashalg_oid"), + [ + (hashes.SHA224, x509.SignatureAlgorithmOID.RSA_WITH_SHA224), + (hashes.SHA256, x509.SignatureAlgorithmOID.RSA_WITH_SHA256), + (hashes.SHA384, x509.SignatureAlgorithmOID.RSA_WITH_SHA384), + (hashes.SHA512, x509.SignatureAlgorithmOID.RSA_WITH_SHA512), + (hashes.SHA3_224, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_224), + (hashes.SHA3_256, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_256), + (hashes.SHA3_384, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_384), + (hashes.SHA3_512, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_512), + ], + ) + def test_build_cert( + self, rsa_key_2048: rsa.RSAPrivateKey, hashalg, hashalg_oid, backend + ): + if not backend.signature_hash_supported(hashalg()): + pytest.skip(f"{hashalg} signature not supported") + + issuer_private_key = rsa_key_2048 + subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) @@ -1649,14 +2130,14 @@ def test_build_cert(self, backend): .issuer_name( x509.Name( [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io" + NameOID.COMMON_NAME, "cryptography.io" ), ] ) @@ -1664,14 +2145,14 @@ def test_build_cert(self, backend): .subject_name( x509.Name( [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io" + NameOID.COMMON_NAME, "cryptography.io" ), ] ) @@ -1682,48 +2163,54 @@ def test_build_cert(self, backend): True, ) .add_extension( - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ), + x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) - cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 + assert cert.signature_algorithm_oid == hashalg_oid + assert type(cert.signature_hash_algorithm) is hashalg assert cert.not_valid_before == not_valid_before assert cert.not_valid_after == not_valid_after basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) + assert isinstance( + subject_alternative_name.value, x509.SubjectAlternativeName + ) assert list(subject_alternative_name.value) == [ - x509.DNSName(u"cryptography.io"), + x509.DNSName("cryptography.io"), ] - def test_build_cert_private_type_encoding(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_build_cert_private_type_encoding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + issuer_private_key = rsa_key_2048 + subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) name = x509.Name( [ x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, - u"Texas", + "Texas", _ASN1Type.PrintableString, ), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute( NameOID.COMMON_NAME, - u"cryptography.io", + "cryptography.io", _ASN1Type.IA5String, ), ] @@ -1757,9 +2244,11 @@ def test_build_cert_private_type_encoding(self, backend): == _ASN1Type.UTF8String ) - def test_build_cert_printable_string_country_name(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_build_cert_printable_string_country_name( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + issuer_private_key = rsa_key_2048 + subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) @@ -1770,12 +2259,12 @@ def test_build_cert_printable_string_country_name(self, backend): .issuer_name( x509.Name( [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( - NameOID.JURISDICTION_COUNTRY_NAME, u"US" + NameOID.JURISDICTION_COUNTRY_NAME, "US" ), x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) @@ -1783,12 +2272,12 @@ def test_build_cert_printable_string_country_name(self, backend): .subject_name( x509.Name( [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( - NameOID.JURISDICTION_COUNTRY_NAME, u"US" + NameOID.JURISDICTION_COUNTRY_NAME, "US" ), x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) @@ -1800,47 +2289,29 @@ def test_build_cert_printable_string_country_name(self, backend): cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) - parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) - subject = parsed.subject - issuer = parsed.issuer - - def read_next_rdn_value_tag(reader): - # Assume each RDN has a single attribute. - with reader.read_element(SET) as rdn: - attribute = rdn.read_element(SEQUENCE) - - with attribute: - _ = attribute.read_element(OBJECT_IDENTIFIER) - tag, value = attribute.read_any_element() - return tag + parsed = asn1.test_parse_certificate( + cert.public_bytes(serialization.Encoding.DER) + ) # Check that each value was encoded as an ASN.1 PRINTABLESTRING. - assert read_next_rdn_value_tag(subject) == PRINTABLE_STRING - assert read_next_rdn_value_tag(issuer) == PRINTABLE_STRING - if ( - # This only works correctly in OpenSSL 1.1.0f+ and 1.0.2l+ - backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER - or ( - backend._lib.CRYPTOGRAPHY_OPENSSL_102L_OR_GREATER - and not backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - ) - ): - assert read_next_rdn_value_tag(subject) == PRINTABLE_STRING - assert read_next_rdn_value_tag(issuer) == PRINTABLE_STRING + assert parsed.issuer_value_tags[0] == 0x13 + assert parsed.subject_value_tags[0] == 0x13 + assert parsed.issuer_value_tags[1] == 0x13 + assert parsed.subject_value_tags[1] == 0x13 -class TestCertificateBuilder(object): - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_checks_for_unsupported_extensions(self, backend): - private_key = RSA_KEY_2048.private_key(backend) +class TestCertificateBuilder: + def test_checks_for_unsupported_extensions( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) @@ -1850,18 +2321,18 @@ def test_checks_for_unsupported_extensions(self, backend): ) with pytest.raises(NotImplementedError): - builder.sign(private_key, hashes.SHA1(), backend) + builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_encode_nonstandard_aia(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_encode_nonstandard_aia( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( x509.ObjectIdentifier("2.999.7"), - x509.UniformResourceIdentifier(u"http://example.com"), + x509.UniformResourceIdentifier("http://example.com"), ), ] ) @@ -1869,10 +2340,10 @@ def test_encode_nonstandard_aia(self, backend): builder = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) @@ -1883,16 +2354,16 @@ def test_encode_nonstandard_aia(self, backend): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_encode_nonstandard_sia(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_encode_nonstandard_sia( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 sia = x509.SubjectInformationAccess( [ x509.AccessDescription( x509.ObjectIdentifier("2.999.7"), - x509.UniformResourceIdentifier(u"http://example.com"), + x509.UniformResourceIdentifier("http://example.com"), ), ] ) @@ -1900,10 +2371,10 @@ def test_encode_nonstandard_sia(self, backend): builder = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) @@ -1918,41 +2389,41 @@ def test_encode_nonstandard_sia(self, backend): ) assert ext.value == sia - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_subject_dn_asn1_types(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_subject_dn_asn1_types( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 name = x509.Name( [ - x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"value"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"value"), - x509.NameAttribute(NameOID.STREET_ADDRESS, u"value"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"value"), - x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"value"), - x509.NameAttribute(NameOID.SERIAL_NUMBER, u"value"), - x509.NameAttribute(NameOID.SURNAME, u"value"), - x509.NameAttribute(NameOID.GIVEN_NAME, u"value"), - x509.NameAttribute(NameOID.TITLE, u"value"), - x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u"value"), - x509.NameAttribute(NameOID.X500_UNIQUE_IDENTIFIER, u"value"), - x509.NameAttribute(NameOID.DN_QUALIFIER, u"value"), - x509.NameAttribute(NameOID.PSEUDONYM, u"value"), - x509.NameAttribute(NameOID.USER_ID, u"value"), - x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"value"), - x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"value"), - x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "value"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "value"), + x509.NameAttribute(NameOID.STREET_ADDRESS, "value"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "value"), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "value"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "value"), + x509.NameAttribute(NameOID.SURNAME, "value"), + x509.NameAttribute(NameOID.GIVEN_NAME, "value"), + x509.NameAttribute(NameOID.TITLE, "value"), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "value"), + x509.NameAttribute(NameOID.X500_UNIQUE_IDENTIFIER, "value"), + x509.NameAttribute(NameOID.DN_QUALIFIER, "value"), + x509.NameAttribute(NameOID.PSEUDONYM, "value"), + x509.NameAttribute(NameOID.USER_ID, "value"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "value"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, "value"), + x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, "US"), x509.NameAttribute( - NameOID.JURISDICTION_LOCALITY_NAME, u"value" + NameOID.JURISDICTION_LOCALITY_NAME, "value" ), x509.NameAttribute( - NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, u"value" + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, "value" ), - x509.NameAttribute(NameOID.BUSINESS_CATEGORY, u"value"), - x509.NameAttribute(NameOID.POSTAL_ADDRESS, u"value"), - x509.NameAttribute(NameOID.POSTAL_CODE, u"value"), + x509.NameAttribute(NameOID.BUSINESS_CATEGORY, "value"), + x509.NameAttribute(NameOID.POSTAL_ADDRESS, "value"), + x509.NameAttribute(NameOID.POSTAL_CODE, "value"), ] ) cert = ( @@ -1977,17 +2448,21 @@ def test_subject_dn_asn1_types(self, backend): [datetime.datetime(1970, 2, 1), datetime.datetime(9999, 12, 31)], ], ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_extreme_times(self, not_valid_before, not_valid_after, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_extreme_times( + self, + rsa_key_2048: rsa.RSAPrivateKey, + not_valid_before, + not_valid_after, + backend, + ): + private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) @@ -1997,37 +2472,211 @@ def test_extreme_times(self, not_valid_before, not_valid_after, backend): cert = builder.sign(private_key, hashes.SHA256(), backend) assert cert.not_valid_before == not_valid_before assert cert.not_valid_after == not_valid_after - parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) - assert parsed.not_before_tag == UTC_TIME - assert parsed.not_after_tag == GENERALIZED_TIME - - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_subject_name(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) - builder = ( - x509.CertificateBuilder() - .serial_number(777) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .public_key(subject_private_key.public_key()) - .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) - .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) + parsed = asn1.test_parse_certificate( + cert.public_bytes(serialization.Encoding.DER) ) - with pytest.raises(ValueError): - builder.sign(subject_private_key, hashes.SHA256(), backend) + # UTC TIME + assert parsed.not_before_tag == 0x17 + # GENERALIZED TIME + assert parsed.not_after_tag == 0x18 - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_issuer_name(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) - builder = ( + def test_rdns_preserve_iteration_order( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + """ + This test checks that RDN ordering is consistent when loading + data from a certificate. Since the underlying RDN is an ASN.1 + set these values get lexicographically ordered on encode and + the parsed value won't necessarily be in the same order as + the originally provided list. However, we want to make sure + that the order is always consistent since it confuses people + when it isn't. + """ + name = x509.Name( + [ + x509.RelativeDistinguishedName( + [ + x509.NameAttribute(NameOID.TITLE, "Test"), + x509.NameAttribute(NameOID.COMMON_NAME, "Multivalue"), + x509.NameAttribute(NameOID.SURNAME, "RDNs"), + ] + ), + ] + ) + + cert = ( x509.CertificateBuilder() - .serial_number(777) - .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) + .serial_number(1) + .issuer_name(name) + .subject_name(name) + .public_key(rsa_key_2048.public_key()) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) + .sign(rsa_key_2048, hashes.SHA256(), backend) + ) + loaded_cert = x509.load_pem_x509_certificate( + cert.public_bytes(encoding=serialization.Encoding.PEM) + ) + assert next(iter(loaded_cert.subject.rdns[0])) == x509.NameAttribute( + NameOID.SURNAME, "RDNs" + ) + + @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.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(rsa_key_2048.public_key()) + .serial_number(777) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) + ) + pss = padding.PSS( + mgf=padding.MGF1(mgf_alg), salt_length=alg.digest_size + ) + cert = builder.sign(rsa_key_2048, alg, rsa_padding=pss) + pk = cert.public_key() + assert isinstance(pk, rsa.RSAPublicKey) + assert isinstance(cert.signature_hash_algorithm, type(alg)) + cert_params = cert.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( + cert.signature, + cert.tbs_certificate_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.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(rsa_key_2048.public_key()) + .serial_number(777) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) + ) + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len + ) + cert = builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) + assert isinstance(cert.signature_algorithm_parameters, padding.PSS) + assert cert.signature_algorithm_parameters._salt_length == computed_len + + def test_sign_pss_auto_unsupported( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + builder = ( + x509.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(rsa_key_2048.public_key()) + .serial_number(777) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) + ) + 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.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(rsa_key_2048.public_key()) + .serial_number(777) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) + ) + 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.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(rsa_key_2048.public_key()) + .serial_number(777) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) + ) + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32) + with pytest.raises(TypeError): + builder.sign(rsa_key_2048, None, rsa_padding=pss) + + def test_no_subject_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + subject_private_key = rsa_key_2048 + builder = ( + x509.CertificateBuilder() + .serial_number(777) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) @@ -2035,18 +2684,31 @@ def test_no_issuer_name(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_public_key(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_no_issuer_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + subject_private_key = rsa_key_2048 + builder = ( + x509.CertificateBuilder() + .serial_number(777) + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(subject_private_key.public_key()) + .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) + .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + def test_no_public_key(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) @@ -2054,18 +2716,18 @@ def test_no_public_key(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_not_valid_before(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_no_not_valid_before( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) @@ -2073,18 +2735,18 @@ def test_no_not_valid_before(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_not_valid_after(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_no_not_valid_after( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) @@ -2092,17 +2754,15 @@ def test_no_not_valid_after(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_serial_number(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_no_serial_number(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) @@ -2115,13 +2775,13 @@ def test_issuer_name_must_be_a_name_type(self): builder = x509.CertificateBuilder() with pytest.raises(TypeError): - builder.issuer_name("subject") + builder.issuer_name("subject") # type:ignore[arg-type] with pytest.raises(TypeError): - builder.issuer_name(object) + builder.issuer_name(object) # type:ignore[arg-type] def test_issuer_name_may_only_be_set_once(self): - name = x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + name = x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) builder = x509.CertificateBuilder().issuer_name(name) with pytest.raises(ValueError): @@ -2131,13 +2791,13 @@ def test_subject_name_must_be_a_name_type(self): builder = x509.CertificateBuilder() with pytest.raises(TypeError): - builder.subject_name("subject") + builder.subject_name("subject") # type:ignore[arg-type] with pytest.raises(TypeError): - builder.subject_name(object) + builder.subject_name(object) # type:ignore[arg-type] def test_subject_name_may_only_be_set_once(self): - name = x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + name = x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) builder = x509.CertificateBuilder().subject_name(name) with pytest.raises(ValueError): @@ -2159,19 +2819,19 @@ def test_not_valid_after_before_not_valid_before(self): with pytest.raises(ValueError): builder.not_valid_after(datetime.datetime(2001, 1, 1, 12, 1)) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_public_key_must_be_public_key(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_public_key_must_be_public_key( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = x509.CertificateBuilder() with pytest.raises(TypeError): - builder.public_key(private_key) + builder.public_key(private_key) # type: ignore[arg-type] - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_public_key_may_only_be_set_once(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_public_key_may_only_be_set_once( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 public_key = private_key.public_key() builder = x509.CertificateBuilder().public_key(public_key) @@ -2180,7 +2840,9 @@ def test_public_key_may_only_be_set_once(self, backend): def test_serial_number_must_be_an_integer_type(self): with pytest.raises(TypeError): - x509.CertificateBuilder().serial_number(10.0) + x509.CertificateBuilder().serial_number( + 10.0 # type:ignore[arg-type] + ) def test_serial_number_must_be_non_negative(self): with pytest.raises(ValueError): @@ -2190,18 +2852,18 @@ def test_serial_number_must_be_positive(self): with pytest.raises(ValueError): x509.CertificateBuilder().serial_number(0) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_minimal_serial_number(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_minimal_serial_number( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(1) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"RU")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"RU")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) @@ -2210,18 +2872,18 @@ def test_minimal_serial_number(self, backend): cert = builder.sign(subject_private_key, hashes.SHA256(), backend) assert cert.serial_number == 1 - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_biggest_serial_number(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_biggest_serial_number( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number((1 << 159) - 1) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"RU")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"RU")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) @@ -2240,21 +2902,20 @@ def test_serial_number_may_only_be_set_once(self): with pytest.raises(ValueError): builder.serial_number(20) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_aware_not_valid_after(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - time = tz.localize(time) + def test_aware_not_valid_after( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + tz = datetime.timezone(datetime.timedelta(hours=-8)) + time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_time = datetime.datetime(2012, 1, 17, 6, 43) - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 cert_builder = x509.CertificateBuilder().not_valid_after(time) cert_builder = ( cert_builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) @@ -2264,18 +2925,16 @@ def test_aware_not_valid_after(self, backend): cert = cert_builder.sign(private_key, hashes.SHA256(), backend) assert cert.not_valid_after == utc_time - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_earliest_time(self, backend): + def test_earliest_time(self, rsa_key_2048: rsa.RSAPrivateKey, backend): time = datetime.datetime(1950, 1, 1) - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 cert_builder = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) @@ -2285,16 +2944,23 @@ def test_earliest_time(self, backend): cert = cert_builder.sign(private_key, hashes.SHA256(), backend) assert cert.not_valid_before == time assert cert.not_valid_after == time - parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) - assert parsed.not_before_tag == UTC_TIME - assert parsed.not_after_tag == UTC_TIME + parsed = asn1.test_parse_certificate( + cert.public_bytes(serialization.Encoding.DER) + ) + # UTC TIME + assert parsed.not_before_tag == 0x17 + assert parsed.not_after_tag == 0x17 def test_invalid_not_valid_after(self): with pytest.raises(TypeError): - x509.CertificateBuilder().not_valid_after(104204304504) + x509.CertificateBuilder().not_valid_after( + 104204304504 # type:ignore[arg-type] + ) with pytest.raises(TypeError): - x509.CertificateBuilder().not_valid_after(datetime.time()) + x509.CertificateBuilder().not_valid_after( + datetime.time() # type:ignore[arg-type] + ) with pytest.raises(ValueError): x509.CertificateBuilder().not_valid_after( @@ -2309,21 +2975,20 @@ def test_not_valid_after_may_only_be_set_once(self): with pytest.raises(ValueError): builder.not_valid_after(datetime.datetime.now()) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_aware_not_valid_before(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - time = tz.localize(time) + def test_aware_not_valid_before( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + tz = datetime.timezone(datetime.timedelta(hours=-8)) + time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_time = datetime.datetime(2012, 1, 17, 6, 43) - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 cert_builder = x509.CertificateBuilder().not_valid_before(time) cert_builder = ( cert_builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) @@ -2335,10 +3000,14 @@ def test_aware_not_valid_before(self, backend): def test_invalid_not_valid_before(self): with pytest.raises(TypeError): - x509.CertificateBuilder().not_valid_before(104204304504) + x509.CertificateBuilder().not_valid_before( + 104204304504 # type:ignore[arg-type] + ) with pytest.raises(TypeError): - x509.CertificateBuilder().not_valid_before(datetime.time()) + x509.CertificateBuilder().not_valid_before( + datetime.time() # type:ignore[arg-type] + ) with pytest.raises(ValueError): x509.CertificateBuilder().not_valid_before( @@ -2369,20 +3038,23 @@ def test_add_invalid_extension_type(self): builder = x509.CertificateBuilder() with pytest.raises(TypeError): - builder.add_extension(object(), False) + builder.add_extension( + object(), # type:ignore[arg-type] + False, + ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) @pytest.mark.parametrize("algorithm", [object(), None]) - def test_sign_with_unsupported_hash(self, algorithm, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign_with_unsupported_hash( + self, rsa_key_2048: rsa.RSAPrivateKey, algorithm, backend + ): + private_key = rsa_key_2048 builder = x509.CertificateBuilder() builder = ( builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) @@ -2397,16 +3069,15 @@ def test_sign_with_unsupported_hash(self, algorithm, backend): only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_unsupported_hash_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() builder = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) @@ -2421,16 +3092,15 @@ def test_sign_with_unsupported_hash_ed25519(self, backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_unsupported_hash_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() builder = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) @@ -2441,56 +3111,42 @@ def test_sign_with_unsupported_hash_ed448(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Requires OpenSSL with MD5 support", ) - def test_sign_rsa_with_md5(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - builder = x509.CertificateBuilder() - builder = ( - builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .serial_number(1) - .public_key(private_key.public_key()) - .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) - .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) - ) - cert = builder.sign(private_key, hashes.MD5(), backend) - assert isinstance(cert.signature_hash_algorithm, hashes.MD5) - - @pytest.mark.requires_backend_interface(interface=DSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5()), - skip_message="Requires OpenSSL with MD5 support", + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) + @pytest.mark.parametrize( + "hash_algorithm", + [ + hashes.MD5(), + hashes.SHA3_224(), + hashes.SHA3_256(), + hashes.SHA3_384(), + hashes.SHA3_512(), + ], ) - def test_sign_dsa_with_md5(self, backend): + def test_sign_dsa_with_unsupported_hash(self, hash_algorithm, backend): private_key = DSA_KEY_2048.private_key(backend) builder = x509.CertificateBuilder() builder = ( builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) + with pytest.raises(UnsupportedAlgorithm): + builder.sign(private_key, hash_algorithm, backend) - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Requires OpenSSL with MD5 support", @@ -2501,22 +3157,37 @@ def test_sign_ec_with_md5(self, backend): builder = x509.CertificateBuilder() builder = ( builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) + with pytest.raises(UnsupportedAlgorithm): + builder.sign( + private_key, hashes.MD5(), backend # type: ignore[arg-type] + ) - @pytest.mark.requires_backend_interface(interface=DSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_dsa_private_key(self, backend): + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) + @pytest.mark.parametrize( + ("hashalg", "hashalg_oid"), + [ + (hashes.SHA224, x509.SignatureAlgorithmOID.DSA_WITH_SHA224), + (hashes.SHA256, x509.SignatureAlgorithmOID.DSA_WITH_SHA256), + (hashes.SHA384, x509.SignatureAlgorithmOID.DSA_WITH_SHA384), + (hashes.SHA512, x509.SignatureAlgorithmOID.DSA_WITH_SHA512), + ], + ) + def test_build_cert_with_dsa_private_key( + self, hashalg, hashalg_oid, backend + ): issuer_private_key = DSA_KEY_2048.private_key(backend) subject_private_key = DSA_KEY_2048.private_key(backend) @@ -2527,10 +3198,10 @@ def test_build_cert_with_dsa_private_key(self, backend): x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( @@ -2538,36 +3209,55 @@ def test_build_cert_with_dsa_private_key(self, backend): True, ) .add_extension( - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ), + x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) - cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 + assert cert.signature_algorithm_oid == hashalg_oid assert cert.not_valid_before == not_valid_before assert cert.not_valid_after == not_valid_after basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) + assert isinstance( + subject_alternative_name.value, x509.SubjectAlternativeName + ) assert list(subject_alternative_name.value) == [ - x509.DNSName(u"cryptography.io"), + x509.DNSName("cryptography.io"), ] - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_ec_private_key(self, backend): + @pytest.mark.parametrize( + ("hashalg", "hashalg_oid"), + [ + (hashes.SHA224, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA224), + (hashes.SHA256, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA256), + (hashes.SHA384, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA384), + (hashes.SHA512, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA512), + (hashes.SHA3_224, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_224), + (hashes.SHA3_256, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_256), + (hashes.SHA3_384, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_384), + (hashes.SHA3_512, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_512), + ], + ) + def test_build_cert_with_ec_private_key( + self, hashalg, hashalg_oid, backend + ): _skip_curve_unsupported(backend, ec.SECP256R1()) + if not backend.signature_hash_supported(hashalg()): + pytest.skip(f"{hashalg} signature not supported") + issuer_private_key = ec.generate_private_key(ec.SECP256R1(), backend) subject_private_key = ec.generate_private_key(ec.SECP256R1(), backend) @@ -2578,10 +3268,10 @@ def test_build_cert_with_ec_private_key(self, backend): x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( @@ -2589,37 +3279,77 @@ def test_build_cert_with_ec_private_key(self, backend): True, ) .add_extension( - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ), + x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) - cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 + assert cert.signature_algorithm_oid == hashalg_oid + assert type(cert.signature_hash_algorithm) is hashalg assert cert.not_valid_before == not_valid_before assert cert.not_valid_after == not_valid_after basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) + assert isinstance( + subject_alternative_name.value, x509.SubjectAlternativeName + ) assert list(subject_alternative_name.value) == [ - x509.DNSName(u"cryptography.io"), + x509.DNSName("cryptography.io"), ] + def test_build_cert_with_bmpstring_universalstring_name( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 + issuer = x509.Name( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, + "cryptography.io", + _ASN1Type.BMPString, + ), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), + ] + ) + subject = x509.Name( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, + "cryptography.io", + _ASN1Type.UniversalString, + ), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), + ] + ) + builder = x509.CertificateBuilder() + builder = ( + builder.subject_name(subject) + .issuer_name(issuer) + .serial_number(1) + .public_key(private_key.public_key()) + .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) + .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) + ) + cert = builder.sign(private_key, hashes.SHA256(), backend) + assert cert.issuer == issuer + assert cert.subject == subject + @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_build_cert_with_ed25519(self, backend): issuer_private_key = ed25519.Ed25519PrivateKey.generate() subject_private_key = ed25519.Ed25519PrivateKey.generate() @@ -2631,10 +3361,10 @@ def test_build_cert_with_ed25519(self, backend): x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( @@ -2642,9 +3372,7 @@ def test_build_cert_with_ed25519(self, backend): True, ) .add_extension( - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ), + x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) @@ -2664,23 +3392,27 @@ def test_build_cert_with_ed25519(self, backend): basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) + assert isinstance( + subject_alternative_name.value, x509.SubjectAlternativeName + ) assert list(subject_alternative_name.value) == [ - x509.DNSName(u"cryptography.io"), + x509.DNSName("cryptography.io"), ] @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_cert_with_public_ed25519_rsa_sig(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) + def test_build_cert_with_public_ed25519_rsa_sig( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + issuer_private_key = rsa_key_2048 subject_private_key = ed25519.Ed25519PrivateKey.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) @@ -2690,10 +3422,10 @@ def test_build_cert_with_public_ed25519_rsa_sig(self, backend): x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) @@ -2701,6 +3433,7 @@ def test_build_cert_with_public_ed25519_rsa_sig(self, backend): ) cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + assert cert.signature_hash_algorithm is not None issuer_private_key.public_key().verify( cert.signature, cert.tbs_certificate_bytes, @@ -2717,7 +3450,6 @@ def test_build_cert_with_public_ed25519_rsa_sig(self, backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_build_cert_with_ed448(self, backend): issuer_private_key = ed448.Ed448PrivateKey.generate() subject_private_key = ed448.Ed448PrivateKey.generate() @@ -2729,10 +3461,10 @@ def test_build_cert_with_ed448(self, backend): x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( @@ -2740,9 +3472,7 @@ def test_build_cert_with_ed448(self, backend): True, ) .add_extension( - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ), + x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) @@ -2762,23 +3492,27 @@ def test_build_cert_with_ed448(self, backend): basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) + assert isinstance( + subject_alternative_name.value, x509.SubjectAlternativeName + ) assert list(subject_alternative_name.value) == [ - x509.DNSName(u"cryptography.io"), + x509.DNSName("cryptography.io"), ] @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_cert_with_public_ed448_rsa_sig(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) + def test_build_cert_with_public_ed448_rsa_sig( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + issuer_private_key = rsa_key_2048 subject_private_key = ed448.Ed448PrivateKey.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) @@ -2788,10 +3522,10 @@ def test_build_cert_with_public_ed448_rsa_sig(self, backend): x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) @@ -2799,6 +3533,7 @@ def test_build_cert_with_public_ed448_rsa_sig(self, backend): ) cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + assert cert.signature_hash_algorithm is not None issuer_private_key.public_key().verify( cert.signature, cert.tbs_certificate_bytes, @@ -2811,11 +3546,65 @@ def test_build_cert_with_public_ed448_rsa_sig(self, backend): assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), ed448.Ed448PublicKey) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_rsa_key_too_small(self, backend): - issuer_private_key = RSA_KEY_512.private_key(backend) - subject_private_key = RSA_KEY_512.private_key(backend) + @pytest.mark.supported( + only_if=lambda backend: ( + backend.x25519_supported() and backend.x448_supported() + ), + skip_message="Requires OpenSSL with x25519 & x448 support", + ) + @pytest.mark.parametrize( + ("priv_key_cls", "pub_key_cls"), + [ + (x25519.X25519PrivateKey, x25519.X25519PublicKey), + (x448.X448PrivateKey, x448.X448PublicKey), + ], + ) + def test_build_cert_with_public_x25519_x448_rsa_sig( + self, + rsa_key_2048: rsa.RSAPrivateKey, + priv_key_cls, + pub_key_cls, + backend, + ): + issuer_private_key = rsa_key_2048 + subject_private_key = priv_key_cls.generate() + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = ( + x509.CertificateBuilder() + .serial_number(777) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(subject_private_key.public_key()) + .not_valid_before(not_valid_before) + .not_valid_after(not_valid_after) + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + assert cert.signature_hash_algorithm is not None + issuer_private_key.public_key().verify( + cert.signature, + cert.tbs_certificate_bytes, + padding.PKCS1v15(), + cert.signature_hash_algorithm, + ) + assert cert.signature_algorithm_oid == ( + SignatureAlgorithmOID.RSA_WITH_SHA256 + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + assert isinstance(cert.public_key(), pub_key_cls) + + def test_build_cert_with_rsa_key_too_small( + self, rsa_key_512: rsa.RSAPrivateKey, backend + ): + issuer_private_key = rsa_key_512 + subject_private_key = rsa_key_512 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) @@ -2824,10 +3613,10 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) @@ -2837,8 +3626,6 @@ def test_build_cert_with_rsa_key_too_small(self, backend): with pytest.raises(ValueError): builder.sign(issuer_private_key, hashes.SHA512(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) @pytest.mark.parametrize( "add_ext", [ @@ -2846,12 +3633,12 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ # These examples exist to verify compatibility with # certificates that have utf8 encoded data in the ia5string - x509.DNSName._init_without_validation(u"a\xedt\xe1s.test"), + x509.DNSName._init_without_validation("a\xedt\xe1s.test"), x509.RFC822Name._init_without_validation( - u"test@a\xedt\xe1s.test" + "test@a\xedt\xe1s.test" ), x509.UniformResourceIdentifier._init_without_validation( - u"http://a\xedt\xe1s.test" + "http://a\xedt\xe1s.test" ), ] ), @@ -2859,7 +3646,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [u"http://other.com/cps"], + ["http://other.com/cps"], ) ] ), @@ -2876,11 +3663,11 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ - u"http://example.com/cps", - u"http://other.com/cps", + "http://example.com/cps", + "http://other.com/cps", x509.UserNotice( - x509.NoticeReference(u"my org", [1, 2, 3, 4]), - u"thing", + x509.NoticeReference("my org", [1, 2, 3, 4]), + "thing", ), ], ) @@ -2891,12 +3678,12 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ - u"http://example.com/cps", + "http://example.com/cps", x509.UserNotice( x509.NoticeReference( - u"UTF8\u2122'", [1, 2, 3, 4] + "UTF8\u2122'", [1, 2, 3, 4] ), - u"We heart UTF8!\u2122", + "We heart UTF8!\u2122", ), ], ) @@ -2906,7 +3693,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [x509.UserNotice(None, u"thing")], + [x509.UserNotice(None, "thing")], ) ] ), @@ -2916,7 +3703,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ x509.UserNotice( - x509.NoticeReference(u"my org", [1, 2, 3, 4]), + x509.NoticeReference("my org", [1, 2, 3, 4]), None, ) ], @@ -2925,8 +3712,8 @@ def test_build_cert_with_rsa_key_too_small(self, backend): ), x509.IssuerAlternativeName( [ - x509.DNSName(u"myissuer"), - x509.RFC822Name(u"email@domain.com"), + x509.DNSName("myissuer"), + x509.RFC822Name("email@domain.com"), ] ), x509.ExtendedKeyUsage( @@ -2947,29 +3734,29 @@ def test_build_cert_with_rsa_key_too_small(self, backend): ), x509.NameConstraints( permitted_subtrees=[ - x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")), - x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29")), - x509.IPAddress(ipaddress.IPv4Network(u"127.0.0.1/32")), - x509.IPAddress(ipaddress.IPv4Network(u"8.0.0.0/8")), - x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")), + x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24")), + x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/29")), + x509.IPAddress(ipaddress.IPv4Network("127.0.0.1/32")), + x509.IPAddress(ipaddress.IPv4Network("8.0.0.0/8")), + x509.IPAddress(ipaddress.IPv4Network("0.0.0.0/0")), x509.IPAddress( - ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/96") + ipaddress.IPv6Network("FF:0:0:0:0:0:0:0/96") ), x509.IPAddress( - ipaddress.IPv6Network(u"FF:FF:0:0:0:0:0:0/128") + ipaddress.IPv6Network("FF:FF:0:0:0:0:0:0/128") ), ], - excluded_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName("name.local")], ), x509.NameConstraints( permitted_subtrees=[ - x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")), + x509.IPAddress(ipaddress.IPv4Network("0.0.0.0/0")), ], excluded_subtrees=None, ), x509.NameConstraints( permitted_subtrees=None, - excluded_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName("name.local")], ), x509.PolicyConstraints( require_explicit_policy=None, inhibit_policy_mapping=1 @@ -2988,7 +3775,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ x509.NameAttribute( NameOID.COMMON_NAME, - u"indirect CRL for indirectCRL CA3", + "indirect CRL for indirectCRL CA3", ), ] ), @@ -2998,15 +3785,15 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, - u"Test Certificates 2011", + "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, - u"indirectCRL CA3 cRLIssuer", + "indirectCRL CA3 cRLIssuer", ), ] ) @@ -3023,7 +3810,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), ] ) @@ -3037,7 +3824,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ x509.NameAttribute( NameOID.ORGANIZATION_NAME, - u"cryptography Testing", + "cryptography Testing", ), ] ) @@ -3051,10 +3838,10 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ), x509.UniformResourceIdentifier( - u"http://backup.myhost.com/myca.crl" + "http://backup.myhost.com/myca.crl" ), ], relative_name=None, @@ -3069,11 +3856,11 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.COMMON_NAME, - u"cryptography CA", + "cryptography CA", ), ] ) @@ -3087,7 +3874,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://domain.com/some.crl" + "http://domain.com/some.crl" ) ], relative_name=None, @@ -3119,7 +3906,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ x509.NameAttribute( NameOID.COMMON_NAME, - u"cryptography CA", + "cryptography CA", ), ] ) @@ -3133,7 +3920,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://domain.com/some.crl" + "http://domain.com/some.crl" ) ], relative_name=None, @@ -3147,7 +3934,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://domain.com/some.crl" + "http://domain.com/some.crl" ) ], relative_name=None, @@ -3175,7 +3962,7 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ x509.NameAttribute( NameOID.COMMON_NAME, - u"indirect CRL for indirectCRL CA3", + "indirect CRL for indirectCRL CA3", ), ] ), @@ -3192,11 +3979,9 @@ def test_build_cert_with_rsa_key_too_small(self, backend): [ x509.NameAttribute( NameOID.COMMON_NAME, - u"indirect CRL for indirectCRL CA3", - ), - x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + "indirect CRL for indirectCRL CA3", ), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ] ), reasons=None, @@ -3204,103 +3989,152 @@ def test_build_cert_with_rsa_key_too_small(self, backend): ) ] ), - ], - ) - def test_ext(self, add_ext, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) - - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - - cert = ( - x509.CertificateBuilder() - .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .not_valid_before(not_valid_before) - .not_valid_after(not_valid_after) - .public_key(subject_private_key.public_key()) - .serial_number(123) - .add_extension(add_ext, critical=False) - .sign(issuer_private_key, hashes.SHA256(), backend) - ) - - ext = cert.extensions.get_extension_for_class(type(add_ext)) - assert ext.critical is False - assert ext.value == add_ext - - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_key_usage(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) - - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - - cert = ( - x509.CertificateBuilder() + x509.AuthorityInformationAccess( + [ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier( + "http://ocsp.domain.com" + ), + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier( + "http://domain.com/ca.crt" + ), + ), + ] + ), + x509.SubjectInformationAccess( + [ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier("http://ca.domain.com"), + ), + ] + ), + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + None, + None, + ), + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + [ + x509.DirectoryName( + x509.Name( + [ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, "PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, "cryptography CA" + ), + ] + ) + ) + ], + 333, + ), + x509.AuthorityKeyIdentifier( + None, + [ + x509.DirectoryName( + x509.Name( + [ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, "PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, "cryptography CA" + ), + ] + ) + ) + ], + 333, + ), + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ), + x509.OCSPNoCheck(), + x509.SubjectKeyIdentifier, + ], + ) + def test_extensions( + self, rsa_key_2048: rsa.RSAPrivateKey, add_ext, backend + ): + issuer_private_key = rsa_key_2048 + subject_private_key = rsa_key_2048 + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + if add_ext is x509.SubjectKeyIdentifier: + add_ext = x509.SubjectKeyIdentifier.from_public_key( + subject_private_key.public_key() + ) + + # Cert + cert = ( + x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) .public_key(subject_private_key.public_key()) .serial_number(123) - .add_extension( - x509.KeyUsage( - digital_signature=True, - content_commitment=True, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=False, - ), - critical=False, - ) + .add_extension(add_ext, critical=False) .sign(issuer_private_key, hashes.SHA256(), backend) ) - ext = cert.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + ext = cert.extensions.get_extension_for_class(type(add_ext)) + assert ext.critical is False + assert ext.value == add_ext + + # CSR + csr = ( + x509.CertificateSigningRequestBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .add_extension(add_ext, False) + .sign(subject_private_key, hashes.SHA256()) + ) + ext = csr.extensions.get_extension_for_class(type(add_ext)) assert ext.critical is False - assert ext.value == x509.KeyUsage( - digital_signature=True, - content_commitment=True, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=False, - ) - - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_ca_request_with_path_length_none(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + assert ext.value == add_ext + + def test_build_ca_request_with_path_length_none( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( - [x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA")] + [x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True ) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) loaded_request = x509.load_pem_x509_csr( @@ -3311,6 +4145,7 @@ def test_build_ca_request_with_path_length_none(self, backend): basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.path_length is None @pytest.mark.parametrize( @@ -3322,18 +4157,18 @@ def test_build_ca_request_with_path_length_none(self, backend): ) ], ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_unrecognized_extension(self, backend, unrecognized): - private_key = RSA_KEY_2048.private_key(backend) + def test_unrecognized_extension( + self, rsa_key_2048: rsa.RSAPrivateKey, backend, unrecognized + ): + private_key = rsa_key_2048 cert = ( x509.CertificateBuilder() .subject_name( - x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, "US")]) ) .issuer_name( - x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, "US")]) ) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) @@ -3348,27 +4183,28 @@ def test_unrecognized_extension(self, backend, unrecognized): assert ext.value == unrecognized -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCertificateSigningRequestBuilder(object): - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_sign_invalid_hash_algorithm(self, backend): - private_key = RSA_KEY_2048.private_key(backend) +class TestCertificateSigningRequestBuilder: + def test_sign_invalid_hash_algorithm( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([]) ) with pytest.raises(TypeError): - builder.sign(private_key, "NotAHash", backend) + builder.sign( + private_key, "NotAHash", backend # type: ignore[arg-type] + ) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_request_with_unsupported_hash_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): @@ -3378,99 +4214,59 @@ def test_request_with_unsupported_hash_ed25519(self, backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_request_with_unsupported_hash_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5()), - skip_message="Requires OpenSSL with MD5 support", - ) - def test_sign_rsa_with_md5(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - - builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA")]) - ) - request = builder.sign(private_key, hashes.MD5(), backend) - assert isinstance(request.signature_hash_algorithm, hashes.MD5) - - @pytest.mark.requires_backend_interface(interface=DSABackend) - @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5()), - skip_message="Requires OpenSSL with MD5 support", - ) - def test_sign_dsa_with_md5(self, backend): - private_key = DSA_KEY_2048.private_key(backend) - builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA")]) - ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) - - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) - @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5()), - skip_message="Requires OpenSSL with MD5 support", - ) - def test_sign_ec_with_md5(self, backend): - _skip_curve_unsupported(backend, ec.SECP256R1()) - private_key = EC_KEY_SECP256R1.private_key(backend) - builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA")]) - ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) - - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_no_subject_name(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_no_subject_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder() with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_ca_request_with_rsa(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_ca_request_with_rsa( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( - [x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA")] + [x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_ca_request_with_unicode(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_ca_request_with_unicode( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() @@ -3478,7 +4274,7 @@ def test_build_ca_request_with_unicode(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"PyCA\U0001f37a" + NameOID.ORGANIZATION_NAME, "PyCA\U0001f37a" ), ] ) @@ -3486,7 +4282,7 @@ def test_build_ca_request_with_unicode(self, backend): .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) loaded_request = x509.load_pem_x509_csr( @@ -3495,61 +4291,58 @@ def test_build_ca_request_with_unicode(self, backend): subject = loaded_request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA\U0001f37a"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA\U0001f37a"), ] - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_subject_dn_asn1_types(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_subject_dn_asn1_types( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ - x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"value"), - x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"value" - ), - x509.NameAttribute(NameOID.STREET_ADDRESS, u"value"), + x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "value"), x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"value" + NameOID.STATE_OR_PROVINCE_NAME, "value" ), + x509.NameAttribute(NameOID.STREET_ADDRESS, "value"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "value"), x509.NameAttribute( - NameOID.ORGANIZATIONAL_UNIT_NAME, u"value" + NameOID.ORGANIZATIONAL_UNIT_NAME, "value" ), - x509.NameAttribute(NameOID.SERIAL_NUMBER, u"value"), - x509.NameAttribute(NameOID.SURNAME, u"value"), - x509.NameAttribute(NameOID.GIVEN_NAME, u"value"), - x509.NameAttribute(NameOID.TITLE, u"value"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "value"), + x509.NameAttribute(NameOID.SURNAME, "value"), + x509.NameAttribute(NameOID.GIVEN_NAME, "value"), + x509.NameAttribute(NameOID.TITLE, "value"), x509.NameAttribute( - NameOID.GENERATION_QUALIFIER, u"value" + NameOID.GENERATION_QUALIFIER, "value" ), x509.NameAttribute( - NameOID.X500_UNIQUE_IDENTIFIER, u"value" + NameOID.X500_UNIQUE_IDENTIFIER, "value" ), - x509.NameAttribute(NameOID.DN_QUALIFIER, u"value"), - x509.NameAttribute(NameOID.PSEUDONYM, u"value"), - x509.NameAttribute(NameOID.USER_ID, u"value"), - x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"value"), - x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"value"), + x509.NameAttribute(NameOID.DN_QUALIFIER, "value"), + x509.NameAttribute(NameOID.PSEUDONYM, "value"), + x509.NameAttribute(NameOID.USER_ID, "value"), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "value"), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, "value"), x509.NameAttribute( - NameOID.JURISDICTION_COUNTRY_NAME, u"US" + NameOID.JURISDICTION_COUNTRY_NAME, "US" ), x509.NameAttribute( - NameOID.JURISDICTION_LOCALITY_NAME, u"value" + NameOID.JURISDICTION_LOCALITY_NAME, "value" ), x509.NameAttribute( NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, - u"value", + "value", ), - x509.NameAttribute( - NameOID.BUSINESS_CATEGORY, u"value" - ), - x509.NameAttribute(NameOID.POSTAL_ADDRESS, u"value"), - x509.NameAttribute(NameOID.POSTAL_CODE, u"value"), + x509.NameAttribute(NameOID.BUSINESS_CATEGORY, "value"), + x509.NameAttribute(NameOID.POSTAL_ADDRESS, "value"), + x509.NameAttribute(NameOID.POSTAL_CODE, "value"), ] ) ) @@ -3561,20 +4354,21 @@ def test_subject_dn_asn1_types(self, backend): == asn1_type ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_ca_request_with_multivalue_rdns(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_ca_request_with_multivalue_rdns( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 subject = x509.Name( [ x509.RelativeDistinguishedName( [ - x509.NameAttribute(NameOID.TITLE, u"Test"), - x509.NameAttribute(NameOID.COMMON_NAME, u"Multivalue"), - x509.NameAttribute(NameOID.SURNAME, u"RDNs"), + x509.NameAttribute(NameOID.TITLE, "Test"), + x509.NameAttribute(NameOID.COMMON_NAME, "Multivalue"), + x509.NameAttribute(NameOID.SURNAME, "RDNs"), ] ), x509.RelativeDistinguishedName( - [x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA")] + [x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")] ), ] ) @@ -3582,7 +4376,7 @@ def test_build_ca_request_with_multivalue_rdns(self, backend): request = ( x509.CertificateSigningRequestBuilder() .subject_name(subject) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) loaded_request = x509.load_pem_x509_csr( @@ -3591,37 +4385,38 @@ def test_build_ca_request_with_multivalue_rdns(self, backend): assert isinstance(loaded_request.subject, x509.Name) assert loaded_request.subject == subject - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_build_nonca_request_with_rsa(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_nonca_request_with_rsa( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True, ) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_build_ca_request_with_ec(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) @@ -3632,7 +4427,7 @@ def test_build_ca_request_with_ec(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) @@ -3640,20 +4435,21 @@ def test_build_ca_request_with_ec(self, backend): .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @@ -3661,7 +4457,6 @@ def test_build_ca_request_with_ec(self, backend): only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_build_ca_request_with_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() @@ -3671,7 +4466,7 @@ def test_build_ca_request_with_ed25519(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) @@ -3688,10 +4483,10 @@ def test_build_ca_request_with_ed25519(self, backend): subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] - basic_constraints = request.extensions.get_extension_for_oid( - ExtensionOID.BASIC_CONSTRAINTS + basic_constraints = request.extensions.get_extension_for_class( + x509.BasicConstraints ) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @@ -3700,7 +4495,6 @@ def test_build_ca_request_with_ed25519(self, backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_build_ca_request_with_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() @@ -3710,7 +4504,7 @@ def test_build_ca_request_with_ed448(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) @@ -3727,40 +4521,44 @@ def test_build_ca_request_with_ed448(self, backend): subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] - basic_constraints = request.extensions.get_extension_for_oid( - ExtensionOID.BASIC_CONSTRAINTS + basic_constraints = request.extensions.get_extension_for_class( + x509.BasicConstraints ) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 - @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", + ) def test_build_ca_request_with_dsa(self, backend): private_key = DSA_KEY_2048.private_key(backend) request = ( x509.CertificateSigningRequestBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, dsa.DSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @@ -3778,25 +4576,28 @@ def test_add_duplicate_extension(self): def test_set_invalid_subject(self): builder = x509.CertificateSigningRequestBuilder() with pytest.raises(TypeError): - builder.subject_name("NotAName") + builder.subject_name("NotAName") # type:ignore[arg-type] def test_add_invalid_extension_type(self): builder = x509.CertificateSigningRequestBuilder() with pytest.raises(TypeError): - builder.add_extension(object(), False) + builder.add_extension( + object(), # type:ignore[arg-type] + False, + ) - def test_add_unsupported_extension(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_add_unsupported_extension( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder() builder = ( builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ), + x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .add_extension(DummyExtension(), False) @@ -3804,115 +4605,40 @@ def test_add_unsupported_extension(self, backend): with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) - def test_key_usage(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - builder = x509.CertificateSigningRequestBuilder() - request = ( - builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .add_extension( - x509.KeyUsage( - digital_signature=True, - content_commitment=True, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=False, - ), - critical=False, - ) - .sign(private_key, hashes.SHA256(), backend) - ) - assert len(request.extensions) == 1 - ext = request.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) - assert ext.critical is False - assert ext.value == x509.KeyUsage( - digital_signature=True, - content_commitment=True, - key_encipherment=False, - data_encipherment=False, - key_agreement=False, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=False, - ) - - def test_key_usage_key_agreement_bit(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - builder = x509.CertificateSigningRequestBuilder() - request = ( - builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .add_extension( - x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=True, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=True, - ), - critical=False, - ) - .sign(private_key, hashes.SHA256(), backend) - ) - assert len(request.extensions) == 1 - ext = request.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) - assert ext.critical is False - assert ext.value == x509.KeyUsage( - digital_signature=False, - content_commitment=False, - key_encipherment=False, - data_encipherment=False, - key_agreement=True, - key_cert_sign=True, - crl_sign=False, - encipher_only=False, - decipher_only=True, - ) - - def test_add_two_extensions(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_add_two_extensions( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder() request = ( builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( - x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io")] - ), + x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) - .sign(private_key, hashes.SHA1(), backend) + .sign(private_key, hashes.SHA256(), backend) ) - assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) + assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 ext = request.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) - assert list(ext.value) == [x509.DNSName(u"cryptography.io")] + assert isinstance(ext.value, x509.SubjectAlternativeName) + assert list(ext.value) == [x509.DNSName("cryptography.io")] - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_add_attributes(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) @@ -3926,7 +4652,7 @@ def test_add_attributes(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) @@ -3938,34 +4664,65 @@ def test_add_attributes(self, backend): x509.oid.AttributeOID.UNSTRUCTURED_NAME, unstructured_name ) .add_attribute(x509.oid.NameOID.LOCALITY_NAME, locality) + .add_extension( + x509.ExtendedKeyUsage( + [ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ] + ), + False, + ) .sign(private_key, hashes.SHA256(), backend) ) assert ( - request.get_attribute_for_oid( + request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD - ) + ).value == challenge_password ) assert ( - request.get_attribute_for_oid( + request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.UNSTRUCTURED_NAME - ) + ).value == unstructured_name ) assert ( - request.get_attribute_for_oid(x509.oid.NameOID.LOCALITY_NAME) + request.attributes.get_attribute_for_oid( + x509.oid.NameOID.LOCALITY_NAME + ).value == locality ) + assert len(request.attributes) == 4 + + def test_add_attributes_non_utf8(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = ( + x509.CertificateSigningRequestBuilder() + .subject_name(x509.Name([])) + .add_attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"\xbb\xad\x16\x9a\xac\xc9\x03i\xec\xcc\xdd6\xcbh\xfc\xf3", + ) + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) def test_add_attribute_bad_types(self, backend): request = x509.CertificateSigningRequestBuilder() with pytest.raises(TypeError): - request.add_attribute(b"not an oid", b"val") + request.add_attribute( + b"not an oid", # type:ignore[arg-type] + b"val", + ) with pytest.raises(TypeError): request.add_attribute( - x509.oid.AttributeOID.CHALLENGE_PASSWORD, 383 + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + 383, # type:ignore[arg-type] ) def test_duplicate_attribute(self, backend): @@ -3977,87 +4734,154 @@ def test_duplicate_attribute(self, backend): x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"val2" ) + def test_add_attribute_tag(self, backend): + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = ( + x509.CertificateSigningRequestBuilder() + .subject_name(x509.Name([])) + .add_attribute( + x509.ObjectIdentifier("1.2.3.4"), + b"\x00\x00", + _tag=_ASN1Type.GeneralizedTime, + ) + ) + request = builder.sign(private_key, hashes.SHA256(), backend) + attr = request.attributes.get_attribute_for_oid( + x509.ObjectIdentifier("1.2.3.4") + ) + + assert attr.value == b"\x00\x00" + assert attr._type == _ASN1Type.GeneralizedTime.value + + def test_add_attribute_tag_non_int(self, backend): + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([]) + ) + with pytest.raises(TypeError): + builder.add_attribute( + x509.ObjectIdentifier("1.2.3.4"), + b"", + _tag=object(), # type:ignore[arg-type] + ) + def test_set_subject_twice(self): builder = x509.CertificateSigningRequestBuilder() builder = builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) - def test_subject_alt_names(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - - san = x509.SubjectAlternativeName( - [ - x509.DNSName(u"example.com"), - x509.DNSName(u"*.example.com"), - x509.RegisteredID(x509.ObjectIdentifier("1.2.3.4.5.6.7")), - x509.DirectoryName( - x509.Name( - [ - x509.NameAttribute(NameOID.COMMON_NAME, u"PyCA"), - x509.NameAttribute( - NameOID.ORGANIZATION_NAME, - u"We heart UTF8!\u2122", - ), - ] - ) - ), - x509.IPAddress(ipaddress.ip_address(u"127.0.0.1")), - x509.IPAddress(ipaddress.ip_address(u"ff::")), - x509.OtherName( - type_id=x509.ObjectIdentifier("1.2.3.3.3.3"), - value=b"0\x03\x02\x01\x05", - ), - x509.RFC822Name(u"test@example.com"), - x509.RFC822Name(u"email"), - x509.RFC822Name(u"email@xn--eml-vla4c.com"), - x509.UniformResourceIdentifier( - u"https://xn--80ato2c.cryptography" - ), - x509.UniformResourceIdentifier( - u"gopher://cryptography:70/some/path" - ), - ] - ) + @pytest.mark.parametrize( + "add_ext", + [ + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ), + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=True, + ), + x509.SubjectAlternativeName( + [ + x509.DNSName("example.com"), + x509.DNSName("*.example.com"), + x509.RegisteredID(x509.ObjectIdentifier("1.2.3.4.5.6.7")), + x509.DirectoryName( + x509.Name( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, "PyCA" + ), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + "We heart UTF8!\u2122", + ), + ] + ) + ), + x509.IPAddress(ipaddress.ip_address("127.0.0.1")), + x509.IPAddress(ipaddress.ip_address("ff::")), + x509.OtherName( + type_id=x509.ObjectIdentifier("1.2.3.3.3.3"), + value=b"0\x03\x02\x01\x05", + ), + x509.RFC822Name("test@example.com"), + x509.RFC822Name("email"), + x509.RFC822Name("email@xn--eml-vla4c.com"), + x509.UniformResourceIdentifier( + "https://xn--80ato2c.cryptography" + ), + x509.UniformResourceIdentifier( + "gopher://cryptography:70/some/path" + ), + ] + ), + x509.ExtendedKeyUsage( + [ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ] + ), + ], + ) + def test_extensions( + self, rsa_key_2048: rsa.RSAPrivateKey, add_ext, backend + ): + private_key = rsa_key_2048 csr = ( x509.CertificateSigningRequestBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"SAN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "SAN")]) ) .add_extension( - san, + add_ext, critical=False, ) .sign(private_key, hashes.SHA256(), backend) ) assert len(csr.extensions) == 1 - ext = csr.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME - ) + ext = csr.extensions.get_extension_for_class(type(add_ext)) assert not ext.critical - assert ext.oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME - assert ext.value == san + assert ext.value == add_ext - def test_invalid_asn1_othername(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_invalid_asn1_othername( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = ( x509.CertificateSigningRequestBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"SAN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "SAN")]) ) .add_extension( x509.SubjectAlternativeName( [ x509.OtherName( type_id=x509.ObjectIdentifier("1.2.3.3.3.3"), - value=b"\x01\x02\x01\x05", + # Invalid length + value=b"\x01\x05\x01\x05", ), ] ), @@ -4067,13 +4891,15 @@ def test_invalid_asn1_othername(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - def test_subject_alt_name_unsupported_general_name(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_subject_alt_name_unsupported_general_name( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = ( x509.CertificateSigningRequestBuilder() .subject_name( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"SAN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "SAN")]) ) .add_extension( x509.SubjectAlternativeName([FakeGeneralName("")]), @@ -4084,281 +4910,37 @@ def test_subject_alt_name_unsupported_general_name(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - def test_extended_key_usage(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - eku = x509.ExtendedKeyUsage( - [ - ExtendedKeyUsageOID.CLIENT_AUTH, - ExtendedKeyUsageOID.SERVER_AUTH, - ExtendedKeyUsageOID.CODE_SIGNING, - ] - ) - builder = x509.CertificateSigningRequestBuilder() - request = ( - builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .add_extension(eku, critical=False) - .sign(private_key, hashes.SHA256(), backend) - ) - - ext = request.extensions.get_extension_for_oid( - ExtensionOID.EXTENDED_KEY_USAGE - ) - assert ext.critical is False - assert ext.value == eku - - @pytest.mark.requires_backend_interface(interface=RSABackend) - def test_rsa_key_too_small(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_rsa_key_too_small(self, rsa_key_512: rsa.RSAPrivateKey, backend): + private_key = rsa_key_512 builder = x509.CertificateSigningRequestBuilder() builder = builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_aia(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) - - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - - aia = x509.AuthorityInformationAccess( - [ - x509.AccessDescription( - AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), - ), - x509.AccessDescription( - AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), - ), - ] - ) - - builder = ( - x509.CertificateBuilder() - .serial_number(777) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .public_key(subject_private_key.public_key()) - .add_extension(aia, critical=False) - .not_valid_before(not_valid_before) - .not_valid_after(not_valid_after) - ) - - cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) - - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.AUTHORITY_INFORMATION_ACCESS - ) - assert ext.value == aia - - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_sia(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) - - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - - sia = x509.SubjectInformationAccess( - [ - x509.AccessDescription( - SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), - ), - ] - ) - - builder = ( - x509.CertificateBuilder() - .serial_number(777) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .public_key(subject_private_key.public_key()) - .add_extension(sia, critical=False) - .not_valid_before(not_valid_before) - .not_valid_after(not_valid_after) - ) - - cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) - - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_INFORMATION_ACCESS - ) - assert ext.value == sia - - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_ski(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) - - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - - ski = x509.SubjectKeyIdentifier.from_public_key( - subject_private_key.public_key() - ) - - builder = ( - x509.CertificateBuilder() - .serial_number(777) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .public_key(subject_private_key.public_key()) - .add_extension(ski, critical=False) - .not_valid_before(not_valid_before) - .not_valid_after(not_valid_after) - ) - - cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_KEY_IDENTIFIER - ) - assert ext.value == ski - - @pytest.mark.parametrize( - "aki", - [ - x509.AuthorityKeyIdentifier( - b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" - b"\xcbY", - None, - None, - ), - x509.AuthorityKeyIdentifier( - b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" - b"\xcbY", - [ - x509.DirectoryName( - x509.Name( - [ - x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"PyCA" - ), - x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography CA" - ), - ] - ) - ) - ], - 333, - ), - x509.AuthorityKeyIdentifier( - None, - [ - x509.DirectoryName( - x509.Name( - [ - x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"PyCA" - ), - x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography CA" - ), - ] - ) - ) - ], - 333, - ), - ], +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", +) +class TestDSACertificate: + @pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported( + hashes.SHA1() + ), + skip_message="Does not support SHA-1 signature.", ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_build_cert_with_aki(self, aki, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) - - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - - builder = ( - x509.CertificateBuilder() - .serial_number(777) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .public_key(subject_private_key.public_key()) - .add_extension(aki, critical=False) - .not_valid_before(not_valid_before) - .not_valid_after(not_valid_after) - ) - - cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) - - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.AUTHORITY_KEY_IDENTIFIER - ) - assert ext.value == aki - - def test_ocsp_nocheck(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) - - not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) - not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) - - builder = ( - x509.CertificateBuilder() - .serial_number(777) - .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) - ) - .public_key(subject_private_key.public_key()) - .add_extension(x509.OCSPNoCheck(), critical=False) - .not_valid_before(not_valid_before) - .not_valid_after(not_valid_after) - ) - - cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) - - ext = cert.extensions.get_extension_for_oid(ExtensionOID.OCSP_NO_CHECK) - assert isinstance(ext.value, x509.OCSPNoCheck) - - -@pytest.mark.requires_backend_interface(interface=DSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestDSACertificate(object): def test_load_dsa_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, - backend, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) public_key = cert.public_key() assert isinstance(public_key, dsa.DSAPublicKey) + assert cert.signature_algorithm_parameters is None num = public_key.public_numbers() assert num.y == int( "4c08bfe5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa94" @@ -4404,7 +4986,6 @@ def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.signature == binascii.unhexlify( b"302c021425c4a84a936ab311ee017d3cbd9a3c650bb3ae4a02145d30c64b4326" @@ -4418,7 +4999,6 @@ def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"3082051aa003020102020900a37352e0b2142f86300906072a8648ce3804033" @@ -4464,16 +5044,43 @@ def test_tbs_certificate_bytes(self, backend): b"4c7464311430120603550403130b5079434120445341204341820900a37352e" b"0b2142f86300c0603551d13040530030101ff" ) - cert.public_key().verify( + assert cert.signature_hash_algorithm is not None + public_key = cert.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + public_key.verify( cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm, ) + def test_verify_directly_issued_by_dsa(self, backend): + issuer_private_key = DSA_KEY_3072.private_key() + subject_private_key = DSA_KEY_2048.private_key() + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert.verify_directly_issued_by(ca) + + def test_verify_directly_issued_by_dsa_bad_sig(self, backend): + issuer_private_key = DSA_KEY_3072.private_key() + subject_private_key = DSA_KEY_2048.private_key() + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert_bad_sig = _break_cert_sig(cert) + with pytest.raises(InvalidSignature): + cert_bad_sig.verify_directly_issued_by(ca) + -@pytest.mark.requires_backend_interface(interface=DSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestDSACertificateRequest(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Does not support DSA.", +) +@pytest.mark.supported( + only_if=lambda backend: backend.signature_hash_supported(hashes.SHA1()), + skip_message="Does not support SHA-1 signature.", +) +class TestDSACertificateRequest: @pytest.mark.parametrize( ("path", "loader_func"), [ @@ -4488,25 +5095,24 @@ class TestDSACertificateRequest(object): ], ) def test_load_dsa_request(self, path, loader_func, backend): - request = _load_cert(path, loader_func, backend) + request = _load_cert(path, loader_func) assert isinstance(request.signature_hash_algorithm, hashes.SHA1) public_key = request.public_key() assert isinstance(public_key, dsa.DSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"), + x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), ] def test_signature(self, backend): request = _load_cert( os.path.join("x509", "requests", "dsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) assert request.signature == binascii.unhexlify( b"302c021461d58dc028d0110818a7d817d74235727c4acfdf0214097b52e198e" @@ -4517,7 +5123,6 @@ def test_tbs_certrequest_bytes(self, backend): request = _load_cert( os.path.join("x509", "requests", "dsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) assert request.tbs_certrequest_bytes == binascii.unhexlify( b"3082021802010030573118301606035504030c0f63727970746f677261706879" @@ -4538,44 +5143,129 @@ def test_tbs_certrequest_bytes(self, backend): b"04a697bc8fd965b952f9f7e850edf13c8acdb5d753b6d10e59e0b5732e3c82ba" b"fa140342bc4a3bba16bd0681c8a6a2dbbb7efe6ce2b8463b170ba000" ) - request.public_key().verify( + assert request.signature_hash_algorithm is not None + public_key = request.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + public_key.verify( request.signature, request.tbs_certrequest_bytes, request.signature_hash_algorithm, ) - -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestECDSACertificate(object): - def test_load_ecdsa_cert(self, backend): - _skip_curve_unsupported(backend, ec.SECP384R1()) + +class TestGOSTCertificate: + def test_numeric_string_x509_name_entry(self): + cert = _load_cert( + os.path.join("x509", "e-trust.ru.der"), + x509.load_der_x509_certificate, + ) + assert ( + cert.subject.get_attributes_for_oid( + x509.ObjectIdentifier("1.2.643.3.131.1.1") + )[0].value + == "007710474375" + ) + + +class TestECDSACertificate: + def test_load_ecdsa_cert(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) + public_key = cert.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + num = public_key.public_numbers() + assert num.x == int( + "dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34eadec69bbcd095f" + "6f0ccd00bba615b51467e9e2d9fee8e630c17", + 16, + ) + assert num.y == int( + "ec0770f5cf842e40839ce83f416d3badd3a4145936789d0343ee10136c7" + "2deae88a7a16bb543ce67dc23ff031ca3e23e", + 16, + ) + assert isinstance(num.curve, ec.SECP384R1) + assert isinstance(cert.signature_algorithm_parameters, ec.ECDSA) + assert isinstance( + cert.signature_algorithm_parameters.algorithm, hashes.SHA384 + ) + public_key.verify( + cert.signature, + cert.tbs_certificate_bytes, + cert.signature_algorithm_parameters, + ) + + def test_load_ecdsa_cert_null_alg_params(self, backend): + """ + This test verifies that we successfully load certificates with encoded + null parameters in the signature AlgorithmIdentifier. This is invalid, + but Java 11 (up to at least 11.0.19) generates certificates with this + encoding so we need to tolerate it at the moment. + """ + with pytest.warns(utils.DeprecatedIn41): + cert = _load_cert( + os.path.join("x509", "custom", "ecdsa_null_alg.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + assert isinstance(cert.public_key(), ec.EllipticCurvePublicKey) + + def test_load_bitstring_dn(self, backend): + cert = _load_cert( + os.path.join("x509", "scottishpower-bitstring-dn.pem"), + x509.load_pem_x509_certificate, + ) + assert cert.subject == x509.Name( + [ + x509.NameAttribute(x509.NameOID.COMMON_NAME, "ScottishPower"), + x509.NameAttribute( + x509.NameOID.ORGANIZATIONAL_UNIT_NAME, "02" + ), + x509.NameAttribute( + NameOID.X500_UNIQUE_IDENTIFIER, + b"\x00\x70\xb3\xd5\x1f\x30\x5f\x00\x01", + _ASN1Type.BitString, + ), + ] + ) + assert repr(cert.subject) == ( + "" + ) + + def test_load_name_attribute_long_form_asn1_tag(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "long-form-name-attribute.pem"), + x509.load_pem_x509_certificate, + ) + with pytest.raises(ValueError, match="Long-form"): + cert.subject + with pytest.raises(ValueError, match="Long-form"): + cert.issuer + + def test_ms_certificate_template(self, backend): cert = _load_cert( - os.path.join("x509", "ecdsa_root.pem"), + os.path.join("x509", "custom", "ms-certificate-template.pem"), x509.load_pem_x509_certificate, - backend, ) - assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) - public_key = cert.public_key() - assert isinstance(public_key, ec.EllipticCurvePublicKey) - num = public_key.public_numbers() - assert num.x == int( - "dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34eadec69bbcd095f" - "6f0ccd00bba615b51467e9e2d9fee8e630c17", - 16, + ext = cert.extensions.get_extension_for_class( + x509.MSCertificateTemplate ) - assert num.y == int( - "ec0770f5cf842e40839ce83f416d3badd3a4145936789d0343ee10136c7" - "2deae88a7a16bb543ce67dc23ff031ca3e23e", - 16, + tpl = ext.value + assert isinstance(tpl, x509.MSCertificateTemplate) + assert tpl == x509.MSCertificateTemplate( + template_id=x509.ObjectIdentifier("1.2.3.4.5.6.7.8.9.0"), + major_version=1, + minor_version=None, ) - assert isinstance(num.curve, ec.SECP384R1) def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.signature == binascii.unhexlify( b"3065023100adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085" @@ -4600,7 +5290,6 @@ def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"308201c5a0030201020210055556bcf25ea43535c3a40fd5ab4572300a06082" @@ -4619,7 +5308,10 @@ def test_tbs_certificate_bytes(self, backend): b"f300e0603551d0f0101ff040403020186301d0603551d0e04160414b3db48a4" b"f9a1c5d8ae3641cc1163696229bc4bc6" ) - cert.public_key().verify( + assert cert.signature_hash_algorithm is not None + public_key = cert.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + public_key.verify( cert.signature, cert.tbs_certificate_bytes, ec.ECDSA(cert.signature_hash_algorithm), @@ -4630,15 +5322,33 @@ def test_load_ecdsa_no_named_curve(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ec_no_named_curve.pem"), x509.load_pem_x509_certificate, - backend, ) - with pytest.raises(NotImplementedError): + # This test can trigger three different value errors depending + # on OpenSSL/BoringSSL and versions. Match on the text to ensure + # we are getting the right error. + with pytest.raises(ValueError, match="explicit parameters"): cert.public_key() + def test_verify_directly_issued_by_ec(self): + issuer_private_key = ec.generate_private_key(ec.SECP256R1()) + subject_private_key = ec.generate_private_key(ec.SECP256R1()) + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert.verify_directly_issued_by(ca) + + def test_verify_directly_issued_by_ec_bad_sig(self): + issuer_private_key = ec.generate_private_key(ec.SECP256R1()) + subject_private_key = ec.generate_private_key(ec.SECP256R1()) + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert_bad_sig = _break_cert_sig(cert) + with pytest.raises(InvalidSignature): + cert_bad_sig.verify_directly_issued_by(ca) + -@pytest.mark.requires_backend_interface(interface=X509Backend) -@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) -class TestECDSACertificateRequest(object): +class TestECDSACertificateRequest: @pytest.mark.parametrize( ("path", "loader_func"), [ @@ -4654,18 +5364,18 @@ class TestECDSACertificateRequest(object): ) def test_load_ecdsa_certificate_request(self, path, loader_func, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) - request = _load_cert(path, loader_func, backend) + request = _load_cert(path, loader_func) assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ - x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"), + x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), ] def test_signature(self, backend): @@ -4673,7 +5383,6 @@ def test_signature(self, backend): request = _load_cert( os.path.join("x509", "requests", "ec_sha256.pem"), x509.load_pem_x509_csr, - backend, ) assert request.signature == binascii.unhexlify( b"306502302c1a9f7de8c1787332d2307a886b476a59f172b9b0e250262f3238b1" @@ -4687,7 +5396,6 @@ def test_tbs_certrequest_bytes(self, backend): request = _load_cert( os.path.join("x509", "requests", "ec_sha256.pem"), x509.load_pem_x509_csr, - backend, ) assert request.tbs_certrequest_bytes == binascii.unhexlify( b"3081d602010030573118301606035504030c0f63727970746f6772617068792" @@ -4698,39 +5406,37 @@ def test_tbs_certrequest_bytes(self, backend): b"04d8b32a551038d09086803a6d3fb91a1a1167ec02158b00efad39c9396462f" b"accff0ffaf7155812909d3726bd59fde001cff4bb9b2f5af8cbaa000" ) - request.public_key().verify( + assert request.signature_hash_algorithm is not None + public_key = request.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + public_key.verify( request.signature, request.tbs_certrequest_bytes, ec.ECDSA(request.signature_hash_algorithm), ) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestOtherCertificate(object): +class TestOtherCertificate: def test_unsupported_subject_public_key_info(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "unsupported_subject_public_key_info.pem" ), x509.load_pem_x509_certificate, - backend, ) with pytest.raises(ValueError): cert.public_key() def test_bad_time_in_validity(self, backend): - cert = _load_cert( - os.path.join("x509", "badasn1time.pem"), - x509.load_pem_x509_certificate, - backend, - ) - - with pytest.raises(ValueError, match="19020701025736Z"): - cert.not_valid_after + with pytest.raises(ValueError, match="Validity::not_after"): + _load_cert( + os.path.join("x509", "badasn1time.pem"), + x509.load_pem_x509_certificate, + ) -class TestNameAttribute(object): +class TestNameAttribute: EXPECTED_TYPES = [ (NameOID.COMMON_NAME, _ASN1Type.UTF8String), (NameOID.COUNTRY_NAME, _ASN1Type.PrintableString), @@ -4760,105 +5466,149 @@ class TestNameAttribute(object): def test_default_types(self): for oid, asn1_type in TestNameAttribute.EXPECTED_TYPES: - na = x509.NameAttribute(oid, u"US") + na = x509.NameAttribute(oid, "US") assert na._type == asn1_type def test_alternate_type(self): na2 = x509.NameAttribute( - NameOID.COMMON_NAME, u"common", _ASN1Type.IA5String + NameOID.COMMON_NAME, "common", _ASN1Type.IA5String ) assert na2._type == _ASN1Type.IA5String def test_init_bad_oid(self): with pytest.raises(TypeError): - x509.NameAttribute(None, u"value") + x509.NameAttribute( + None, # type:ignore[arg-type] + "value", + ) def test_init_bad_value(self): with pytest.raises(TypeError): - x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), b"bytes") + x509.NameAttribute( + x509.ObjectIdentifier("2.999.1"), + b"bytes", + ) + + def test_init_bitstring_not_bytes(self): + with pytest.raises(TypeError): + x509.NameAttribute( + x509.ObjectIdentifier("2.5.4.45"), "str", _ASN1Type.BitString + ) + + def test_init_bitstring_not_allowed_random_oid(self): + # We only allow BitString type with X500_UNIQUE_IDENTIFIER + with pytest.raises(TypeError): + x509.NameAttribute( + x509.NameOID.COMMON_NAME, b"ok", _ASN1Type.BitString + ) def test_init_none_value(self): with pytest.raises(TypeError): - x509.NameAttribute(NameOID.ORGANIZATION_NAME, None) + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, None # type:ignore[arg-type] + ) def test_init_bad_country_code_value(self): with pytest.raises(ValueError): - x509.NameAttribute(NameOID.COUNTRY_NAME, u"United States") + x509.NameAttribute(NameOID.COUNTRY_NAME, "United States") # unicode string of length 2, but > 2 bytes with pytest.raises(ValueError): - x509.NameAttribute(NameOID.COUNTRY_NAME, u"\U0001F37A\U0001F37A") + x509.NameAttribute(NameOID.COUNTRY_NAME, "\U0001F37A\U0001F37A") def test_invalid_type(self): with pytest.raises(TypeError): - x509.NameAttribute(NameOID.COMMON_NAME, u"common", "notanenum") + x509.NameAttribute( + NameOID.COMMON_NAME, + "common", + "notanenum", # type:ignore[arg-type] + ) def test_eq(self): assert x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value" - ) == x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value") + x509.ObjectIdentifier("2.999.1"), "value" + ) == x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value") def test_ne(self): assert x509.NameAttribute( - x509.ObjectIdentifier("2.5.4.3"), u"value" - ) != x509.NameAttribute(x509.ObjectIdentifier("2.5.4.5"), u"value") + x509.ObjectIdentifier("2.5.4.3"), "value" + ) != x509.NameAttribute(x509.ObjectIdentifier("2.5.4.5"), "value") assert x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value" - ) != x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value2") + x509.ObjectIdentifier("2.999.1"), "value" + ) != x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value2") assert ( - x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), u"value") + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value") != object() ) def test_repr(self): - na = x509.NameAttribute(x509.ObjectIdentifier("2.5.4.3"), u"value") - if not six.PY2: - assert repr(na) == ( - ", value='value')>" - ) - else: - assert repr(na) == ( - ", value=u'value')>" - ) + na = x509.NameAttribute(x509.ObjectIdentifier("2.5.4.3"), "value") + assert repr(na) == ( + ", value='value')>" + ) - def test_distinugished_name(self): + def test_distinguished_name(self): # Escaping - na = x509.NameAttribute(NameOID.COMMON_NAME, u'James "Jim" Smith, III') + na = x509.NameAttribute(NameOID.COMMON_NAME, 'James "Jim" Smith, III') assert na.rfc4514_string() == r"CN=James \"Jim\" Smith\, III" - na = x509.NameAttribute(NameOID.USER_ID, u"# escape+,;\0this ") + na = x509.NameAttribute(NameOID.USER_ID, "# escape+,;\0this ") assert na.rfc4514_string() == r"UID=\# escape\+\,\;\00this\ " # Nonstandard attribute OID - na = x509.NameAttribute(NameOID.EMAIL_ADDRESS, u"somebody@example.com") - assert ( - na.rfc4514_string() == "1.2.840.113549.1.9.1=somebody@example.com" + na = x509.NameAttribute(NameOID.BUSINESS_CATEGORY, "banking") + assert na.rfc4514_string() == "2.5.4.15=banking" + + # non-utf8 attribute (bitstring with raw bytes) + na = x509.NameAttribute( + x509.ObjectIdentifier("2.5.4.45"), + b"\x01\x02\x03\x04", + _ASN1Type.BitString, ) + assert na.rfc4514_string() == "2.5.4.45=#01020304" + + def test_distinguished_name_custom_attrs(self): + name = x509.Name( + [ + x509.NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"), + x509.NameAttribute(NameOID.COMMON_NAME, "Santa Claus"), + ] + ) + assert name.rfc4514_string({}) == ( + "CN=Santa Claus,1.2.840.113549.1.9.1=santa@north.pole" + ) + assert name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"}) == ( + "CN=Santa Claus,E=santa@north.pole" + ) + assert name.rfc4514_string( + {NameOID.COMMON_NAME: "CommonName", NameOID.EMAIL_ADDRESS: "E"} + ) == ("CommonName=Santa Claus,E=santa@north.pole") def test_empty_value(self): - na = x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"") + na = x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "") assert na.rfc4514_string() == r"ST=" -class TestRelativeDistinguishedName(object): +class TestRelativeDistinguishedName: def test_init_empty(self): with pytest.raises(ValueError): x509.RelativeDistinguishedName([]) def test_init_not_nameattribute(self): with pytest.raises(TypeError): - x509.RelativeDistinguishedName(["not-a-NameAttribute"]) + x509.RelativeDistinguishedName( + ["not-a-NameAttribute"] # type:ignore[list-item] + ) def test_init_duplicate_attribute(self): with pytest.raises(ValueError): x509.RelativeDistinguishedName( [ x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"val1" + x509.ObjectIdentifier("2.999.1"), "val1" ), x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"val1" + x509.ObjectIdentifier("2.999.1"), "val1" ), ] ) @@ -4866,32 +5616,20 @@ def test_init_duplicate_attribute(self): def test_hash(self): rdn1 = x509.RelativeDistinguishedName( [ - x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" - ), - x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value2" - ), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), ] ) rdn2 = x509.RelativeDistinguishedName( [ - x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value2" - ), - x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" - ), + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), ] ) rdn3 = x509.RelativeDistinguishedName( [ - x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" - ), - x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value3" - ), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value3"), ] ) assert hash(rdn1) == hash(rdn2) @@ -4900,22 +5638,14 @@ def test_hash(self): def test_eq(self): rdn1 = x509.RelativeDistinguishedName( [ - x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" - ), - x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value2" - ), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), ] ) rdn2 = x509.RelativeDistinguishedName( [ - x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value2" - ), - x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" - ), + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), ] ) assert rdn1 == rdn2 @@ -4923,22 +5653,14 @@ def test_eq(self): def test_ne(self): rdn1 = x509.RelativeDistinguishedName( [ - x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" - ), - x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value2" - ), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), ] ) rdn2 = x509.RelativeDistinguishedName( [ - x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" - ), - x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value3" - ), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value3"), ] ) assert rdn1 != rdn2 @@ -4947,9 +5669,9 @@ def test_ne(self): def test_iter_input(self): # Order must be preserved too attrs = [ - x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1"), - x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value2"), - x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value3"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value2"), + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value3"), ] rdn = x509.RelativeDistinguishedName(iter(attrs)) assert list(rdn) == attrs @@ -4957,13 +5679,13 @@ def test_iter_input(self): def test_get_attributes_for_oid(self): oid = x509.ObjectIdentifier("2.999.1") - attr = x509.NameAttribute(oid, u"value1") + attr = x509.NameAttribute(oid, "value1") rdn = x509.RelativeDistinguishedName([attr]) assert rdn.get_attributes_for_oid(oid) == [attr] assert rdn.get_attributes_for_oid(x509.ObjectIdentifier("1.2.3")) == [] -class TestObjectIdentifier(object): +class TestObjectIdentifier: def test_eq(self): oid1 = x509.ObjectIdentifier("2.999.1") oid2 = x509.ObjectIdentifier("2.999.1") @@ -4974,6 +5696,12 @@ def test_ne(self): assert oid1 != x509.ObjectIdentifier("2.999.2") assert oid1 != object() + def test_comparison(self): + oid1 = x509.ObjectIdentifier("2.999.1") + oid2 = x509.ObjectIdentifier("2.999.2") + with pytest.raises(TypeError): + oid1 < oid2 # type: ignore[operator] + def test_repr(self): oid = x509.ObjectIdentifier("2.5.4.3") assert repr(oid) == "" @@ -5007,13 +5735,16 @@ def test_valid(self): x509.ObjectIdentifier("1.39.999") x509.ObjectIdentifier("2.5.29.3") x509.ObjectIdentifier("2.999.37.5.22.8") - x509.ObjectIdentifier("2.25.305821105408246119474742976030998643995") + + def test_oid_arc_too_large(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier(f"2.25.{2**128 - 1}") -class TestName(object): +class TestName: def test_eq(self): - ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1") - ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), u"value2") + ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") + ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([ava1, ava2]) name2 = x509.Name( [ @@ -5027,8 +5758,8 @@ def test_eq(self): assert name3 == name4 def test_ne(self): - ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1") - ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), u"value2") + ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") + ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([ava1, ava2]) name2 = x509.Name([ava2, ava1]) name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) @@ -5037,8 +5768,8 @@ def test_ne(self): assert name1 != object() def test_hash(self): - ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1") - ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), u"value2") + ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") + ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([ava1, ava2]) name2 = x509.Name( [ @@ -5056,15 +5787,15 @@ def test_hash(self): def test_iter_input(self): attrs = [ - x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1") + x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") ] name = x509.Name(iter(attrs)) assert list(name) == attrs assert list(name) == attrs def test_rdns(self): - rdn1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1") - rdn2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), u"value2") + rdn1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") + rdn2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([rdn1, rdn2]) assert name1.rdns == [ x509.RelativeDistinguishedName([rdn1]), @@ -5077,13 +5808,13 @@ def test_rdns(self): ("common_name", "org_name", "expected_repr"), [ ( - u"cryptography.io", - u"PyCA", + "cryptography.io", + "PyCA", "", ), ( - u"Certificación", - u"Certificación", + "Certificación", + "Certificación", "", ), ], @@ -5098,21 +5829,27 @@ def test_repr(self, common_name, org_name, expected_repr): assert repr(name) == expected_repr + def test_rfc4514_attribute_name(self): + a = x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io") + assert a.rfc4514_attribute_name == "CN" + b = x509.NameAttribute(NameOID.PSEUDONYM, "cryptography.io") + assert b.rfc4514_attribute_name == "2.5.4.65" + def test_rfc4514_string(self): n = x509.Name( [ x509.RelativeDistinguishedName( - [x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"net")] + [x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "net")] ), x509.RelativeDistinguishedName( - [x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u"example")] + [x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "example")] ), x509.RelativeDistinguishedName( [ x509.NameAttribute( - NameOID.ORGANIZATIONAL_UNIT_NAME, u"Sales" + NameOID.ORGANIZATIONAL_UNIT_NAME, "Sales" ), - x509.NameAttribute(NameOID.COMMON_NAME, u"J. Smith"), + x509.NameAttribute(NameOID.COMMON_NAME, "J. Smith"), ] ), ] @@ -5122,25 +5859,24 @@ def test_rfc4514_string(self): def test_rfc4514_string_empty_values(self): n = x509.Name( [ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u""), - x509.NameAttribute(NameOID.LOCALITY_NAME, u""), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), - x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, ""), + x509.NameAttribute(NameOID.LOCALITY_NAME, ""), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), + x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), ] ) assert n.rfc4514_string() == "CN=cryptography.io,O=PyCA,L=,ST=,C=US" def test_not_nameattribute(self): with pytest.raises(TypeError): - x509.Name(["not-a-NameAttribute"]) + x509.Name(["not-a-NameAttribute"]) # type: ignore[list-item] - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_bytes(self, backend): name = x509.Name( [ - x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) assert name.public_bytes(backend) == binascii.unhexlify( @@ -5148,7 +5884,22 @@ def test_bytes(self, backend): b"b060355040a0c0450794341" ) - @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_bitstring_encoding(self): + name = x509.Name( + [ + x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), + x509.NameAttribute( + x509.ObjectIdentifier("2.5.4.45"), + b"\x01\x02", + _ASN1Type.BitString, + ), + ] + ) + assert name.public_bytes() == binascii.unhexlify( + b"30273118301606035504030c0f63727970746f6772617068792e696f310b3" + b"009060355042d03020102" + ) + def test_bmpstring_bytes(self, backend): # For this test we need an odd length string. BMPString is UCS-2 # encoded so it will always be even length and OpenSSL will error if @@ -5157,10 +5908,10 @@ def test_bmpstring_bytes(self, backend): [ x509.NameAttribute( NameOID.COMMON_NAME, - u"cryptography.io", + "cryptography.io", _ASN1Type.BMPString, ), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) assert name.public_bytes(backend) == binascii.unhexlify( @@ -5168,17 +5919,16 @@ def test_bmpstring_bytes(self, backend): b"7000680079002e0069006f310d300b060355040a0c0450794341" ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_universalstring_bytes(self, backend): # UniversalString is UCS-4 name = x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, - u"cryptography.io", + "cryptography.io", _ASN1Type.UniversalString, ), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) assert name.public_bytes(backend) == binascii.unhexlify( @@ -5192,75 +5942,117 @@ def test_universalstring_bytes(self, backend): only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestEd25519Certificate(object): +class TestEd25519Certificate: def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "ed25519", "root-ed25519.pem"), x509.load_pem_x509_certificate, - backend, ) # self-signed, so this will work - cert.public_key().verify(cert.signature, cert.tbs_certificate_bytes) + public_key = cert.public_key() + assert isinstance(public_key, ed25519.Ed25519PublicKey) + public_key.verify(cert.signature, cert.tbs_certificate_bytes) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 9579446940964433301 assert cert.signature_hash_algorithm is None assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 + assert cert.signature_algorithm_parameters is None def test_deepcopy(self, backend): cert = _load_cert( os.path.join("x509", "ed25519", "root-ed25519.pem"), x509.load_pem_x509_certificate, - backend, ) assert copy.deepcopy(cert) is cert + def test_verify_directly_issued_by_ed25519(self, backend): + issuer_private_key = ed25519.Ed25519PrivateKey.generate() + subject_private_key = ed25519.Ed25519PrivateKey.generate() + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert.verify_directly_issued_by(ca) + + def test_verify_directly_issued_by_ed25519_bad_sig(self, backend): + issuer_private_key = ed25519.Ed25519PrivateKey.generate() + subject_private_key = ed25519.Ed25519PrivateKey.generate() + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert_bad_sig = _break_cert_sig(cert) + with pytest.raises(InvalidSignature): + cert_bad_sig.verify_directly_issued_by(ca) + @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestEd448Certificate(object): +class TestEd448Certificate: def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "ed448", "root-ed448.pem"), x509.load_pem_x509_certificate, - backend, ) # self-signed, so this will work - cert.public_key().verify(cert.signature, cert.tbs_certificate_bytes) + public_key = cert.public_key() + assert isinstance(public_key, ed448.Ed448PublicKey) + public_key.verify(cert.signature, cert.tbs_certificate_bytes) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 448 assert cert.signature_hash_algorithm is None assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED448 + assert cert.signature_algorithm_parameters is None + + def test_verify_directly_issued_by_ed448(self, backend): + issuer_private_key = ed448.Ed448PrivateKey.generate() + subject_private_key = ed448.Ed448PrivateKey.generate() + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert.verify_directly_issued_by(ca) + + def test_verify_directly_issued_by_ed448_bad_sig(self, backend): + issuer_private_key = ed448.Ed448PrivateKey.generate() + subject_private_key = ed448.Ed448PrivateKey.generate() + ca, cert = _generate_ca_and_leaf( + issuer_private_key, subject_private_key + ) + cert_bad_sig = _break_cert_sig(cert) + with pytest.raises(InvalidSignature): + cert_bad_sig.verify_directly_issued_by(ca) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestSignatureRejection(object): +@pytest.mark.supported( + only_if=lambda backend: backend.dh_supported(), + skip_message="DH not supported", +) +class TestSignatureRejection: """Test if signing rejects DH keys properly.""" def load_key(self, backend): - data = load_vectors_from_file( - os.path.join("asymmetric", "DH", "dhkey.pem"), - lambda pemfile: pemfile.read(), - mode="rb", - ) - return serialization.load_pem_private_key(data, None, backend) - - def test_crt_signing_check(self, backend): + vector = load_vectors_from_file( + os.path.join("asymmetric", "DH", "rfc3526.txt"), + load_nist_vectors, + )[1] + p = int.from_bytes(binascii.unhexlify(vector["p"]), "big") + params = dh.DHParameterNumbers(p, int(vector["g"])) + param = params.parameters(backend) + return param.generate_private_key() + + def test_crt_signing_check(self, rsa_key_2048: rsa.RSAPrivateKey, backend): issuer_private_key = self.load_key(backend) - public_key = RSA_KEY_2048.private_key(backend).public_key() + public_key = rsa_key_2048.public_key() not_valid_before = datetime.datetime(2020, 1, 1, 1, 1) not_valid_after = datetime.datetime(2050, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(public_key) .not_valid_before(not_valid_before) @@ -5273,7 +6065,7 @@ def test_crt_signing_check(self, backend): def test_csr_signing_check(self, backend): private_key = self.load_key(backend) builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(TypeError): @@ -5286,7 +6078,7 @@ def test_crl_signing_check(self, backend): builder = ( x509.CertificateRevocationListBuilder() .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"CA")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "CA")]) ) .last_update(last_time) .next_update(next_time) @@ -5307,5 +6099,295 @@ def notrandom(size): serial_number = x509.random_serial_number() - assert serial_number == utils.int_from_bytes(sample_data, "big") >> 1 + assert serial_number == int.from_bytes(sample_data, "big") >> 1 assert serial_number.bit_length() < 160 + + +class TestAttribute: + def test_eq(self): + attr1 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + ) + attr2 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + ) + assert attr1 == attr2 + + def test_ne(self): + attr1 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + ) + attr2 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + _ASN1Type.IA5String.value, + ) + attr3 = x509.Attribute( + x509.oid.AttributeOID.UNSTRUCTURED_NAME, + b"value", + ) + attr4 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"other value", + ) + assert attr1 != attr2 + assert attr1 != attr3 + assert attr1 != attr4 + assert attr1 != object() + + def test_repr(self): + attr1 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + ) + assert repr(attr1) == ( + ", value=b'value')>" + ) + + def test_hash(self): + attr1 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + _ASN1Type.UTF8String.value, + ) + attr2 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + _ASN1Type.UTF8String.value, + ) + attr3 = x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"value", + _ASN1Type.IA5String.value, + ) + assert hash(attr1) == hash(attr2) + assert hash(attr1) != hash(attr3) + + +class TestAttributes: + def test_no_attributes(self): + attrs = x509.Attributes([]) + assert len(attrs) == 0 + + def test_get_attribute_for_oid(self): + attr_list = [ + x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"nonsense", + ), + x509.Attribute( + x509.oid.AttributeOID.UNSTRUCTURED_NAME, + b"montessori", + _ASN1Type.PrintableString.value, + ), + ] + attrs = x509.Attributes(attr_list) + attr = attrs.get_attribute_for_oid( + x509.oid.AttributeOID.UNSTRUCTURED_NAME + ) + assert attr.oid == x509.oid.AttributeOID.UNSTRUCTURED_NAME + assert attr.value == b"montessori" + assert attr._type == _ASN1Type.PrintableString.value + + def test_indexing(self): + attr_list = [ + x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"nonsense", + ), + x509.Attribute( + x509.oid.AttributeOID.UNSTRUCTURED_NAME, + b"montessori", + ), + x509.Attribute( + x509.ObjectIdentifier("2.999.2"), + b"meaningless", + ), + x509.Attribute( + x509.ObjectIdentifier("2.999.1"), + b"meaningless", + ), + ] + attrs = x509.Attributes(attr_list) + assert len(attrs) == 4 + assert list(attrs) == attr_list + assert attrs[-1] == attrs[3] + assert attrs[0:3:2] == [attrs[0], attrs[2]] + + def test_get_attribute_not_found(self): + attrs = x509.Attributes([]) + with pytest.raises(x509.AttributeNotFound) as exc: + attrs.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + assert exc.value.oid == x509.oid.AttributeOID.CHALLENGE_PASSWORD + + def test_repr(self): + attrs = x509.Attributes( + [ + x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"nonsense", + ), + ] + ) + assert repr(attrs) == ( + ", value=b'nonsense')>])>" + ) + + +class TestRequestAttributes: + def test_get_attribute_for_oid_challenge(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "challenge.pem"), + x509.load_pem_x509_csr, + ) + with pytest.warns(utils.DeprecatedIn36): + assert ( + request.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + == b"challenge me!" + ) + + assert request.attributes.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) == x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"challenge me!", + ) + + def test_get_attribute_for_oid_multiple(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "challenge-unstructured.pem"), + x509.load_pem_x509_csr, + ) + with pytest.warns(utils.DeprecatedIn36): + assert ( + request.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + == b"beauty" + ) + + with pytest.warns(utils.DeprecatedIn36): + assert ( + request.get_attribute_for_oid( + x509.oid.AttributeOID.UNSTRUCTURED_NAME + ) + == b"an unstructured field" + ) + + assert request.attributes.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) == x509.Attribute( + x509.oid.AttributeOID.CHALLENGE_PASSWORD, + b"beauty", + ) + + assert request.attributes.get_attribute_for_oid( + x509.oid.AttributeOID.UNSTRUCTURED_NAME + ) == x509.Attribute( + x509.oid.AttributeOID.UNSTRUCTURED_NAME, + b"an unstructured field", + ) + + def test_unsupported_asn1_type_in_attribute(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "challenge-invalid.der"), + x509.load_der_x509_csr, + ) + + # Unsupported in the legacy path + with pytest.raises(ValueError): + with pytest.warns(utils.DeprecatedIn36): + request.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + + # supported in the new path where we just store the type and + # return raw bytes + attr = request.attributes.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + assert attr._type == 2 + + def test_long_form_asn1_tag_in_attribute(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "long-form-attribute.pem"), + x509.load_pem_x509_csr, + ) + with pytest.raises(ValueError, match="Long-form"): + request.attributes + + def test_challenge_multivalued(self, backend): + """ + We only support single-valued SETs in our X509 request attributes + """ + request = _load_cert( + os.path.join("x509", "requests", "challenge-multi-valued.der"), + x509.load_der_x509_csr, + ) + with pytest.raises(ValueError, match="Only single-valued"): + with pytest.warns(utils.DeprecatedIn36): + request.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + + with pytest.raises(ValueError, match="Only single-valued"): + request.attributes + + def test_no_challenge_password(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha256.pem"), + x509.load_pem_x509_csr, + ) + with pytest.raises(x509.AttributeNotFound) as exc: + with pytest.warns(utils.DeprecatedIn36): + request.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + assert exc.value.oid == x509.oid.AttributeOID.CHALLENGE_PASSWORD + + with pytest.raises(x509.AttributeNotFound) as exc: + request.attributes.get_attribute_for_oid( + x509.oid.AttributeOID.CHALLENGE_PASSWORD + ) + assert exc.value.oid == x509.oid.AttributeOID.CHALLENGE_PASSWORD + + def test_no_attributes(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha256.pem"), + x509.load_pem_x509_csr, + ) + assert len(request.attributes) == 0 + + +def test_load_pem_x509_certificates(): + with pytest.raises(ValueError): + x509.load_pem_x509_certificates(b"") + + certs = load_vectors_from_file( + filename=os.path.join("x509", "cryptography.io.chain.pem"), + loader=lambda pemfile: x509.load_pem_x509_certificates(pemfile.read()), + mode="rb", + ) + assert len(certs) == 2 + assert certs[0].serial_number == 16160 + assert certs[1].serial_number == 146039 + + certs = load_vectors_from_file( + filename=os.path.join( + "x509", "cryptography.io.chain_with_garbage.pem" + ), + loader=lambda pemfile: x509.load_pem_x509_certificates(pemfile.read()), + mode="rb", + ) + assert len(certs) == 2 + assert certs[0].serial_number == 16160 + assert certs[1].serial_number == 146039 diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py index 922d24917979..95c0677bb777 100644 --- a/tests/x509/test_x509_crlbuilder.py +++ b/tests/x509/test_x509_crlbuilder.py @@ -2,23 +2,15 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import datetime import pytest -import pytz - from cryptography import x509 -from cryptography.hazmat.backends.interfaces import ( - DSABackend, - EllipticCurveBackend, - RSABackend, - X509Backend, -) +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448 +from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa from cryptography.x509.oid import ( AuthorityInformationAccessOID, NameOID, @@ -27,41 +19,43 @@ from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 -from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 from ..hazmat.primitives.test_ec import _skip_curve_unsupported +from ..hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 +from .test_x509 import DummyExtension + +# Make ruff happy since we're importing fixtures that pytest patches in as +# func args +__all__ = ["rsa_key_512", "rsa_key_2048"] -class TestCertificateRevocationListBuilder(object): +class TestCertificateRevocationListBuilder: def test_issuer_name_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): - builder.issuer_name("notanx509name") + builder.issuer_name("notanx509name") # type:ignore[arg-type] def test_set_issuer_name_twice(self): builder = x509.CertificateRevocationListBuilder().issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_aware_last_update(self, backend): - last_time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - last_time = tz.localize(last_time) + def test_aware_last_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + tz = datetime.timezone(datetime.timedelta(hours=-8)) + last_time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_last = datetime.datetime(2012, 1, 17, 6, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -76,7 +70,7 @@ def test_aware_last_update(self, backend): def test_last_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): - builder.last_update("notadatetime") + builder.last_update("notadatetime") # type:ignore[arg-type] def test_last_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() @@ -90,22 +84,19 @@ def test_set_last_update_twice(self): with pytest.raises(ValueError): builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_aware_next_update(self, backend): - next_time = datetime.datetime(2022, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - next_time = tz.localize(next_time) + def test_aware_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + tz = datetime.timezone(datetime.timedelta(hours=-8)) + next_time = datetime.datetime(2022, 1, 16, 22, 43, tzinfo=tz) utc_next = datetime.datetime(2022, 1, 17, 6, 43) last_time = datetime.datetime(2012, 1, 17, 6, 43) - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -120,7 +111,7 @@ def test_aware_next_update(self, backend): def test_next_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): - builder.next_update("notadatetime") + builder.next_update("notadatetime") # type:ignore[arg-type] def test_next_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() @@ -160,18 +151,16 @@ def test_add_invalid_extension(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): - builder.add_extension(object(), False) + builder.add_extension(object(), False) # type:ignore[arg-type] def test_add_invalid_revoked_certificate(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): - builder.add_revoked_certificate(object()) + builder.add_revoked_certificate(object()) # type:ignore[arg-type] - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_issuer_name(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_no_issuer_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .last_update(datetime.datetime(2002, 1, 1, 12, 1)) @@ -181,14 +170,12 @@ def test_no_issuer_name(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_last_update(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_no_last_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .next_update(datetime.datetime(2030, 1, 1, 12, 1)) ) @@ -196,14 +183,12 @@ def test_no_last_update(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_no_next_update(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_no_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( - x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US")]) + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .last_update(datetime.datetime(2030, 1, 1, 12, 1)) ) @@ -211,10 +196,8 @@ def test_no_next_update(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_empty_list(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign_empty_list(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( @@ -223,7 +206,7 @@ def test_sign_empty_list(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -252,19 +235,19 @@ def test_sign_empty_list(self, backend): [ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.DNSName(u"cryptography.io"), + x509.DNSName("cryptography.io"), ) ] ), x509.IssuerAlternativeName( - [x509.UniformResourceIdentifier(u"https://cryptography.io")] + [x509.UniformResourceIdentifier("https://cryptography.io")] ), ], ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_extensions(self, backend, extension): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign_extensions( + self, rsa_key_2048: rsa.RSAPrivateKey, backend, extension + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( @@ -273,7 +256,7 @@ def test_sign_extensions(self, backend, extension): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -290,14 +273,14 @@ def test_sign_extensions(self, backend, extension): assert ext.critical is False assert ext.value == extension - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_multiple_extensions_critical(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign_multiple_extensions_critical( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) ian = x509.IssuerAlternativeName( - [x509.UniformResourceIdentifier(u"https://cryptography.io")] + [x509.UniformResourceIdentifier("https://cryptography.io")] ) crl_number = x509.CRLNumber(13) builder = ( @@ -306,7 +289,7 @@ def test_sign_multiple_extensions_critical(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -329,16 +312,16 @@ def test_sign_multiple_extensions_critical(self, backend): assert ext2.critical is True assert ext2.value == ian - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_freshestcrl_extension(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_freshestcrl_extension( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) freshest = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://d.om/delta")], + [x509.UniformResourceIdentifier("http://d.om/delta")], None, None, None, @@ -351,7 +334,7 @@ def test_freshestcrl_extension(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -366,15 +349,17 @@ def test_freshestcrl_extension(self, backend): assert len(crl.extensions) == 1 ext1 = crl.extensions.get_extension_for_class(x509.FreshestCRL) assert ext1.critical is False + assert isinstance(ext1.value, x509.FreshestCRL) assert isinstance(ext1.value[0], x509.DistributionPoint) + assert ext1.value[0].full_name is not None uri = ext1.value[0].full_name[0] assert isinstance(uri, x509.UniformResourceIdentifier) - assert uri.value == u"http://d.om/delta" + assert uri.value == "http://d.om/delta" - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_add_unsupported_extension(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_add_unsupported_extension( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( @@ -383,22 +368,22 @@ def test_add_unsupported_extension(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) - .add_extension(x509.OCSPNoCheck(), False) + .add_extension(DummyExtension(), False) ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_rsa_key_too_small(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_add_unsupported_entry_extension( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( @@ -407,7 +392,37 @@ def test_sign_rsa_key_too_small(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" + ) + ] + ) + ) + .last_update(last_update) + .next_update(next_update) + .add_revoked_certificate( + x509.RevokedCertificateBuilder() + .serial_number(1234) + .revocation_date(datetime.datetime.utcnow()) + .add_extension(DummyExtension(), critical=False) + .build() + ) + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256(), backend) + + def test_sign_rsa_key_too_small( + self, rsa_key_512: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_512 + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = ( + x509.CertificateRevocationListBuilder() + .issuer_name( + x509.Name( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -419,10 +434,10 @@ def test_sign_rsa_key_too_small(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_with_invalid_hash(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign_with_invalid_hash( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( @@ -431,7 +446,7 @@ def test_sign_with_invalid_hash(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -441,13 +456,14 @@ def test_sign_with_invalid_hash(self, backend): ) with pytest.raises(TypeError): - builder.sign(private_key, object(), backend) + builder.sign( + private_key, object(), backend # type: ignore[arg-type] + ) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_invalid_hash_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() last_update = datetime.datetime(2002, 1, 1, 12, 1) @@ -458,7 +474,7 @@ def test_sign_with_invalid_hash_ed25519(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -467,8 +483,12 @@ def test_sign_with_invalid_hash_ed25519(self, backend): .next_update(next_update) ) - with pytest.raises(ValueError): - builder.sign(private_key, object(), backend) + with pytest.raises(TypeError): + builder.sign( + private_key, + object(), # type:ignore[arg-type] + backend, + ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @@ -476,7 +496,6 @@ def test_sign_with_invalid_hash_ed25519(self, backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_invalid_hash_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() last_update = datetime.datetime(2002, 1, 1, 12, 1) @@ -487,7 +506,7 @@ def test_sign_with_invalid_hash_ed448(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -496,20 +515,26 @@ def test_sign_with_invalid_hash_ed448(self, backend): .next_update(next_update) ) - with pytest.raises(ValueError): - builder.sign(private_key, object(), backend) + with pytest.raises(TypeError): + builder.sign( + private_key, + object(), # type:ignore[arg-type] + backend, + ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - @pytest.mark.requires_backend_interface(interface=DSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.supported( + only_if=lambda backend: backend.dsa_supported(), + skip_message="Requires OpenSSL with DSA support", + ) def test_sign_dsa_key(self, backend): private_key = DSA_KEY_2048.private_key(backend) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( - [x509.UniformResourceIdentifier(u"https://cryptography.io")] + [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() @@ -526,7 +551,7 @@ def test_sign_dsa_key(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -551,8 +576,6 @@ def test_sign_dsa_key(self, backend): assert ext.critical is False assert ext.value == invalidity_date - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_ec_key(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) @@ -560,7 +583,7 @@ def test_sign_ec_key(self, backend): datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( - [x509.UniformResourceIdentifier(u"https://cryptography.io")] + [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() @@ -577,7 +600,7 @@ def test_sign_ec_key(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -606,14 +629,13 @@ def test_sign_ec_key(self, backend): only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_ed25519_key(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( - [x509.UniformResourceIdentifier(u"https://cryptography.io")] + [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() @@ -630,7 +652,7 @@ def test_sign_ed25519_key(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -661,14 +683,13 @@ def test_sign_ed25519_key(self, backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_ed448_key(self, backend): private_key = ed448.Ed448PrivateKey.generate() invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( - [x509.UniformResourceIdentifier(u"https://cryptography.io")] + [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() @@ -685,7 +706,7 @@ def test_sign_ed448_key(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -712,8 +733,6 @@ def test_sign_ed448_key(self, backend): assert ext.critical is False assert ext.value == invalidity_date - @pytest.mark.requires_backend_interface(interface=DSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_dsa_key_sign_md5(self, backend): private_key = DSA_KEY_2048.private_key(backend) last_time = datetime.datetime(2012, 1, 16, 22, 43) @@ -724,7 +743,7 @@ def test_dsa_key_sign_md5(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -733,11 +752,11 @@ def test_dsa_key_sign_md5(self, backend): .next_update(next_time) ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) + with pytest.raises(UnsupportedAlgorithm): + builder.sign( + private_key, hashes.MD5(), backend # type: ignore[arg-type] + ) - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_ec_key_sign_md5(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = EC_KEY_SECP256R1.private_key(backend) @@ -749,7 +768,7 @@ def test_ec_key_sign_md5(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -758,13 +777,15 @@ def test_ec_key_sign_md5(self, backend): .next_update(next_time) ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) + with pytest.raises(UnsupportedAlgorithm): + builder.sign( + private_key, hashes.MD5(), backend # type: ignore[arg-type] + ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) - def test_sign_with_revoked_certificates(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign_with_revoked_certificates( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) invalidity_date = x509.InvalidityDate( @@ -781,6 +802,17 @@ def test_sign_with_revoked_certificates(self, backend): .serial_number(2) .revocation_date(datetime.datetime(2012, 1, 1, 1, 1)) .add_extension(invalidity_date, False) + .add_extension( + x509.CRLReason(x509.ReasonFlags.ca_compromise), False + ) + .build(backend) + ) + ci = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) + revoked_cert2 = ( + x509.RevokedCertificateBuilder() + .serial_number(40) + .revocation_date(datetime.datetime(2011, 1, 1, 1, 1)) + .add_extension(ci, False) .build(backend) ) builder = ( @@ -789,7 +821,7 @@ def test_sign_with_revoked_certificates(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -798,10 +830,11 @@ def test_sign_with_revoked_certificates(self, backend): .next_update(next_update) .add_revoked_certificate(revoked_cert0) .add_revoked_certificate(revoked_cert1) + .add_revoked_certificate(revoked_cert2) ) crl = builder.sign(private_key, hashes.SHA256(), backend) - assert len(crl) == 2 + assert len(crl) == 3 assert crl.last_update == last_update assert crl.next_update == next_update assert crl[0].serial_number == revoked_cert0.serial_number @@ -809,7 +842,13 @@ def test_sign_with_revoked_certificates(self, backend): assert len(crl[0].extensions) == 0 assert crl[1].serial_number == revoked_cert1.serial_number assert crl[1].revocation_date == revoked_cert1.revocation_date - assert len(crl[1].extensions) == 1 + assert len(crl[1].extensions) == 2 ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date + assert ( + crl[2] + .extensions.get_extension_for_class(x509.CertificateIssuer) + .value + == ci + ) diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py index 2cd216fb688a..fd7ff957b1dd 100644 --- a/tests/x509/test_x509_ext.py +++ b/tests/x509/test_x509_ext.py @@ -2,30 +2,31 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import binascii import datetime import ipaddress import os +import typing import pretend - import pytest -import six - from cryptography import x509 -from cryptography.hazmat.backends.interfaces import ( - DSABackend, - EllipticCurveBackend, - RSABackend, - X509Backend, -) +from cryptography.hazmat._oid import _OID_NAMES +from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.x509 import DNSName, NameConstraints, SubjectAlternativeName -from cryptography.x509.extensions import _key_identifier_from_public_key +from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.x509 import ( + DNSName, + NameConstraints, + SubjectAlternativeName, + ocsp, +) +from cryptography.x509.extensions import ( + ExtensionType, + _key_identifier_from_public_key, +) from cryptography.x509.oid import ( AuthorityInformationAccessOID, ExtendedKeyUsageOID, @@ -33,17 +34,20 @@ NameOID, ObjectIdentifier, SubjectInformationAccessOID, - _OID_NAMES, ) -from .test_x509 import _load_cert -from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048 from ..hazmat.primitives.test_ec import _skip_curve_unsupported +from ..hazmat.primitives.test_rsa import rsa_key_2048 from ..utils import load_vectors_from_file +from .test_x509 import _load_cert + +# Make ruff happy since we're importing fixtures that pytest patches in as +# func args +__all__ = ["rsa_key_2048"] def _make_certbuilder(private_key): - name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"example.org")]) + name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "example.org")]) return ( x509.CertificateBuilder() .subject_name(name) @@ -55,16 +59,20 @@ def _make_certbuilder(private_key): ) -class TestExtension(object): +class TestExtension: def test_not_an_oid(self): bc = x509.BasicConstraints(ca=False, path_length=None) with pytest.raises(TypeError): - x509.Extension("notanoid", True, bc) + x509.Extension("notanoid", True, bc) # type:ignore[arg-type] def test_critical_not_a_bool(self): bc = x509.BasicConstraints(ca=False, path_length=None) with pytest.raises(TypeError): - x509.Extension(ExtensionOID.BASIC_CONSTRAINTS, "notabool", bc) + x509.Extension( + ExtensionOID.BASIC_CONSTRAINTS, + "notabool", # type:ignore[arg-type] + bc, + ) def test_repr(self): bc = x509.BasicConstraints(ca=False, path_length=None) @@ -76,16 +84,38 @@ def test_repr(self): ) def test_eq(self): - ext1 = x509.Extension(x509.ObjectIdentifier("1.2.3.4"), False, "value") - ext2 = x509.Extension(x509.ObjectIdentifier("1.2.3.4"), False, "value") + ext1 = x509.Extension( + x509.ObjectIdentifier("1.2.3.4"), + False, + x509.BasicConstraints(ca=False, path_length=None), + ) + ext2 = x509.Extension( + x509.ObjectIdentifier("1.2.3.4"), + False, + x509.BasicConstraints(ca=False, path_length=None), + ) assert ext1 == ext2 def test_ne(self): - ext1 = x509.Extension(x509.ObjectIdentifier("1.2.3.4"), False, "value") - ext2 = x509.Extension(x509.ObjectIdentifier("1.2.3.5"), False, "value") - ext3 = x509.Extension(x509.ObjectIdentifier("1.2.3.4"), True, "value") + ext1 = x509.Extension( + x509.ObjectIdentifier("1.2.3.4"), + False, + x509.BasicConstraints(ca=False, path_length=None), + ) + ext2 = x509.Extension( + x509.ObjectIdentifier("1.2.3.5"), + False, + x509.BasicConstraints(ca=False, path_length=None), + ) + ext3 = x509.Extension( + x509.ObjectIdentifier("1.2.3.4"), + True, + x509.BasicConstraints(ca=False, path_length=None), + ) ext4 = x509.Extension( - x509.ObjectIdentifier("1.2.3.4"), False, "value4" + x509.ObjectIdentifier("1.2.3.4"), + False, + x509.BasicConstraints(ca=True, path_length=None), ) assert ext1 != ext2 assert ext1 != ext3 @@ -112,10 +142,10 @@ def test_hash(self): assert hash(ext1) != hash(ext3) -class TestTLSFeature(object): +class TestTLSFeature: def test_not_enum_type(self): with pytest.raises(TypeError): - x509.TLSFeature([3]) + x509.TLSFeature([3]) # type:ignore[list-item] def test_empty_list(self): with pytest.raises(TypeError): @@ -180,11 +210,19 @@ def test_indexing(self): assert ext[-1] == ext[1] assert ext[0] == x509.TLSFeatureType.status_request + def test_public_bytes(self): + ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) + assert ext1.public_bytes() == b"\x30\x03\x02\x01\x05" + ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request_v2]) + assert ext2.public_bytes() == b"\x30\x03\x02\x01\x11" + -class TestUnrecognizedExtension(object): +class TestUnrecognizedExtension: def test_invalid_oid(self): with pytest.raises(TypeError): - x509.UnrecognizedExtension("notanoid", b"somedata") + x509.UnrecognizedExtension( + "notanoid", b"somedata" # type:ignore[arg-type] + ) def test_eq(self): ext1 = x509.UnrecognizedExtension( @@ -213,16 +251,10 @@ def test_repr(self): ext1 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" ) - if not six.PY2: - assert repr(ext1) == ( - ", value=b'\\x03\\x02\\x01')>" - ) - else: - assert repr(ext1) == ( - ", value='\\x03\\x02\\x01')>" - ) + assert repr(ext1) == ( + ", value=b'\\x03\\x02\\x01')>" + ) def test_hash(self): ext1 = x509.UnrecognizedExtension( @@ -237,74 +269,86 @@ def test_hash(self): assert hash(ext1) == hash(ext2) assert hash(ext1) != hash(ext3) + def test_public_bytes(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" + ) + assert ext1.public_bytes() == b"\x03\x02\x01" + + # The following creates a BasicConstraints extension with an invalid + # value. The serialization code should still handle it correctly by + # special-casing UnrecognizedExtension. + ext2 = x509.UnrecognizedExtension( + x509.oid.ExtensionOID.BASIC_CONSTRAINTS, b"\x03\x02\x01" + ) + assert ext2.public_bytes() == b"\x03\x02\x01" + -class TestCertificateIssuer(object): +class TestCertificateIssuer: def test_iter_names(self): ci = x509.CertificateIssuer( - [x509.DNSName(u"cryptography.io"), x509.DNSName(u"crypto.local")] + [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(ci) == 2 assert list(ci) == [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), ] def test_indexing(self): ci = x509.CertificateIssuer( [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - x509.DNSName(u"another.local"), - x509.RFC822Name(u"email@another.local"), - x509.UniformResourceIdentifier(u"http://another.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), + x509.DNSName("another.local"), + x509.RFC822Name("email@another.local"), + x509.UniformResourceIdentifier("http://another.local"), ] ) assert ci[-1] == ci[4] assert ci[2:6:2] == [ci[2], ci[4]] def test_eq(self): - ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) - ci2 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci1 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) assert ci1 == ci2 def test_ne(self): - ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) - ci2 = x509.CertificateIssuer([x509.DNSName(u"somethingelse.tld")]) + ci1 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName("somethingelse.tld")]) assert ci1 != ci2 assert ci1 != object() def test_repr(self): - ci = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) - if not six.PY2: - assert repr(ci) == ( - "])>)>" - ) - else: - assert repr(ci) == ( - "])>)>" - ) + ci = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) + assert repr(ci) == ( + "])>)>" + ) def test_get_values_for_type(self): - ci = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) names = ci.get_values_for_type(x509.DNSName) - assert names == [u"cryptography.io"] + assert names == ["cryptography.io"] def test_hash(self): - ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) - ci2 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci1 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) ci3 = x509.CertificateIssuer( - [x509.UniformResourceIdentifier(u"http://something")] + [x509.UniformResourceIdentifier("http://something")] ) assert hash(ci1) == hash(ci2) assert hash(ci1) != hash(ci3) + def test_public_bytes(self): + ext = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) + assert ext.public_bytes() == b"0\x11\x82\x0fcryptography.io" + -class TestCRLReason(object): +class TestCRLReason: def test_invalid_reason_flags(self): with pytest.raises(TypeError): - x509.CRLReason("notareason") + x509.CRLReason("notareason") # type:ignore[arg-type] def test_eq(self): reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) @@ -329,11 +373,15 @@ def test_repr(self): reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) assert repr(reason1) == ("") + def test_public_bytes(self): + ext = x509.CRLReason(x509.ReasonFlags.ca_compromise) + assert ext.public_bytes() == b"\n\x01\x02" -class TestDeltaCRLIndicator(object): + +class TestDeltaCRLIndicator: def test_not_int(self): with pytest.raises(TypeError): - x509.DeltaCRLIndicator("notanint") + x509.DeltaCRLIndicator("notanint") # type:ignore[arg-type] def test_eq(self): delta1 = x509.DeltaCRLIndicator(1) @@ -357,11 +405,15 @@ def test_hash(self): assert hash(delta1) == hash(delta2) assert hash(delta1) != hash(delta3) + def test_public_bytes(self): + ext = x509.DeltaCRLIndicator(2) + assert ext.public_bytes() == b"\x02\x01\x02" + -class TestInvalidityDate(object): +class TestInvalidityDate: def test_invalid_invalidity_date(self): with pytest.raises(TypeError): - x509.InvalidityDate("notadate") + x509.InvalidityDate("notadate") # type:ignore[arg-type] def test_eq(self): invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) @@ -387,34 +439,34 @@ def test_hash(self): assert hash(invalid1) == hash(invalid2) assert hash(invalid1) != hash(invalid3) + def test_public_bytes(self): + ext = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + assert ext.public_bytes() == b"\x18\x0f20150101010100Z" -class TestNoticeReference(object): + +class TestNoticeReference: def test_notice_numbers_not_all_int(self): with pytest.raises(TypeError): - x509.NoticeReference("org", [1, 2, "three"]) + x509.NoticeReference( + "org", [1, 2, "three"] # type:ignore[list-item] + ) def test_notice_numbers_none(self): with pytest.raises(TypeError): - x509.NoticeReference("org", None) + x509.NoticeReference("org", None) # type:ignore[arg-type] def test_iter_input(self): numbers = [1, 3, 4] - nr = x509.NoticeReference(u"org", iter(numbers)) + nr = x509.NoticeReference("org", iter(numbers)) assert list(nr.notice_numbers) == numbers def test_repr(self): - nr = x509.NoticeReference(u"org", [1, 3, 4]) + nr = x509.NoticeReference("org", [1, 3, 4]) - if not six.PY2: - assert repr(nr) == ( - "" - ) - else: - assert repr(nr) == ( - "" - ) + assert repr(nr) == ( + "" + ) def test_eq(self): nr = x509.NoticeReference("org", [1, 2]) @@ -437,10 +489,10 @@ def test_hash(self): assert hash(nr) != hash(nr3) -class TestUserNotice(object): +class TestUserNotice: def test_notice_reference_invalid(self): with pytest.raises(TypeError): - x509.UserNotice("invalid", None) + x509.UserNotice("invalid", None) # type:ignore[arg-type] def test_notice_reference_none(self): un = x509.UserNotice(None, "text") @@ -448,17 +500,11 @@ def test_notice_reference_none(self): assert un.explicit_text == "text" def test_repr(self): - un = x509.UserNotice(x509.NoticeReference(u"org", [1]), u"text") - if not six.PY2: - assert repr(un) == ( - ", explicit_text='text')>" - ) - else: - assert repr(un) == ( - ", explicit_text=u'text')>" - ) + un = x509.UserNotice(x509.NoticeReference("org", [1]), "text") + assert repr(un) == ( + ", explicit_text='text')>" + ) def test_eq(self): nr = x509.NoticeReference("org", [1, 2]) @@ -487,10 +533,10 @@ def test_hash(self): assert hash(un) != hash(un3) -class TestPolicyInformation(object): +class TestPolicyInformation: def test_invalid_policy_identifier(self): with pytest.raises(TypeError): - x509.PolicyInformation("notanoid", None) + x509.PolicyInformation("notanoid", None) # type:ignore[arg-type] def test_none_policy_qualifiers(self): pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) @@ -498,56 +544,54 @@ def test_none_policy_qualifiers(self): assert pi.policy_qualifiers is None def test_policy_qualifiers(self): - pq = [u"string"] + pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") assert pi.policy_qualifiers == pq def test_invalid_policy_identifiers(self): with pytest.raises(TypeError): - x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [1, 2]) + x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), + [1, 2], # type:ignore[list-item] + ) def test_iter_input(self): - qual = [u"foo", u"bar"] + qual = ["foo", "bar"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), iter(qual)) + assert pi.policy_qualifiers is not None assert list(pi.policy_qualifiers) == qual def test_repr(self): - pq = [u"string", x509.UserNotice(None, u"hi")] + pq: typing.List[typing.Union[str, x509.UserNotice]] = [ + "string", + x509.UserNotice(None, "hi"), + ] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) - if not six.PY2: - assert repr(pi) == ( - ", policy_qualifiers=['string', ])>" - ) - else: - assert repr(pi) == ( - ", policy_qualifiers=[u'string', ])>" - ) + assert repr(pi) == ( + ", policy_qualifiers=['string', ])>" + ) def test_eq(self): pi = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), - [u"string", x509.UserNotice(None, u"hi")], + ["string", x509.UserNotice(None, "hi")], ) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), - [u"string", x509.UserNotice(None, u"hi")], + ["string", x509.UserNotice(None, "hi")], ) assert pi == pi2 def test_ne(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string2"] + x509.ObjectIdentifier("1.2.3"), ["string2"] ) pi3 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3.4"), [u"string"] + x509.ObjectIdentifier("1.2.3.4"), ["string"] ) assert pi != pi2 assert pi != pi3 @@ -556,27 +600,26 @@ def test_ne(self): def test_hash(self): pi = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), - [u"string", x509.UserNotice(None, u"hi")], + ["string", x509.UserNotice(None, "hi")], ) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), - [u"string", x509.UserNotice(None, u"hi")], + ["string", x509.UserNotice(None, "hi")], ) pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) assert hash(pi) == hash(pi2) assert hash(pi) != hash(pi3) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCertificatePolicies(object): +class TestCertificatePolicies: def test_invalid_policies(self): - pq = [u"string"] + pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) with pytest.raises(TypeError): - x509.CertificatePolicies([1, pi]) + x509.CertificatePolicies([1, pi]) # type:ignore[list-item] def test_iter_len(self): - pq = [u"string"] + pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) cp = x509.CertificatePolicies([pi]) assert len(cp) == 1 @@ -585,57 +628,46 @@ def test_iter_len(self): def test_iter_input(self): policies = [ - x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [u"string"]) + x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) ] cp = x509.CertificatePolicies(iter(policies)) assert list(cp) == policies def test_repr(self): - pq = [u"string"] + pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) cp = x509.CertificatePolicies([pi]) - if not six.PY2: - assert repr(cp) == ( - ", policy_qualifi" - "ers=['string'])>])>" - ) - else: - assert repr(cp) == ( - ", policy_qualifi" - "ers=[u'string'])>])>" - ) + assert repr(cp) == ( + ", policy_qualifi" + "ers=['string'])>])>" + ) def test_eq(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) cp = x509.CertificatePolicies([pi]) pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] + x509.ObjectIdentifier("1.2.3"), ["string"] ) cp2 = x509.CertificatePolicies([pi2]) assert cp == cp2 def test_ne(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) cp = x509.CertificatePolicies([pi]) pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string2"] + x509.ObjectIdentifier("1.2.3"), ["string2"] ) cp2 = x509.CertificatePolicies([pi2]) assert cp != cp2 assert cp != object() def test_indexing(self): - pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [u"test"]) - pi2 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.4"), [u"test"]) - pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.5"), [u"test"]) - pi4 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.6"), [u"test"]) - pi5 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.7"), [u"test"]) + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["test"]) + pi2 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.4"), ["test"]) + pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.5"), ["test"]) + pi4 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.6"), ["test"]) + pi5 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.7"), ["test"]) cp = x509.CertificatePolicies([pi, pi2, pi3, pi4, pi5]) assert cp[-1] == cp[4] assert cp[2:6:2] == [cp[2], cp[4]] @@ -648,7 +680,6 @@ def test_long_oid(self, backend): cert = _load_cert( os.path.join("x509", "bigoid.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_class(x509.CertificatePolicies) @@ -660,30 +691,25 @@ def test_long_oid(self, backend): assert ext.value[0].policy_identifier == oid def test_hash(self): - pi = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] - ) + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) cp = x509.CertificatePolicies([pi]) pi2 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [u"string"] + x509.ObjectIdentifier("1.2.3"), ["string"] ) cp2 = x509.CertificatePolicies([pi2]) pi3 = x509.PolicyInformation( - x509.ObjectIdentifier("1.2.3"), [x509.UserNotice(None, b"text")] + x509.ObjectIdentifier("1.2.3"), [x509.UserNotice(None, "text")] ) cp3 = x509.CertificatePolicies([pi3]) assert hash(cp) == hash(cp2) assert hash(cp) != hash(cp3) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCertificatePoliciesExtension(object): +class TestCertificatePoliciesExtension: def test_cps_uri_policy_qualifier(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cp_cps_uri.pem"), x509.load_pem_x509_certificate, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -694,7 +720,7 @@ def test_cps_uri_policy_qualifier(self, backend): [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [u"http://other.com/cps"], + ["http://other.com/cps"], ) ] ) @@ -705,7 +731,6 @@ def test_user_notice_with_notice_reference(self, backend): "x509", "custom", "cp_user_notice_with_notice_reference.pem" ), x509.load_pem_x509_certificate, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -717,11 +742,11 @@ def test_user_notice_with_notice_reference(self, backend): x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ - u"http://example.com/cps", - u"http://other.com/cps", + "http://example.com/cps", + "http://other.com/cps", x509.UserNotice( - x509.NoticeReference(u"my org", [1, 2, 3, 4]), - u"thing", + x509.NoticeReference("my org", [1, 2, 3, 4]), + "thing", ), ], ) @@ -734,7 +759,6 @@ def test_user_notice_with_explicit_text(self, backend): "x509", "custom", "cp_user_notice_with_explicit_text.pem" ), x509.load_pem_x509_certificate, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -745,7 +769,7 @@ def test_user_notice_with_explicit_text(self, backend): [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), - [x509.UserNotice(None, u"thing")], + [x509.UserNotice(None, "thing")], ) ] ) @@ -756,7 +780,6 @@ def test_user_notice_no_explicit_text(self, backend): "x509", "custom", "cp_user_notice_no_explicit_text.pem" ), x509.load_pem_x509_certificate, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -769,15 +792,71 @@ def test_user_notice_no_explicit_text(self, backend): x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ x509.UserNotice( - x509.NoticeReference(u"my org", [1, 2, 3, 4]), None + x509.NoticeReference("my org", [1, 2, 3, 4]), None + ) + ], + ) + ] + ) + + def test_non_ascii_qualifier( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + issuer_private_key = rsa_key_2048 + subject_private_key = rsa_key_2048 + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = ( + x509.CertificateBuilder() + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .not_valid_before(not_valid_before) + .not_valid_after(not_valid_after) + .public_key(subject_private_key.public_key()) + .serial_number(123) + .add_extension( + x509.CertificatePolicies( + [ + x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), "🤓" + ) + ] + ), + critical=False, + ) + ) + + with pytest.raises(ValueError, match="Qualifier"): + builder.sign(issuer_private_key, hashes.SHA256(), backend) + + def test_public_bytes(self): + ext = x509.CertificatePolicies( + [ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + x509.UserNotice( + x509.NoticeReference("my org", [1, 2, 3, 4]), None ) ], ) ] ) + assert ( + ext.public_bytes() + == b"0705\x06\x0b`\x86H\x01\xe09\x01\x02\x03\x04\x010&0$\x06\x08+" + b"\x06\x01\x05\x05\x07\x02\x020\x180\x16\x0c\x06my org0\x0c\x02" + b"\x01\x01\x02\x01\x02\x02\x01\x03\x02\x01\x04" + ) -class TestKeyUsage(object): +class TestKeyUsage: def test_key_agreement_false_encipher_decipher_true(self): with pytest.raises(ValueError): x509.KeyUsage( @@ -999,32 +1078,103 @@ def test_hash(self): assert hash(ku) == hash(ku2) assert hash(ku) != hash(ku3) + @pytest.mark.parametrize( + ("ext", "serialized"), + [ + ( + x509.KeyUsage( + digital_signature=False, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ), + b"\x03\x02\x06@", + ), + ( + x509.KeyUsage( + digital_signature=False, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True, + ), + b"\x03\x03\x07H\x80", + ), + ( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=True, + decipher_only=False, + ), + b"\x03\x02\x00\x89", + ), + ( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=True, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ), + b"\x03\x02\x02\x94", + ), + ( + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ), + b"\x03\x01\x00", + ), + ], + ) + def test_public_bytes(self, ext, serialized): + assert ext.public_bytes() == serialized + -class TestSubjectKeyIdentifier(object): +class TestSubjectKeyIdentifier: def test_properties(self): value = binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ski = x509.SubjectKeyIdentifier(value) assert ski.digest == value + assert ski.key_identifier == value def test_repr(self): ski = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) ext = x509.Extension(ExtensionOID.SUBJECT_KEY_IDENTIFIER, False, ski) - if not six.PY2: - assert repr(ext) == ( - ", critical=False, value=)>" - ) - else: - assert repr(ext) == ( - ", critical=False, value=)>" - ) + assert repr(ext) == ( + ", critical=False, value=)>" + ) def test_eq(self): ski = x509.SubjectKeyIdentifier( @@ -1058,27 +1208,41 @@ def test_hash(self): assert hash(ski1) == hash(ski2) assert hash(ski1) != hash(ski3) + def test_public_bytes(self): + ext = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + assert ( + ext.public_bytes() + == b'\x04\x14\t#\x84\x93"0I\x8b\xc9\x80\xaa\x80\x98Eoo\xf7\xff:' + b"\xc9" + ) -class TestAuthorityKeyIdentifier(object): + +class TestAuthorityKeyIdentifier: def test_authority_cert_issuer_not_generalname(self): with pytest.raises(TypeError): - x509.AuthorityKeyIdentifier(b"identifier", ["notname"], 3) + x509.AuthorityKeyIdentifier( + b"identifier", ["notname"], 3 # type:ignore[list-item] + ) def test_authority_cert_serial_number_not_integer(self): dirname = x509.DirectoryName( x509.Name( [ x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" + x509.ObjectIdentifier("2.999.1"), "value1" ), x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value2" + x509.ObjectIdentifier("2.999.2"), "value2" ), ] ) ) with pytest.raises(TypeError): - x509.AuthorityKeyIdentifier(b"identifier", [dirname], "notanint") + x509.AuthorityKeyIdentifier( + b"identifier", [dirname], "notanint" # type:ignore[arg-type] + ) def test_authority_issuer_none_serial_not_none(self): with pytest.raises(ValueError): @@ -1089,10 +1253,10 @@ def test_authority_issuer_not_none_serial_none(self): x509.Name( [ x509.NameAttribute( - x509.ObjectIdentifier("2.999.1"), u"value1" + x509.ObjectIdentifier("2.999.1"), "value1" ), x509.NameAttribute( - x509.ObjectIdentifier("2.999.2"), u"value2" + x509.ObjectIdentifier("2.999.2"), "value2" ), ] ) @@ -1107,7 +1271,7 @@ def test_authority_cert_serial_and_issuer_none(self): assert aki.authority_cert_serial_number is None def test_authority_cert_serial_zero(self): - dns = x509.DNSName(u"SomeIssuer") + dns = x509.DNSName("SomeIssuer") aki = x509.AuthorityKeyIdentifier(b"id", [dns], 0) assert aki.key_identifier == b"id" assert aki.authority_cert_issuer == [dns] @@ -1116,48 +1280,42 @@ def test_authority_cert_serial_zero(self): def test_iter_input(self): dirnames = [ x509.DirectoryName( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) ] aki = x509.AuthorityKeyIdentifier(b"digest", iter(dirnames), 1234) + assert aki.authority_cert_issuer is not None assert list(aki.authority_cert_issuer) == dirnames def test_repr(self): dirname = x509.DirectoryName( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) - if not six.PY2: - assert repr(aki) == ( - ")>], author" - "ity_cert_serial_number=1234)>" - ) - else: - assert repr(aki) == ( - ")>], author" - "ity_cert_serial_number=1234)>" - ) + assert repr(aki) == ( + ")>], author" + "ity_cert_serial_number=1234)>" + ) def test_eq(self): dirname = x509.DirectoryName( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) dirname2 = x509.DirectoryName( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname2], 1234) assert aki == aki2 def test_ne(self): dirname = x509.DirectoryName( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) dirname5 = x509.DirectoryName( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"aCN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "aCN")]) ) aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) aki2 = x509.AuthorityKeyIdentifier(b"diges", [dirname], 1234) @@ -1172,7 +1330,7 @@ def test_ne(self): def test_hash(self): dirname = x509.DirectoryName( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")]) + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki1 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) @@ -1180,11 +1338,24 @@ def test_hash(self): assert hash(aki1) == hash(aki2) assert hash(aki1) != hash(aki3) + def test_public_bytes(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) + ) + ext = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + assert ( + ext.public_bytes() + == b"0!\x80\x06digest\xa1\x13\xa4\x110\x0f1\r0\x0b\x06\x03U\x04" + b"\x03\x0c\x04myCN\x82\x02\x04\xd2" + ) + -class TestBasicConstraints(object): +class TestBasicConstraints: def test_ca_not_boolean(self): with pytest.raises(TypeError): - x509.BasicConstraints(ca="notbool", path_length=None) + x509.BasicConstraints( + ca="notbool", path_length=None # type:ignore[arg-type] + ) def test_path_length_not_ca(self): with pytest.raises(ValueError): @@ -1192,10 +1363,14 @@ def test_path_length_not_ca(self): def test_path_length_not_int(self): with pytest.raises(TypeError): - x509.BasicConstraints(ca=True, path_length=1.1) + x509.BasicConstraints( + ca=True, path_length=1.1 # type:ignore[arg-type] + ) with pytest.raises(TypeError): - x509.BasicConstraints(ca=True, path_length="notint") + x509.BasicConstraints( + ca=True, path_length="notint" # type:ignore[arg-type] + ) def test_path_length_negative(self): with pytest.raises(TypeError): @@ -1225,11 +1400,15 @@ def test_ne(self): assert na != na3 assert na != object() + def test_public_bytes(self): + ext = x509.BasicConstraints(ca=True, path_length=None) + assert ext.public_bytes() == b"0\x03\x01\x01\xff" -class TestExtendedKeyUsage(object): + +class TestExtendedKeyUsage: def test_not_all_oids(self): with pytest.raises(TypeError): - x509.ExtendedKeyUsage(["notoid"]) + x509.ExtendedKeyUsage(["notoid"]) # type:ignore[list-item] def test_iter_len(self): eku = x509.ExtendedKeyUsage( @@ -1291,15 +1470,18 @@ def test_hash(self): assert hash(eku) == hash(eku2) assert hash(eku) != hash(eku3) + def test_public_bytes(self): + ext = x509.ExtendedKeyUsage( + [x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7")] + ) + assert ext.public_bytes() == b"0\x08\x06\x02+\x06\x06\x02+\x07" + -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestExtensions(object): +class TestExtensions: def test_no_extensions(self, backend): cert = _load_cert( os.path.join("x509", "verisign_md2_root.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions assert len(ext) == 0 @@ -1315,10 +1497,8 @@ def test_one_extension(self, backend): "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) - extensions = cert.extensions - ext = extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) + ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None assert ext.value.ca is False @@ -1326,7 +1506,6 @@ def test_duplicate_extension(self, backend): cert = _load_cert( os.path.join("x509", "custom", "two_basic_constraints.pem"), x509.load_pem_x509_certificate, - backend, ) with pytest.raises(x509.DuplicateExtension) as exc: cert.extensions @@ -1339,19 +1518,17 @@ def test_unsupported_critical_extension(self, backend): "x509", "custom", "unsupported_extension_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( x509.ObjectIdentifier("1.2.3.4") ) + assert isinstance(ext.value, x509.UnrecognizedExtension) assert ext.value.value == b"value" - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_unsupported_extension(self, backend): cert = _load_cert( os.path.join("x509", "custom", "unsupported_extension_2.pem"), x509.load_pem_x509_certificate, - backend, ) extensions = cert.extensions assert len(extensions) == 2 @@ -1375,7 +1552,6 @@ def test_no_extensions_get_for_class(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) exts = cert.extensions with pytest.raises(x509.ExtensionNotFound) as exc: @@ -1391,7 +1567,6 @@ def test_indexing(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) exts = cert.extensions assert exts[-1] == exts[7] @@ -1403,11 +1578,9 @@ def test_one_extension_get_for_class(self, backend): "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None - assert isinstance(ext.value, x509.BasicConstraints) def test_repr(self, backend): cert = _load_cert( @@ -1415,7 +1588,6 @@ def test_repr(self, backend): "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) assert repr(cert.extensions) == ( ")>" def test_eq(self): name = x509.Name( - [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1")] + [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) name2 = x509.Name( - [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1")] + [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) gn = x509.DirectoryName(name) gn2 = x509.DirectoryName(name2) @@ -1779,10 +1919,10 @@ def test_eq(self): def test_ne(self): name = x509.Name( - [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1")] + [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) name2 = x509.Name( - [x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), u"value2")] + [x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2")] ) gn = x509.DirectoryName(name) gn2 = x509.DirectoryName(name2) @@ -1791,10 +1931,10 @@ def test_ne(self): def test_hash(self): name = x509.Name( - [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), u"value1")] + [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) name2 = x509.Name( - [x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), u"value2")] + [x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2")] ) gn = x509.DirectoryName(name) gn2 = x509.DirectoryName(name) @@ -1803,107 +1943,101 @@ def test_hash(self): assert hash(gn) != hash(gn3) -class TestRFC822Name(object): +class TestRFC822Name: def test_repr(self): - gn = x509.RFC822Name(u"string") - if not six.PY2: - assert repr(gn) == "" - else: - assert repr(gn) == "" + gn = x509.RFC822Name("string") + assert repr(gn) == "" def test_equality(self): - gn = x509.RFC822Name(u"string") - gn2 = x509.RFC822Name(u"string2") - gn3 = x509.RFC822Name(u"string") + gn = x509.RFC822Name("string") + gn2 = x509.RFC822Name("string2") + gn3 = x509.RFC822Name("string") assert gn != gn2 assert gn != object() assert gn == gn3 def test_not_text(self): with pytest.raises(TypeError): - x509.RFC822Name(1.3) + x509.RFC822Name(1.3) # type:ignore[arg-type] with pytest.raises(TypeError): - x509.RFC822Name(b"bytes") + x509.RFC822Name(b"bytes") # type:ignore[arg-type] def test_invalid_email(self): with pytest.raises(ValueError): - x509.RFC822Name(u"Name ") + x509.RFC822Name("Name ") with pytest.raises(ValueError): - x509.RFC822Name(u"") + x509.RFC822Name("") def test_single_label(self): - gn = x509.RFC822Name(u"administrator") - assert gn.value == u"administrator" + gn = x509.RFC822Name("administrator") + assert gn.value == "administrator" def test_non_a_label(self): with pytest.raises(ValueError): - x509.RFC822Name(u"email@em\xe5\xefl.com") + x509.RFC822Name("email@em\xe5\xefl.com") def test_hash(self): - g1 = x509.RFC822Name(u"email@host.com") - g2 = x509.RFC822Name(u"email@host.com") - g3 = x509.RFC822Name(u"admin@host.com") + g1 = x509.RFC822Name("email@host.com") + g2 = x509.RFC822Name("email@host.com") + g3 = x509.RFC822Name("admin@host.com") assert hash(g1) == hash(g2) assert hash(g1) != hash(g3) -class TestUniformResourceIdentifier(object): +class TestUniformResourceIdentifier: def test_equality(self): - gn = x509.UniformResourceIdentifier(u"string") - gn2 = x509.UniformResourceIdentifier(u"string2") - gn3 = x509.UniformResourceIdentifier(u"string") + gn = x509.UniformResourceIdentifier("string") + gn2 = x509.UniformResourceIdentifier("string2") + gn3 = x509.UniformResourceIdentifier("string") assert gn != gn2 assert gn != object() assert gn == gn3 def test_not_text(self): with pytest.raises(TypeError): - x509.UniformResourceIdentifier(1.3) + x509.UniformResourceIdentifier(1.3) # type:ignore[arg-type] def test_no_parsed_hostname(self): - gn = x509.UniformResourceIdentifier(u"singlelabel") - assert gn.value == u"singlelabel" + gn = x509.UniformResourceIdentifier("singlelabel") + assert gn.value == "singlelabel" def test_with_port(self): - gn = x509.UniformResourceIdentifier(u"singlelabel:443/test") - assert gn.value == u"singlelabel:443/test" + gn = x509.UniformResourceIdentifier("singlelabel:443/test") + assert gn.value == "singlelabel:443/test" def test_non_a_label(self): with pytest.raises(ValueError): x509.UniformResourceIdentifier( - u"http://\u043f\u044b\u043a\u0430.cryptography" + "http://\u043f\u044b\u043a\u0430.cryptography" ) def test_empty_hostname(self): - gn = x509.UniformResourceIdentifier(u"ldap:///some-nonsense") + gn = x509.UniformResourceIdentifier("ldap:///some-nonsense") assert gn.value == "ldap:///some-nonsense" def test_hash(self): - g1 = x509.UniformResourceIdentifier(u"http://host.com") - g2 = x509.UniformResourceIdentifier(u"http://host.com") - g3 = x509.UniformResourceIdentifier(u"http://other.com") + g1 = x509.UniformResourceIdentifier("http://host.com") + g2 = x509.UniformResourceIdentifier("http://host.com") + g3 = x509.UniformResourceIdentifier("http://other.com") assert hash(g1) == hash(g2) assert hash(g1) != hash(g3) def test_repr(self): - gn = x509.UniformResourceIdentifier(u"string") - if not six.PY2: - assert repr(gn) == ("") - else: - assert repr(gn) == ("") + gn = x509.UniformResourceIdentifier("string") + assert repr(gn) == ("") -class TestRegisteredID(object): +class TestRegisteredID: def test_not_oid(self): with pytest.raises(TypeError): - x509.RegisteredID(b"notanoid") + x509.RegisteredID(b"notanoid") # type:ignore[arg-type] with pytest.raises(TypeError): - x509.RegisteredID(1.3) + x509.RegisteredID(1.3) # type:ignore[arg-type] def test_repr(self): gn = x509.RegisteredID(NameOID.COMMON_NAME) @@ -1931,78 +2065,72 @@ def test_hash(self): assert hash(gn) != hash(gn3) -class TestIPAddress(object): +class TestIPAddress: def test_not_ipaddress(self): with pytest.raises(TypeError): - x509.IPAddress(b"notanipaddress") + x509.IPAddress(b"notanipaddress") # type:ignore[arg-type] with pytest.raises(TypeError): - x509.IPAddress(1.3) + x509.IPAddress(1.3) # type:ignore[arg-type] def test_repr(self): - gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) assert repr(gn) == "" - gn2 = x509.IPAddress(ipaddress.IPv6Address(u"ff::")) + gn2 = x509.IPAddress(ipaddress.IPv6Address("ff::")) assert repr(gn2) == "" - gn3 = x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")) + gn3 = x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24")) assert repr(gn3) == "" - gn4 = x509.IPAddress(ipaddress.IPv6Network(u"ff::/96")) + gn4 = x509.IPAddress(ipaddress.IPv6Network("ff::/96")) assert repr(gn4) == "" def test_eq(self): - gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) assert gn == gn2 def test_ne(self): - gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.2")) + gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.2")) assert gn != gn2 assert gn != object() def test_hash(self): - gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) - gn3 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.2")) + gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) + gn3 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.2")) assert hash(gn) == hash(gn2) assert hash(gn) != hash(gn3) -class TestOtherName(object): +class TestOtherName: def test_invalid_args(self): with pytest.raises(TypeError): - x509.OtherName(b"notanobjectidentifier", b"derdata") + x509.OtherName( + b"notanobjectidentifier", # type:ignore[arg-type] + b"derdata", + ) with pytest.raises(TypeError): - x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), u"notderdata") + x509.OtherName( + x509.ObjectIdentifier("1.2.3.4"), + "notderdata", # type:ignore[arg-type] + ) def test_repr(self): gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") - if not six.PY2: - assert repr(gn) == ( - ", value=b'derdata')>" - ) - else: - assert repr(gn) == ( - ", value='derdata')>" - ) + assert repr(gn) == ( + ", value=b'derdata')>" + ) gn = x509.OtherName(x509.ObjectIdentifier("2.5.4.65"), b"derdata") - if not six.PY2: - assert repr(gn) == ( - ", value=b'derdata')>" - ) - else: - assert repr(gn) == ( - ", value='derdata')>" - ) + assert repr(gn) == ( + ", value=b'derdata')>" + ) def test_eq(self): gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") @@ -2027,26 +2155,26 @@ def test_hash(self): assert hash(gn) != hash(gn3) -class TestGeneralNames(object): +class TestGeneralNames: def test_get_values_for_type(self): - gns = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) + gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) names = gns.get_values_for_type(x509.DNSName) - assert names == [u"cryptography.io"] + assert names == ["cryptography.io"] def test_iter_names(self): gns = x509.GeneralNames( - [x509.DNSName(u"cryptography.io"), x509.DNSName(u"crypto.local")] + [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(gns) == 2 assert list(gns) == [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), ] def test_iter_input(self): names = [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), ] gns = x509.GeneralNames(iter(names)) assert list(gns) == names @@ -2054,11 +2182,11 @@ def test_iter_input(self): def test_indexing(self): gn = x509.GeneralNames( [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - x509.DNSName(u"another.local"), - x509.RFC822Name(u"email@another.local"), - x509.UniformResourceIdentifier(u"http://another.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), + x509.DNSName("another.local"), + x509.RFC822Name("email@another.local"), + x509.UniformResourceIdentifier("http://another.local"), ] ) assert gn[-1] == gn[4] @@ -2066,62 +2194,62 @@ def test_indexing(self): def test_invalid_general_names(self): with pytest.raises(TypeError): - x509.GeneralNames([x509.DNSName(u"cryptography.io"), "invalid"]) + x509.GeneralNames( + [ + x509.DNSName("cryptography.io"), + "invalid", # type:ignore[list-item] + ] + ) def test_repr(self): - gns = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) - if not six.PY2: - assert repr(gns) == ( - "])>" - ) - else: - assert repr(gns) == ( - "])>" - ) + gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) + assert repr(gns) == ( + "])>" + ) def test_eq(self): - gns = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) - gns2 = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) + gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) + gns2 = x509.GeneralNames([x509.DNSName("cryptography.io")]) assert gns == gns2 def test_ne(self): - gns = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) - gns2 = x509.GeneralNames([x509.RFC822Name(u"admin@cryptography.io")]) + gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) + gns2 = x509.GeneralNames([x509.RFC822Name("admin@cryptography.io")]) assert gns != gns2 assert gns != object() def test_hash(self): - gns = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) - gns2 = x509.GeneralNames([x509.DNSName(u"cryptography.io")]) - gns3 = x509.GeneralNames([x509.RFC822Name(u"admin@cryptography.io")]) + gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) + gns2 = x509.GeneralNames([x509.DNSName("cryptography.io")]) + gns3 = x509.GeneralNames([x509.RFC822Name("admin@cryptography.io")]) assert hash(gns) == hash(gns2) assert hash(gns) != hash(gns3) -class TestIssuerAlternativeName(object): +class TestIssuerAlternativeName: def test_get_values_for_type(self): - san = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) + san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) names = san.get_values_for_type(x509.DNSName) - assert names == [u"cryptography.io"] + assert names == ["cryptography.io"] def test_iter_names(self): san = x509.IssuerAlternativeName( - [x509.DNSName(u"cryptography.io"), x509.DNSName(u"crypto.local")] + [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(san) == 2 assert list(san) == [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), ] def test_indexing(self): ian = x509.IssuerAlternativeName( [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - x509.DNSName(u"another.local"), - x509.RFC822Name(u"email@another.local"), - x509.UniformResourceIdentifier(u"http://another.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), + x509.DNSName("another.local"), + x509.RFC822Name("email@another.local"), + x509.UniformResourceIdentifier("http://another.local"), ] ) assert ian[-1] == ian[4] @@ -2130,63 +2258,61 @@ def test_indexing(self): def test_invalid_general_names(self): with pytest.raises(TypeError): x509.IssuerAlternativeName( - [x509.DNSName(u"cryptography.io"), "invalid"] + [ + x509.DNSName("cryptography.io"), + "invalid", # type:ignore[list-item] + ] ) def test_repr(self): - san = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) - if not six.PY2: - assert repr(san) == ( - "])>)>" - ) - else: - assert repr(san) == ( - "])>)>" - ) + san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) + assert repr(san) == ( + "])>)>" + ) def test_eq(self): - san = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) - san2 = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) + san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) + san2 = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) assert san == san2 def test_ne(self): - san = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) + san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) san2 = x509.IssuerAlternativeName( - [x509.RFC822Name(u"admin@cryptography.io")] + [x509.RFC822Name("admin@cryptography.io")] ) assert san != san2 assert san != object() def test_hash(self): - ian = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) - ian2 = x509.IssuerAlternativeName([x509.DNSName(u"cryptography.io")]) + ian = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) + ian2 = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) ian3 = x509.IssuerAlternativeName( - [x509.RFC822Name(u"admin@cryptography.io")] + [x509.RFC822Name("admin@cryptography.io")] ) assert hash(ian) == hash(ian2) assert hash(ian) != hash(ian3) + def test_public_bytes(self): + ext = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) + assert ext.public_bytes() == b"0\x11\x82\x0fcryptography.io" -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRSAIssuerAlternativeNameExtension(object): + +class TestRSAIssuerAlternativeNameExtension: def test_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ian_uri.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.ISSUER_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.IssuerAlternativeName ) assert list(ext.value) == [ - x509.UniformResourceIdentifier(u"http://path.to.root/root.crt"), + x509.UniformResourceIdentifier("http://path.to.root/root.crt"), ] -class TestCRLNumber(object): +class TestCRLNumber: def test_eq(self): crl_number = x509.CRLNumber(15) assert crl_number == x509.CRLNumber(15) @@ -2202,7 +2328,7 @@ def test_repr(self): def test_invalid_number(self): with pytest.raises(TypeError): - x509.CRLNumber("notanumber") + x509.CRLNumber("notanumber") # type:ignore[arg-type] def test_hash(self): c1 = x509.CRLNumber(1) @@ -2211,31 +2337,35 @@ def test_hash(self): assert hash(c1) == hash(c2) assert hash(c1) != hash(c3) + def test_public_bytes(self): + ext = x509.CRLNumber(15) + assert ext.public_bytes() == b"\x02\x01\x0f" -class TestSubjectAlternativeName(object): + +class TestSubjectAlternativeName: def test_get_values_for_type(self): - san = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) + san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) names = san.get_values_for_type(x509.DNSName) - assert names == [u"cryptography.io"] + assert names == ["cryptography.io"] def test_iter_names(self): san = x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io"), x509.DNSName(u"crypto.local")] + [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(san) == 2 assert list(san) == [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), ] def test_indexing(self): san = x509.SubjectAlternativeName( [ - x509.DNSName(u"cryptography.io"), - x509.DNSName(u"crypto.local"), - x509.DNSName(u"another.local"), - x509.RFC822Name(u"email@another.local"), - x509.UniformResourceIdentifier(u"http://another.local"), + x509.DNSName("cryptography.io"), + x509.DNSName("crypto.local"), + x509.DNSName("another.local"), + x509.RFC822Name("email@another.local"), + x509.UniformResourceIdentifier("http://another.local"), ] ) assert san[-1] == san[4] @@ -2244,56 +2374,54 @@ def test_indexing(self): def test_invalid_general_names(self): with pytest.raises(TypeError): x509.SubjectAlternativeName( - [x509.DNSName(u"cryptography.io"), "invalid"] + [ + x509.DNSName("cryptography.io"), + "invalid", # type:ignore[list-item] + ] ) def test_repr(self): - san = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) - if not six.PY2: - assert repr(san) == ( - "])>)>" - ) - else: - assert repr(san) == ( - "])>)>" - ) + san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) + assert repr(san) == ( + "])>)>" + ) def test_eq(self): - san = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) - san2 = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) + san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) + san2 = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) assert san == san2 def test_ne(self): - san = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) + san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) san2 = x509.SubjectAlternativeName( - [x509.RFC822Name(u"admin@cryptography.io")] + [x509.RFC822Name("admin@cryptography.io")] ) assert san != san2 assert san != object() def test_hash(self): - san = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) - san2 = x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]) + san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) + san2 = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) san3 = x509.SubjectAlternativeName( - [x509.RFC822Name(u"admin@cryptography.io")] + [x509.RFC822Name("admin@cryptography.io")] ) assert hash(san) == hash(san2) assert hash(san) != hash(san3) + def test_public_bytes(self): + ext = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) + assert ext.public_bytes() == b"0\x11\x82\x0fcryptography.io" -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestRSASubjectAlternativeNameExtension(object): + +class TestRSASubjectAlternativeNameExtension: def test_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False @@ -2301,71 +2429,65 @@ def test_dns_name(self, backend): san = ext.value dns = san.get_values_for_type(x509.DNSName) - assert dns == [u"www.cryptography.io", u"cryptography.io"] + assert dns == ["www.cryptography.io", "cryptography.io"] def test_wildcard_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "wildcard_san.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) dns = ext.value.get_values_for_type(x509.DNSName) assert dns == [ - u"*.langui.sh", - u"langui.sh", - u"*.saseliminator.com", - u"saseliminator.com", + "*.langui.sh", + "langui.sh", + "*.saseliminator.com", + "saseliminator.com", ] def test_san_empty_hostname(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_empty_hostname.pem"), x509.load_pem_x509_certificate, - backend, ) san = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) + assert isinstance(san.value, x509.SubjectAlternativeName) dns = san.value.get_values_for_type(x509.DNSName) - assert dns == [u""] + assert dns == [""] def test_san_wildcard_idna_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_wildcard_idna.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) dns = ext.value.get_values_for_type(x509.DNSName) - assert dns == [u"*.xn--80ato2c.cryptography"] + assert dns == ["*.xn--80ato2c.cryptography"] def test_unsupported_gn(self, backend): cert = _load_cert( os.path.join("x509", "san_x400address.der"), x509.load_der_x509_certificate, - backend, ) - with pytest.raises(x509.UnsupportedGeneralNameType) as exc: + with pytest.raises(x509.UnsupportedGeneralNameType): cert.extensions - assert exc.value.type == 3 - def test_registered_id(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_registered_id.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False @@ -2378,26 +2500,24 @@ def test_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_uri_with_port.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) assert uri == [ - u"gopher://xn--80ato2c.cryptography:70/path?q=s#hel" u"lo", - u"http://someregulardomain.com", + "gopher://xn--80ato2c.cryptography:70/path?q=s#hel" "lo", + "http://someregulardomain.com", ] def test_ipaddress(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_ipaddr.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False @@ -2406,18 +2526,17 @@ def test_ipaddress(self, backend): ip = san.get_values_for_type(x509.IPAddress) assert [ - ipaddress.ip_address(u"127.0.0.1"), - ipaddress.ip_address(u"ff::"), + ipaddress.ip_address("127.0.0.1"), + ipaddress.ip_address("ff::"), ] == ip def test_dirname(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_dirname.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False @@ -2428,10 +2547,10 @@ def test_dirname(self, backend): assert [ x509.Name( [ - x509.NameAttribute(NameOID.COMMON_NAME, u"test"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Org"), + x509.NameAttribute(NameOID.COMMON_NAME, "test"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Org"), x509.NameAttribute( - NameOID.STATE_OR_PROVINCE_NAME, u"Texas" + NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) @@ -2441,10 +2560,9 @@ def test_rfc822name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_rfc822_idna.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False @@ -2452,13 +2570,12 @@ def test_rfc822name(self, backend): san = ext.value rfc822name = san.get_values_for_type(x509.RFC822Name) - assert [u"email@xn--eml-vla4c.com"] == rfc822name + assert ["email@xn--eml-vla4c.com"] == rfc822name def test_idna2003_invalid(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_idna2003_dnsname.pem"), x509.load_pem_x509_certificate, - backend, ) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName @@ -2466,33 +2583,31 @@ def test_idna2003_invalid(self, backend): assert len(san) == 1 [name] = san - assert name.value == u"xn--k4h.ws" + assert name.value == "xn--k4h.ws" def test_unicode_rfc822_name_dns_name_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_idna_names.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None rfc822_name = ext.value.get_values_for_type(x509.RFC822Name) dns_name = ext.value.get_values_for_type(x509.DNSName) uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) - assert rfc822_name == [u"email@xn--80ato2c.cryptography"] - assert dns_name == [u"xn--80ato2c.cryptography"] - assert uri == [u"https://www.xn--80ato2c.cryptography"] + assert rfc822_name == ["email@xn--80ato2c.cryptography"] + assert dns_name == ["xn--80ato2c.cryptography"] + assert uri == ["https://www.xn--80ato2c.cryptography"] def test_rfc822name_dnsname_ipaddress_directoryname_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_email_dns_ip_dirname_uri.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False @@ -2504,51 +2619,49 @@ def test_rfc822name_dnsname_ipaddress_directoryname_uri(self, backend): dns = san.get_values_for_type(x509.DNSName) ip = san.get_values_for_type(x509.IPAddress) dirname = san.get_values_for_type(x509.DirectoryName) - assert [u"user@cryptography.io"] == rfc822_name - assert [u"https://cryptography.io"] == uri - assert [u"cryptography.io"] == dns + assert ["user@cryptography.io"] == rfc822_name + assert ["https://cryptography.io"] == uri + assert ["cryptography.io"] == dns assert [ x509.Name( [ - x509.NameAttribute(NameOID.COMMON_NAME, u"dirCN"), + x509.NameAttribute(NameOID.COMMON_NAME, "dirCN"), x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"Cryptographic Authority" + NameOID.ORGANIZATION_NAME, "Cryptographic Authority" ), ] ) ] == dirname assert [ - ipaddress.ip_address(u"127.0.0.1"), - ipaddress.ip_address(u"ff::"), + ipaddress.ip_address("127.0.0.1"), + ipaddress.ip_address("ff::"), ] == ip def test_invalid_rfc822name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_rfc822_names.pem"), x509.load_pem_x509_certificate, - backend, ) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ).value values = san.get_values_for_type(x509.RFC822Name) assert values == [ - u"email", - u"email ", - u"email ", - u"email ", - u"myemail:", + "email", + "email ", + "email ", + "email ", + "myemail:", ] def test_other_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_other_name.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False @@ -2562,19 +2675,19 @@ def test_other_name(self, backend): othernames = ext.value.get_values_for_type(x509.OtherName) assert othernames == [expected] - def test_certbuilder(self, backend): + def test_certbuilder(self, rsa_key_2048: rsa.RSAPrivateKey, backend): sans = [ - u"*.example.org", - u"*.xn--4ca7aey.example.com", - u"foobar.example.net", + "*.example.org", + "*.xn--4ca7aey.example.com", + "foobar.example.net", ] - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 builder = _make_certbuilder(private_key) builder = builder.add_extension( SubjectAlternativeName(list(map(DNSName, sans))), True ) - cert = builder.sign(private_key, hashes.SHA1(), backend) + cert = builder.sign(private_key, hashes.SHA256(), backend) result = [ x.value for x in cert.extensions.get_extension_for_class( @@ -2584,18 +2697,13 @@ def test_certbuilder(self, backend): assert result == sans -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestExtendedKeyUsageExtension(object): +class TestExtendedKeyUsageExtension: def test_eku(self, backend): cert = _load_cert( os.path.join("x509", "custom", "extended_key_usage.pem"), x509.load_pem_x509_certificate, - backend, - ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.EXTENDED_KEY_USAGE ) + ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) assert ext is not None assert ext.critical is False @@ -2611,65 +2719,61 @@ def test_eku(self, backend): ] == list(ext.value) -class TestAccessDescription(object): +class TestAccessDescription: def test_invalid_access_method(self): with pytest.raises(TypeError): - x509.AccessDescription("notanoid", x509.DNSName(u"test")) + x509.AccessDescription( + "notanoid", x509.DNSName("test") # type:ignore[arg-type] + ) def test_invalid_access_location(self): with pytest.raises(TypeError): x509.AccessDescription( - AuthorityInformationAccessOID.CA_ISSUERS, "invalid" + AuthorityInformationAccessOID.CA_ISSUERS, + "invalid", # type:ignore[arg-type] ) def test_valid_nonstandard_method(self): ad = x509.AccessDescription( ObjectIdentifier("2.999.1"), - x509.UniformResourceIdentifier(u"http://example.com"), + x509.UniformResourceIdentifier("http://example.com"), ) assert ad is not None def test_repr(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), + ) + assert repr(ad) == ( + ", access_location=)>" ) - if not six.PY2: - assert repr(ad) == ( - ", access_location=)>" - ) - else: - assert repr(ad) == ( - ", access_location=)>" - ) def test_eq(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad2 = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) assert ad == ad2 def test_ne(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad2 = x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad3 = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://notthesame"), + x509.UniformResourceIdentifier("http://notthesame"), ) assert ad != ad2 assert ad != ad3 @@ -2678,28 +2782,28 @@ def test_ne(self): def test_hash(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad2 = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad3 = x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) assert hash(ad) == hash(ad2) assert hash(ad) != hash(ad3) -class TestPolicyConstraints(object): +class TestPolicyConstraints: def test_invalid_explicit_policy(self): with pytest.raises(TypeError): - x509.PolicyConstraints("invalid", None) + x509.PolicyConstraints("invalid", None) # type:ignore[arg-type] def test_invalid_inhibit_policy(self): with pytest.raises(TypeError): - x509.PolicyConstraints(None, "invalid") + x509.PolicyConstraints(None, "invalid") # type:ignore[arg-type] def test_both_none(self): with pytest.raises(ValueError): @@ -2709,8 +2813,8 @@ def test_repr(self): pc = x509.PolicyConstraints(0, None) assert repr(pc) == ( - u"" + "" ) def test_eq(self): @@ -2733,15 +2837,16 @@ def test_hash(self): assert hash(pc) == hash(pc2) assert hash(pc) != hash(pc3) + def test_public_bytes(self): + ext = x509.PolicyConstraints(2, 1) + assert ext.public_bytes() == b"0\x06\x80\x01\x02\x81\x01\x01" + -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestPolicyConstraintsExtension(object): +class TestPolicyConstraintsExtension: def test_inhibit_policy_mapping(self, backend): cert = _load_cert( os.path.join("x509", "department-of-state-root.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.POLICY_CONSTRAINTS, @@ -2757,7 +2862,6 @@ def test_require_explicit_policy(self, backend): cert = _load_cert( os.path.join("x509", "custom", "policy_constraints_explicit.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.POLICY_CONSTRAINTS @@ -2768,24 +2872,31 @@ def test_require_explicit_policy(self, backend): inhibit_policy_mapping=None, ) + def test_public_bytes(self): + ext = x509.PolicyConstraints( + require_explicit_policy=None, + inhibit_policy_mapping=0, + ) + assert ext.public_bytes() == b"\x30\x03\x81\x01\x00" + -class TestAuthorityInformationAccess(object): +class TestAuthorityInformationAccess: def test_invalid_descriptions(self): with pytest.raises(TypeError): - x509.AuthorityInformationAccess(["notanAccessDescription"]) + x509.AuthorityInformationAccess( + ["notanAccessDescription"] # type:ignore[list-item] + ) def test_iter_len(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) @@ -2793,11 +2904,11 @@ def test_iter_len(self): assert list(aia) == [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt"), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] @@ -2805,7 +2916,7 @@ def test_iter_input(self): desc = [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ] aia = x509.AuthorityInformationAccess(iter(desc)) @@ -2816,49 +2927,34 @@ def test_repr(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) - if not six.PY2: - assert repr(aia) == ( - ", acces" - "s_location=)>, , access_lo" - "cation=)>])>" - ) - else: - assert repr(aia) == ( - ", acces" - "s_location=)>, , access_lo" - "cation=)>])>" - ) + assert repr(aia) == ( + ", acces" + "s_location=)>, , access_lo" + "cation=)>])>" + ) def test_eq(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) @@ -2866,13 +2962,11 @@ def test_eq(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) @@ -2883,13 +2977,11 @@ def test_ne(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) @@ -2897,7 +2989,7 @@ def test_ne(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), ] ) @@ -2910,25 +3002,23 @@ def test_indexing(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp2.domain.com"), + x509.UniformResourceIdentifier("http://ocsp2.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp3.domain.com"), + x509.UniformResourceIdentifier("http://ocsp3.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp4.domain.com"), + x509.UniformResourceIdentifier("http://ocsp4.domain.com"), ), ] ) @@ -2940,13 +3030,11 @@ def test_hash(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) @@ -2954,13 +3042,11 @@ def test_hash(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) @@ -2968,35 +3054,55 @@ def test_hash(self): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.other.com"), + x509.UniformResourceIdentifier("http://ocsp.other.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier( - u"http://domain.com/ca.crt" - ), + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) assert hash(aia) == hash(aia2) assert hash(aia) != hash(aia3) + def test_public_bytes(self): + ext = x509.AuthorityInformationAccess( + [ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier("http://ocsp.other.com"), + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier("http://domain.com/ca.crt"), + ), + ] + ) + assert ( + ext.public_bytes() + == b"0I0!\x06\x08+\x06\x01\x05\x05\x070\x01\x86\x15http://" + b"ocsp.other.com0$\x06\x08+\x06\x01\x05\x05\x070\x02\x86\x18" + b"http://domain.com/ca.crt" + ) + -class TestSubjectInformationAccess(object): +class TestSubjectInformationAccess: def test_invalid_descriptions(self): with pytest.raises(TypeError): - x509.SubjectInformationAccess(["notanAccessDescription"]) + x509.SubjectInformationAccess( + ["notanAccessDescription"] # type:ignore[list-item] + ) def test_iter_len(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) @@ -3004,11 +3110,11 @@ def test_iter_len(self): assert list(sia) == [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] @@ -3016,7 +3122,7 @@ def test_iter_input(self): desc = [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ) ] sia = x509.SubjectInformationAccess(iter(desc)) @@ -3027,35 +3133,27 @@ def test_repr(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ) ] ) - if not six.PY2: - assert repr(sia) == ( - ", access_location=)>])>" - ) - else: - assert repr(sia) == ( - ", access_location=)>])>" - ) + assert repr(sia) == ( + ", access_location=)>])>" + ) def test_eq(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) @@ -3063,11 +3161,11 @@ def test_eq(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) @@ -3078,11 +3176,11 @@ def test_ne(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) @@ -3090,7 +3188,7 @@ def test_ne(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), ] ) @@ -3103,23 +3201,23 @@ def test_indexing(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca3.domain.com"), + x509.UniformResourceIdentifier("http://ca3.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca4.domain.com"), + x509.UniformResourceIdentifier("http://ca4.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca5.domain.com"), + x509.UniformResourceIdentifier("http://ca5.domain.com"), ), ] ) @@ -3131,11 +3229,11 @@ def test_hash(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) @@ -3143,11 +3241,11 @@ def test_hash(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca2.domain.com"), + x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) @@ -3155,26 +3253,43 @@ def test_hash(self): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca.domain.com"), + x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"http://ca3.domain.com"), + x509.UniformResourceIdentifier("http://ca3.domain.com"), ), ] ) assert hash(sia) == hash(sia2) assert hash(sia) != hash(sia3) + def test_public_bytes(self): + ext = x509.SubjectInformationAccess( + [ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier("http://ca.domain.com"), + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier("http://ca3.domain.com"), + ), + ] + ) + assert ( + ext.public_bytes() + == b"0E0 \x06\x08+\x06\x01\x05\x05\x070\x05\x86\x14http://" + b"ca.domain.com0!\x06\x08+\x06\x01\x05\x05\x070\x05\x86\x15" + b"http://ca3.domain.com" + ) + -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestSubjectInformationAccessExtension(object): +class TestSubjectInformationAccessExtension: def test_sia(self, backend): cert = _load_cert( os.path.join("x509", "custom", "sia.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_INFORMATION_ACCESS @@ -3186,26 +3301,23 @@ def test_sia(self, backend): [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, - x509.UniformResourceIdentifier(u"https://my.ca.issuer/"), + x509.UniformResourceIdentifier("https://my.ca.issuer/"), ), x509.AccessDescription( x509.ObjectIdentifier("2.999.7"), x509.UniformResourceIdentifier( - u"gopher://info-mac-archive" + "gopher://info-mac-archive" ), ), ] ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestAuthorityInformationAccessExtension(object): +class TestAuthorityInformationAccessExtension: def test_aia_ocsp_ca_issuers(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS @@ -3217,12 +3329,12 @@ def test_aia_ocsp_ca_issuers(self, backend): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://gv.symcd.com"), + x509.UniformResourceIdentifier("http://gv.symcd.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier( - u"http://gv.symcb.com/gv.crt" + "http://gv.symcb.com/gv.crt" ), ), ] @@ -3232,7 +3344,6 @@ def test_aia_multiple_ocsp_ca_issuers(self, backend): cert = _load_cert( os.path.join("x509", "custom", "aia_ocsp_ca_issuers.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS @@ -3244,11 +3355,11 @@ def test_aia_multiple_ocsp_ca_issuers(self, backend): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp2.domain.com"), + x509.UniformResourceIdentifier("http://ocsp2.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, @@ -3256,10 +3367,10 @@ def test_aia_multiple_ocsp_ca_issuers(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"myCN" + NameOID.COMMON_NAME, "myCN" ), x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"some Org" + NameOID.ORGANIZATION_NAME, "some Org" ), ] ) @@ -3272,7 +3383,6 @@ def test_aia_ocsp_only(self, backend): cert = _load_cert( os.path.join("x509", "custom", "aia_ocsp.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS @@ -3284,7 +3394,7 @@ def test_aia_ocsp_only(self, backend): [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com"), + x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), ] ) @@ -3293,7 +3403,6 @@ def test_aia_ca_issuers_only(self, backend): cert = _load_cert( os.path.join("x509", "custom", "aia_ca_issuers.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS @@ -3309,10 +3418,31 @@ def test_aia_ca_issuers_only(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"myCN" + NameOID.COMMON_NAME, "myCN" + ), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, "some Org" + ), + ] + ) + ), + ), + ] + ) + + def test_public_bytes(self): + ext = x509.AuthorityInformationAccess( + [ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DirectoryName( + x509.Name( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, "myCN" ), x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"some Org" + NameOID.ORGANIZATION_NAME, "some Org" ), ] ) @@ -3320,19 +3450,22 @@ def test_aia_ca_issuers_only(self, backend): ), ] ) + assert ( + ext.public_bytes() + == b'0200\x06\x08+\x06\x01\x05\x05\x070\x02\xa4$0"1\r0\x0b\x06' + b"\x03U\x04\x03\x0c\x04myCN1\x110\x0f\x06\x03U\x04\n\x0c\x08" + b"some Org" + ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestAuthorityKeyIdentifierExtension(object): +class TestAuthorityKeyIdentifierExtension: def test_aki_keyid(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ext = cert.extensions.get_extension_for_class( + x509.AuthorityKeyIdentifier ) assert ext is not None assert ext.critical is False @@ -3347,10 +3480,9 @@ def test_aki_all_fields(self, backend): cert = _load_cert( os.path.join("x509", "custom", "authority_key_identifier.pem"), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ext = cert.extensions.get_extension_for_class( + x509.AuthorityKeyIdentifier ) assert ext is not None assert ext.critical is False @@ -3362,9 +3494,9 @@ def test_aki_all_fields(self, backend): x509.DirectoryName( x509.Name( [ - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io" + NameOID.COMMON_NAME, "cryptography.io" ), ] ) @@ -3378,10 +3510,9 @@ def test_aki_no_keyid(self, backend): "x509", "custom", "authority_key_identifier_no_keyid.pem" ), x509.load_pem_x509_certificate, - backend, ) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ext = cert.extensions.get_extension_for_class( + x509.AuthorityKeyIdentifier ) assert ext is not None assert ext.critical is False @@ -3391,9 +3522,9 @@ def test_aki_no_keyid(self, backend): x509.DirectoryName( x509.Name( [ - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"PyCA"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io" + NameOID.COMMON_NAME, "cryptography.io" ), ] ) @@ -3405,31 +3536,27 @@ def test_from_certificate(self, backend): issuer_cert = _load_cert( os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), x509.load_pem_x509_certificate, - backend, ) cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_KEY_IDENTIFIER ) - aki = x509.AuthorityKeyIdentifier.from_issuer_public_key( - issuer_cert.public_key() - ) + public_key = issuer_cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + aki = x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key) assert ext.value == aki def test_from_issuer_subject_key_identifier(self, backend): issuer_cert = _load_cert( os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), x509.load_pem_x509_certificate, - backend, ) cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_KEY_IDENTIFIER @@ -3443,12 +3570,12 @@ def test_from_issuer_subject_key_identifier(self, backend): assert ext.value == aki -class TestNameConstraints(object): +class TestNameConstraints: def test_ipaddress_wrong_type(self): with pytest.raises(TypeError): x509.NameConstraints( permitted_subtrees=[ - x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) ], excluded_subtrees=None, ) @@ -3457,13 +3584,35 @@ def test_ipaddress_wrong_type(self): x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[ - x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) ], ) def test_ipaddress_allowed_type(self): - permitted = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29"))] - excluded = [x509.IPAddress(ipaddress.IPv4Network(u"10.10.0.0/24"))] + permitted = [x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/29"))] + excluded = [x509.IPAddress(ipaddress.IPv4Network("10.10.0.0/24"))] + nc = x509.NameConstraints( + permitted_subtrees=permitted, excluded_subtrees=excluded + ) + assert nc.permitted_subtrees == permitted + assert nc.excluded_subtrees == excluded + + def test_dnsname_wrong_value(self): + with pytest.raises(ValueError): + x509.NameConstraints( + permitted_subtrees=[x509.DNSName("*.example.com")], + excluded_subtrees=None, + ) + + with pytest.raises(ValueError): + x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[x509.DNSName("*.example.com")], + ) + + def test_dnsname_allowed_value(self): + permitted = [x509.DNSName("example.com")] + excluded = [x509.DNSName("www.example.com")] nc = x509.NameConstraints( permitted_subtrees=permitted, excluded_subtrees=excluded ) @@ -3472,18 +3621,18 @@ def test_ipaddress_allowed_type(self): def test_invalid_permitted_subtrees(self): with pytest.raises(TypeError): - x509.NameConstraints("badpermitted", None) + x509.NameConstraints("badpermitted", None) # type:ignore[arg-type] def test_invalid_excluded_subtrees(self): with pytest.raises(TypeError): - x509.NameConstraints(None, "badexcluded") + x509.NameConstraints(None, "badexcluded") # type:ignore[arg-type] def test_no_subtrees(self): with pytest.raises(ValueError): x509.NameConstraints(None, None) def test_permitted_none(self): - excluded = [x509.DNSName(u"name.local")] + excluded = [x509.DNSName("name.local")] nc = x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=excluded ) @@ -3491,7 +3640,7 @@ def test_permitted_none(self): assert nc.excluded_subtrees is not None def test_excluded_none(self): - permitted = [x509.DNSName(u"name.local")] + permitted = [x509.DNSName("name.local")] nc = x509.NameConstraints( permitted_subtrees=permitted, excluded_subtrees=None ) @@ -3499,52 +3648,53 @@ def test_excluded_none(self): assert nc.excluded_subtrees is None def test_iter_input(self): - subtrees = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24"))] + subtrees = [x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24"))] nc = x509.NameConstraints(iter(subtrees), iter(subtrees)) + assert nc.permitted_subtrees is not None assert list(nc.permitted_subtrees) == subtrees + assert nc.excluded_subtrees is not None assert list(nc.excluded_subtrees) == subtrees + def test_empty_lists(self): + with pytest.raises(ValueError): + x509.NameConstraints(permitted_subtrees=None, excluded_subtrees=[]) + with pytest.raises(ValueError): + x509.NameConstraints(permitted_subtrees=[], excluded_subtrees=None) + def test_repr(self): - permitted = [x509.DNSName(u"name.local"), x509.DNSName(u"name2.local")] + permitted = [x509.DNSName("name.local"), x509.DNSName("name2.local")] nc = x509.NameConstraints( permitted_subtrees=permitted, excluded_subtrees=None ) - if not six.PY2: - assert repr(nc) == ( - ", ], excluded_subtrees=None)>" - ) - else: - assert repr(nc) == ( - ", ], excluded_subtrees=None)>" - ) + assert repr(nc) == ( + ", ], excluded_subtrees=None)>" + ) def test_eq(self): nc = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")], + permitted_subtrees=[x509.DNSName("name.local")], + excluded_subtrees=[x509.DNSName("name2.local")], ) nc2 = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")], + permitted_subtrees=[x509.DNSName("name.local")], + excluded_subtrees=[x509.DNSName("name2.local")], ) assert nc == nc2 def test_ne(self): nc = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")], + permitted_subtrees=[x509.DNSName("name.local")], + excluded_subtrees=[x509.DNSName("name2.local")], ) nc2 = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], + permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=None, ) nc3 = x509.NameConstraints( permitted_subtrees=None, - excluded_subtrees=[x509.DNSName(u"name2.local")], + excluded_subtrees=[x509.DNSName("name2.local")], ) assert nc != nc2 @@ -3553,44 +3703,51 @@ def test_ne(self): def test_hash(self): nc = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")], + permitted_subtrees=[x509.DNSName("name.local")], + excluded_subtrees=[x509.DNSName("name2.local")], ) nc2 = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], - excluded_subtrees=[x509.DNSName(u"name2.local")], + permitted_subtrees=[x509.DNSName("name.local")], + excluded_subtrees=[x509.DNSName("name2.local")], ) nc3 = x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"name.local")], + permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=None, ) nc4 = x509.NameConstraints( permitted_subtrees=None, - excluded_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName("name.local")], ) assert hash(nc) == hash(nc2) assert hash(nc) != hash(nc3) assert hash(nc3) != hash(nc4) + def test_public_bytes(self): + ext = x509.NameConstraints( + permitted_subtrees=[x509.DNSName("name.local")], + excluded_subtrees=[x509.DNSName("name2.local")], + ) + assert ( + ext.public_bytes() + == b"0!\xa0\x0e0\x0c\x82\nname.local\xa1\x0f0\r\x82\x0bname2.local" + ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestNameConstraintsExtension(object): + +class TestNameConstraintsExtension: def test_permitted_excluded(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted_excluded_2.pem"), x509.load_pem_x509_certificate, - backend, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"zombo.local")], + permitted_subtrees=[x509.DNSName("zombo.local")], excluded_subtrees=[ x509.DirectoryName( x509.Name( - [x509.NameAttribute(NameOID.COMMON_NAME, u"zombo")] + [x509.NameAttribute(NameOID.COMMON_NAME, "zombo")] ) ) ], @@ -3600,13 +3757,12 @@ def test_permitted(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted_2.pem"), x509.load_pem_x509_certificate, - backend, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( - permitted_subtrees=[x509.DNSName(u"zombo.local")], + permitted_subtrees=[x509.DNSName("zombo.local")], excluded_subtrees=None, ) @@ -3614,15 +3770,14 @@ def test_permitted_with_leading_period(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted.pem"), x509.load_pem_x509_certificate, - backend, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[ - x509.DNSName(u".cryptography.io"), - x509.UniformResourceIdentifier(u"ftp://cryptography.test"), + x509.DNSName(".cryptography.io"), + x509.UniformResourceIdentifier("ftp://cryptography.test"), ], excluded_subtrees=None, ) @@ -3631,7 +3786,6 @@ def test_excluded_with_leading_period(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_excluded.pem"), x509.load_pem_x509_certificate, - backend, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS @@ -3639,8 +3793,8 @@ def test_excluded_with_leading_period(self, backend): assert nc == x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[ - x509.DNSName(u".cryptography.io"), - x509.UniformResourceIdentifier(u"gopher://cryptography.test"), + x509.DNSName(".cryptography.io"), + x509.UniformResourceIdentifier("gopher://cryptography.test"), ], ) @@ -3648,19 +3802,18 @@ def test_permitted_excluded_with_ips(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted_excluded.pem"), x509.load_pem_x509_certificate, - backend, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[ - x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")), - x509.IPAddress(ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/96")), + x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24")), + x509.IPAddress(ipaddress.IPv6Network("FF:0:0:0:0:0:0:0/96")), ], excluded_subtrees=[ - x509.DNSName(u".domain.com"), - x509.UniformResourceIdentifier(u"http://test.local"), + x509.DNSName(".domain.com"), + x509.UniformResourceIdentifier("http://test.local"), ], ) @@ -3668,47 +3821,65 @@ def test_single_ip_netmask(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_single_ip_netmask.pem"), x509.load_pem_x509_certificate, - backend, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[ - x509.IPAddress(ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/128")), - x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.1/32")), + x509.IPAddress(ipaddress.IPv6Network("FF:0:0:0:0:0:0:0/128")), + x509.IPAddress(ipaddress.IPv4Network("192.168.0.1/32")), ], excluded_subtrees=None, ) - def test_invalid_netmask(self, backend): + def test_ip_invalid_length(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "nc_ip_invalid_length.pem"), + x509.load_pem_x509_certificate, + ) + with pytest.raises(ValueError): + cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ) + + def test_invalid_ipv6_netmask(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_invalid_ip_netmask.pem"), x509.load_pem_x509_certificate, - backend, ) with pytest.raises(ValueError): cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ) - def test_certbuilder(self, backend): + def test_invalid_ipv4_netmask(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "nc_invalid_ip4_netmask.der"), + x509.load_der_x509_certificate, + ) + with pytest.raises(ValueError): + cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ) + + def test_certbuilder(self, rsa_key_2048: rsa.RSAPrivateKey, backend): permitted = [ - u".example.org", - u".xn--4ca7aey.example.com", - u"foobar.example.net", + ".example.org", + ".xn--4ca7aey.example.com", + "foobar.example.net", ] - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 builder = _make_certbuilder(private_key) builder = builder.add_extension( NameConstraints( permitted_subtrees=list(map(DNSName, permitted)), - excluded_subtrees=[], + excluded_subtrees=None, ), True, ) - cert = builder.sign(private_key, hashes.SHA1(), backend) + cert = builder.sign(private_key, hashes.SHA256(), backend) result = [ x.value for x in cert.extensions.get_extension_for_class( @@ -3717,46 +3888,75 @@ def test_certbuilder(self, backend): ] assert result == permitted + def test_public_bytes(self): + ext = x509.NameConstraints( + permitted_subtrees=[x509.DNSName("zombo.local")], + excluded_subtrees=[ + x509.DirectoryName( + x509.Name( + [x509.NameAttribute(NameOID.COMMON_NAME, "zombo")] + ) + ) + ], + ) + assert ( + ext.public_bytes() + == b"0)\xa0\x0f0\r\x82\x0bzombo.local\xa1\x160\x14\xa4\x120\x101" + b"\x0e0\x0c\x06\x03U\x04\x03\x0c\x05zombo" + ) + -class TestDistributionPoint(object): +class TestDistributionPoint: def test_distribution_point_full_name_not_general_names(self): with pytest.raises(TypeError): - x509.DistributionPoint(["notgn"], None, None, None) + x509.DistributionPoint( + ["notgn"], None, None, None # type:ignore[list-item] + ) def test_distribution_point_relative_name_not_name(self): with pytest.raises(TypeError): - x509.DistributionPoint(None, "notname", None, None) + x509.DistributionPoint( + None, "notname", None, None # type:ignore[arg-type] + ) def test_distribution_point_full_and_relative_not_none(self): with pytest.raises(ValueError): - x509.DistributionPoint("data", "notname", None, None) + x509.DistributionPoint( + "data", "notname", None, None # type:ignore[arg-type] + ) + + def test_no_full_name_relative_name_or_crl_issuer(self): + with pytest.raises(ValueError): + x509.DistributionPoint(None, None, None, None) def test_crl_issuer_not_general_names(self): with pytest.raises(TypeError): - x509.DistributionPoint(None, None, None, ["notgn"]) + x509.DistributionPoint( + None, None, None, ["notgn"] # type:ignore[list-item] + ) def test_reason_not_reasonflags(self): with pytest.raises(TypeError): x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, - frozenset(["notreasonflags"]), + frozenset(["notreasonflags"]), # type:ignore[list-item] None, ) def test_reason_not_frozenset(self): with pytest.raises(TypeError): x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, - [x509.ReasonFlags.ca_compromise], + [x509.ReasonFlags.ca_compromise], # type:ignore[arg-type] None, ) def test_disallowed_reasons(self): with pytest.raises(ValueError): x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.unspecified]), None, @@ -3764,7 +3964,7 @@ def test_disallowed_reasons(self): with pytest.raises(ValueError): x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.remove_from_crl]), None, @@ -3778,7 +3978,7 @@ def test_reason_only(self): def test_eq(self): dp = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ @@ -3786,7 +3986,7 @@ def test_eq(self): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"Important CA" + NameOID.COMMON_NAME, "Important CA" ) ] ) @@ -3794,7 +3994,7 @@ def test_eq(self): ], ) dp2 = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ @@ -3802,7 +4002,7 @@ def test_eq(self): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"Important CA" + NameOID.COMMON_NAME, "Important CA" ) ] ) @@ -3813,7 +4013,7 @@ def test_eq(self): def test_ne(self): dp = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ @@ -3821,7 +4021,7 @@ def test_ne(self): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"Important CA" + NameOID.COMMON_NAME, "Important CA" ) ] ) @@ -3829,7 +4029,7 @@ def test_ne(self): ], ) dp2 = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, None, None, @@ -3838,11 +4038,11 @@ def test_ne(self): assert dp != object() def test_iter_input(self): - name = [x509.UniformResourceIdentifier(u"http://crypt.og/crl")] + name = [x509.UniformResourceIdentifier("http://crypt.og/crl")] issuer = [ x509.DirectoryName( x509.Name( - [x509.NameAttribute(NameOID.COMMON_NAME, u"Important CA")] + [x509.NameAttribute(NameOID.COMMON_NAME, "Important CA")] ) ) ] @@ -3852,14 +4052,16 @@ def test_iter_input(self): frozenset([x509.ReasonFlags.ca_compromise]), iter(issuer), ) + assert dp.full_name is not None assert list(dp.full_name) == name + assert dp.crl_issuer is not None assert list(dp.crl_issuer) == issuer def test_repr(self): dp = x509.DistributionPoint( None, x509.RelativeDistinguishedName( - [x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")] + [x509.NameAttribute(NameOID.COMMON_NAME, "myCN")] ), frozenset([x509.ReasonFlags.ca_compromise]), [ @@ -3867,31 +4069,23 @@ def test_repr(self): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"Important CA" + NameOID.COMMON_NAME, "Important CA" ) ] ) ) ], ) - if not six.PY2: - assert repr(dp) == ( - ", reasons=frozenset({}), crl_issuer=[)>])>" - ) - else: - assert repr(dp) == ( - ", reasons=frozenset([]), crl_issuer=[)>])>" - ) + assert repr(dp) == ( + ", reasons=frozenset({}), crl_issuer=[)>])>" + ) def test_hash(self): dp = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ @@ -3899,7 +4093,7 @@ def test_hash(self): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"Important CA" + NameOID.COMMON_NAME, "Important CA" ) ] ) @@ -3907,7 +4101,7 @@ def test_hash(self): ], ) dp2 = x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ @@ -3915,7 +4109,7 @@ def test_hash(self): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"Important CA" + NameOID.COMMON_NAME, "Important CA" ) ] ) @@ -3925,7 +4119,7 @@ def test_hash(self): dp3 = x509.DistributionPoint( None, x509.RelativeDistinguishedName( - [x509.NameAttribute(NameOID.COMMON_NAME, u"myCN")] + [x509.NameAttribute(NameOID.COMMON_NAME, "myCN")] ), None, None, @@ -3934,16 +4128,18 @@ def test_hash(self): assert hash(dp) != hash(dp3) -class TestFreshestCRL(object): +class TestFreshestCRL: def test_invalid_distribution_points(self): with pytest.raises(TypeError): - x509.FreshestCRL(["notadistributionpoint"]) + x509.FreshestCRL( + ["notadistributionpoint"] # type:ignore[list-item] + ) def test_iter_len(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], + [x509.UniformResourceIdentifier("http://domain")], None, None, None, @@ -3953,7 +4149,7 @@ def test_iter_len(self): assert len(fcrl) == 1 assert list(fcrl) == [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], + [x509.UniformResourceIdentifier("http://domain")], None, None, None, @@ -3963,7 +4159,7 @@ def test_iter_len(self): def test_iter_input(self): points = [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], + [x509.UniformResourceIdentifier("http://domain")], None, None, None, @@ -3976,33 +4172,25 @@ def test_repr(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), None, ), ] ) - if not six.PY2: - assert repr(fcrl) == ( - "], relative" - "_name=None, reasons=frozenset({}), crl_issuer=None)>])>" - ) - else: - assert repr(fcrl) == ( - "], relative" - "_name=None, reasons=frozenset([]), crl_issuer=None)>])>" - ) + assert repr(fcrl) == ( + "], relative" + "_name=None, reasons=frozenset({}), crl_issuer=None)>])>" + ) def test_eq(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4010,14 +4198,14 @@ def test_eq(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl2 = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4025,7 +4213,7 @@ def test_eq(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) @@ -4035,7 +4223,7 @@ def test_ne(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4043,14 +4231,14 @@ def test_ne(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl2 = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain2")], + [x509.UniformResourceIdentifier("ftp://domain2")], None, frozenset( [ @@ -4058,24 +4246,24 @@ def test_ne(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl3 = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl4 = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4083,7 +4271,7 @@ def test_ne(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing2")], + [x509.UniformResourceIdentifier("uri://thing2")], ), ] ) @@ -4096,7 +4284,7 @@ def test_hash(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4104,14 +4292,14 @@ def test_hash(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl2 = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4119,17 +4307,17 @@ def test_hash(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl3 = x509.FreshestCRL( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) @@ -4143,54 +4331,72 @@ def test_indexing(self): None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing2")], + [x509.UniformResourceIdentifier("uri://thing2")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing3")], + [x509.UniformResourceIdentifier("uri://thing3")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing4")], + [x509.UniformResourceIdentifier("uri://thing4")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing5")], + [x509.UniformResourceIdentifier("uri://thing5")], ), ] ) assert fcrl[-1] == fcrl[4] assert fcrl[2:6:2] == [fcrl[2], fcrl[4]] + def test_public_bytes(self): + ext = x509.FreshestCRL( + [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier("ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + None, + ), + ] + ) + assert ( + ext.public_bytes() + == b"0\x180\x16\xa0\x10\xa0\x0e\x86\x0cftp://domain\x81\x02\x06@" + ) -class TestCRLDistributionPoints(object): + +class TestCRLDistributionPoints: def test_invalid_distribution_points(self): with pytest.raises(TypeError): - x509.CRLDistributionPoints(["notadistributionpoint"]) + x509.CRLDistributionPoints( + ["notadistributionpoint"], # type:ignore[list-item] + ) def test_iter_len(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], + [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4205,13 +4411,13 @@ def test_iter_len(self): assert len(cdp) == 2 assert list(cdp) == [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], + [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4226,7 +4432,7 @@ def test_iter_len(self): def test_iter_input(self): points = [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"http://domain")], + [x509.UniformResourceIdentifier("http://domain")], None, None, None, @@ -4239,33 +4445,25 @@ def test_repr(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), None, ), ] ) - if not six.PY2: - assert repr(cdp) == ( - "], relative" - "_name=None, reasons=frozenset({}), crl_issuer=None)>])>" - ) - else: - assert repr(cdp) == ( - "], relative" - "_name=None, reasons=frozenset([]), crl_issuer=None)>])>" - ) + assert repr(cdp) == ( + "], relative" + "_name=None, reasons=frozenset({}), crl_issuer=None)>])>" + ) def test_eq(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4273,14 +4471,14 @@ def test_eq(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp2 = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4288,7 +4486,7 @@ def test_eq(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) @@ -4298,7 +4496,7 @@ def test_ne(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4306,14 +4504,14 @@ def test_ne(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp2 = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain2")], + [x509.UniformResourceIdentifier("ftp://domain2")], None, frozenset( [ @@ -4321,24 +4519,24 @@ def test_ne(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp3 = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp4 = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4346,7 +4544,7 @@ def test_ne(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing2")], + [x509.UniformResourceIdentifier("uri://thing2")], ), ] ) @@ -4359,7 +4557,7 @@ def test_hash(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4367,14 +4565,14 @@ def test_hash(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp2 = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ @@ -4382,17 +4580,17 @@ def test_hash(self): x509.ReasonFlags.ca_compromise, ] ), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp3 = x509.CRLDistributionPoints( [ x509.DistributionPoint( - [x509.UniformResourceIdentifier(u"ftp://domain")], + [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), ] ) @@ -4406,48 +4604,67 @@ def test_indexing(self): None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing")], + [x509.UniformResourceIdentifier("uri://thing")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing2")], + [x509.UniformResourceIdentifier("uri://thing2")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing3")], + [x509.UniformResourceIdentifier("uri://thing3")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing4")], + [x509.UniformResourceIdentifier("uri://thing4")], ), x509.DistributionPoint( None, None, None, - [x509.UniformResourceIdentifier(u"uri://thing5")], + [x509.UniformResourceIdentifier("uri://thing5")], ), ] ) assert ci[-1] == ci[4] assert ci[2:6:2] == [ci[2], ci[4]] + def test_public_bytes(self): + ext = x509.CRLDistributionPoints( + [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier("ftp://domain")], + None, + frozenset( + [ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ] + ), + [x509.UniformResourceIdentifier("uri://thing")], + ), + ] + ) + assert ( + ext.public_bytes() + == b"0'0%\xa0\x10\xa0\x0e\x86\x0cftp://domain\x81\x02\x05`\xa2\r" + b"\x86\x0buri://thing" + ) + -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestCRLDistributionPointsExtension(object): +class TestCRLDistributionPointsExtension: def test_fullname_and_crl_issuer(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidcRLIssuerTest28EE.crt" ), x509.load_der_x509_certificate, - backend, ) cdps = cert.extensions.get_extension_for_oid( @@ -4462,19 +4679,19 @@ def test_fullname_and_crl_issuer(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, - u"Test Certificates 2011", + "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, - u"indirectCRL CA3 cRLIssuer", + "indirectCRL CA3 cRLIssuer", ), x509.NameAttribute( NameOID.COMMON_NAME, - u"indirect CRL for indirectCRL CA3", + "indirect CRL for indirectCRL CA3", ), ] ) @@ -4487,15 +4704,15 @@ def test_fullname_and_crl_issuer(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, - u"Test Certificates 2011", + "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, - u"indirectCRL CA3 cRLIssuer", + "indirectCRL CA3 cRLIssuer", ), ] ) @@ -4511,7 +4728,6 @@ def test_relativename_and_crl_issuer(self, backend): "x509", "PKITS_data", "certs", "ValidcRLIssuerTest29EE.crt" ), x509.load_der_x509_certificate, - backend, ) cdps = cert.extensions.get_extension_for_oid( @@ -4526,7 +4742,7 @@ def test_relativename_and_crl_issuer(self, backend): [ x509.NameAttribute( NameOID.COMMON_NAME, - u"indirect CRL for indirectCRL CA3", + "indirect CRL for indirectCRL CA3", ), ] ), @@ -4536,15 +4752,15 @@ def test_relativename_and_crl_issuer(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, - u"Test Certificates 2011", + "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, - u"indirectCRL CA3 cRLIssuer", + "indirectCRL CA3 cRLIssuer", ), ] ) @@ -4560,7 +4776,6 @@ def test_fullname_crl_issuer_reasons(self, backend): "x509", "custom", "cdp_fullname_reasons_crl_issuer.pem" ), x509.load_pem_x509_certificate, - backend, ) cdps = cert.extensions.get_extension_for_oid( @@ -4572,7 +4787,7 @@ def test_fullname_crl_issuer_reasons(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -4587,13 +4802,13 @@ def test_fullname_crl_issuer_reasons(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( - NameOID.ORGANIZATION_NAME, u"PyCA" + NameOID.ORGANIZATION_NAME, "PyCA" ), x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography CA" + NameOID.COMMON_NAME, "cryptography CA" ), ] ) @@ -4607,7 +4822,6 @@ def test_all_reasons(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_all_reasons.pem"), x509.load_pem_x509_certificate, - backend, ) cdps = cert.extensions.get_extension_for_oid( @@ -4619,7 +4833,7 @@ def test_all_reasons(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://domain.com/some.crl" + "http://domain.com/some.crl" ) ], relative_name=None, @@ -4644,7 +4858,6 @@ def test_single_reason(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_reason_aa_compromise.pem"), x509.load_pem_x509_certificate, - backend, ) cdps = cert.extensions.get_extension_for_oid( @@ -4656,7 +4869,7 @@ def test_single_reason(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://domain.com/some.crl" + "http://domain.com/some.crl" ) ], relative_name=None, @@ -4670,7 +4883,6 @@ def test_crl_issuer_only(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_crl_issuer.pem"), x509.load_pem_x509_certificate, - backend, ) cdps = cert.extensions.get_extension_for_oid( @@ -4688,7 +4900,7 @@ def test_crl_issuer_only(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography CA" + NameOID.COMMON_NAME, "cryptography CA" ), ] ) @@ -4702,7 +4914,6 @@ def test_crl_empty_hostname(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_empty_hostname.pem"), x509.load_pem_x509_certificate, - backend, ) cdps = cert.extensions.get_extension_for_oid( @@ -4714,7 +4925,7 @@ def test_crl_empty_hostname(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" + "ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" ) ], relative_name=None, @@ -4724,15 +4935,32 @@ def test_crl_empty_hostname(self, backend): ] ) + def test_public_bytes(self): + ext = x509.CRLDistributionPoints( + [ + x509.DistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + "ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" + ) + ], + relative_name=None, + reasons=None, + crl_issuer=None, + ) + ] + ) + assert ( + ext.public_bytes() + == b"0-0+\xa0)\xa0'\x86%ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" + ) + -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestFreshestCRLExtension(object): +class TestFreshestCRLExtension: def test_vector(self, backend): cert = _load_cert( os.path.join("x509", "custom", "freshestcrl.pem"), x509.load_pem_x509_certificate, - backend, ) fcrl = cert.extensions.get_extension_for_class(x509.FreshestCRL).value @@ -4741,10 +4969,10 @@ def test_vector(self, backend): x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ), x509.UniformResourceIdentifier( - u"http://backup.myhost.com/myca.crl" + "http://backup.myhost.com/myca.crl" ), ], relative_name=None, @@ -4759,10 +4987,10 @@ def test_vector(self, backend): x509.Name( [ x509.NameAttribute( - NameOID.COUNTRY_NAME, u"US" + NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography CA" + NameOID.COMMON_NAME, "cryptography CA" ), ] ) @@ -4772,15 +5000,57 @@ def test_vector(self, backend): ] ) + def test_public_bytes(self): + ext = x509.FreshestCRL( + [ + x509.DistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + "http://myhost.com/myca.crl" + ), + x509.UniformResourceIdentifier( + "http://backup.myhost.com/myca.crl" + ), + ], + relative_name=None, + reasons=frozenset( + [ + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.aa_compromise, + ] + ), + crl_issuer=[ + x509.DirectoryName( + x509.Name( + [ + x509.NameAttribute( + NameOID.COUNTRY_NAME, "US" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, "cryptography CA" + ), + ] + ) + ) + ], + ) + ] + ) + assert ( + ext.public_bytes() + == b"0w0u\xa0A\xa0?\x86\x1ahttp://myhost.com/myca.crl\x86!http://" + b"backup.myhost.com/myca.crl\x81\x03\x07`\x80\xa2+\xa4)0'1\x0b0\t" + b"\x06\x03U\x04\x06\x13\x02US1\x180\x16\x06\x03U\x04\x03\x0c\x0fc" + b"ryptography CA" + ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestOCSPNoCheckExtension(object): + +class TestOCSPNoCheckExtension: def test_nocheck(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ocsp_nocheck.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid(ExtensionOID.OCSP_NO_CHECK) assert isinstance(ext.value, x509.OCSPNoCheck) @@ -4810,11 +5080,15 @@ def test_repr(self): assert repr(onc) == "" + def test_public_bytes(self): + ext = x509.OCSPNoCheck() + assert ext.public_bytes() == b"\x05\x00" + -class TestInhibitAnyPolicy(object): +class TestInhibitAnyPolicy: def test_not_int(self): with pytest.raises(TypeError): - x509.InhibitAnyPolicy("notint") + x509.InhibitAnyPolicy("notint") # type:ignore[arg-type] def test_negative_int(self): with pytest.raises(ValueError): @@ -4842,23 +5116,24 @@ def test_hash(self): assert hash(iap) == hash(iap2) assert hash(iap) != hash(iap3) + def test_public_bytes(self): + ext = x509.InhibitAnyPolicy(1) + assert ext.public_bytes() == b"\x02\x01\x01" + -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestInhibitAnyPolicyExtension(object): +class TestInhibitAnyPolicyExtension: def test_inhibit_any_policy(self, backend): cert = _load_cert( os.path.join("x509", "custom", "inhibit_any_policy_5.pem"), x509.load_pem_x509_certificate, - backend, ) - iap = cert.extensions.get_extension_for_oid( - ExtensionOID.INHIBIT_ANY_POLICY + iap = cert.extensions.get_extension_for_class( + x509.InhibitAnyPolicy ).value assert iap.skip_certs == 5 -class TestIssuingDistributionPointExtension(object): +class TestIssuingDistributionPointExtension: @pytest.mark.parametrize( ("filename", "expected"), [ @@ -4867,7 +5142,7 @@ class TestIssuingDistributionPointExtension(object): x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -4883,7 +5158,7 @@ class TestIssuingDistributionPointExtension(object): x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -4899,7 +5174,7 @@ class TestIssuingDistributionPointExtension(object): x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -4915,7 +5190,7 @@ class TestIssuingDistributionPointExtension(object): x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -4934,7 +5209,7 @@ class TestIssuingDistributionPointExtension(object): [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, - value=u"PyCA", + value="PyCA", ) ] ), @@ -4967,7 +5242,7 @@ class TestIssuingDistributionPointExtension(object): [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, - value=u"PyCA", + value="PyCA", ) ] ), @@ -4997,7 +5272,7 @@ class TestIssuingDistributionPointExtension(object): [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, - value=u"PyCA", + value="PyCA", ) ] ), @@ -5010,13 +5285,10 @@ class TestIssuingDistributionPointExtension(object): ), ], ) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_vectors(self, filename, expected, backend): crl = _load_cert( os.path.join("x509", "custom", filename), x509.load_pem_x509_crl, - backend, ) idp = crl.extensions.get_extension_for_class( x509.IssuingDistributionPoint @@ -5116,22 +5388,13 @@ def test_repr(self): False, False, ) - if not six.PY2: - assert repr(idp) == ( - "}), indirect_crl=False, only_contains_attribut" - "e_certs=False)>" - ) - else: - assert repr(idp) == ( - "]), indirect_crl=False, only_contains_attribut" - "e_certs=False)>" - ) + assert repr(idp) == ( + "}), indirect_crl=False, only_contains_attribut" + "e_certs=False)>" + ) def test_eq(self): idp1 = x509.IssuingDistributionPoint( @@ -5144,7 +5407,7 @@ def test_eq(self): relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), @@ -5159,7 +5422,7 @@ def test_eq(self): relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), @@ -5177,7 +5440,7 @@ def test_ne(self): relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), @@ -5192,7 +5455,7 @@ def test_ne(self): relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), @@ -5212,7 +5475,7 @@ def test_hash(self): x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), @@ -5225,15 +5488,13 @@ def test_hash(self): assert hash(idp1) == hash(idp2) assert hash(idp1) != hash(idp3) - @pytest.mark.requires_backend_interface(interface=RSABackend) - @pytest.mark.requires_backend_interface(interface=X509Backend) @pytest.mark.parametrize( "idp", [ x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -5246,7 +5507,7 @@ def test_hash(self): x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -5259,7 +5520,7 @@ def test_hash(self): x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -5272,7 +5533,7 @@ def test_hash(self): x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( - u"http://myhost.com/myca.crl" + "http://myhost.com/myca.crl" ) ], relative_name=None, @@ -5287,7 +5548,7 @@ def test_hash(self): relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), @@ -5311,10 +5572,10 @@ def test_hash(self): relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ), x509.NameAttribute( - oid=x509.NameOID.COMMON_NAME, value=u"cryptography" + oid=x509.NameOID.COMMON_NAME, value="cryptography" ), ] ), @@ -5337,7 +5598,7 @@ def test_hash(self): relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA" + oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), @@ -5349,8 +5610,8 @@ def test_hash(self): ), ], ) - def test_generate(self, idp, backend): - key = RSA_KEY_2048.private_key(backend) + def test_generate(self, rsa_key_2048: rsa.RSAPrivateKey, idp, backend): + key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( @@ -5359,7 +5620,7 @@ def test_generate(self, idp, backend): x509.Name( [ x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" + NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) @@ -5376,15 +5637,34 @@ def test_generate(self, idp, backend): assert ext.critical is True assert ext.value == idp + def test_public_bytes(self): + ext = x509.IssuingDistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName( + [ + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, + value="PyCA", + ) + ] + ), + only_contains_user_certs=False, + only_contains_ca_certs=False, + only_some_reasons=None, + indirect_crl=False, + only_contains_attribute_certs=False, + ) + assert ( + ext.public_bytes() + == b"0\x11\xa0\x0f\xa1\r0\x0b\x06\x03U\x04\n\x0c\x04PyCA" + ) + -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestPrecertPoisonExtension(object): +class TestPrecertPoisonExtension: def test_load(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.precert.pem"), x509.load_pem_x509_certificate, - backend, ) poison = cert.extensions.get_extension_for_oid( ExtensionOID.PRECERT_POISON @@ -5395,8 +5675,8 @@ def test_load(self, backend): ).value assert isinstance(poison, x509.PrecertPoison) - def test_generate(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_generate(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 cert = ( _make_certbuilder(private_key) .add_extension(x509.PrecertPoison(), critical=True) @@ -5432,20 +5712,17 @@ def test_repr(self): assert repr(pcp) == "" + def test_public_bytes(self): + ext = x509.PrecertPoison() + assert ext.public_bytes() == b"\x05\x00" -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestSignedCertificateTimestamps(object): - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) + +class TestSignedCertificateTimestamps: def test_eq(self, backend): sct = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5456,7 +5733,6 @@ def test_eq(self, backend): _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5465,16 +5741,11 @@ def test_eq(self, backend): ) assert sct == sct2 - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_ne(self, backend): sct = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5485,7 +5756,6 @@ def test_ne(self, backend): _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5495,16 +5765,11 @@ def test_ne(self, backend): assert sct != sct2 assert sct != object() - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_hash(self, backend): sct = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5515,7 +5780,6 @@ def test_hash(self, backend): _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5526,7 +5790,6 @@ def test_hash(self, backend): _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5536,29 +5799,64 @@ def test_hash(self, backend): assert hash(sct) == hash(sct2) assert hash(sct) != hash(sct3) + def test_public_bytes(self, backend): + ext = ( + load_vectors_from_file( + os.path.join("x509", "ocsp", "resp-sct-extension.der"), + lambda data: ocsp.load_der_ocsp_response(data.read()), + mode="rb", + ) + .single_extensions.get_extension_for_class( + x509.SignedCertificateTimestamps + ) + .value + ) -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestPrecertificateSignedCertificateTimestampsExtension(object): + assert ext.public_bytes() == ( + b"\x04\x82\x01\xe6\x01\xe4\x00w\x00D\x94e.\xb0\xee\xce\xaf\xc4" + b"@\x07\xd8\xa8\xfe(\xc0\xda\xe6\x82\xbe\xd8\xcb1\xb5?\xd33" + b"\x96\xb5\xb6\x81\xa8\x00\x00\x01no\xc33h\x00\x00\x04\x03\x00" + b"H0F\x02!\x00\xa0}J\xa7\xb1Y\xb4\x15P\xd7\x95Y\x12\xfb\xa1" + b"\xdfh\x96u\xa3\x0f_\x01\xf2\xfd\xcbMI\x9bt\xe2\xfe\x02!\x00" + b"\x89E\xd7\x86N<>\xe8\x07\xc4\xca\xdbO:\xb7\x9f]E\xbc\x1az" + b"\xe5h\xab%\xdaukT\x8a\xf7\xc1\x00w\x00oSv\xac1\xf01\x19\xd8" + b"\x99\x00\xa4Q\x15\xffw\x15\x1c\x11\xd9\x02\xc1\x00)\x06\x8d" + b"\xb2\x08\x9a7\xd9\x13\x00\x00\x01no\xc33m\x00\x00\x04\x03" + b"\x00H0F\x02!\x00\xd4\xe06\xd2\xed~{\x9fs-E2\xd8\xd2\xb41\xc6" + b"v\x8b3\xf2\tS\x1d\xd8SUe\xe1\xcf\xfc;\x02!\x00\xd9cF[\x8e\xac" + b'4\x02@\xd6\x8a\x10y\x98\x92\xbee\xf4\n\x11L\xbfpI(Y"O\x1al' + b"\xe9g\x00w\x00\xbb\xd9\xdf\xbc\x1f\x8aq\xb5\x93\x94#\x97\xaa" + b"\x92{G8W\x95\n\xabR\xe8\x1a\x90\x96d6\x8e\x1e\xd1\x85\x00" + b"\x00\x01no\xc34g\x00\x00\x04\x03\x00H0F\x02!\x00\xf4:\xec" + b"\x1b\xdeQ\r\xf8S\x9c\xf2\xeee<\xcf\xc5:\x0f\x0f\xeb\x8bv\x9f" + b'8d.z\x9c"K\x9b\x11\x02!\x00\xe7`\xe9Ex\xf7)B<\xf7\xd62b\xfa' + b"\xa2\xc7!\xc4\xbau\xcb\xad\x0ezEZ\x11\x13\xa1+\x89J\x00w\x00" + b"\xeeK\xbd\xb7u\xce`\xba\xe1Bi\x1f\xab\xe1\x9ef\xa3\x0f~_\xb0" + b"r\xd8\x83\x00\xc4{\x89z\xa8\xfd\xcb\x00\x00\x01no\xc32\xdd" + b"\x00\x00\x04\x03\x00H0F\x02!\x00\x95Y\x81\x7f\xa4\xe5\x17o" + b"\x06}\xac\xcdt-\xb0\xb8L\x18H\xecB\xcc-\xe5\x13>\x07\xba\xc0" + b"}\xa3\xe6\x02!\x00\xbf\xc8\x88\x93m\x8d\xc3(GS\xaf=4}\x97" + b"\xe6\xc2\x1djQ\x0e0\x8c\xcc\x9d\xc2\xc7\xc3\xb1\x0f\xec\x98" + ) + + +class TestPrecertificateSignedCertificateTimestampsExtension: def test_init(self): with pytest.raises(TypeError): - x509.PrecertificateSignedCertificateTimestamps([object()]) + x509.PrecertificateSignedCertificateTimestamps( + [object()] # type:ignore[list-item] + ) def test_repr(self): assert repr(x509.PrecertificateSignedCertificateTimestamps([])) == ( "" ) - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_eq(self, backend): psct1 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5569,7 +5867,6 @@ def test_eq(self, backend): _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5578,16 +5875,11 @@ def test_eq(self, backend): ) assert psct1 == psct2 - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_ne(self, backend): psct1 = ( _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5598,7 +5890,6 @@ def test_ne(self, backend): _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5608,16 +5899,35 @@ def test_ne(self, backend): assert psct1 != psct2 assert psct1 != object() - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) + def test_ordering(self, backend): + psct1 = ( + _load_cert( + os.path.join("x509", "cryptography-scts.pem"), + x509.load_pem_x509_certificate, + ) + .extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ) + .value + ) + psct2 = ( + _load_cert( + os.path.join("x509", "badssl-sct.pem"), + x509.load_pem_x509_certificate, + ) + .extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ) + .value + ) + with pytest.raises(TypeError): + psct1[0] < psct2[0] + def test_hash(self, backend): psct1 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5628,7 +5938,6 @@ def test_hash(self, backend): _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5639,7 +5948,6 @@ def test_hash(self, backend): _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, - backend, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5649,15 +5957,10 @@ def test_hash(self, backend): assert hash(psct1) == hash(psct2) assert hash(psct1) != hash(psct3) - @pytest.mark.supported( - only_if=lambda backend: (backend._lib.Cryptography_HAS_SCT), - skip_message="Requires CT support", - ) def test_simple(self, backend): cert = _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) scts = cert.extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps @@ -5677,48 +5980,130 @@ def test_simple(self, backend): sct.entry_type == x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE ) + assert isinstance(sct.signature_hash_algorithm, hashes.SHA256) + assert ( + sct.signature_algorithm + == x509.certificate_transparency.SignatureAlgorithm.ECDSA + ) + assert sct.signature == ( + b"\x30\x45\x02\x21\x00\xB8\x03\xAD\x34\xF6\xFC\x0F\x2C\xFF\x84\xA0" + b"\x86\xE5\xD7\xCF\x5A\xF0\x0A\x07\x62\x6A\x7F\xB3\xA6\x44\x64\xF1" + b"\x95\xA4\x48\x45\x11\x02\x20\x2F\x61\x8D\x53\x1B\x6F\x4A\xB8\x0A" + b"\x67\xB2\x07\xE1\x8F\x6D\xAD\xD1\x04\x4A\x5E\xB3\x89\xEF\x7C\x60" + b"\xC2\x68\x53\xF9\x3D\x1F\x6D" + ) + assert sct.extension_bytes == b"" - @pytest.mark.supported( - only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER - ), - skip_message="Requires OpenSSL < 1.1.0", - ) - def test_skips_scts_if_unsupported(self, backend): + def test_generate(self, rsa_key_2048: rsa.RSAPrivateKey, backend): cert = _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, - backend, ) - assert len(cert.extensions) == 10 - with pytest.raises(x509.ExtensionNotFound): - cert.extensions.get_extension_for_class( - x509.PrecertificateSignedCertificateTimestamps - ) + scts = cert.extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + assert len(scts) == 1 + [sct] = scts - ext = cert.extensions.get_extension_for_oid( - x509.ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS + private_key = rsa_key_2048 + builder = _make_certbuilder(private_key).add_extension( + x509.PrecertificateSignedCertificateTimestamps([sct]), + critical=False, ) - assert isinstance(ext.value, x509.UnrecognizedExtension) + cert = builder.sign(private_key, hashes.SHA256(), backend) + ext = cert.extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ).value + assert list(ext) == [sct] + + def test_invalid_version(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "invalid-sct-version.der"), + x509.load_der_x509_certificate, + ) + with pytest.raises(ValueError): + cert.extensions + + def test_invalid_hash_algorithm(self, backend): + cert = _load_cert( + os.path.join("x509", "badssl-sct-none-hash.der"), + x509.load_der_x509_certificate, + ) + with pytest.raises( + ValueError, match="Invalid/unsupported hash algorithm for SCT: 0" + ): + cert.extensions + def test_invalid_signature_algorithm(self, backend): + cert = _load_cert( + os.path.join("x509", "badssl-sct-anonymous-sig.der"), + x509.load_der_x509_certificate, + ) + with pytest.raises( + ValueError, + match="Invalid/unsupported signature algorithm for SCT: 0", + ): + cert.extensions + + def test_invalid_length(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "invalid-sct-length.der"), + x509.load_der_x509_certificate, + ) + with pytest.raises(ValueError): + cert.extensions -@pytest.mark.requires_backend_interface(interface=RSABackend) -@pytest.mark.requires_backend_interface(interface=X509Backend) -class TestInvalidExtension(object): + def test_public_bytes(self, backend): + ext = ( + _load_cert( + os.path.join("x509", "cryptography-scts.pem"), + x509.load_pem_x509_certificate, + ) + .extensions.get_extension_for_class( + x509.PrecertificateSignedCertificateTimestamps + ) + .value + ) + assert ( + ext.public_bytes() + == b"\x04\x81\xf4\x00\xf2\x00w\x00)" - else: - assert repr(nonce1) == "" + assert repr(nonce1) == "" def test_hash(self): nonce1 = x509.OCSPNonce(b"0" * 5) @@ -5745,9 +6127,174 @@ def test_hash(self): assert hash(nonce1) == hash(nonce2) assert hash(nonce1) != hash(nonce3) + def test_public_bytes(self): + ext = x509.OCSPNonce(b"0" * 5) + assert ext.public_bytes() == b"\x04\x0500000" + + +class TestOCSPAcceptableResponses: + def test_invalid_types(self): + with pytest.raises(TypeError): + x509.OCSPAcceptableResponses(38) # type:ignore[arg-type] + with pytest.raises(TypeError): + x509.OCSPAcceptableResponses([38]) # type:ignore[list-item] + + def test_eq(self): + acceptable_responses1 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.3")] + ) + acceptable_responses2 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.3")] + ) + assert acceptable_responses1 == acceptable_responses2 + + def test_ne(self): + acceptable_responses1 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.3")] + ) + acceptable_responses2 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.4")] + ) + assert acceptable_responses1 != acceptable_responses2 + assert acceptable_responses1 != object() + + def test_repr(self): + acceptable_responses = x509.OCSPAcceptableResponses([]) + assert ( + repr(acceptable_responses) + == "" + ) + + def test_hash(self): + acceptable_responses1 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.3")] + ) + acceptable_responses2 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.3")] + ) + acceptable_responses3 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.4")] + ) + + assert hash(acceptable_responses1) == hash(acceptable_responses2) + assert hash(acceptable_responses1) != hash(acceptable_responses3) + + def test_iter(self): + acceptable_responses1 = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.2.3")] + ) + + assert list(acceptable_responses1) == [ObjectIdentifier("1.2.3")] + + def test_public_bytes(self): + ext = x509.OCSPAcceptableResponses([]) + assert ext.public_bytes() == b"\x30\x00" + + ext = x509.OCSPAcceptableResponses( + [ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")] + ) + assert ( + ext.public_bytes() + == b"\x30\x0b\x06\t+\x06\x01\x05\x05\x07\x30\x01\x01" + ) + + +class TestMSCertificateTemplate: + def test_invalid_type(self): + with pytest.raises(TypeError): + x509.MSCertificateTemplate( + "notanoid", None, None # type:ignore[arg-type] + ) + oid = x509.ObjectIdentifier("1.2.3.4") + with pytest.raises(TypeError): + x509.MSCertificateTemplate( + oid, "notanint", None # type:ignore[arg-type] + ) + with pytest.raises(TypeError): + x509.MSCertificateTemplate( + oid, None, "notanint" # type:ignore[arg-type] + ) + + def test_eq(self): + template1 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, None + ) + template2 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, None + ) + assert template1 == template2 + + def test_ne(self): + template1 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, None + ) + template2 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), 1, None + ) + template3 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, 1 + ) + template4 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3"), None, None + ) + assert template1 != template2 + assert template1 != template3 + assert template1 != template4 + assert template1 != object() + + def test_repr(self): + template = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, None + ) + assert repr(template) == ( + ", major_version=None, minor_version=None)>" + ) + + def test_hash(self): + template1 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, None + ) + template2 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, None + ) + template3 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, 1 + ) + template4 = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3"), None, None + ) + + assert hash(template1) == hash(template2) + assert hash(template1) != hash(template3) + assert hash(template1) != hash(template4) + + def test_public_bytes(self): + ext = x509.MSCertificateTemplate( + ObjectIdentifier("1.2.3.4"), None, None + ) + assert ext.public_bytes() == b"0\x05\x06\x03*\x03\x04" + + ext = x509.MSCertificateTemplate(ObjectIdentifier("1.2.3.4"), 1, 0) + assert ( + ext.public_bytes() + == b"0\x0b\x06\x03*\x03\x04\x02\x01\x01\x02\x01\x00" + ) + def test_all_extension_oid_members_have_names_defined(): for oid in dir(ExtensionOID): if oid.startswith("__"): continue assert getattr(ExtensionOID, oid) in _OID_NAMES + + +def test_unknown_extension(): + class MyExtension(ExtensionType): + oid = x509.ObjectIdentifier("1.2.3.4") + + with pytest.raises(NotImplementedError): + MyExtension().public_bytes() + + with pytest.raises(NotImplementedError): + rust_x509.encode_extension_value(MyExtension()) diff --git a/tests/x509/test_x509_revokedcertbuilder.py b/tests/x509/test_x509_revokedcertbuilder.py index 0db6d2a6f7de..e0f53f856f02 100644 --- a/tests/x509/test_x509_revokedcertbuilder.py +++ b/tests/x509/test_x509_revokedcertbuilder.py @@ -2,22 +2,20 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import datetime import pytest -import pytz - from cryptography import x509 -from cryptography.hazmat.backends.interfaces import X509Backend -class TestRevokedCertificateBuilder(object): +class TestRevokedCertificateBuilder: def test_serial_number_must_be_integer(self): with pytest.raises(TypeError): - x509.RevokedCertificateBuilder().serial_number("notanx509name") + x509.RevokedCertificateBuilder().serial_number( + "notanx509name" # type: ignore[arg-type] + ) def test_serial_number_must_be_non_negative(self): with pytest.raises(ValueError): @@ -27,7 +25,6 @@ def test_serial_number_must_be_positive(self): with pytest.raises(ValueError): x509.RevokedCertificateBuilder().serial_number(0) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_minimal_serial_number(self, backend): revocation_date = datetime.datetime(2002, 1, 1, 12, 1) builder = ( @@ -39,7 +36,6 @@ def test_minimal_serial_number(self, backend): revoked_certificate = builder.build(backend) assert revoked_certificate.serial_number == 1 - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_biggest_serial_number(self, backend): revocation_date = datetime.datetime(2002, 1, 1, 12, 1) builder = ( @@ -60,11 +56,9 @@ def test_set_serial_number_twice(self): with pytest.raises(ValueError): builder.serial_number(4) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_aware_revocation_date(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - time = tz.localize(time) + tz = datetime.timezone(datetime.timedelta(hours=-8)) + time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_time = datetime.datetime(2012, 1, 17, 6, 43) serial_number = 333 builder = ( @@ -78,7 +72,9 @@ def test_aware_revocation_date(self, backend): def test_revocation_date_invalid(self): with pytest.raises(TypeError): - x509.RevokedCertificateBuilder().revocation_date("notadatetime") + x509.RevokedCertificateBuilder().revocation_date( + "notadatetime" # type: ignore[arg-type] + ) def test_revocation_date_before_1950(self): with pytest.raises(ValueError): @@ -106,10 +102,9 @@ def test_add_extension_checks_for_duplicates(self): def test_add_invalid_extension(self): with pytest.raises(TypeError): x509.RevokedCertificateBuilder().add_extension( - "notanextension", False + "notanextension", False # type: ignore[arg-type] ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_serial_number(self, backend): builder = x509.RevokedCertificateBuilder().revocation_date( datetime.datetime(2002, 1, 1, 12, 1) @@ -118,14 +113,12 @@ def test_no_serial_number(self, backend): with pytest.raises(ValueError): builder.build(backend) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_revocation_date(self, backend): builder = x509.RevokedCertificateBuilder().serial_number(3) with pytest.raises(ValueError): builder.build(backend) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_create_revoked(self, backend): serial_number = 333 revocation_date = datetime.datetime(2002, 1, 1, 12, 1) @@ -145,10 +138,9 @@ def test_create_revoked(self, backend): [ x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)), x509.CRLReason(x509.ReasonFlags.ca_compromise), - x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]), + x509.CertificateIssuer([x509.DNSName("cryptography.io")]), ], ) - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_add_extensions(self, backend, extension): serial_number = 333 revocation_date = datetime.datetime(2002, 1, 1, 12, 1) @@ -169,7 +161,6 @@ def test_add_extensions(self, backend, extension): assert ext.critical is False assert ext.value == extension - @pytest.mark.requires_backend_interface(interface=X509Backend) def test_add_multiple_extensions(self, backend): serial_number = 333 revocation_date = datetime.datetime(2002, 1, 1, 12, 1) @@ -177,7 +168,7 @@ def test_add_multiple_extensions(self, backend): datetime.datetime(2015, 1, 1, 0, 0) ) certificate_issuer = x509.CertificateIssuer( - [x509.DNSName(u"cryptography.io")] + [x509.DNSName("cryptography.io")] ) crl_reason = x509.CRLReason(x509.ReasonFlags.aa_compromise) builder = ( diff --git a/tox.ini b/tox.ini deleted file mode 100644 index e94d3c1e0753..000000000000 --- a/tox.ini +++ /dev/null @@ -1,93 +0,0 @@ -[tox] -minversion = 2.4 -envlist = py27,pypy,py35,py36,py37,py38,docs,pep8,packaging -isolated_build = True - -[testenv] -extras = - test - ssh: ssh -deps = - # This must be kept in sync with .travis/install.sh and .github/workflows/ci.yml - coverage - ./vectors - randomorder: pytest-randomly -passenv = ARCHFLAGS LDFLAGS CFLAGS INCLUDE LIB LD_LIBRARY_PATH USERNAME PYTHONIOENCODING OPENSSL_FORCE_FIPS_MODE -setenv = - CRYPTOGRAPHY_ALLOW_OPENSSL_102=1 -commands = - pip list - # We use parallel mode and then combine here so that coverage.py will take - # the paths like .tox/py38/lib/python3.8/site-packages/cryptography/__init__.py - # and collapse them into src/cryptography/__init__.py. - coverage run --parallel-mode -m pytest --capture=no --strict {posargs} - coverage combine - coverage report -m - -# This target disables coverage on pypy because of performance problems with -# coverage.py on pypy. -[testenv:pypy-nocoverage] -basepython = pypy -commands = - pip list - pytest --capture=no --strict {posargs} - -# This target disables coverage on pypy because of performance problems with -# coverage.py on pypy. -[testenv:pypy3-nocoverage] -basepython = pypy3 -commands = - pip list - pytest --capture=no --strict {posargs} - -[testenv:docs] -extras = - docs - docstest -basepython = python3 -commands = - sphinx-build -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html - sphinx-build -T -W -b latex -d {envtmpdir}/doctrees docs docs/_build/latex - sphinx-build -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html - sphinx-build -T -W -b spelling docs docs/_build/html - doc8 --allow-long-titles README.rst CHANGELOG.rst docs/ --ignore-path docs/_build/ - python setup.py sdist - twine check dist/* - -[testenv:docs-linkcheck] -extras = - docs -basepython = python3 -commands = - sphinx-build -W -b linkcheck docs docs/_build/html - -[testenv:pep8] -basepython = python3 -extras = - pep8test -commands = - flake8 . - black --check . - -[testenv:packaging] -deps = - check-manifest -commands = - check-manifest - -[flake8] -ignore = E203,E211,W503,W504 -exclude = .tox,*.egg,.git,_build,.hypothesis -select = E,W,F,N,I -application-import-names = cryptography,cryptography_vectors,tests - -[doc8] -extensions = rst - -[pytest] -addopts = -r s -markers = - requires_backend_interface: this test requires a specific backend interface - skip_fips: this test is not executed in FIPS mode - supported: parametrized test requiring only_if and skip_message - wycheproof_tests: this test runs a wycheproof fixture diff --git a/vectors/cryptography_vectors/__about__.py b/vectors/cryptography_vectors/__about__.py index 3b41b82e0ab9..f4c7a6fb90c4 100644 --- a/vectors/cryptography_vectors/__about__.py +++ b/vectors/cryptography_vectors/__about__.py @@ -2,28 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - __all__ = [ - "__title__", - "__summary__", - "__uri__", "__version__", - "__author__", - "__email__", - "__license__", - "__copyright__", ] -__title__ = "cryptography_vectors" -__summary__ = "Test vectors for the cryptography package." - -__uri__ = "https://github.com/pyca/cryptography" - -__version__ = "3.2" - -__author__ = "The cryptography developers" -__email__ = "cryptography-dev@python.org" - -__license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2013-2019 %s" % __author__ +__version__ = "41.0.4" diff --git a/vectors/cryptography_vectors/__init__.py b/vectors/cryptography_vectors/__init__.py index f39ffe03ab0c..443357b28d56 100644 --- a/vectors/cryptography_vectors/__init__.py +++ b/vectors/cryptography_vectors/__init__.py @@ -2,34 +2,16 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function - import os +import typing -from cryptography_vectors.__about__ import ( - __author__, - __copyright__, - __email__, - __license__, - __summary__, - __title__, - __uri__, - __version__, -) - +from cryptography_vectors.__about__ import __version__ __all__ = [ - "__title__", - "__summary__", - "__uri__", "__version__", - "__author__", - "__email__", - "__license__", - "__copyright__", ] -def open_vector_file(filename, mode): +def open_vector_file(filename: str, mode: str) -> typing.IO: base = os.path.dirname(__file__) return open(os.path.join(base, filename), mode) diff --git a/vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem b/vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem new file mode 100644 index 000000000000..1c01dd3eaf7e --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFwCAQAwMwYJKoZIhvcNAQMBMCYCIQCBPg6BS+5nbb09nSjtc9NnNdIf9kVyNvaN +PWFFVgwPqwIBAgQiAiBmJ3qBbu72ZnUxnCrr8ujWFU7jWTcOjhsZSqobmiD6vA== +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/dsa-p256.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/dsa-p256.pub new file mode 100644 index 000000000000..3e9cd30ef1fc --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/dsa-p256.pub @@ -0,0 +1 @@ +ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgIE0DERxEocGf8SILUWuT5wakv34qIiz0+1/fQVf0PYQAAACBAPloKFUpz5YJF4doiftzBT3wlM37PvuklYVczyOr8HCn4srUK4Zwe13Ce/Ee+Ibpy3nECDI9AICjYycWOVVbpDAy4nFDpsWal/nxL1wFIocSVD3eiDK0sCJI0JoKN8CtARnX4EmMwa8VJW9VyHZyRQ2LMKHsIY3WYoJswmMk+uRtAAAAFQCbeL/Q/whJsTM5Mz42+p2NA0MAJwAAAIEAjhE+6yBllAn2AFUoMN9kC5dSxWrcVG8flczdhGDfryTTO7ouUlZEQXjtgc8XDaz3ABttq8WDmihsJ10F9QY/jh4D6ml8tFL4F59CiiImZHSnUv5GBVzkp/P26a9q/PJr9VwQEhIwDQ4lEOHPApBTQFMjDhEO+/5seqWDerzcuJkAAACAJn80y6FFao0jV4WVK8O5x2DFAmX2ic3DcjZiehRtB7ar9FQJfkLRFgq4AqR683n2DyO2Mta4mNVuLEcmHO+uC08BRbwx3CuftAXUDsMK+4bcZFQGNtBfIQHugA58sIvjBENlEfbZwNNLiyzKtG7W+61WZEnNb5FBNHH8lSCMJ04AAAAAAAAAAAAAAAEAAAAEdGVzdAAAABAAAAADZXZlAAAABWFsaWNlAAAAAGS0cnQAAAAAY8XhBAAAACAAAAAYY3JpdGljYWxAY3J5cHRvZ3JhcGh5LmlvAAAAAAAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSVuxZ77Yb35R81SnzVzIhFlOJ8KDTC7O3KL/OFzutJJ71Uah4j9ixchyr+uY3SpUnE0NqHqKTZYOMe/9MJzCdWAAAAZAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASQAAACEA99MMfmfSFIYvB1EF3s7mWXqUsch4sdf+I28jr1SUKD8AAAAgY04RTcT1vqMzs5bVoO0vF/RfQyr06Z1IDzCX1l3h70k= \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-dsa.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-dsa.pub new file mode 100644 index 000000000000..fafb77de94c0 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-dsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgqlL0IDRu6PBXcHrEqr66w+nW4TWpgWcqB9/SiSVsHk0AAAAIbmlzdHAyNTYAAABBBKxrpV0taLLnqbE08WQoMGmopbPBTC1Vj3GLi4/+n9BINlKBskAjhVfQdfLzse4REtYbNH2iOseHS/r1l+jj2REAAAAAAAAAAAAAAAEAAAAEdGVzdAAAABAAAAADZXZlAAAABWFsaWNlAAAAAGS0cnQAAAAAY8XhBAAAACAAAAAYY3JpdGljYWxAY3J5cHRvZ3JhcGh5LmlvAAAAAAAAAAAAAAAAAAABsgAAAAdzc2gtZHNzAAAAgQC2BuglMCTWhV809q9omijAGP+z54k1BwCdFngTXr4k4Nm5DAsbUUaaQuELVK+oeB/iClhq17ghSM6gbGRZRZYlyBNfp05w77aKzw7w+0Z3JY8FDjwvsE6hWK7oWTUIF+79Nwe6QdHuryVsXKOG6YmKC+CPzSnpZ8jEGtCnI/HGqQAAABUA0UVWDteeovroX1jW5a0kbBbqnxUAAACAYkibo7SpYozbSObn2YlwLPa0phfpYcLmtwtZFaraYcJWsUSA8F4UATXNCxVPqwsl7PJfmGcJ+T+wvL16VxoCXsGqOgSNox3eVkGlwxF964AQ8BON77gi+ho6zw9ALoHUEDAKLwupRKXLoqLoRe+0T3//zokR3PX1JMbdraIgBcUAAACBAK41XlUiNgUc2fgcRhcPgFP7Ru1NxQ/a+muyfbcWaHPrOW5Jb90MfhmIqktc6tbqUceGtH2qqch1lxdaCw4e1sjeDsd0wnrIQcg4occQqZ2PYu3K3tcclyBXtVbzpxXq18QPUtl9idIDZ17L64iZgO1+EKlMsl0dZBtH37SWxIoWAAAANwAAAAdzc2gtZHNzAAAAKIvmGjVssyGoO4sCGCyz2wDCXUtMMbDxAp8nKRk6H0XdXoixPGY0oAo= \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-crit-opt-val.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-crit-opt-val.pub new file mode 100644 index 000000000000..5510bd5f0f35 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-crit-opt-val.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIbmlzdHAyNTYAAABBBCZWRs4GYIHGJpyXuqvfFGWN49dnJRkZJLDkFrHf6mNHhIMI3vtrLfCZwxPSfnCYWK6YofssZ1FYA6TkVJq8Xi8AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAABjsM0AAAAAAGOyHoAAAABtAAAADWZvcmNlLWNvbW1hbmQAAABBAAAAKGVjaG8gYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEAAAARaW52YWxpZF9taXNjX2RhdGEAAAAPdmVyaWZ5LXJlcXVpcmVkAAAAAAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDdZgztgAFFC7T5PifrUy/kMu0Pnwq1au3vStKHe7FFMAAAAFMAAAALc3NoLWVkMjU1MTkAAABAt/0pBSDBFy1crBPHOBoKFoxRjKd1tKVdOrD3QVgbBfpaHfxi4vrgYe6JfQ54+vu5P+8yrMyACekT8H6hhvxHDw== \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-ext-val.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-ext-val.pub new file mode 100644 index 000000000000..c44b49fceccd --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-ext-val.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIbmlzdHAyNTYAAABBBCZWRs4GYIHGJpyXuqvfFGWN49dnJRkZJLDkFrHf6mNHhIMI3vtrLfCZwxPSfnCYWK6YofssZ1FYA6TkVJq8Xi8AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAABjsM0AAAAAAGOyHoAAAAAXAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAAAvAAAAFGNvbnRhaW5zLWV4dHJhLXZhbHVlAAAAEwAAAAVoZWxsbwAAAAYgd29ybGQAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDdZgztgAFFC7T5PifrUy/kMu0Pnwq1au3vStKHe7FFMAAAAFMAAAALc3NoLWVkMjU1MTkAAABAY80oIEvooz/k3x9a+yVkjSNRfi4y/q87wVYiT7keTpP4n9JV/Vlc0u7O2QYOHfb4DUkcrvbsksKVsiqoQu5qDg== \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-broken-signature-key-type.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-broken-signature-key-type.pub new file mode 100644 index 000000000000..83d90f8909c7 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-broken-signature-key-type.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg5F1PDG/y2CzJp73derFFtoa/OJjJQZ1jaQKqf2nKciQAAAAIbmlzdHAyNTYAAABBBL4QG3yEahvircH4Px+aVHip40dqWot+JJB3TuyFGN5rCDP4eZmM5gmTIDJw+Y3uJk/8oP44nSXmUhceEI01LlMAAAAAAAAAAAAAAAEAAAAEdGVzdAAAABAAAAADZXZlAAAABWFsaWNlAAAAAGS0cnQAAAAAY8XhBAAAACIAAAAaaW52YWxpZHNpZ0BjcnlwdG9ncmFwaHkuaW8AAAAAAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBINdZJ7jyyOqECwnOcDNw8OHgHwTU8uexxAU8Qs/QYv3bmjR7ExP9QVGKzWJ2LxyCzIwt+wjjt/7Y/DGWJ5ja5kAAABeAAAADHJzYS1zaGEyLTI1NgAAAEoAAAAhAIYe8pC0okwX8m6lbsdSNZFhNl5GXGKMvy0Czi33NktHAAAAIQDLKohBedhNdftYor3+G2mSFAJFGM/fDJgs8L3cOEsYfQ== \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-duplicate-crit-opts.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-duplicate-crit-opts.pub new file mode 100644 index 000000000000..01b6233a1511 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-duplicate-crit-opts.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLhqcBHfeQKtL85NO1EYeTF6EWUPuCswcuhsj4jN8+9gAAAAIbmlzdHAyNTYAAABBBMnYLqlS1l6aEf5MAzEnTd2rfYYAdaesNxUsSW+2/QyIE0KiR1OOqnth+V+8JDTWBf78EcqxLOaz7Icw8d4dOzAAAAAAAAAAAAAAAAEAAAAEdGVzdAAAABAAAAADZXZlAAAABWFsaWNlAAAAAGS0cnQAAAAAY8XhBAAAAEIAAAAZZHVwbGljYXRlQGNyeXB0b2dyYXBoeS5pbwAAAAAAAAAZZHVwbGljYXRlQGNyeXB0b2dyYXBoeS5pbwAAAAAAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEbA70TLh7ri83WgjRWsQc0dP146vVEFhHqSdKHw8TMScFt4pcJnBcCPmr2TvxUdNcDHfum11bQWuAxvOOZ/RfxAAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhAOP5SYGetAQ849rV+ezStHi2WRkRLA6QnBrWXFWjtTNzAAAAIESwusuQ5ucNFnxR/B+mF8Fcwk22mx09i5vATfJSTNxW \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-duplicate-extension.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-duplicate-extension.pub new file mode 100644 index 000000000000..900d700094f4 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-duplicate-extension.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgN6rRGEpLjA1BohPwr8SEa33Vn4+H+94Jifl6hg6n5IIAAAAIbmlzdHAyNTYAAABBBFQDtz1+0wnP2wLnHZhES+XJVxImzusYhXd3G2cWCZL4HKZHRMIWj1x6oU9JdoEOMHppb6DK/OIFsXHwJeRBOOwAAAAAAAAAAAAAAAEAAAAEdGVzdAAAABAAAAADZXZlAAAABWFsaWNlAAAAAGS0cnQAAAAAY8XhBAAAAAAAAAA+AAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQlhngteNXbqpH+iOYQyIWciB0SCfHkjXir3gZXszfzWka1sP0LIHAtszMTofPG0+SosIKtZWfiJvh0BoxtbthJAAAAZQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASgAAACEAyAyVc+9ORWbQ/LdtR82+rKUxfMkD1wWDarYHk4mAWWIAAAAhALnEsJAtB4AVsNIC8COVeEEjmsPSQt4seHF6V5u7IWw9 \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-empty-principals.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-empty-principals.pub new file mode 100644 index 000000000000..9f2e27130fca --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-empty-principals.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgBXWXwDzsP++5IxnXmRrXXXP5wyJzMXW1nnQh7DPYZx8AAAAIbmlzdHAyNTYAAABBBCuO1AQsw/HmbXrjAHBJ7YA1ydYJeNbZ6LSArnxvrTX9kXDWF4bfOTSpUsC6OBKfzOiryc2Jr7QzXsFaOX0KPXUAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAZLRydAAAAABjxeEEAAAAAAAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRKq69tU5OhiF+0NOuZQU7X9BPL9aiW9Srxh5nizieNn1TLRkdpUXJisOk0t9Q5OgeFmo2JOcY3Cc2zXpBKWmp0AAAAZQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASgAAACEAyfMU07vNgYpI+vi8O3XmBkKnuYFcofPoaq0H1FCWj0oAAAAhAIhv9JCLdJM2fKc++EFu51glQUggTzgBQR+zR8HEbpFN \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-invalid-cert-type.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-invalid-cert-type.pub new file mode 100644 index 000000000000..5a06827bf56b --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-invalid-cert-type.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgSgPwfPbsfbii+9RAUJYav6zJ0oU54htu+vOuTdctY88AAAAIbmlzdHAyNTYAAABBBCARilffhR67GJ6CjUyP78jhu8eGCJ6bb68l86BnJIudjlYAQ/sgiFsQjmrMmDVqNrKGFudmJ3l+Nr78qump4pcAAAAAAAAAAAAAADIAAAAAAAAAAAAAAABktHJ0AAAAAGPFmrQAAAAAAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK8dtlOuaMxRf6l2vYPkXBalJrwPuvA95aXVn7Y4TEwfKwlztl9x2kYVPq0z1zCcrST5xbObEkqpvwI7ibEYVNkAAABkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIGxkTfbvXgevWi7ggjKUhaXQwT8cAKFoBaR1MX2602CGAAAAIQDmhJ+Jb4ZEb1vJU/9ekCUkAJg4MJ2nxLO4Rh02Gd1kDg== \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-non-lexical-crit-opts.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-non-lexical-crit-opts.pub new file mode 100644 index 000000000000..c16acae61953 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-non-lexical-crit-opts.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAghD4Bg5C3PR6Yjo0Onv9jTmMJL/ysWxOWM3lvbjkwSbgAAAAIbmlzdHAyNTYAAABBBMXF33sjujlcCsz5pnR7SXXO34s49Ofqw6wIoqNe2zJy2MxaBTQzMi+WXLTpv1+c5fKW1+jhlZEM+8aWuQehE8UAAAAAAAAAAAAAAAEAAAAEdGVzdAAAABAAAAADZXZlAAAABWFsaWNlAAAAAGS0cnQAAAAAY8XhBAAAAEgAAAAab21lZ2EtcGVybUBjcnlwdG9ncmFwaHkuaW8AAAAAAAAAHmFscGhhLW9yZGVyaW5nQGNyeXB0b2dyYXBoeS5pbwAAAAAAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEET7TXKVMaHak8L5dyeoHUtWPY7ozrqZm82pkGh4nDDhV/ftU4eHq/D4FAVX0ETt1mvhZMWdcB8cKBkM75y96zUAAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEgAAAAgVN9wU6zFsYQbPdI4MeRXpPuWRBx+jJM8PWVWvP7QYysAAAAgWXE00j8SKuyJO4X/Jz9QIyLfkEmcXqoHkLVnPRoRZ10= \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-non-lexical-extensions.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-non-lexical-extensions.pub new file mode 100644 index 000000000000..72238e05e969 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p256-non-lexical-extensions.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgTbZxg0yj7wdJdgLZWEmyQ27GJzA6hm0qmproBqnoGc0AAAAIbmlzdHAyNTYAAABBBKGL/tGE87lFxV86H/xeWCOc5EOwjojk5Mju0yM/z37Jgg1UJT0RYbZQdPEuFHlXrUsxNCgf7skyPFWoc784IoQAAAAAAAAAAAAAAAEAAAAEdGVzdAAAABAAAAADZXZlAAAABWFsaWNlAAAAAGS0cnQAAAAAY8XhBAAAAAAAAAAxAAAACnBlcm1pdC1wdHkAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOfVQv1ctoUn0QWVDCUViWOscuAi/ccYii4reJ9oUFMI0yJAM8HfLH8SQpGOQLzdP+Gr0YaLyDu/EFiZt9cMXwIAAABkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIQCOQPb59BMqmZVlGvo/JYclnpWqIGiPB0c3Y1QZux/lXQAAACB0R5iKCzLmHo5A9wfsOv7oYLfDYGWSaA4WbBKJK3ETRw== \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p384.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p384.pub new file mode 100644 index 000000000000..3016df3fd5ab --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAghNd9jFDm9sueG2QuKV7fTRhKPKB0pprZqRP44dYXSZwAAAAIbmlzdHAyNTYAAABBBIdWgz67U4Fg01DzNm9UmX9EdFEqrfSyWj0ay2JLiCj8I+GHUoSTxOSrj0XkWvVdVpTAkmOX/cQrfJwRwDO71MUAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAZLRydAAAAABjxeEEAAAAAAAAAAAAAAAAAAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQSSpNYuZi/yHiolKGD8FG5oJH8aohYgCxp+u2U3sH7UKnaTOwQUU7hYdbl/YH2Ab+vCZ3ZHx/5naCgo4nTzotyegQITU3aeOS+Ivh8P4zsNc0PrnihFknh1DVLW9w09+8gAAACEAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMQCtcUgAwYP8j657ThbfsTUZz1aiZZL0s1OfSkQKYJ49PJIEWDwZbgTohIG3D0j8PbkAAAAwT0ELlMrSu2sMcqRzbhcOK3KZgkZqdRW+5BHrc7TvYQQiYtOepqPjAC0LhRObcrep \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p521.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p521.pub new file mode 100644 index 000000000000..cff72de7a8d2 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-p521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAga9pP3iYGmmVlFMM97Lb2Nuj7LCi4fVeLN++/xsyHLOoAAAAIbmlzdHAyNTYAAABBBD42p7j3tjDcn1YoNjKgDgec+9Czi+6KNZM9EV51kpSq1ZZz1D5H+iqWByjmT5EtWCPq+2EgdprznvJgRMVejswAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAZLRydAAAAABjxeEEAAAAAAAAAAAAAAAAAAAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQAP2p5Dgl+/P+tRZAsAoV3enPh2oCHheAWHjMLb1tlLPd9uZWH6vG/lb1SElA0KqMsRMNIjGfx2m1DGHuV6LirL/MBMWYTBhvrbMi+ltTd8cMiKaZKwAGcxJGbPtuRR/26qM8w8Aw04l5k4Ipy1l7dDjmO4QkI1rB2kAXHw0u/Wf0UBIsAAACnAAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAACMAAAAQgHbsKp+DSYdwqFDwk3/sF7vAy1rliZG8LYJqwAS6KvyncYgRVGGy2dP3KcBVnGn1DVwBXG4JNzHMZZolUgrK2nxiwAAAEIBhW3gjZ/7vA/Dy8azlubecyPfeLyegulmxvvJ6whmT/xQtkdSAx+JnLERN3cGRLToKA4TTmF0DG/dnfFOQygkMJE= \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha1.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha1.pub new file mode 100644 index 000000000000..27442beafc3c --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha1.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgUvBup0dKwDazWqFmuY+CX0+O9SvgYe6XXwZdGSsCFoAAAAAIbmlzdHAyNTYAAABBBCRtIgvrukZwrNM62pkYACgJjoL0ViIzOZTvyuoaPRxbKTpVUQVeLt+H6kZDnrsloOzfH0P12DN3L4W6+rSyZnAAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAZLRydAAAAABjxeEEAAAAAAAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEA5KypvcBCHtATD1GEgz8s2diLeb7efvSzeYHWmxA/gbuhEtT8grhqi9fi7ZU8Y4+5jL5Wk8fRMhiscle8EM7JeQb20Pxd3c67OfuIXK654q1wyQs60xd9LXEwdJguUsC3KsayZgM42vEg0Z0ZTv3P/i9ZnIdRp+3avIrMmIUrizm7oTIyu/u1QAO8fOLtULtkOLU47vs1h9vM9q3LJMn+s/DcrrHX8Wfto+9Cvbb+B99LUycP2rDNzebs8tMYYAVKwAxEYLbbXeTIhj649/QDyUtGbA5GvYZEB6XVDwD5EYtKDJtZv2lCq3eECklAlzP9j+VGBd6WThNaHZEPklZ/ewAAAQ8AAAAHc3NoLXJzYQAAAQBzQwOIXymvAyvyNhgTSPIi2FFKQdTcjz/2hgoLtroreN/5NMz2DkACChQo6qe5YMJHHsv5JVA4+gdVEMRxp1pL8RASPuSHHN9PSizDrNKZ2MT+woEE1nSFeKj9YzhSTMxvLti67W+FPnw7S39Bf7xrLkWbds+/rwoBeeYXAvwA/4DH0aplF1BUpC5dCFxhaPyVY6MgDC9J5nRxgbuATE40Hn1XwEl5EjKCMSyjnWU6Aqe3b5jJ5HAQFTe3Jmb4fBkivKxvwmbEHDfOLWk0ZMMGZMbHMUT7dSiy5Hjh1J5S8xMnbgPXuFer3q0+4FmdJwvqty+XHSTkwFP53Msw//Gz \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha256.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha256.pub new file mode 100644 index 000000000000..a2d9630f74c6 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgEZ5t7LyaprT8g277kTvOMv/kgHECdpguBplHoRgAYpoAAAAIbmlzdHAyNTYAAABBBLQNIj2s2PZDa7LFPocsNN6ORF1ZsPmXqII23UPlpvFcUa8vDIccoO2shfUrFAMoudRiiyEHaLnpJCjIAvlM+0wAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAZLRydAAAAABjxeEEAAAAAAAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAnT7TBoEM1/ezloAEJ77uOOyRcx9K/PB/NPVVjTR6QgYirEGBkHOfxYBw7d2LlbdaZwiSOirwxj+rbox9+68w5XK/83P4YrxblxycCL6m/9R7y9k0B2TtAnq41Hf59CFZ3dI5WI3tCM2nUr/sUNRIjWelxA3EdTZ5eGhKia2B+dWVn+4YePcJXbasmPUdIA+7a+jdJ5znPiCUOA7bg3JjAOky9rKE/LiXJbpKl20D3RvXbIWosPGo+c93I3buGOFKGYNyWkYI4fljEZlVS5mZYGwG7xpdUq4ATK85lRB/RYU85gJMUdHA+2kqhXKBmMY1PPBoac2RF0zkDrQMbw1SmwAAARQAAAAMcnNhLXNoYTItMjU2AAABAIvIhSDBZwIL81IFD5dCOkT9UWy6PqHZo6xOIWXq/v2y0xZ7WLG4i5alCwxr9R8trBnrEjIrcA9XZVKawC11kaXxTxloLf5HnnEs6e49YewymgmwPMalLUqqJsAIDgdFN42g3UbeLF06a8PsHtcAui08/+xffgKNGV83+QpH+J5F8ykwWKmpc48NjulcTgUbvFmL1qOkt+Jq7ac/Hyrvu/KFx9YibCWSVLOSwL9wwQLBwq3Ham9ggnjBVj2rOfPwMBFGKaXvjb5LUlYMt2LjiPQJVVdAAKjvo/UjTWb5N21hQdtd3QEHViD+dYMkmLCCyo1U2Wfy5yfk05Hy3XqFLe8= \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha512.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha512.pub new file mode 100644 index 000000000000..914a84801bce --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-rsa-sha512.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgIOnlOEWH3RJ+U/Yr/pGEU2ig0h5qeLO9XgwIzocUxgUAAAAIbmlzdHAyNTYAAABBBAR7IuTauq/eQ9DgGVuv9qJ1jmI89k/9VIpKD/edK/mI9Rhz7RprmXC2Heopinwt9vyzvrNts7EwXasmIQcGbq4AAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAZLRydAAAAABjxeEEAAAAAAAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAtsv6VhNt0q6UiBtzVOamVUpjEpLljN/r1yE9YkZdbEX9dmJHFMjr5uJxBT+PQ4w3lzdW0rnDUgX2HU8UkmzwOXCwj8USzs9Mxt9yL78hIVeZL2yRx4jYaWJsw58LzT48GyQGoW+/+FmCI+GBCMoiQ4ehTRhqREpwe0PM3iChc/9XBagjeXDmZir7rHvP3X3R04qCf+ybVv56mWIqWJAizfMMLASe6tnWvz5vvIVwJoCG6oZQeCMVXe4eBbq/gOLrUrnFtHtdpVAS15mE2tvpmG39wYc/fSmgJJC4owg62s1rCSYm+U8SWt9mkyK8BpVZvOcbf/6TXP81AnKqhL0nVwAAARQAAAAMcnNhLXNoYTItNTEyAAABAJpzmavIr0HwFiEX5wCDO+hvR7pKor2kWxSo1dYW+Hgk3DN/pyEc8GQ+jFFY1eiK1mOJ6MPIR/SfJbtIUM0OmfuLIYVGTl6bl+a/faTuvmzXgOWZ9DYtY1NYTWqLkIGgfXFwc3E4Hjamc+t9TD3aN35dYRfJFuMPZr4RmJCKEKlXBnrjFQfJQFCgzFmoFVp3jr+K5odM7oQ/JXmM3xZILeUHTNcC7xXljmweCbqfmRQtlkUheASkyPiV/tR7iz/e+hk17au+X2yymmL27SgK4CHSTcb4hGePUW1DQF7fk2AocEYr/UrXep3TXShBDQtYcJ0A7JaQaI+cEav6wN1EQZQ= \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/ed25519-aesgcm-psw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/ed25519-aesgcm-psw.key new file mode 100644 index 000000000000..673cf2d79101 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/ed25519-aesgcm-psw.key @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAAFmFlczI1Ni1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA +AAGAAAABBxwbaftabtGFPlzbCIuqOIAAAAIAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA +ICuPdFT6OORNyXh9rMfOx3LUCm9yANYovOfNlGd2hg01AAAAkBl0VICPNwd88NHm9w10X0 +bn0WTOJMzyQBw8cNZvswPvczViEFmW0pZwDmeVrBBTLmktn4b3D7IfCMJIbfAq+N+rRZ0p +xhPi6toZopq1wP4dE44DYQ1dr2K4evLv5pRCLJUkmNny/7jFEOggVx8N5o8pOSuf0tNhYd +SCn7oNc1syjS2w0Zjb2ZTiX4L9d60tSLDwLOolS1Xc0nPUMnzC5hM= +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/ed25519-aesgcm-psw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/ed25519-aesgcm-psw.key.pub new file mode 100644 index 000000000000..ed7c311aee03 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/ed25519-aesgcm-psw.key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICuPdFT6OORNyXh9rMfOx3LUCm9yANYovOfNlGd2hg01 diff --git a/vectors/cryptography_vectors/asymmetric/PEM_Serialization/rsa-bad-1025-q-is-2.pem b/vectors/cryptography_vectors/asymmetric/PEM_Serialization/rsa-bad-1025-q-is-2.pem new file mode 100644 index 000000000000..0fc29492adb4 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PEM_Serialization/rsa-bad-1025-q-is-2.pem @@ -0,0 +1,17 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICoQIBAAKBgQHuRPTpIUVPb2xakO/6v/MuBfDglctfg1BYKkXg9Q9S5CKmrRfp +zDMygdqlTjr46SF1vqcsMPk1mswE0O+AW7ljXjq0nrkHoIQiZnDZbHMAyXIgsENd +lZ1ZLGuwf8QD2hMUqGPYYfoVH0LeNFJkZU045znqYGuWXarmZv+9V6mkcgIDAQAB +AoGBAKrZWCTblMcz9yrJDcLJpefjMtOWw8FEtTl8h0IOw1i+NgISNAFjTdEoFKlu +RLE0eJXoLIX4ebQfSWViyV6/lfH0qOs1MDUkNXhkm550P75mY5ZMB0UqicTAt4q/ +Z0SDlXPfDwBFQboaX7VKNa6xW4OPXbQX1yiXQRM6SC0tR5zpAoGBAPcienSQoqe3 +ti1Id/1f+ZcC+HBK5a/BqCwVIvB6h6lyEVNWi/TmGZlA7VKnHXx0kLrfU5YYfJrN +ZgJod8At3LGvHVpPXIPQQhEzOGy2OYBkuRBYIa7KzqyWNdg/4gHtCYpUMeww/QqP +oW8aKTIyppxznPUwNcsu1XMzf96r1NI5AgECAoGBAKrZWCTblMcz9yrJDcLJpefj +MtOWw8FEtTl8h0IOw1i+NgISNAFjTdEoFKluRLE0eJXoLIX4ebQfSWViyV6/lfH0 +qOs1MDUkNXhkm550P75mY5ZMB0UqicTAt4q/Z0SDlXPfDwBFQboaX7VKNa6xW4OP +XbQX1yiXQRM6SC0tR5zpAgEAAoGAe5E9OkhRU9vbFqQ7/q/8y4F8OCVy1+DUFgqR +eD1D1LkIqatF+nMMzKB2qVOOvjpIXW+pyww+TWazATQ74BbuWNeOrSeuQeghCJmc +NlscwDJciCwQ12VnVksa7B/xAPaExSoY9hh+hUfQt40UmRlTTjnOepga5ZdquZm/ +71XqaR0= +-----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048.pem new file mode 100644 index 000000000000..1e8471e961bb --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADALBgkqhkiG9w0BAQoEggSoMIIEpAIBAAKCAQEAt1jpboUoNppBVamc ++nA+zEjljn/gPbRFCvyveRd8Yr0p8y1mlmjKXcQlXcHPVM4TopgFXqDykIHXxJxL +V56ysb4KUGe0nxpmhEso5ZGUgkDIIoH0NAQAsS8rS2ZzNJcLrLGrMY6DRgFsa+G6 +h2DvMwglnsX++a8FIm7Vu+OZnfWpDEuhJU4TRtHVviJSYkFMckyYBB48k1MU+0b4 +pezHconZmMEisBFFbwarNvowf2i/tRESe3myKXfiJsZZ2UzdE3FqycSgw1tx8qV/ +Z8myozUWuihIdw8TGbbsJhEeVFxQEP/DVzC6HHDI3EVpr2jPYeIE60hhZwM7jUmQ +scLerQIDAQABAoIBABHHBrdHJPuKYGxgak6kJIqlRNDY2FLTUGB82LzKiK6APfmM +vOY3meuWkbLyEFreMmwxBlBDFdHqLRQsvWdtBVGTpidertZAdpE8QmZkA7zPcDhc +VmPWwYRsmOuSLvh57tFbVsiS02qtx6f8Npxay0as8wzekNb/3+UTTxkNO/9jQOct +14d8zTRJo6eL93Iv1zyU1lj9utwABCF+NAcAxFT4fdeFjmhx14oq8jekrN67pE/o +4yurS0r2XtQBKjse15u/rQ9NHM5CL6m6ytJ8Kdvcy8qiBia9eRE+P8/omd+8cDfj +we1M751jyG7P5jlGCJVEWpiP7DcXb+Kdhndx3ucCgYEA4pBxftTQ3LH9delpVRQH +rxJdbzARXVbdZf42vnD2SvO5ObFh0XYGV4xnk7DCkLfJsMdH4QPL27FxlCyhg3cz +o15uETHjADVDDb+OtMU7BsK5ujYPnYHzgKJnwcGOr6k3z8cc8OaJRyY6bdX5olfv +pgrZBcc6aN9gRa6bEA7aBp8CgYEAzysRZgrxAj7nrII2DxX5QqH7Fk2xJVhsaZMd +516lvSU+xnUVeaJLOhtTPLmlr6LcB6zbN9nB+4WihgprfYUf51uleF9+W23ECOn4 +kxvw2w0c7ICjn9PoHS8ApSi6W80H8J0zBP9GBRFEzi3x0VNk3OeQr9H9iv2Ro2Gd +uEsSUzMCgYEA0FMq2QmMp3HOcm5WWVGaoyNK4KMdRGtMFq2C3uf1wAONLHxrSnOw +7y1+S/I7ZWBpR3BmKoQYHgFyQ2IqfTzNMYnxwUPSy+0to+WgrZ2xYc0JhCyTfSvx +oDU1HJcCwYjidd5LQUNptQ90qGwZJ2qeRFozJboEfkvvNQORN1nAplcCgYB13HXA +jTcCZRFe9pGU0ZaGzyrPTJIcwgqjobwglptKWbc2JwR5t9h+jW80nBXkL45om3H4 +e12+IBAPnDv9JFC7SkuAiSuVDoS54Yq2/u1vYi1za9grJN7oQ4ZlcB9d/O6oeHa/ +QA/w8BsqBb+OrJg0iVWqgZhyi8JgpjeZ0rPxOwKBgQCeo9gT4oQ4GoK6exhsVz4d +SYUxOf7zp5AxEVTgACX2ubyrcUOUuE/muy/2QVBzcnuFRGVa0Kfo+sE7OnNhAYkx +rI2R9pds8vCjHaoSFAznyaR9Am2DuXsRSc4Qo0KQujvFzAE4fXRslqyA35gTZ1Z+ +u+Cs8ivM7mJcroeZp4pebA== +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash.pem new file mode 100644 index 000000000000..cf085058e003 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEzAIBADAeBgkqhkiG9w0BAQowEaAPMA0GCWCGSAFlAwQCAQUABIIEpTCCBKEC +AQACggEBAKYWKqcVytq9mcB+SX/LCONdaO0xdP6J6lpv0qUfn43VUo3JjWNtGA9k +ZLejx/VtcUgiQrYQbw8dvBclxc2anra/487Wv2RLH4F8uFJa57M9kMvl6Qw1DztY +8KhNwdFTI2jRE6yvUYYSFdnBO9J0WZ48yPuJTgnou3XnMAAoUDjnnpf5qFLSQRxM +ikmPU8GNWMzDT1TBNLG576cMHOqm9yv8l4Pi21F/awFulpaOwmbKxXSY+JDV0XWC +jT3ktYoQlhoil3taJRvRMHFMLyjDc/RelE+75wM72cRMu/5M1z8Gseqov8neb7WX +TmYPkcpI4HaD/1mu6vlpPk6pwuVol60CAwEAAQKB/2SQY8U5GvEH6TSR8zqRbAj4 +CoXwsYwmsrmvMlQdGzFwGMUVXEHAh3bCDczYVBMKWVBm5/mtmSy+NwGresfK3d3F +nkXDHjUosHNc5THjiWfjD4y6QnC690VvrBXXpwOtBc44/MOdSwCZmqWdj6XYOnwZ +z/zaiSUN7jk9EFvRfmVJRJ1I0dh1MmWEmxmi7tGZ/TkSDrDo3ct2vvCyMqOPS/N4 +KKNMi1Y081hZn26asCR/MH/lEYNQn46qq+dU0CREG6phXYuaiXymtIYuclIflwnQ +WKSIKqswX4oazJs6vgk89SPJHlb9o3zFZpi8SvyJGhcJ8VupYG4D9nysIv1lwQKB +gQDRrpJUv+iF4eOdpzr+dKju9meSySf6Z+Zui2ILH9UFZ+zthuPbnnVkmwJmcP1m +R2KOZ1V15UjEcbpr4/KCYu9Xvk9iTMfq/F4OaOXfRuY8cLwzAdyUNPjHPOkTdmvr +aULQaQIWQpPhI8ZVbqzRlTllli//9rHz6IZFVjDZWvLQTQKBgQDKxkvmnZ9v/GFJ +EsZC0KHwSxyqG+QImDO6AB4SL8Ym07QwQvU41PRZ9cH4tOcU2S7Bu1Hld6A2A1fv +nyuQafecVn89eyk4F3Mj/Ypo4bHGVYzl1h8StvWckYLNhHzOndlAvQ5nUICJsRLa +ToP6L37oIT/sDM0Md1Ls/HV/7V+U4QKBgA5qrFD7aOdboqTCTMIWD09uzaw//Gmx +HxzWpIUTSTg37whdz+jXukaSidW1SxbvLY2Q+UVD4H7xOtoUMCZa2w3zXc3qbYxw +kZ74A2YYn9fkAGyZYismgTxhqbzW1ZC4Cgn+TlBtf3FpXkeddnBqjCm5687zjUSx +5hl6VZ18LVm5AoGBALvnlBBqAoRo8NIhZr4lzdr6D98HJ4JbYJu9XiBmSw5R4klS +0yFOHf17QruxD+5+79gxOMwW1c0XvhZcfqc9u2oRsamMhv7mpBk261sTwoTTZFTb +3kGeb+4d3YOLgYiKN/fI+h79N4/hGmJYne5qswRzQ2P/3Mfvj1XzAQOCOa+hAoGA +HXGz2ulswnp+X/Vi92H+xZUPR/VDPoWaUEXXGr9ZnlGo+u3tyfpwzJKFjBDQ09lD +vAvNl4s4jC9oorbOrOcMcN1GTo6ZLvAK7fQc0oeiWUkAfi35cxDvVE9r2kbn3Gjc +10VfBns4ejF/bqy7pwpm3LG2BxPIm1B+dnesNbeQ8oU= +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask.pem new file mode 100644 index 000000000000..ea6f0076515c --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE7AIBADA8BgkqhkiG9w0BAQowL6APMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI +hvcNAQEIMA0GCWCGSAFlAwQCAQUABIIEpzCCBKMCAQACggEBALlAN/dJwsP7y8N0 +mKBKaQb5Hjtbu7KrvFamXA6g0IzUPHESwisYD45VeD1OJ4Hx9eR61Cbhvvdmn81n +1C2tRuBrP8tsbnvt+n35w3BXqqQ+ByAdAvsHF4FpP4zEdhH8tfn6ZSEcqLzXH9HK +M50i1OcAEDvXh+V5t78uWIrt9V8vCw//1ViHhqfprttxybWDXnt7J/FYSKLr+1Le +f9p8vgmtPt2K5pvMa2IGsXz8fWwMGZti64RxXa2k3hno/ove/sxOSefDfpXI2yJn +xlA4XuFOfK6uHz9k6cFDlQdStqznWRj25RGZRW58KE0rjZruVJ/2dsaXe1RB7ps2 +3JElBZsCAwEAAQKCAQBEm9QeceMAUrEUoookU2qyenEH6uGJOrF2JgbSJB0ZC0GX +Xysqaq7YOC9gBSH8rnAzPop0HAdt+UQV/u5GPHaThyUJYg9JNsoe/fG0GcPJMG/T +JOuFrQq3kxNGPzy7TKzY+DOcH9Een03ZlNmoyM2xAAUDJL/f7URwOenxClBl/5Lm +9hnS4NE5B73M6iByP3lUeG8YLqoqJClMx7mN6EAuiJgjDubuaRJU94rqvZJsofwU +5Ka2MHtOgGb3ylPled5wuRR+gm6bbzEtYDvLGqwS6IlEeamW0YfgufJct+MtbMa4 +AzKuacxIS7irVkcQRH8/wvzWKUtMO24mCPYtM6MBAoGBALmFplhqGgD5TA5suVCC +aLAywp/t8a3aFTXSAaIc3t7yCjMNlNvYANIpy1D6xzO7eDwInJUlJzBEjxjewwce +dC2hKCRkVC0gg8Q+yUI06gQIgaJuobFwFT95l7lchhiJd0ss0YzETH1ujTbhcii2 ++y9EFw8R5jgF6z1ymQkmQHI5AoGBAP+gMVD16NaUiHXbe7/yIn1490UuHoWY7jEo +l/KbVy8CnmtffyI//5Ej4JanlQBXxDS/JjxwQg+4cdo9KOF9z1psNXK5Bo1jZ7hB +GBdPNtKE4XsauRW3D+PNIvdFxrZiC8VOwy+dfsnVfd4bvvALJcb6+/XeepI5gsvO +bUO+J2ZzAoGAF7X1JKeq2yUBi3Zp2NhR+PMD3NzUXpvYyiAlBUsbUPMuSogZ1l8s ++69LxPXIL9xt6X5QRN+SuqCIiW0vD+Hch1hpgP0xpPLa5GIB5uxMXGeZ6eCp2bux +e4NW2OHyYYBwNrNrtMoB3KYcdj8qD/oS8F+LcumeutpGznuvA3RYGEECgYBdf5dq +OHfwvKVpDl2mKIeLA0rWR/csAHLnEiT5vO3XqQqO1YAn4+azjL7h++vZE0EV1fDD +XIAdReaG36XrTFwig7/M9XY7EufmEhEgvX2c5LOglnaqRaoPNYIbla8IGLabdaKY +8O9mHauLKPTe0gUAUd8E4FpOz7BSoW9/vraklwKBgQCXfOUPAAGxngzPKWyhyaKM +mUshh353Cjzx/NNwr/sBj93WCiDp4SpfHZ8wgrZJ8UCMm2CiCtK2raiPEzrNo2Wg +sphlQSVoCA0WelT99Eq49ouWnUkQWYU2Bpz2/2e7aCuhVYAKLqCfQg2jw82shewa +RYr0V8K4K8DBgXTuOkgxXg== +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask_diff.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask_diff.pem new file mode 100644 index 000000000000..cd7845e29103 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask_diff.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE6wIBADA8BgkqhkiG9w0BAQowL6APMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI +hvcNAQEIMA0GCWCGSAFlAwQCAwUABIIEpjCCBKICAQACggEBAMoBLOx8mx942jR3 +nSsWLagYH16UVLxiV4E1+noCLLMm99bkWumRIlsJyCEKe9UuekzPrZlICq0VMdMy +dvQGoHBF15YQYuZmMI6Am1afJjNTeV+f9rDotqQ2Ix4S93ngjgNi4BPT70F/dwD1 +4Xe32+IHdyrp99+jtMGj5IUUAVhGg4x8xDUj1mD8Ro94MbRb6Acf86SsvyiTpx5W +ZbnOlnzH1fK2qAtpqEWgrypb3MdJPgpZIx9o1ZYuVQBI3RPFZK0p73viEvyl9mO6 +QLoqdmcvxo/3mkrMG2HNFESjlo28f4eTVhDxK32Uwo7Yv5WaFxIhYBgyYyGiwkxl +OrFCkO8CAwEAAQKCAQAkfJjeMFWelignsPFJDoz5nz3PShCSJFs04giXkBv90gyT +GpUXOhlQA1DMMwYSB/6cMCjlll8jS0BAKw3UXvwMu3jIyLXscsnTe4RTXZS7UZkL +PiwDYU1YFNU8AeYEdByCnRHnUvEUzg6zNDZg9us3BO0v6anVkc686TsGFIp3pJaF +wnZCDlUMMZq4LrJn7BYFxyU3i13C9b4c2NbscPkkldumrobdOaZwdXaxDCiIuaMV +tM800Yn2tZg8YQyFVvtw9jqrYqUK0myNgWtteqhi9pvuZzelbpnuUaAbvwqwkxSY +CCtEJk9DJUmoT+7T7iVL9oO9Ox4/6ozZUBSUVzjNAoGBAPZJ9SqfKZBmXZ6kmSmM +GPSdZ6JKXtNfsFjqUiIowsBqBOgyDxC5goNlJF4R4aiU6x7C7dqyIe61dalPPZY8 +eU6HnfyWUdF6vXHpyBSv6ak9PiNatY/HibPq+snGUCG/TgzXhojnjoeA41OCkSyL +QIMXcWhWYbU48KzCBM3mjIilAoGBANH4Ni1Nmogv3vaZIEPt8lnpyWGGaMCk7GiV +z7IUxgkvfedUJvflpFON/p9DYw/mU4eKzv4Kc2cHfCwvI+O1ftOHRZBEnVdfS9lq +K5dMHSSKtdqtz1hzRbSCWNQ+hockktHRoTnvOxlXsey0a9almBVo3bawfB8RN3U6 +gz7WgGsDAoGAZpj2lbPKF8pc86pz13fyKWys8FF04S76gn/SiUJbptZDhwrbdcch +1GS82qcuTxECRUVE2pbcRdm30zkcWcqFai5apQ9ltBMieiK+Y8fIWeUWTpoKCoRA +HAAmSwne9cAA3p6l/8AegtoxWOeKXHkB/do1NxbNCzZWJFGKuM9y+bUCgYBCovKW +uB1GAWNSgdBinp6eeHrH779I/E5m9ryeuMcM3Tyo8OUZIZFgTx0y8FD9F80EpEID +D9AGL7Lx1tgeCVjByxmBqrUAqKbKzk4dSzOoiDkkuKqoWJUTr5Z/bYSGWU4bNttj +JpBr/4/hHnVm/tDgYpKSyznpJi6ijrpec/b3fwKBgD2snVQ/UXV7G8oy7xwgXcLO +PxMa9xUPsmNL3Gytd/L4xaefxW3oENeBM/UU/PfL13zBROSi5Mf7RTiMTwFBSzR1 +YCu75tJ2fx8sghjVq1fZCfOQn/StG2IYsx7cVwv5Z7diESjU50QffipZM6WSdJKi +/Ux4TLILXEMvL4V1VX3q +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask_salt.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask_salt.pem new file mode 100644 index 000000000000..c4867a138082 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_hash_mask_salt.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE8QIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI +hvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAEggSnMIIEowIBAAKCAQEA4Hw2apS/ +fIsG6ESJY8RZ0wW38eb8onfDA3KYnI+uICySrngMVMILd3+TrQjRCcTAeB/9tu9q +zdOoEWGfg51O2nI+DkETD7lCRfMe4GvrfgL7ii3Qu6ozkYBLKAk6zt3PYIsrSEff +DmgK4ef77d0CfDwKMJrJIj4ptiVuRB0rhRtViZ++zJmzocidFI+o29bGEgD85gdM +acIeXfnUnnwVpxLYyNKSWG1oZV2Y00p0BzRYJutWvcJ+diwfsxO62Wh36uwyRaOS +CJ9Cxtm0bn+YwmrSV/uSfCrD0ZREQpB6CnJErOUATI/Vbe0XaQ3VUpgiiS3gZMW1 +aCiYMARNKiq4vwIDAQABAoIBAADbQqYSdAjC1tmuGxSiOr2v9sMFSV2+mGnWLmmE +U13i4I8eq3/rd/mj+I9QxJ054bjWhJc/8/ZSQkaU4hbIyEYZuk2FWNuuFzlp6qSP +YHxpGDrd9lZTJPaWkAoeSuWOhYxw4sOGswl8oAt1bM+wkJaiql8bAv0rxovIroDi +HfjMtqibs4liTQyRYFk67nuZ/taFyQkY0KkYqc4+raHeT6DKExLNCKJnvaw5O4j2 +kHQaABjRKG+KG++o6h+IIJBkZdkUzLJXu5vY3RVVduH26V/l2ldLmGzD5DqRP3ff +ruYte9xHi2rCr42BnlTpDhFBAqFGL1hsFQwMp5tN7BgmEPUCgYEA5bJCbcMIb+aL +OXCCanDL+jV3o6jAod+9GLfnELYvnDpITT08pXw3HnFF1cS13wV1B+RpvWVNNOrJ +dH1sz2mVCglqO5PPZSuIMuiOSGbDYhNxkTv6OU5sDMjC7dDKZdAYsWm/7bAetUrN +HKz+8qYCUb1GRkh9hdZkNK+fmIDa1f0CgYEA+jEvSGMhmQdDtfLguPZxVSx0NTNg +goPTDHGwazTo+1i2ZTu2JgYJcMFTKFqRFYMHGl7pfXQx4U4z15FaUJahV5xvb8cf +Mi4XvPhOdTlqJcDAJD6N5bk322AZM2rHGkfTaqUn5XvOq4FHs6SgYgJF++C1xzAu +S9J6yyJ/qUe+6GsCgYBuaazy9DCHEcxU9RdLsSLsCG2VNxY5+cH9MtGYv+rM71s3 +/bq8VaRtNsf6BQ/jv8zM2WhWyW4+hKoIHA6E+VzSMUpmjxu/pxhWWGGkvfknmO8b +gDg8+cyIrKy/AoF4RXrJNWs0B1gLj4RfR21aGKC+x/wS5t+nyTHr/Yv7E92dxQKB +gCtOeDC/eAFVEJNeByf9AIENwM+0pO/ygYWV6EOmVO2s3WWIgG70fI3X6N0DUDm5 +BHG8HA5rHncxYifeMRPh/ut7WI6wmOXGtLUxBeOknIsMYjXj3gv1k4WVjMcppG0Y +IbBEBjPiylNFfXPK+zf7zMFclBp2bI0TUc33msFiedkhAoGBANpL3yiw73HWGjEL +8KHhxPFWjQX6EsUnzkBvymxiOR1f2KpsSC/4iaOsFmqWezYurOCUvs9+PkdZKGs8 +jXMwc+3Pbg5FQ4IG0kBvkSjLKWxZUOzjRF5MRW8ilwGQEj+5A8e18kyQtisIh+3S +pyqbyULX0/e0El4YiFjtfxvsWtiy +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_pub.der b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_pub.der new file mode 100644 index 000000000000..ff761ddb5273 Binary files /dev/null and b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pss_2048_pub.der differ diff --git a/vectors/cryptography_vectors/ciphers/AES/OCB3/openssl.txt b/vectors/cryptography_vectors/ciphers/AES/OCB3/openssl.txt new file mode 100644 index 000000000000..f253f3ddc03c --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/OCB3/openssl.txt @@ -0,0 +1,51 @@ +# Vectors from https://github.com/openssl/openssl/commit/2f19ab18a29cf9c82cdd68bc8c7e5be5061b19be +# Reformatted to fit our NIST loader + +COUNT = 0 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = 000000000001020304050607 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F +Ciphertext = F5186C9CC3506386919B6FD9443956E05B203313F8AB35E916AB36932EBDDCD2945901BABE7CF29404929F322F954C916065FABF8F1E52F4BD7C538C0F96899519DBC6BC504D837D8EBD1436B45D33F528CB642FA2EB2C403FE604C12B819333C14DFF7D62A13C4A3422456207453190 + +COUNT = 1 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = 000000000001020304050607 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F +Ciphertext = F5186C9CC3506386919B6FD9443956E05B203313F8AB35E916AB36932EBDDCD2945901BABE7CF29404929F322F954C916065FABF8F1E52F4BD7C538C0F96899519DBC6BC504D837D8EBD1436B45D33F528CB642FA2EB2C403FE604C12B8193332374120A78A1171D23ED9E9CB1ADC204D47D84F6FF912C79B6A4223AB9BE2DB8 + +COUNT = 2 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = 000000000001020304050607 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071000102030405060708090A0B0C0D +Ciphertext = F5186C9CC3506386919B6FD9443956E05B203313F8AB35E916AB36932EBDDCD2945901BABE7CF29404929F322F954C916065FABF8F1E52F4BD7C538C0F96899519DBC6BC504D837D8EBD1436B45D33F528CB642FA2EB2C403FE604C12B8193332374120A78A1171D23ED9E9CB1ADC20412C017AD0CA498827C768DDD99B26E9141970D13737B7BD1B5FBF49ED4412CA5 + +COUNT = 3 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = 000000000001020304050607 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D +Ciphertext = F5186C9CC3506386919B6FD9443956E05B203313F8AB35E916AB36932EBDDCD2945901BABE7CF29404929F322F954C916065FABF8F1E52F4BD7C538C0F96899519DBC6BC504D837D8EBD1436B45D33F528CB642FA2EB2C403FE604C12B8193332374120A78A1171D23ED9E9CB1ADC20412C017AD0CA498827C768DDD99B26E91EDB8681700FF30366F07AEDE8CEACC1FBE0228651ED4E48A11BDED68D953F3A0 + +COUNT = 4 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = 000000000001020304050607 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D +Ciphertext = F5186C9CC3506386919B6FD9443956E05B203313F8AB35E916AB36932EBDDCD2945901BABE7CF29404929F322F954C916065FABF8F1E52F4BD7C538C0F96899519DBC6BC504D837D8EBD1436B45D33F528CB642FA2EB2C403FE604C12B8193332374120A78A1171D23ED9E9CB1ADC20412C017AD0CA498827C768DDD99B26E91EDB8681700FF30366F07AEDE8CEACC1F39BE69B91BC808FA7A193F7EEA43137B17BC6E10B16E5FDC52836E7D589518C7 + +COUNT = 5 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = 000000000001020304050607 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D +Ciphertext = F5186C9CC3506386919B6FD9443956E05B203313F8AB35E916AB36932EBDDCD2945901BABE7CF29404929F322F954C916065FABF8F1E52F4BD7C538C0F96899519DBC6BC504D837D8EBD1436B45D33F528CB642FA2EB2C403FE604C12B8193332374120A78A1171D23ED9E9CB1ADC20412C017AD0CA498827C768DDD99B26E91EDB8681700FF30366F07AEDE8CEACC1F39BE69B91BC808FA7A193F7EEA43137B11CF99263D693AEBDF8ADE1A1D838DEDE84AAC18666116990A3A37B3A5FC55BD + +COUNT = 6 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = 000000000001020304050607 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D +Ciphertext = F5186C9CC3506386919B6FD9443956E05B203313F8AB35E916AB36932EBDDCD2945901BABE7CF29404929F322F954C916065FABF8F1E52F4BD7C538C0F96899519DBC6BC504D837D8EBD1436B45D33F528CB642FA2EB2C403FE604C12B8193332374120A78A1171D23ED9E9CB1ADC20412C017AD0CA498827C768DDD99B26E91EDB8681700FF30366F07AEDE8CEACC1F39BE69B91BC808FA7A193F7EEA43137B11CF99263D693AEBDF8ADE1A1D838DED48D9E09F452F8E6FBEB76A3DED47611C3E5EA7EE064FE83B313E28D411E91EAD diff --git a/vectors/cryptography_vectors/ciphers/AES/OCB3/rfc7253.txt b/vectors/cryptography_vectors/ciphers/AES/OCB3/rfc7253.txt new file mode 100644 index 000000000000..4ade5aa73b19 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/OCB3/rfc7253.txt @@ -0,0 +1,112 @@ +# AES 128 OCB vectors from RFC 7253 +COUNT = 0 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221100 +AAD = +Plaintext = +Ciphertext = 785407BFFFC8AD9EDCC5520AC9111EE6 + +COUNT = 1 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221101 +AAD = 0001020304050607 +Plaintext = 0001020304050607 +Ciphertext = 6820B3657B6F615A5725BDA0D3B4EB3A257C9AF1F8F03009 + +COUNT = 2 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221102 +AAD = 0001020304050607 +Plaintext = +Ciphertext = 81017F8203F081277152FADE694A0A00 + +COUNT = 3 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221103 +AAD = +Plaintext = 0001020304050607 +Ciphertext = 45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9 + +COUNT = 4 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221104 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 571D535B60B277188BE5147170A9A22C3AD7A4FF3835B8C5701C1CCEC8FC3358 + +COUNT = 5 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221105 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = +Ciphertext = 8CF761B6902EF764462AD86498CA6B97 + +COUNT = 6 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221106 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D + +COUNT = 7 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221107 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = 1CA2207308C87C010756104D8840CE1952F09673A448A122C92C62241051F57356D7F3C90BB0E07F + +COUNT = 8 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221108 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = +Ciphertext = 6DC225A071FC1B9F7C69F93B0F1E10DE + +COUNT = 9 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA99887766554433221109 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = 221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF + +COUNT = 10 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA9988776655443322110A +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = BD6F6C496201C69296C11EFD138A467ABD3C707924B964DEAFFC40319AF5A48540FBBA186C5553C68AD9F592A79A4240 + +COUNT = 11 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA9988776655443322110B +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = +Ciphertext = FE80690BEE8A485D11F32965BC9D2A32 + +COUNT = 12 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA9988776655443322110C +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = 2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973792EF46040C53F1432BCDFB5E1DDE3BC18A5F840B52E653444D5DF + +COUNT = 13 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA9988776655443322110D +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = D5CA91748410C1751FF8A2F618255B68A0A12E093FF454606E59F9C1D0DDC54B65E8628E568BAD7AED07BA06A4A69483A7035490C5769E60 + +COUNT = 14 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA9988776655443322110E +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = +Ciphertext = C5CD9D1850C141E358649994EE701B68 + +COUNT = 15 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = BBAA9988776655443322110F +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = 4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E479AD363AC366B95A98CA5F3000B1479 diff --git a/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce104.txt b/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce104.txt new file mode 100644 index 000000000000..399c9000d358 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce104.txt @@ -0,0 +1,115 @@ +# Vectors https://gitlab.com/dkg/ocb-test-vectors/-/blob/ec0131616679f588c3be0ac7c33b7a663e1a47d4/test-vector-1-nonce104.txt +# 104-bit nonce forms of RFC 7253 +# Reformatted to work with our NIST loader + +COUNT = 0 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221100 +AAD = +Plaintext = +Ciphertext = 1AF957957B85C3D7F6CA08C7C5FC8F4A + +COUNT = 1 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221101 +AAD = 0001020304050607 +Plaintext = 0001020304050607 +Ciphertext = F4132F7B364D13A2303ACE52DDF90774E24E3E8895AC7F88 + +COUNT = 2 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221102 +AAD = 0001020304050607 +Plaintext = +Ciphertext = 20E9B13D02F7B19AFDC4659344960BED + +COUNT = 3 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221103 +AAD = +Plaintext = 0001020304050607 +Ciphertext = 4A2A7E6D7A0A0EA4D652CC24F2208986E47B3251A66B5944 + +COUNT = 4 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221104 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = EEED2C9E7CFA9580551B03DCDB2E1DFA4A60E8225633281B98173DD6F1F1A57F + +COUNT = 5 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221105 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = +Ciphertext = 19BAE49F721302071167C34E02A8BE9B + +COUNT = 6 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221106 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = E254116668AEC1D2663E3E9B914AC47D0337401A0B16E4605B94A2C45F0F53CB + +COUNT = 7 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221107 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = 0C0299BEE2D5B65CBC82A6EE119543B7B89DE85561D149BFC1CBE6EC8749065C6068E046FB2BA7F7 + +COUNT = 8 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221108 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = +Ciphertext = F4E603C017B49123CD3EEBF0F342DE31 + +COUNT = 9 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA99887766554433221109 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = 9A2D75CA34639FF92CACDD3A881ED446B0E790D719A9DFD680C97FAE8ECE18A03A4C67DC1C0763B6 + +COUNT = 10 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA9988776655443322110A +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = 0FEBCFA18BF3D2C4C155266755817F843DFA5A5CFD8987D87BE45F3669599D66B2D98602565E18AC31AD88C7C51A6988 + +COUNT = 11 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA9988776655443322110B +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = +Ciphertext = 6F48A1E1F0D43023CFA84F4143E286B8 + +COUNT = 12 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA9988776655443322110C +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = 1DF09EC159EB74B3B4AC4B440D2D382ADE25D3A26D9A2A2EDCF23F41002FB7EB53417D8AFED547BFD54056BC9EA9C590 + +COUNT = 13 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA9988776655443322110D +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = 3BCFFDEAC246CB305D367A25489ACE1F8FF0317260401B9E1A08DA6C11A0CC490871BD1A5E4FA29FF0E0A7326F0F871583AA3FAE224DD5EB + +COUNT = 14 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA9988776655443322110E +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = +Ciphertext = FA7B7AC1ABD4097F4D547BE9FD5D0BB2 + +COUNT = 15 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = CCBBAA9988776655443322110F +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = C8B16DA1C0AC72E4C993AC75EBD800D0ECDC45EE9DDC2D70C74BA3E4EFCC98A9DF77F8560D7EB2C33EDE4A46D9A2AD0CCAEB653BB6D38725 diff --git a/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce112.txt b/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce112.txt new file mode 100644 index 000000000000..5239f8b2124c --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce112.txt @@ -0,0 +1,115 @@ +# Vectors from https://gitlab.com/dkg/ocb-test-vectors/-/blob/ec0131616679f588c3be0ac7c33b7a663e1a47d4/test-vector-1-nonce112.txt +# 112-bit nonce forms of RFC 7253 +# Reformatted to work with our NIST loader + +COUNT = 0 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221100 +AAD = +Plaintext = +Ciphertext = B22774052013981C3038DA65757A55E4 + +COUNT = 1 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221101 +AAD = 0001020304050607 +Plaintext = 0001020304050607 +Ciphertext = 3791C1215BB0F2E3B008F1A9BFF0A2069BD2B3B93168AD17 + +COUNT = 2 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221102 +AAD = 0001020304050607 +Plaintext = +Ciphertext = C07EDDBB4359D5E68F7618E7D397BFAC + +COUNT = 3 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221103 +AAD = +Plaintext = 0001020304050607 +Ciphertext = AF02506326B86248936845A600CEC91888F52C214ED1674A + +COUNT = 4 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221104 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 165A060B6A9ED2F930A7D6DEC0195B5B19722619DD37749A1B97FF6C63393009 + +COUNT = 5 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221105 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = +Ciphertext = E1616FC25D8A300D8C64CD2018071BFE + +COUNT = 6 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221106 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 14F2D292673A8F1DA6B80543658784F5DEE9FF8FF1B0A40FD16720E2B2970549 + +COUNT = 7 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221107 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = 98B9D538E13A8D12CE94C53F7C36675C77C0A8C9BDE234FDB02E92506483A49976AE5585F1777360 + +COUNT = 8 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221108 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = +Ciphertext = 8B3F1EA59A0D5D7C9FD369B6CEE0F4A6 + +COUNT = 9 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA99887766554433221109 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = AB910BACF2F2409EE871D2818AC31ADFAA39DA637E2D1BF2D87985B4B532966CAF43B13EE754AB07 + +COUNT = 10 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA9988776655443322110A +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = 635623537A7D54ABB68F5B23E151FFAA77A6E0EC19DF46117C2E990154C74539393E11FBC726AACB37023175DD592667 + +COUNT = 11 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA9988776655443322110B +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = +Ciphertext = 97CC4DE80C67DD335771E498944288BF + +COUNT = 12 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA9988776655443322110C +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = A159D066AB9BF3C2B3DC4881BD4DE3A197B12548F11F91972D3B0E881A811AED6427067C99276B1CD0E4382C4C9A8709 + +COUNT = 13 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA9988776655443322110D +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = 259731C171E61C6FEE8E55A7B784DAB3B4B75AC8DE8C4A3CC01C96F19EAFA7F4FC49C84220893993A0B48DF8A969ACECD84BBAA996375A4F + +COUNT = 14 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA9988776655443322110E +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = +Ciphertext = F03AF5CB012CF7228FCAEC2B2B0FFF69 + +COUNT = 15 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = DDCCBBAA9988776655443322110F +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = 45979D0ACA9A7DA537C96BCDBE1F40A288C9FD0E608148C904DA17ACAC18407E3F5D1D5A9546EA59F9F3A1D13EEF2A13676101371D0BAB20 diff --git a/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce120.txt b/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce120.txt new file mode 100644 index 000000000000..9d63b0a35a50 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/OCB3/test-vector-1-nonce120.txt @@ -0,0 +1,115 @@ +# Vectors from https://gitlab.com/dkg/ocb-test-vectors/-/blob/4db265280d1669d57f6a8458ee6c5e8a6db6bd0e/test-vector-1-nonce120.txt +# 120-bit nonce forms of RFC 7253 +# Reformatted to work with our NIST loader + +COUNT = 0 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221100 +AAD = +Plaintext = +Ciphertext = 752ACD2132C41E020E41FB223EFD77B6 + +COUNT = 1 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221101 +AAD = 0001020304050607 +Plaintext = 0001020304050607 +Ciphertext = 201FE4D89EA7BD1EB5B1577DB16283B8AED1715AD6BE5149 + +COUNT = 2 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221102 +AAD = 0001020304050607 +Plaintext = +Ciphertext = 710960B9EE00B8F44D2E8120AABA63AE + +COUNT = 3 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221103 +AAD = +Plaintext = 0001020304050607 +Ciphertext = 084E869570194BD25032FE9E5328E45D507E74F3366E20D2 + +COUNT = 4 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221104 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 9676EE37FD645C07C0D4F70AABF686688E39B2FB3FC4FF30DCD1827B36A298D3 + +COUNT = 5 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221105 +AAD = 000102030405060708090A0B0C0D0E0F +Plaintext = +Ciphertext = 9D510F56EDF72FFA34969BCEF91E6DE9 + +COUNT = 6 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221106 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = D5E15AA1D232AB57F234366DFFB25574A3636A5F3E3433EA4590CBF4F9AC1F4D + +COUNT = 7 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221107 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = 1C4B6777B7F137C30971A93DE3C56CC735686A6F7703142FAB8ACC987C1406DFF96273C5376E6210 + +COUNT = 8 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221108 +AAD = 000102030405060708090A0B0C0D0E0F1011121314151617 +Plaintext = +Ciphertext = 96E670C0238FB969B7ACE4ABAF7438C7 + +COUNT = 9 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA99887766554433221109 +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F1011121314151617 +Ciphertext = 1290A686D825F712E594BE4039C04D3E44F7D1342B84FFCAD68BBDFA04B580EA9A01E2F4565399C3 + +COUNT = 10 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA9988776655443322110A +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = FBDFC11F749217BB7FAE5D4036B8F22803712EFF9EF94342FE1B684968D0E3E381A277DAAB83579406A01E2675A082C9 + +COUNT = 11 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA9988776655443322110B +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Plaintext = +Ciphertext = 90CDA8A05161D2873361374B76F95430 + +COUNT = 12 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA9988776655443322110C +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +Ciphertext = D1320AF4B6FF8AFEECEE7921395D4E8692717753EE15F5038EB674DA43D6EA8DBE7831E723BE471F62D9E7F49A7D3B32 + +COUNT = 13 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA9988776655443322110D +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = 5C79F1C4B9A204ED3323616D576FC500E4A71939F03A3C3DE2C097AF2C6C81DC3F0309E76082B1F50FF8522959FFE41F37EF507E9076D32C + +COUNT = 14 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA9988776655443322110E +AAD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Plaintext = +Ciphertext = 3BF158B7DE76C5151EF6086A825D0CC4 + +COUNT = 15 +Key = 000102030405060708090A0B0C0D0E0F +Nonce = EEDDCCBBAA9988776655443322110F +AAD = +Plaintext = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = 34DA59D2EB08F47822D48C85B6A1D23694E1D3DE680D616D7B1B59472C13E369C68DCA699DA1686A339D5452803632810B0840E6804AB020 diff --git a/vectors/cryptography_vectors/ciphers/AES/SIV/openssl.txt b/vectors/cryptography_vectors/ciphers/AES/SIV/openssl.txt new file mode 100644 index 000000000000..ab3940ba1014 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/SIV/openssl.txt @@ -0,0 +1,33 @@ +# Cipher = aes-128-siv +COUNT = 0 +Key = fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff +AAD = 101112131415161718191a1b1c1d1e1f2021222324252627 +Tag = 85632d07c6e8f37f950acd320a2ecc93 +Plaintext = 112233445566778899aabbccddee +Ciphertext = 40c02b9690c4dc04daef7f6afe5c + +# Cipher = aes-128-siv +COUNT = 1 +Key = 7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f +AAD = 00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100 +AAD2 = 102030405060708090a0 +AAD3 = 09f911029d74e35bd84156c5635688c0 +Tag = 7bdb6e3b432667eb06f4d14bff2fbd0f +Plaintext = 7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553 +Ciphertext = cb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d + +# Cipher = aes-192-siv +COUNT = 2 +Key = fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfefffffefdfcfbfaf9f8f7f6f5f4f3f2f1f0 +AAD = 101112131415161718191a1b1c1d1e1f2021222324252627 +Tag = 89e869b93256785154f0963962fe0740 +Plaintext = 112233445566778899aabbccddee +Ciphertext = eff356e42dec1f4febded36642f2 + +# Cipher = aes-256-siv +COUNT = 3 +Key = fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfefff0f1f2f3f4f5f6f7f8f9fafbfcfdfefffffefdfcfbfaf9f8f7f6f5f4f3f2f1f0 +AAD = 101112131415161718191a1b1c1d1e1f2021222324252627 +Tag = 724dfb2eaf94dbb19b0ba3a299a0801e +Plaintext = 112233445566778899aabbccddee +Ciphertext = f3b05a55498ec2552690b89810e4 diff --git a/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-cbc.txt b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-cbc.txt new file mode 100644 index 000000000000..49c5f8516803 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-cbc.txt @@ -0,0 +1,17 @@ +# Vectors from draft-ribose-cfrg-sm4-10.txt. Reformatted to work with the NIST loader +# SM4 CBC +[ENCRYPT] + +# A.2.2.1 +COUNT = 0 +KEY = 0123456789abcdeffedcba9876543210 +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = 78ebb11cc40b0a48312aaeb2040244cb4cb7016951909226979b0d15dc6a8f6d + +# A.2.2.2 +COUNT = 1 +KEY = fedcba98765432100123456789abcdef +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = 0d3a6ddc2d21c698857215587b7bb59a91f2c147911a4144665e1fa1d40bae38 diff --git a/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-cfb.txt b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-cfb.txt new file mode 100644 index 000000000000..4c2e4abf3706 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-cfb.txt @@ -0,0 +1,17 @@ +# Vectors from draft-ribose-cfrg-sm4-10.txt. Reformatted to work with the NIST loader +# SM4 CFB +[ENCRYPT] + +# A.2.4.1 +COUNT = 0 +KEY = 0123456789abcdeffedcba9876543210 +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = ac3236cb861dd316e6413b4e3c7524b769d4c54ed433b9a0346009beb37b2b3f + +# A.2.4.2 +COUNT = 1 +KEY = fedcba98765432100123456789abcdef +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = 5dcccd25a84ba16560d7f265887068490d9b86ff20c3bfe115ffa02ca6192cc5 diff --git a/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ctr.txt b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ctr.txt new file mode 100644 index 000000000000..0aea1572a8f6 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ctr.txt @@ -0,0 +1,17 @@ +# Vectors from draft-ribose-cfrg-sm4-10.txt. Reformatted to work with the NIST loader +# SM4 CTR +[ENCRYPT] + +# A.2.5.1 +COUNT = 0 +KEY = 0123456789abcdeffedcba9876543210 +PLAINTEXT = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccddddddddddddddddeeeeeeeeeeeeeeeeffffffffffffffffaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = ac3236cb970cc20791364c395a1342d1a3cbc1878c6f30cd074cce385cdd70c7f234bc0e24c11980fd1286310ce37b926e02fcd0faa0baf38b2933851d824514 + +# A.2.5.2 +COUNT = 1 +KEY = fedcba98765432100123456789abcdef +PLAINTEXT = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccddddddddddddddddeeeeeeeeeeeeeeeeffffffffffffffffaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = 5dcccd25b95ab07417a08512ee160e2f8f661521cbbab44cc87138445bc29e5c0ae0297205d62704173b21239b887f6c8cb5b800917a2488284bde9e16ea2906 diff --git a/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ecb.txt b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ecb.txt new file mode 100644 index 000000000000..c9a6874228fe --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ecb.txt @@ -0,0 +1,28 @@ +# Vectors from draft-ribose-cfrg-sm4-10.txt. Reformatted to work with the NIST loader +# Originally from GB/T 32907-2016 Example 1 +# SM4 ECB +[ENCRYPT] + +# A.1.1/A.1.2 +COUNT = 0 +KEY = 0123456789abcdeffedcba9876543210 +PLAINTEXT = 0123456789abcdeffedcba9876543210 +CIPHERTEXT = 681edf34d206965e86b3e94f536e4246 + +# A.1.4/A.1.5 +COUNT = 1 +KEY = fedcba98765432100123456789abcdef +PLAINTEXT = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = f766678f13f01adeac1b3ea955adb594 + +# A.2.1.1 +COUNT = 2 +KEY = 0123456789abcdeffedcba9876543210 +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +CIPHERTEXT = 5ec8143de509cff7b5179f8f474b86192f1d305a7fb17df985f81c8482192304 + +# A.2.1.2 +COUNT = 3 +KEY = fedcba98765432100123456789abcdef +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +CIPHERTEXT = c5876897e4a59bbba72a10c83872245b12dd90bc2d200692b529a4155ac9e600 diff --git a/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ofb.txt b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ofb.txt new file mode 100644 index 000000000000..27c611d2a8f5 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/SM4/draft-ribose-cfrg-sm4-10-ofb.txt @@ -0,0 +1,17 @@ +# Vectors from draft-ribose-cfrg-sm4-10.txt. Reformatted to work with the NIST loader +# SM4 OFB +[ENCRYPT] + +# A.2.3.1 +COUNT = 0 +KEY = 0123456789abcdeffedcba9876543210 +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = ac3236cb861dd316e6413b4e3c7524b71d01aca2487ca582cbf5463e6698539b + +# A.2.3.2 +COUNT = 1 +KEY = fedcba98765432100123456789abcdef +PLAINTEXT = aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbb +IV = 000102030405060708090a0b0c0d0e0f +CIPHERTEXT = 5dcccd25a84ba16560d7f2658870684933fa16bd5cd9c856cacaa1e101897a97 diff --git a/vectors/cryptography_vectors/hashes/SM3/oscca.txt b/vectors/cryptography_vectors/hashes/SM3/oscca.txt new file mode 100644 index 000000000000..b0d0475ce7c5 --- /dev/null +++ b/vectors/cryptography_vectors/hashes/SM3/oscca.txt @@ -0,0 +1,31 @@ +# Vectors from https://raw.githubusercontent.com/torvalds/linux/master/crypto/testmgr.h, +# originally from http://www.oscca.gov.cn/UpFile/20101222141857786.pdf and +# https://github.com/adamws/oscca-sm3 +# Reformatted to work with the NIST loader +# SM3 + +Len = 0 +Msg = 00 +MD = 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b + +Len = 8 +Msg = 61 +MD = 623476ac18f65a2909e43c7fec61b49c7e764a91a18ccb82f1917a29c86c5e88 + +# A.1. Example 1 +Len = 24 +Msg = 616263 +MD = 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0 + +# A.1. Example 2 +Len = 208 +Msg = 6162636465666768696a6b6c6d6e6f707172737475767778797a +MD = b80fe97a4da24afc277564f66a359ef440462ad28dcc6d63adb24d5c20a61595 + +Len = 512 +Msg = 61626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364 +MD = debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732 + +Len = 2048 +Msg = 61626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364616263646162636461626364 +MD = b965764c8bebb091c7602b74afd34eefb531dccb4e0076d9b7cd813199b45971 diff --git a/vectors/cryptography_vectors/pkcs12/name-1-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-1-no-pwd.p12 new file mode 100644 index 000000000000..72a18eedf1cc Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-1-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-1-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-1-pwd.p12 new file mode 100644 index 000000000000..da279ffea958 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-1-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-2-3-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-2-3-no-pwd.p12 new file mode 100644 index 000000000000..e66d95994543 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-2-3-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-2-3-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-2-3-pwd.p12 new file mode 100644 index 000000000000..381dfbe9660b Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-2-3-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-2-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-2-no-pwd.p12 new file mode 100644 index 000000000000..2fb99fb0b540 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-2-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-2-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-2-pwd.p12 new file mode 100644 index 000000000000..076390b63690 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-2-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-3-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-3-no-pwd.p12 new file mode 100644 index 000000000000..b2b5aad21418 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-3-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-3-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-3-pwd.p12 new file mode 100644 index 000000000000..bbe947fb8051 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-3-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-all-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-all-no-pwd.p12 new file mode 100644 index 000000000000..f16920113a68 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-all-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-all-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-all-pwd.p12 new file mode 100644 index 000000000000..4451e5b27838 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-all-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-unicode-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-unicode-no-pwd.p12 new file mode 100644 index 000000000000..aae136663e1d Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-unicode-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/name-unicode-pwd.p12 b/vectors/cryptography_vectors/pkcs12/name-unicode-pwd.p12 new file mode 100644 index 000000000000..9c554aa2147a Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/name-unicode-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-2-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-2-no-pwd.p12 new file mode 100644 index 000000000000..dcbe9aff1a80 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-2-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-2-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-2-pwd.p12 new file mode 100644 index 000000000000..9447e24f02c4 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-2-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-3-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-3-no-pwd.p12 new file mode 100644 index 000000000000..a0d227724081 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-3-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-3-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-3-pwd.p12 new file mode 100644 index 000000000000..431586990057 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-3-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-all-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-all-no-pwd.p12 new file mode 100644 index 000000000000..ef3e3cec2d18 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-all-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-all-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-all-pwd.p12 new file mode 100644 index 000000000000..7e3a6326132c Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-all-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-unicode-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-unicode-no-pwd.p12 new file mode 100644 index 000000000000..60caec4d6fc3 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-unicode-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-name-unicode-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-name-unicode-pwd.p12 new file mode 100644 index 000000000000..a57f49e595c7 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-name-unicode-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-no-name-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-no-name-no-pwd.p12 new file mode 100644 index 000000000000..a1fa2136dd24 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-no-name-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-cert-no-name-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-cert-no-name-pwd.p12 new file mode 100644 index 000000000000..c23a7615e19b Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-cert-no-name-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-name-no-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-name-no-pwd.p12 new file mode 100644 index 000000000000..c71d24dc26aa Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-name-no-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs12/no-name-pwd.p12 b/vectors/cryptography_vectors/pkcs12/no-name-pwd.p12 new file mode 100644 index 000000000000..f2ac4c1a9e63 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/no-name-pwd.p12 differ diff --git a/vectors/cryptography_vectors/pkcs7/amazon-roots.der b/vectors/cryptography_vectors/pkcs7/amazon-roots.der new file mode 100644 index 000000000000..f9eab5c17771 Binary files /dev/null and b/vectors/cryptography_vectors/pkcs7/amazon-roots.der differ diff --git a/vectors/cryptography_vectors/x509/accvraiz1.pem b/vectors/cryptography_vectors/x509/accvraiz1.pem new file mode 100644 index 000000000000..baf74db793ec --- /dev/null +++ b/vectors/cryptography_vectors/x509/accvraiz1.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/alternate-rsa-sha1-oid.pem b/vectors/cryptography_vectors/x509/alternate-rsa-sha1-oid.pem deleted file mode 100644 index 807a28b5547f..000000000000 --- a/vectors/cryptography_vectors/x509/alternate-rsa-sha1-oid.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBwjCCAS+gAwIBAgIQj2d4hVEz0L1DYFVhA9CxCzAJBgUrDgMCHQUAMA8xDTAL -BgNVBAMTBFZQUzEwHhcNMDcwODE4MDkyODUzWhcNMDgwODE3MDkyODUzWjAPMQ0w -CwYDVQQDEwRWUFMxMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaqKn40uaU -DbFL1NXXZ8/b4ZqDJ6eSI5lysMZHfZDs60G3ocbNKofBvURIutabrFuBCB2S5f/z -ICan0LR4uFpGuZ2I/PuVaU8X5fT8gBh7L636cWzHPPScYts00OyywEq381UB7XwX -YuWpM5kUW5rkbq1JV3ystTR/4YnLl48YtQIDAQABoycwJTATBgNVHSUEDDAKBggr -BgEFBQcDATAOBgNVHQ8EBwMFALAAAAAwCQYFKw4DAh0FAAOBgQBuUrU+J2Z5WKcO -VNjJHFUKo8qpbn8jKQZDl2nvVaXCTXQZblz/qxOm4FaGGzJ/m3GybVZNVfdyHg+U -lmDpFpOITkvcyNc3xjJCf2GVBo/VvdtVt7Myq0IQtAi/CXRK22BRNhSt9uu2EcRu -HIXdFWHEzi6eD4PpNw/0X3ID6Gxk4A== ------END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/badssl-sct-anonymous-sig.der b/vectors/cryptography_vectors/x509/badssl-sct-anonymous-sig.der new file mode 100644 index 000000000000..5edff7dc7863 Binary files /dev/null and b/vectors/cryptography_vectors/x509/badssl-sct-anonymous-sig.der differ diff --git a/vectors/cryptography_vectors/x509/badssl-sct-none-hash.der b/vectors/cryptography_vectors/x509/badssl-sct-none-hash.der new file mode 100644 index 000000000000..4b003c46dbc3 Binary files /dev/null and b/vectors/cryptography_vectors/x509/badssl-sct-none-hash.der differ diff --git a/vectors/cryptography_vectors/x509/belgian-eid-invalid-visiblestring.pem b/vectors/cryptography_vectors/x509/belgian-eid-invalid-visiblestring.pem new file mode 100644 index 000000000000..17650782f99f --- /dev/null +++ b/vectors/cryptography_vectors/x509/belgian-eid-invalid-visiblestring.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGYzCCBEugAwIBAgIQEAAAAAAAdQQMgK5bRTyOHTANBgkqhkiG9w0BAQsFADAz +MQswCQYDVQQGEwJCRTETMBEGA1UEAxMKQ2l0aXplbiBDQTEPMA0GA1UEBRMGMjAx +NjIzMB4XDTE2MDgyOTA5NDcwMFoXDTI2MDgyNDIzNTk1OVowbzELMAkGA1UEBhMC +QkUxIjAgBgNVBAMTGUVsc2UgRGUgUHJvZnQgKFNpZ25hdHVyZSkxETAPBgNVBAQT +CERlIFByb2Z0MRMwEQYDVQQqEwpFbHNlIEZyYW5zMRQwEgYDVQQFEws2OTA3MDMz +ODg1MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANSMFzc0v5Fr5GM3 +1cvaF7obKH1mNUR5cAcNPdLbC8U8SzOIvArBIKToYJRQIxgy7S/XOPs7p/cnidQe +5yVNoIZlxWyB1nbbCR2c4rZJjzUz8bAXPKILjY7C+Q+Zxp6+8C6igDfd+n+eYuhU +u1kxPvGiZ+m+DuKTfjzhQAqG0kZteqwwlipJkt7FDsLxsgcxPBpMDm02sVL5pTme +rkY7mQpXZ5fpT2n2nzuNerxlfExeSdROAD/EZAxTAkuOgURWXmFBHPm0A9cipDYO +foyPcMO5/7JUPv7LWhRoMr+XrTBOVmkFxccJ8EXRtNxNVujwbjeUJp7Z+20ST1h/ +rDyNOKMCAwEAAaOCAjUwggIxMB8GA1UdIwQYMBaAFIIiihHTwEk9pIiqBydUoV6f +KmxqMHAGCCsGAQUFBwEBBGQwYjA2BggrBgEFBQcwAoYqaHR0cDovL2NlcnRzLmVp +ZC5iZWxnaXVtLmJlL2JlbGdpdW1yczQuY3J0MCgGCCsGAQUFBzABhhxodHRwOi8v +b2NzcC5laWQuYmVsZ2l1bS5iZS8yMIIBGAYDVR0gBIIBDzCCAQswggEHBgdgOAwB +AQIBMIH7MCwGCCsGAQUFBwIBFiBodHRwOi8vcmVwb3NpdG9yeS5laWQuYmVsZ2l1 +bS5iZTCBygYIKwYBBQUHAgIwgb0agbpHZWJydWlrIG9uZGVyd29ycGVuIGFhbiBh +YW5zcHJha2VsaWpraGVpZHNiZXBlcmtpbmdlbiwgemllIENQUyAtIFVzYWdlIHNv +dW1pcyDDoCBkZXMgbGltaXRhdGlvbnMgZGUgcmVzcG9uc2FiaWxpdMOpLCB2b2ly +IENQUyAtIFZlcndlbmR1bmcgdW50ZXJsaWVndCBIYWZ0dW5nc2Jlc2NocsOkbmt1 +bmdlbiwgZ2Vtw6RzcyBDUFMwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL2NybC5l +aWQuYmVsZ2l1bS5iZS9laWRjMjAxNjIzLmNybDAOBgNVHQ8BAf8EBAMCBkAwEQYJ +YIZIAYb4QgEBBAQDAgUgMCIGCCsGAQUFBwEDBBYwFDAIBgYEAI5GAQEwCAYGBACO +RgEEMA0GCSqGSIb3DQEBCwUAA4ICAQABNGZci7JGuvzXfk5MJCX/2Py3M9//R9iN +E/b8brMP6aCHJuDnEW7RcGAyleQQJYrTQnizWqoHRnkQ4BjQCZCpTEhERvCJz9KC +J0L9+9M3TNDGLMY14Tu/h8Uga6vThXoxI4VK2Y3gEP5qWV0tMdbu+dLSLZ+O2qkj +vtk8apYLn/2MGQ/srbu6HOLATvAKMtkF2za6zY0VL1Se9gHaHQdI9nnXKA3YD/7n +C4UrqozruMqGRNCpWhD/fRgdHotRaD4ZDuC7hUZH2b+ldFII4tsZiXcVhX6RN7KF +h5Ji/F2K9vqA0TbMWUEfiULSQfNc86LOd4riJ5VeVYtUl5kcrfVWMGBPQaq7c3OG +G2L2x4rkB8mvRTeQZCU5ENuEZX34jZuKnv7pabdntzowE5VQWjLgFGQ7UyTFbImZ +cR+H5djrrzO3Uvnu6a9v0ILGCLqES06pgH/apwtpHQPhvCWA8KBqf2aTgpZ8GsFI +qTraP819yyr+GOOp/NO8EvcOsyjgWwzDvtpoLty3/wMXC5DBNoUb3W/uMju5MJ3E +2dthCxnP7ES2PbdGTDK8Jtbgp5sJtfV6GCjgHDsIL5XGy6CagDghEG84TrYvKxTG +PlmUThXhFRVjwv2tbpgFC7z/RwARqcNYxZKFKAHXCx6hWgSQbuEN5j6JFQh3ZUL+ +R2V64/XeBQ== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/cryptography-scts-tbs-precert.der b/vectors/cryptography_vectors/x509/cryptography-scts-tbs-precert.der new file mode 100644 index 000000000000..0223ad6fb49c Binary files /dev/null and b/vectors/cryptography_vectors/x509/cryptography-scts-tbs-precert.der differ diff --git a/vectors/cryptography_vectors/x509/cryptography.io.chain.pem b/vectors/cryptography_vectors/x509/cryptography.io.chain.pem new file mode 100644 index 000000000000..a1071c586081 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.chain.pem @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEJTCCAw2gAwIBAgIDAjp3MA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMTQwODI5MjEzOTMyWhcNMjIwNTIwMjEzOTMyWjBHMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXUmFwaWRTU0wg +U0hBMjU2IENBIC0gRzMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv +VJvZWF0eLFbG1eh/9H0WA//Qi1rkjqfdVC7UBMBdmJyNkA+8EGVf2prWRHzAn7Xp +SowLBkMEu/SW4ib2YQGRZjEiwzQ0Xz8/kS9EX9zHFLYDn4ZLDqP/oIACg8PTH2lS +1p1kD8mD5xvEcKyU58Okaiy9uJ5p2L4KjxZjWmhxgHsw3hUEv8zTvz5IBVV6s9cQ +DAP8m/0Ip4yM26eO8R5j3LMBL3+vV8M8SKeDaCGnL+enP/C1DPz1hNFTvA5yT2AM +QriYrRmIV9cE7Ie/fodOoyH5U/02mEiN1vi7SPIpyGTRzFRIU4uvt2UevykzKdkp +YEj4/5G8V1jlNS67abZZAgMBAAGjggEdMIIBGTAfBgNVHSMEGDAWgBTAephojYn7 +qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUw5zz/NNGCDS7zkZ/oHxb8+IIy1kwEgYD +VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwNQYDVR0fBC4wLDAqoCig +JoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9iYWwuY3JsMC4GCCsGAQUF +BwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMEwGA1UdIARF +MEMwQQYKYIZIAYb4RQEHNjAzMDEGCCsGAQUFBwIBFiVodHRwOi8vd3d3Lmdlb3Ry +dXN0LmNvbS9yZXNvdXJjZXMvY3BzMA0GCSqGSIb3DQEBCwUAA4IBAQCjWB7GQzKs +rC+TeLfqrlRARy1+eI1Q9vhmrNZPc9ZE768LzFvB9E+aj0l+YK/CJ8cW8fuTgZCp +fO9vfm5FlBaEvexJ8cQO9K8EWYOHDyw7l8NaEpt7BDV7o5UzCHuTcSJCs6nZb0+B +kvwHtnm8hEqddwnxxYny8LScVKoSew26T++TGezvfU5ho452nFnPjJSxhJf3GrkH +uLLGTxN5279PURt/aQ1RKsHWFf83UTRlUfQevjhq7A6rvz17OQV79PP7GqHQyH5O +ZI3NjGFVkP46yl0lD/gdo0p0Vk8aVUBwdSWmMy66S6VdU5oNMOGNX2Esr8zvsJmh +gP8L8mJMcCaY +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/cryptography.io.chain_with_garbage.pem b/vectors/cryptography_vectors/x509/cryptography.io.chain_with_garbage.pem new file mode 100644 index 000000000000..7a06f8d2a572 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.chain_with_garbage.pem @@ -0,0 +1,69 @@ +... some garbage here ... + +-----BEGIN CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END CERTIFICATE----- + +... some more garbage here ... + +-----BEGIN CERTIFICATE----- +MIIEJTCCAw2gAwIBAgIDAjp3MA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMTQwODI5MjEzOTMyWhcNMjIwNTIwMjEzOTMyWjBHMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXUmFwaWRTU0wg +U0hBMjU2IENBIC0gRzMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv +VJvZWF0eLFbG1eh/9H0WA//Qi1rkjqfdVC7UBMBdmJyNkA+8EGVf2prWRHzAn7Xp +SowLBkMEu/SW4ib2YQGRZjEiwzQ0Xz8/kS9EX9zHFLYDn4ZLDqP/oIACg8PTH2lS +1p1kD8mD5xvEcKyU58Okaiy9uJ5p2L4KjxZjWmhxgHsw3hUEv8zTvz5IBVV6s9cQ +DAP8m/0Ip4yM26eO8R5j3LMBL3+vV8M8SKeDaCGnL+enP/C1DPz1hNFTvA5yT2AM +QriYrRmIV9cE7Ie/fodOoyH5U/02mEiN1vi7SPIpyGTRzFRIU4uvt2UevykzKdkp +YEj4/5G8V1jlNS67abZZAgMBAAGjggEdMIIBGTAfBgNVHSMEGDAWgBTAephojYn7 +qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUw5zz/NNGCDS7zkZ/oHxb8+IIy1kwEgYD +VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwNQYDVR0fBC4wLDAqoCig +JoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9iYWwuY3JsMC4GCCsGAQUF +BwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMEwGA1UdIARF +MEMwQQYKYIZIAYb4RQEHNjAzMDEGCCsGAQUFBwIBFiVodHRwOi8vd3d3Lmdlb3Ry +dXN0LmNvbS9yZXNvdXJjZXMvY3BzMA0GCSqGSIb3DQEBCwUAA4IBAQCjWB7GQzKs +rC+TeLfqrlRARy1+eI1Q9vhmrNZPc9ZE768LzFvB9E+aj0l+YK/CJ8cW8fuTgZCp +fO9vfm5FlBaEvexJ8cQO9K8EWYOHDyw7l8NaEpt7BDV7o5UzCHuTcSJCs6nZb0+B +kvwHtnm8hEqddwnxxYny8LScVKoSew26T++TGezvfU5ho452nFnPjJSxhJf3GrkH +uLLGTxN5279PURt/aQ1RKsHWFf83UTRlUfQevjhq7A6rvz17OQV79PP7GqHQyH5O +ZI3NjGFVkP46yl0lD/gdo0p0Vk8aVUBwdSWmMy66S6VdU5oNMOGNX2Esr8zvsJmh +gP8L8mJMcCaY +-----END CERTIFICATE----- + +... and more garbage here... + +-----BEGIN PRIVATE KEY----- +aHR0cHM6Ly9iaXQubHkvM3VKOXpZZw== +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/x509/cryptography.io.old_header.pem b/vectors/cryptography_vectors/x509/cryptography.io.old_header.pem new file mode 100644 index 000000000000..84470d96bbb7 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.old_header.pem @@ -0,0 +1,33 @@ +-----BEGIN X509 CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END X509 CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/cryptography.io.with_garbage.pem b/vectors/cryptography_vectors/x509/cryptography.io.with_garbage.pem new file mode 100644 index 000000000000..b85c5d1a5466 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.with_garbage.pem @@ -0,0 +1,49 @@ +This file also contains text before... + +-----BEGIN PRIVATE KEY----- +aHR0cHM6Ly9iaXQubHkvM3VKOXpZZw== +-----END PRIVATE KEY----- + +...and... + +-----BEGIN CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END CERTIFICATE----- + +...between... + +-----BEGIN PRIVATE KEY----- +aHR0cHM6Ly9iaXQubHkvM3VKOXpZZw== +-----END PRIVATE KEY----- + +...sections. diff --git a/vectors/cryptography_vectors/x509/cryptography.io.with_headers.pem b/vectors/cryptography_vectors/x509/cryptography.io.with_headers.pem new file mode 100644 index 000000000000..46f2ecae6695 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.with_headers.pem @@ -0,0 +1,64 @@ +-----BEGIN CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6 + +xXOPSE88uXYkutY15A7kLycP5Tvryb0D2DQgRKiFXeZlVhqIW6n9FUn9/GK81xBx +EEmqzlc//6JDDWyBfYDzLb63BPuUBOIaiUjmHRteS0oQQWCY68cAoSl+Wc3801cB +UB7Yi0xNb6TN6jOx8HlWAHGq2Xm8Gs/k8y0RAEHBsaPGHISj6xNBaDS3PsHEIbBz +Adt6sbe01bQNZpfTiibV4IvZcf/O83TItGAotM83aZzN/N3Yq3OCboIJRUNLZ+aA +5n2StkLqtUqNITjRylc0rGYwWnvYdQtYt9+bKjefHwaQRJta4OTY8qkd8IabOamH +3DNyF4WnXnYWND1erzP3cYWt1rvBLFd2to9QGH54dWX1bsyrGb0iryesFk9iUB3A +JrgmrVcH3rajHN9BsqJ+uffkehe5ZRKTr9qJ7t2Pk3q3DEMmLm8vtkTSKA/q+vN1 +4eOJ8Nbiekp+zXYaJ6Wqt38VzWV3c19qePpBWaMsRWv4mTM+W0ZQtsAxXatNbGPs +Dq/Idc9xwwE/Ou4TDVTS0DlvqvGGB1MsX4uhzdyUF/6/b9/qHEEYjHePz4jX5Hcy +Jg4bpYIU9pszOVKRKjmCKyp6jPnuHISnhZJeG0jJOfKZQeaNoWEqDdy9ctIg1v6K +OnRVnPATCYtDrXpXe3YxT3n8g0+d9ZkyL5614Rj05P+Q7OukEnn/nQnMfUXk1gZ3 +7jSEJL47iOvxGWz4SvtSfHSCNYUFhm/fNSJMJ5TRApEPmZ71NrVTtgGAwWRPU3iM +JJRPZOXmrzt9rdlkTsH7V3Bn/lcdbIHPJ87Pv5dLFEota7we0WRRlAQJXp0NzG17 +Vn2IndftSeLUy7vGmNRAJEDSJe7OozN46n5RX04Q0ax5Z03p2m/mue2swk+Gqtf5 +2hYxgRZypuVIsgHyBOL9w0OV3Jvg246m0iCFRXJH7juaZ5c2MJ4mhTvAd4NdsS2v +46w6Kf5KDHCtgKnDTGtyx9Gzfhhi+wFlYREkMXN1QNpzW/USv0nPBjlVirGGzZBq +z6iJNIe5XEX+mP2EDu47PXoF5uwyhfIPHvM4qM/U8aRUgMwmtq8mR9LYsDrSkzXY +vxNxiXgl5eGG8rkOxQhSka9JIfkJVgA1tdk3ThTOXtm4B3vzRmbWbuYrwYtZ2Ls4 +XafQrVnoiMGX8/+Enn05M0bcTdZWm5vU2BZ/MhrwjAiMUMFV0FeHjk9j0fXDz9LM +GJyK6/4+P7owv3jbeCrQ3v4rvOwQony3LAOprlGSgSAcmq7g+HdoD8Jh+f4Z2Q26 +QLWAT9ikFVYSn2/B+m87Zr2eObZn1rExK40HlQrlyUMaklJ/IRxkWuap/h8N39W9 +8t7NIMdIy6QzCVkNhVx66QKyE7BJw8y4DZOCQ7YNqjPuc81lTt72eBDH3D12VVO9 +ZSOuQs4QV/zltAsWgJFQg7XtHVISELNELjMRm6N/543BkpSOUzInkctBTFBuwHGj ++5F+pR3qlNodEMFKCpm5aC9miDI854h66lv417mGkvZ7mz+Ktk8P7MoWecneVTkO +9WRU42KnNFSxK8C+cmFNxN1/97pWEQnXEV/32S/O5myly+kJPev2MsXtIFpqN1mE +-----END RSA PRIVATE KEY----- + diff --git a/vectors/cryptography_vectors/x509/custom/alternate-rsa-sha1-oid.der b/vectors/cryptography_vectors/x509/custom/alternate-rsa-sha1-oid.der new file mode 100644 index 000000000000..e8f4d4ca6c7d Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/alternate-rsa-sha1-oid.der differ diff --git a/vectors/cryptography_vectors/x509/custom/bad_country.pem b/vectors/cryptography_vectors/x509/custom/bad_country.pem new file mode 100644 index 000000000000..fd4d60170cb2 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/bad_country.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC4DCCAcigAwIBAgICAwkwDQYJKoZIhvcNAQENBQAwMzERMA8GA1UEBhMIdG9v +IGxvbmcxHjAcBgsrBgEEAYI3PAIBAxMNYWxzbyB0b28gbG9uZzAeFw0wMjAxMDEx +MjAxMDBaFw0zMDEyMzEwODMwMDBaMDMxETAPBgNVBAYTCHRvbyBsb25nMR4wHAYL +KwYBBAGCNzwCAQMTDWFsc28gdG9vIGxvbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDBevx+d0dMqlqoMDYVij/797UhaFG6IjDl1qv8wcbP71npI+oT +MLxZO3OAKrYIpuSjMGUjoxFrpao5ZhRRdOE7bEnpt4Bi5EnXLvsQ/UnpH6CLltBR +54Lp9avFtab3mEgnrbjnPaAPIrLv3Nt26rRu2tmO1lZidD/cbA4zal0M26p9wp5T +Y14kyHpbLEIVloBjzetoqXK6u8Hjz/APuagONypNDCySDR6M7jM85HDcLoFFrbBb +8pruHSTxQejMeEmJxYf8b7rNl58/IWPB1ymbNlvHL/4oSOlnrtHkjcxRWzpQ7U3g +T9BThGyhCiI7EMyEHMgP3r7kTzEUwT6IavWDAgMBAAEwDQYJKoZIhvcNAQENBQAD +ggEBALEK2PhqEfqH6/q3M7Guq9E/GuB0qAlqBkZNqIzX8WdRuMKRCnE2I0TDFtbp +jGrhqYcugOB12HeOWT3iSg491KDphsWGFR+La7zZkFKdSf3Cc/ktw6lOgu66CQxI +Bfgp0O4yGexKYkeW1C/gQVoAzczelykfSFthG+BJsX4OGsb6g98y6fsOnHfx7s2t +UkPMYUgom3fhs/J4RhRTKHAOiPBTKg91qGRcGr4TjqCRmiWVw1hFJL0p4vZopnS8 +VX/OrLRnNsj+VxoSIksoEUuxNdUuN4lw14IDZFUEw9CErnyisX2DEozjrg6jca8n +gdJuDRk4TlNl/CpgNraJcu47pME= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/cp_invalid2.der b/vectors/cryptography_vectors/x509/custom/cp_invalid2.der new file mode 100644 index 000000000000..08d31db26b4f Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/cp_invalid2.der differ diff --git a/vectors/cryptography_vectors/x509/custom/crl_all_reasons.pem b/vectors/cryptography_vectors/x509/custom/crl_all_reasons.pem index fdc82ae63895..b8820508d60a 100644 --- a/vectors/cryptography_vectors/x509/custom/crl_all_reasons.pem +++ b/vectors/cryptography_vectors/x509/custom/crl_all_reasons.pem @@ -1,5 +1,5 @@ -----BEGIN X509 CRL----- -MIIGRzCCBS8CAQIwDQYJKoZIhvcNAQELBQAwJzELMAkGA1UEBhMCVVMxGDAWBgNV +MIIGRzCCBS8CAQEwDQYJKoZIhvcNAQELBQAwJzELMAkGA1UEBhMCVVMxGDAWBgNV BAMMD2NyeXB0b2dyYXBoeS5pbxgPMjAxNTAxMDEwMDAwMDBaGA8yMDE2MDEwMTAw MDAwMFowggTOMBQCAQAYDzIwMTUwMTAxMDAwMDAwWjByAgEBGA8yMDE1MDEwMTAw MDAwMFowXDAYBgNVHRgEERgPMjAxNTAxMDEwMDAwMDBaMDQGA1UdHQQtMCukKTAn diff --git a/vectors/cryptography_vectors/x509/custom/crl_almost_10k.pem b/vectors/cryptography_vectors/x509/custom/crl_almost_10k.pem new file mode 100644 index 000000000000..abe89572698b --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/crl_almost_10k.pem @@ -0,0 +1,4382 @@ +-----BEGIN X509 CRL----- +MIMDNSkwgwM0EAIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJjcnlwdG9n +cmFwaHkuaW8gQ0EXDTIyMDkwNzE5MDYyM1oXDTIyMDkwODE5MDYyM1owgwMzvDAS +AgEBFw0yMjA5MDcxOTA2MjNaMBICAQIXDTIyMDkwNzE5MDYyM1owEgIBAxcNMjIw +OTA3MTkwNjIzWjASAgEEFw0yMjA5MDcxOTA2MjNaMBICAQUXDTIyMDkwNzE5MDYy +M1owEgIBBhcNMjIwOTA3MTkwNjIzWjASAgEHFw0yMjA5MDcxOTA2MjNaMBICAQgX +DTIyMDkwNzE5MDYyM1owEgIBCRcNMjIwOTA3MTkwNjIzWjASAgEKFw0yMjA5MDcx +OTA2MjNaMBICAQsXDTIyMDkwNzE5MDYyM1owEgIBDBcNMjIwOTA3MTkwNjIzWjAS +AgENFw0yMjA5MDcxOTA2MjNaMBICAQ4XDTIyMDkwNzE5MDYyM1owEgIBDxcNMjIw +OTA3MTkwNjIzWjASAgEQFw0yMjA5MDcxOTA2MjNaMBICAREXDTIyMDkwNzE5MDYy +M1owEgIBEhcNMjIwOTA3MTkwNjIzWjASAgETFw0yMjA5MDcxOTA2MjNaMBICARQX +DTIyMDkwNzE5MDYyM1owEgIBFRcNMjIwOTA3MTkwNjIzWjASAgEWFw0yMjA5MDcx +OTA2MjNaMBICARcXDTIyMDkwNzE5MDYyM1owEgIBGBcNMjIwOTA3MTkwNjIzWjAS +AgEZFw0yMjA5MDcxOTA2MjNaMBICARoXDTIyMDkwNzE5MDYyM1owEgIBGxcNMjIw +OTA3MTkwNjIzWjASAgEcFw0yMjA5MDcxOTA2MjNaMBICAR0XDTIyMDkwNzE5MDYy +M1owEgIBHhcNMjIwOTA3MTkwNjIzWjASAgEfFw0yMjA5MDcxOTA2MjNaMBICASAX +DTIyMDkwNzE5MDYyM1owEgIBIRcNMjIwOTA3MTkwNjIzWjASAgEiFw0yMjA5MDcx +OTA2MjNaMBICASMXDTIyMDkwNzE5MDYyM1owEgIBJBcNMjIwOTA3MTkwNjIzWjAS +AgElFw0yMjA5MDcxOTA2MjNaMBICASYXDTIyMDkwNzE5MDYyM1owEgIBJxcNMjIw +OTA3MTkwNjIzWjASAgEoFw0yMjA5MDcxOTA2MjNaMBICASkXDTIyMDkwNzE5MDYy +M1owEgIBKhcNMjIwOTA3MTkwNjIzWjASAgErFw0yMjA5MDcxOTA2MjNaMBICASwX +DTIyMDkwNzE5MDYyM1owEgIBLRcNMjIwOTA3MTkwNjIzWjASAgEuFw0yMjA5MDcx +OTA2MjNaMBICAS8XDTIyMDkwNzE5MDYyM1owEgIBMBcNMjIwOTA3MTkwNjIzWjAS +AgExFw0yMjA5MDcxOTA2MjNaMBICATIXDTIyMDkwNzE5MDYyM1owEgIBMxcNMjIw +OTA3MTkwNjIzWjASAgE0Fw0yMjA5MDcxOTA2MjNaMBICATUXDTIyMDkwNzE5MDYy +M1owEgIBNhcNMjIwOTA3MTkwNjIzWjASAgE3Fw0yMjA5MDcxOTA2MjNaMBICATgX +DTIyMDkwNzE5MDYyM1owEgIBORcNMjIwOTA3MTkwNjIzWjASAgE6Fw0yMjA5MDcx +OTA2MjNaMBICATsXDTIyMDkwNzE5MDYyM1owEgIBPBcNMjIwOTA3MTkwNjIzWjAS +AgE9Fw0yMjA5MDcxOTA2MjNaMBICAT4XDTIyMDkwNzE5MDYyM1owEgIBPxcNMjIw +OTA3MTkwNjIzWjASAgFAFw0yMjA5MDcxOTA2MjNaMBICAUEXDTIyMDkwNzE5MDYy +M1owEgIBQhcNMjIwOTA3MTkwNjIzWjASAgFDFw0yMjA5MDcxOTA2MjNaMBICAUQX +DTIyMDkwNzE5MDYyM1owEgIBRRcNMjIwOTA3MTkwNjIzWjASAgFGFw0yMjA5MDcx +OTA2MjNaMBICAUcXDTIyMDkwNzE5MDYyM1owEgIBSBcNMjIwOTA3MTkwNjIzWjAS +AgFJFw0yMjA5MDcxOTA2MjNaMBICAUoXDTIyMDkwNzE5MDYyM1owEgIBSxcNMjIw +OTA3MTkwNjIzWjASAgFMFw0yMjA5MDcxOTA2MjNaMBICAU0XDTIyMDkwNzE5MDYy +M1owEgIBThcNMjIwOTA3MTkwNjIzWjASAgFPFw0yMjA5MDcxOTA2MjNaMBICAVAX +DTIyMDkwNzE5MDYyM1owEgIBURcNMjIwOTA3MTkwNjIzWjASAgFSFw0yMjA5MDcx +OTA2MjNaMBICAVMXDTIyMDkwNzE5MDYyM1owEgIBVBcNMjIwOTA3MTkwNjIzWjAS +AgFVFw0yMjA5MDcxOTA2MjNaMBICAVYXDTIyMDkwNzE5MDYyM1owEgIBVxcNMjIw +OTA3MTkwNjIzWjASAgFYFw0yMjA5MDcxOTA2MjNaMBICAVkXDTIyMDkwNzE5MDYy +M1owEgIBWhcNMjIwOTA3MTkwNjIzWjASAgFbFw0yMjA5MDcxOTA2MjNaMBICAVwX +DTIyMDkwNzE5MDYyM1owEgIBXRcNMjIwOTA3MTkwNjIzWjASAgFeFw0yMjA5MDcx +OTA2MjNaMBICAV8XDTIyMDkwNzE5MDYyM1owEgIBYBcNMjIwOTA3MTkwNjIzWjAS +AgFhFw0yMjA5MDcxOTA2MjNaMBICAWIXDTIyMDkwNzE5MDYyM1owEgIBYxcNMjIw +OTA3MTkwNjIzWjASAgFkFw0yMjA5MDcxOTA2MjNaMBICAWUXDTIyMDkwNzE5MDYy +M1owEgIBZhcNMjIwOTA3MTkwNjIzWjASAgFnFw0yMjA5MDcxOTA2MjNaMBICAWgX +DTIyMDkwNzE5MDYyM1owEgIBaRcNMjIwOTA3MTkwNjIzWjASAgFqFw0yMjA5MDcx +OTA2MjNaMBICAWsXDTIyMDkwNzE5MDYyM1owEgIBbBcNMjIwOTA3MTkwNjIzWjAS +AgFtFw0yMjA5MDcxOTA2MjNaMBICAW4XDTIyMDkwNzE5MDYyM1owEgIBbxcNMjIw +OTA3MTkwNjIzWjASAgFwFw0yMjA5MDcxOTA2MjNaMBICAXEXDTIyMDkwNzE5MDYy +M1owEgIBchcNMjIwOTA3MTkwNjIzWjASAgFzFw0yMjA5MDcxOTA2MjNaMBICAXQX +DTIyMDkwNzE5MDYyM1owEgIBdRcNMjIwOTA3MTkwNjIzWjASAgF2Fw0yMjA5MDcx +OTA2MjNaMBICAXcXDTIyMDkwNzE5MDYyM1owEgIBeBcNMjIwOTA3MTkwNjIzWjAS +AgF5Fw0yMjA5MDcxOTA2MjNaMBICAXoXDTIyMDkwNzE5MDYyM1owEgIBexcNMjIw +OTA3MTkwNjIzWjASAgF8Fw0yMjA5MDcxOTA2MjNaMBICAX0XDTIyMDkwNzE5MDYy +M1owEgIBfhcNMjIwOTA3MTkwNjIzWjASAgF/Fw0yMjA5MDcxOTA2MjNaMBMCAgCA +Fw0yMjA5MDcxOTA2MjNaMBMCAgCBFw0yMjA5MDcxOTA2MjNaMBMCAgCCFw0yMjA5 +MDcxOTA2MjNaMBMCAgCDFw0yMjA5MDcxOTA2MjNaMBMCAgCEFw0yMjA5MDcxOTA2 +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 +-----END X509 CRL----- diff --git a/vectors/cryptography_vectors/x509/custom/crl_bad_version.pem b/vectors/cryptography_vectors/x509/custom/crl_bad_version.pem new file mode 100644 index 000000000000..ff309a2a85a9 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/crl_bad_version.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBpzCBkAIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ +Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoA4wDDAKBgNV +HRQEAwIBATANBgkqhkiG9w0BAQsFAAOCAQEAnrBKKgvd9x9zwK9rtUvVeFeJ7+LN +ZEAc+a5oxpPNEsJx6hXoApYEbzXMxuWBQoCs5iEBycSGudct21L+MVf27M38KrWo +eOkq0a2siqViQZO2Fb/SUFR0k9zb8xl86Zf65lgPplALun0bV/HT7MJcl04Tc4os +dsAReBs5nqTGNEd5AlC1iKHvQZkM//MD51DspKnDpsDiUVi54h9C1SpfZmX8H2Vv +diyu0fZ/bPAM3VAGawatf/SyWfBMyKpoPXEG39oAzmjjOj8en82psn7m474IGaho +/vBbhl1ms5qQiLYPjm4YELtnXQoFyC72tBjbdFd/ZE9k4CNKDbxFUXFbkw== +-----END X509 CRL----- diff --git a/vectors/cryptography_vectors/x509/custom/crl_dup_entry_ext.pem b/vectors/cryptography_vectors/x509/custom/crl_dup_entry_ext.pem index 1b1d313c6297..c6950f4dd84b 100644 --- a/vectors/cryptography_vectors/x509/custom/crl_dup_entry_ext.pem +++ b/vectors/cryptography_vectors/x509/custom/crl_dup_entry_ext.pem @@ -1,5 +1,5 @@ -----BEGIN X509 CRL----- -MIIBpjCBjwIBAjANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE +MIIBpjCBjwIBATANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw MDAwWjAwMC4CAQAYDzIwMTUwMTAxMDAwMDAwWjAYMAoGA1UdFQQDCgEBMAoGA1Ud FQQDCgEBMA0GCSqGSIb3DQEBCwUAA4IBAQAse9C8f10JNCBNgE9nyAU1mlkKHubL diff --git a/vectors/cryptography_vectors/x509/custom/crl_empty_no_sequence.der b/vectors/cryptography_vectors/x509/custom/crl_empty_no_sequence.der new file mode 100644 index 000000000000..7dd7b7ffb340 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/crl_empty_no_sequence.der differ diff --git a/vectors/cryptography_vectors/x509/custom/crl_inner_outer_mismatch.der b/vectors/cryptography_vectors/x509/custom/crl_inner_outer_mismatch.der new file mode 100644 index 000000000000..ceec88fc2b2b Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/crl_inner_outer_mismatch.der differ diff --git a/vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem b/vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem index a54f2409c518..da89d9c96c24 100644 --- a/vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem +++ b/vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem @@ -1,5 +1,5 @@ -----BEGIN X509 CRL----- -MIIBlzCBgAIBAjANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE +MIIBlzCBgAIBATANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw MDAwWjAhMB8CAQAYDzIwMTUwMTAxMDAwMDAwWjAJMAcGA1UdHQQAMA0GCSqGSIb3 DQEBCwUAA4IBAQCRSNP2LfnpubvOrZ8/UsETlVTvMNc38xM6dqzYKQV8vN+fcMXP diff --git a/vectors/cryptography_vectors/x509/custom/crl_invalid_time.der b/vectors/cryptography_vectors/x509/custom/crl_invalid_time.der new file mode 100644 index 000000000000..a01229549447 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/crl_invalid_time.der differ diff --git a/vectors/cryptography_vectors/x509/custom/crl_md2_unknown_crit_entry_ext.pem b/vectors/cryptography_vectors/x509/custom/crl_md2_unknown_crit_entry_ext.pem index c6b378cd0d1f..eb8b4ae462e5 100644 --- a/vectors/cryptography_vectors/x509/custom/crl_md2_unknown_crit_entry_ext.pem +++ b/vectors/cryptography_vectors/x509/custom/crl_md2_unknown_crit_entry_ext.pem @@ -1,5 +1,5 @@ -----BEGIN X509 CRL----- -MIIBnTCBhgIBAjANBgkqhkiG9w0BAQIFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE +MIIBnTCBhgIBATANBgkqhkiG9w0BAQIFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw MDAwWjAnMCUCAQAYDzIwMTUwMTAxMDAwMDAwWjAPMA0GAyoDBAEB/wQDCgEAMA0G CSqGSIb3DQEBAgUAA4IBAQAx/z+KEN+qCjT1nxyKH4QpCyGc4Yo3m0SSdjszfLMc diff --git a/vectors/cryptography_vectors/x509/custom/crl_no_next_update.pem b/vectors/cryptography_vectors/x509/custom/crl_no_next_update.pem new file mode 100644 index 000000000000..9acfa2dc953d --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/crl_no_next_update.pem @@ -0,0 +1,12 @@ +-----BEGIN X509 CRL----- +MIIBtjCBnwIBATANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJVUzERMA8GA1UE +CAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xETAPBgNVBAoMCHI1MDkgTExD +MRowGAYDVQQDDBFyNTA5IENSTCBEZWxlZ2F0ZRcNMTUxMjIwMjM0NDQ3WqAZMBcw +CgYDVR0UBAMCAQEwCQYDVR0jBAIwADANBgkqhkiG9w0BAQUFAAOCAQEAXebqoZfE +VAC4NcSEB5oGqUviUn/AnY6TzB6hUe8XC7yqEkBcyTgkG1Zq+b+T/5X1ewTldvuU +qv19WAU/Epbbu4488PoH5qMV8Aii2XcotLJOR9OBANp0Yy4ir/n6qyw8kM3hXJlo +E+xgkELhd5JmKCnlXihM1BTl7Xp7jyKeQ86omR+DhItbCU+9RoqOK9Hm087Z7Rur +XVrz5RKltQo7VLCp8VmrxFwfALCZENXGEQ+g5VkvoCjcph5jqOSyzp7aZy1pnLE/ +6U6V32ItskrwqA+x4oj2Wvzir/Q23y2zYfqOkuq4fTd2lWW+w5mB167fIWmd6efe +cDn1ZqbdECDPUg== +-----END X509 CRL----- diff --git a/vectors/cryptography_vectors/x509/custom/crl_unrecognized_extension.der b/vectors/cryptography_vectors/x509/custom/crl_unrecognized_extension.der new file mode 100644 index 000000000000..a29fe2025c3d Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/crl_unrecognized_extension.der differ diff --git a/vectors/cryptography_vectors/x509/custom/crl_unsupported_reason.pem b/vectors/cryptography_vectors/x509/custom/crl_unsupported_reason.pem index 3d12675b86e6..04b3b4fdaf78 100644 --- a/vectors/cryptography_vectors/x509/custom/crl_unsupported_reason.pem +++ b/vectors/cryptography_vectors/x509/custom/crl_unsupported_reason.pem @@ -1,5 +1,5 @@ -----BEGIN X509 CRL----- -MIIBmjCBgwIBAjANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE +MIIBmjCBgwIBATANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw MDAwWjAkMCICAQAYDzIwMTUwMTAxMDAwMDAwWjAMMAoGA1UdFQQDCgEMMA0GCSqG SIb3DQEBCwUAA4IBAQDGXlEYOwcEcTjGqvU4JVdGyDkj+5kzJlVOZiHLQ8v4O5qe diff --git a/vectors/cryptography_vectors/x509/custom/ecdsa_null_alg.pem b/vectors/cryptography_vectors/x509/custom/ecdsa_null_alg.pem new file mode 100644 index 000000000000..327ad553ae7f --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/ecdsa_null_alg.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBNDCB2aADAgECAgRnI7YfMAwGCCqGSM49BAMCBQAwDzENMAsGA1UEAxMEdGVz +dDAeFw0yMzA1MzExMjI5MDNaFw0yNDA1MjUxMjI5MDNaMA8xDTALBgNVBAMTBHRl +c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS2LuMFnF5OcuYcldiufvppacg2 +8fF/KeJ/4QLMOTbnkatgx5wNPOUvlkzfT31MscwYyzkv1oTqe58iQ+R75C27oyEw +HzAdBgNVHQ4EFgQUD6COpW8C9Ns86r2BDE0jP0teCTswDAYIKoZIzj0EAwIFAANI +ADBFAiBKOlNsFpW6Bz7CK7Z5zXrCetnMiSH3NrbKSZBXJV62KQIhAKmjGu3rxlJr +xXpK+Uz8AsoFJ0BlgqPpdMtTGSrDq1AN +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/invalid-sct-length.der b/vectors/cryptography_vectors/x509/custom/invalid-sct-length.der new file mode 100644 index 000000000000..3b3b92d4ac8e Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/invalid-sct-length.der differ diff --git a/vectors/cryptography_vectors/x509/custom/invalid-sct-version.der b/vectors/cryptography_vectors/x509/custom/invalid-sct-version.der new file mode 100644 index 000000000000..96cce1d524e5 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/invalid-sct-version.der differ diff --git a/vectors/cryptography_vectors/x509/custom/invalid_signature.pem b/vectors/cryptography_vectors/x509/custom/invalid_signature_cert.pem similarity index 64% rename from vectors/cryptography_vectors/x509/custom/invalid_signature.pem rename to vectors/cryptography_vectors/x509/custom/invalid_signature_cert.pem index 2fc483d95b5e..0c9589fe174a 100644 --- a/vectors/cryptography_vectors/x509/custom/invalid_signature.pem +++ b/vectors/cryptography_vectors/x509/custom/invalid_signature_cert.pem @@ -1,14 +1,3 @@ ------BEGIN X509 CRL----- -MIIBfTBnAgEBMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNVBAMMGmludmFsaWRfc2ln -bmF0dXJlIENSTCB0ZXN0Fw0xNzA4MDYwMTQ4MjVaFw0xNzA5MDUwMTQ4MjVaoA4w -DDAKBgNVHRQEAwIBAzANBgkqhkiG9w0BAQsFAAOCAQEAFgGnFwwqviPvA0bfmnvI -c6oGIlq9Bmx/vSH6gwLCuGWn2BrKCWCIJNEtK4hrTfQRASb/uywHvhnByAE2lQlY -9FiefdvXgF5zEah/gV/2A0azvqfvOlPBLzreeoW3Q1fizmip3XN1fXiq8cXBpEYt -SRTJPzgbHvIu50EB2J0hs+rGo1hPTDtZn/r63hcQzUhIWQVmwP+NOzhpUcdnQj3/ -pn6BAJcxyYO2xDoUIncq586k8XVqshEl9xVwJMKhDDk84m/WQZg8i8szgI/muFsm -3vilMgIISrTMYeFIZWAy8rYfKLDMlmAtPRXYqyqOdTsLqz2X3RDMRHMXf1Vf8V31 -vA== ------END X509 CRL----- -----BEGIN CERTIFICATE----- MIICxjCCAa4CCQCETsDmKRzISDANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpp bnZhbGlkX3NpZ25hdHVyZSBDUkwgdGVzdDAeFw0xNzA4MDYwMTM5MzRaFw0xNzA5 diff --git a/vectors/cryptography_vectors/x509/custom/invalid_signature_crl.pem b/vectors/cryptography_vectors/x509/custom/invalid_signature_crl.pem new file mode 100644 index 000000000000..54f77382d0be --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/invalid_signature_crl.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBfTBnAgEBMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNVBAMMGmludmFsaWRfc2ln +bmF0dXJlIENSTCB0ZXN0Fw0xNzA4MDYwMTQ4MjVaFw0xNzA5MDUwMTQ4MjVaoA4w +DDAKBgNVHRQEAwIBAzANBgkqhkiG9w0BAQsFAAOCAQEAFgGnFwwqviPvA0bfmnvI +c6oGIlq9Bmx/vSH6gwLCuGWn2BrKCWCIJNEtK4hrTfQRASb/uywHvhnByAE2lQlY +9FiefdvXgF5zEah/gV/2A0azvqfvOlPBLzreeoW3Q1fizmip3XN1fXiq8cXBpEYt +SRTJPzgbHvIu50EB2J0hs+rGo1hPTDtZn/r63hcQzUhIWQVmwP+NOzhpUcdnQj3/ +pn6BAJcxyYO2xDoUIncq586k8XVqshEl9xVwJMKhDDk84m/WQZg8i8szgI/muFsm +3vilMgIISrTMYeFIZWAy8rYfKLDMlmAtPRXYqyqOdTsLqz2X3RDMRHMXf1Vf8V31 +vA== +-----END X509 CRL----- diff --git a/vectors/cryptography_vectors/x509/custom/invalid_utf8_common_name.pem b/vectors/cryptography_vectors/x509/custom/invalid_utf8_common_name.pem new file mode 100644 index 000000000000..05a5e8a2420e --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/invalid_utf8_common_name.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICsjCCAZoCCQDfCSnalLBhujANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDDBBX +ZSBoZWFydCBVVEY4IeKEMB4XDTE1MDExODAzNTM1NFoXDTE1MDIxNzAzNTM1NFow +GzEZMBcGA1UEAwwQV2UgaGVhcnQgVVRGOCHihDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOBG4RmNZtn5lqI9PawhjCTHQ3aEBv3G4o/gKu+dqWzhweC7 +Dl2u7Wc9NUJzGLBjqhCMCt/HysbO5rJj0DRQA+uyspe+7SCORHg29NpRCH735nq7 +wdaExkZ/wsnp9l3BVggq9x7tbAmEi383sFTLPcjId3kN6/RTaihWSiOKUc/J2Rzu +vpcpbqbkzmHAMR3wHccuZO3OsSibO59NUR1ogYiTEid51L4glV2PsEDWxB7Epbdv +/Hsf1GY9yMQpBGGQ7YwZ30FKAzWg5sWrWgHdpM4rvnFlta1T4pEqIDmVm13H3TtN +1hTwBJP7tNZcg304vSa55xOz3NbQUEjuiIUZhS0CAwEAATANBgkqhkiG9w0BAQUF +AAOCAQEAEQ2bnpTYlnWzDGI7sTwTOBDzc8WlbPzF+2ZkbtRI3GfCeAdp2+UgBJgC +pIzOMzn73hHhCZ8/4Ca9v2KumetCZJh38uIjzzzTre8yfSAZBHD8QpYKhsR4RYl6 +syWitP5j49W860C5SKGAio8tSuJpSzpwaF8xl/UoyAthBu7xTPtEmWsvUJLLW23C +qSpXCA8iefePk2l/Y7iCIZrv4QB85pTKlL+LVcaTFOHRi8sHwFrA/O2hbdUp8YKt +SdYV1ERjwfNYy7Ci3MamIaVlIxsuJSo8wY0aXcoqqAy3c33vdrvUP+HQiAQSUt56 +1KhGQMAo+zuCVuI18WZ0gsaAYWL+CA== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/long-form-name-attribute.pem b/vectors/cryptography_vectors/x509/custom/long-form-name-attribute.pem new file mode 100644 index 000000000000..46810db054f6 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/long-form-name-attribute.pem @@ -0,0 +1,19 @@ +-----BEGIN X509 CERTIFICATE----- +MIIDDzCCAfegAwIBAgIBATANBgkqhkiG9w0BAQUFADAMMQowCAYDVQwNfyAAMB4X +DTE1MDExODAyMzUwNloXDTE2MDExODAyMzUwNlowDDEKMAgGA1UMDX8gADCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN8uSMGG/hunFIhMldeAH2DGzN/B +zBFjK9tMQmYPWUtaE6TXeCcgtqBCC6lkHff3Ta7eUKGBA9jjjXw1CSK8kl4zDiN+ +X9Sx7uAXFrHmf/piVTcD3cLE1j7Z6LZ6OBYEPzC0R28WL0VKxpH7Z2zn+/I40l0e +QLoNHdpl2iQJpTRfWHXUnEqEmCw88/jWkvw/QDWxyN7T3SV+gZtW20PprLG9xyv7 +0bCRZV8eBhZQY5wCL/h7vCpem+pmqUI4ftCdVQLHKSfI+MROJbaSLhFpyMBmHmAT +Dqr2Y0U7hd6jfS+YPH/tVY8Gp2Lsws9UKIhoez5icORJZXSqndq4j4xmpWMCAwEA +AaN8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU8BNvvgbKxGVyUFuGMnKABlaeTmMw +CwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMCwGA1UdHwQlMCMwIaAf +oB2GG2h0dHA6Ly9wYXRoLnRvLmNybC9teWNhLmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAsQY4N4iXn82DpsEcAkFhOmYZdifwYFj8VNZy8/YdlfVjfzUUpKKpKbYawLpo +az3gafIBaRXR4PH3OdP+NexxzoO2ZsEJ8GoVTrFb/NgRUf1r47xDOHKw4gIrnGlT +TbWsT/V8yEgXoxKkK8jzK7NY4m2TIqZWBirdF5wNm5AhvkMylH56gPlamT1Qb+ss +HevbzIU25o+uaIrL4lwSZyGPWECpmX9LHWkwCSJvZePMKlrfq9x3gFpW9fpj68es +imv7B/MWeUDNhhkufr1YtJfTmh5C/mLgKfqfBSF8UeUUDQthinHKj3FzUdF1fOPW +W40q82VIUOzHpDCmXGXUUgqcYQ== +-----END X509 CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/mismatch_inner_outer_sig_algorithm.der b/vectors/cryptography_vectors/x509/custom/mismatch_inner_outer_sig_algorithm.der new file mode 100644 index 000000000000..bf7a473f31d7 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/mismatch_inner_outer_sig_algorithm.der differ diff --git a/vectors/cryptography_vectors/x509/custom/ms-certificate-template.pem b/vectors/cryptography_vectors/x509/custom/ms-certificate-template.pem new file mode 100644 index 000000000000..ccf02e58a21f --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/ms-certificate-template.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBKDCB0KADAgECAgEBMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAlVTMB4XDTIz +MDEwMTEyMDEwMFoXDTMzMDEwMTEyMDEwMFowDTELMAkGA1UEBhMCVVMwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARtDYTQ38TdHTMQb6pr7IAVcFjoW15DPK8V2rsR +kcOS2XJSWVpUkGttfUi1XQyVrIXDBA+Fma4s+lAHO5UrKtR9oyEwHzAdBgkrBgEE +AYI3FQcEEDAOBgkqAwQFBgcICQACAQEwCgYIKoZIzj0EAwIDRwAwRAIgcbUufnLk +Jd23LBlFM1fRhoW8wxi6VuwNCmFqx9n7E+gCIFPAi0/ZhTMyfK/X9BHVtR/B4r84 +R/YOuYr4MtmIMM4Q +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/nc_invalid_ip4_netmask.der b/vectors/cryptography_vectors/x509/custom/nc_invalid_ip4_netmask.der new file mode 100644 index 000000000000..ea3e3515b5c4 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/nc_invalid_ip4_netmask.der differ diff --git a/vectors/cryptography_vectors/x509/custom/nc_ip_invalid_length.pem b/vectors/cryptography_vectors/x509/custom/nc_ip_invalid_length.pem new file mode 100644 index 000000000000..e4df5184d19f --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/nc_ip_invalid_length.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIC/jCCAeagAwIBAgITBnA4pkis5m3OGusBaihd9qH0hzANBgkqhkiG9w0BAQsF +ADAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwHhcNMTUwNzAxMjAxNDAwWhcNMTYw +NjMwMjAxNDAwWjAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCYyaGtu90vcm+jN+SoQHXxWMQyplY1neL9KjfE ++TsKKcy8TKJEqlT8qZr6bIL3KVbTIiYO8bCW9fHSMgHWrmtr37LlFoQ3emcLfDbM +kybmOolAxA78im0L2BIW1wT2iSHh1p/ZO5QLdt+e8zP5AkZAnXCZk912RcJYyGUW +7JQzzRfEANSLE9Gmh78NsxWNI1Ipc3dhyuk3+YHwePGCzLCeXCiF4FHGNMg8Drtr +rENNHZjHJCbMLfK9irHV5Xh1FHTK8xlqEq+YecpqboUyqgWVOOvpxUxiKagfp//Z ++iFDC1+GgpuupzFUiHPSVCZGMnE3bHvIBOkoHkNu7kNK7VX3AgMBAAGjQzBBMD8G +A1UdHgEB/wQ1MDOgMTAjhyEA/wAAAAAAAAAAAAAAAAAA//////////////////// +//8wCocIwKgAAf////8wDQYJKoZIhvcNAQELBQADggEBAF0g5qJ5waYr7FvzShPO +XNYaOOPSvfPtXBVA+dVXiuNqD1HdBkUAlNxE2CeWMiuzjKEKnuC07TQ8emQhfus/ +67WXLX3acEZqodnmxp96g7NRQHJJMMEgkbZCU3YM55rTuvNC7ORr3jRa4GCZGHxY +4zlqcwsqbHv9497lYEmpJowUUuATrMl+KO7azfpNTJkDqzKVhLS5Zq2SaTOurID9 +I1qSPeZeKWiDKZBWq8AgkHyQjQaZe1KJfgQ+Lyb3/ye3+2cMUDBFggT52JNxCjJy +mmgrJqkTFdVu0s8S9O3RzM1p7AvyOaCTY+B3bYRnCCX9SbrfQPShVaHTiQMSjs4s +9mk= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem new file mode 100644 index 000000000000..e0509174c823 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDfTCCAjCgAwIBAgIUP4D/5rcT93vdYGPhsKf+hbes/JgwQgYJKoZIhvcNAQEK +MDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF +AKIEAgIA3jAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8wHhcNMjIwNDMwMjAz +MTE4WhcNMzMwNDEyMjAzMTE4WjAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8w +ggEgMAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKCAQEAt1jpboUoNppBVamc+nA+zEjl +jn/gPbRFCvyveRd8Yr0p8y1mlmjKXcQlXcHPVM4TopgFXqDykIHXxJxLV56ysb4K +UGe0nxpmhEso5ZGUgkDIIoH0NAQAsS8rS2ZzNJcLrLGrMY6DRgFsa+G6h2DvMwgl +nsX++a8FIm7Vu+OZnfWpDEuhJU4TRtHVviJSYkFMckyYBB48k1MU+0b4pezHconZ +mMEisBFFbwarNvowf2i/tRESe3myKXfiJsZZ2UzdE3FqycSgw1tx8qV/Z8myozUW +uihIdw8TGbbsJhEeVFxQEP/DVzC6HHDI3EVpr2jPYeIE60hhZwM7jUmQscLerQID +AQABo1MwUTAdBgNVHQ4EFgQUb1QD8QEIQn5DALIAujTDATssNcQwHwYDVR0jBBgw +FoAUb1QD8QEIQn5DALIAujTDATssNcQwDwYDVR0TAQH/BAUwAwEB/zBCBgkqhkiG +9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFl +AwQCAQUAogQCAgDeA4IBAQAvKBXlx07tdmtfhNTPn16dupBIS5344ZE4tfGSE5Ir +iA1X0bukKQ6V+6xJXGreaIw0wvwtIeI/R0JwcR114HBDqjt40vklyNSpGCJzgkfD +Q/d8JXN/MLyQrk+5F9JMy+HuZAgefAQAjugC6389Klpqx2Z1CgwmALhjIs48GnMp +Iz9vU2O6RDkMBlBRdmfkJVjhhPvJYpDDW1ic5O3pxtMoiC1tAHHMm4gzM1WCFeOh +cDNxABlvVNPTnqkOhKBmmwRaBwdvvksgeu2RyBNR0KEy44gWzYB9/Ter2t4Z8ASq +qCv8TuYr2QGaCnI2FVS5S9n6l4JNkFHqPMtuhrkr3gEz +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_invalid_mgf.der b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_invalid_mgf.der new file mode 100644 index 000000000000..3141976caed0 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_invalid_mgf.der differ diff --git a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_no_sig_params.der b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_no_sig_params.der new file mode 100644 index 000000000000..33df2ec52f18 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_no_sig_params.der differ diff --git a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_unsupported_mgf_hash.der b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_unsupported_mgf_hash.der new file mode 100644 index 000000000000..d6276d098866 Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert_unsupported_mgf_hash.der differ diff --git a/vectors/cryptography_vectors/x509/custom/rsa_pss_sha256_no_null.pem b/vectors/cryptography_vectors/x509/custom/rsa_pss_sha256_no_null.pem new file mode 100644 index 000000000000..3780fe0d56e4 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/rsa_pss_sha256_no_null.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDADCCAbgCCQDEHaWKEwyb7zA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQC +AaEaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiAwIBIDASMRAwDgYDVQQDDAd0 +ZXN0aW5nMB4XDTIzMDUyNzA2NDExOVoXDTMzMDUyNDA2NDExOVowEjEQMA4GA1UE +AwwHdGVzdGluZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM2INK8b +04HqQ1ZYt94tDO0lFPOeCswGhKJQ9SRzTNpNB1XaJvrzz999gimmedUwgwVXHRdt +9WS/QXuyKzyeHcQFN8IPVylIMNGS9IEVa9NGNXzLVMIJYzDlwrEhQm6O4fUW8VtE +U85BXEw0yTEgeQxfuR688kjp/1bjkYsvLE/ID9EMgnXXmzunuqYxG+nmonfIYTgR +NpmXJJgp096sJHKaRkDaC7eApl6776kueFRRSiAIHY10wHqgOL0pBwIMSd/F/EKv +G0weUBLqjzus7G/+LdC6UoGWgV4EybvYlisH4SnLbNdvFilLWaNbgbD2R07hVaHs +8010rCq5RT766dcCAwEAATA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEa +MBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiAwIBIAOCAQEAdKmJnR+UZaMi9RSI +ZBTN5SRv0nTJCwX/citYo8MMcsJ+DOLxR4tC9haYhRD9mIjks1NXcEKN+LqW9hDF +C5ptas03HeEY1NByS3wFSDRHggNFxpwmvX4hGp/8fjaf8EOb1rzh0TsJEgcv4h4Z +KeeSYvCtk5pMe+2lDgLfSegM22RFgXBj/wcI5JDxkGJ4M56++IM55HdXTY1cy7KY +woTtP8G6xzmKdVC+E8XGjBAbyzyommMpAI6aUnjW6oa4fD4ev1X17+/CQb1VyAYs +7nz4uBV1FTNAiUzjrf95KV5p2ir6YcOdspwuRbUJwGP+/1nXeN1pksnh56Fe3J5b +8Zw4cw== +-----END CERTIFICATE----- + diff --git a/vectors/cryptography_vectors/x509/custom/valid_signature.pem b/vectors/cryptography_vectors/x509/custom/valid_signature_cert.pem similarity index 64% rename from vectors/cryptography_vectors/x509/custom/valid_signature.pem rename to vectors/cryptography_vectors/x509/custom/valid_signature_cert.pem index 9c2180985001..0c9589fe174a 100644 --- a/vectors/cryptography_vectors/x509/custom/valid_signature.pem +++ b/vectors/cryptography_vectors/x509/custom/valid_signature_cert.pem @@ -1,14 +1,3 @@ ------BEGIN X509 CRL----- -MIIBfTBnAgEBMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNVBAMMGmludmFsaWRfc2ln -bmF0dXJlIENSTCB0ZXN0Fw0xNzA4MDYwMTQ4MjVaFw0xNzA5MDUwMTQ4MjVaoA4w -DDAKBgNVHRQEAwIBAzANBgkqhkiG9w0BAQsFAAOCAQEAFgGnFwwqviPvA0bfmnvI -c6oGIlq9Bmx/vSH6gwLCuGWn2BrKCWCIJNEtK4hrTfQRASb/uywHvhnByAE2lQlY -9FiefdvXgF5zEah/gV/2A0azvqfvOlPBLzreeoW3Q1fizmip3XN1fXiq8cXBpEYt -SRTJPzgbHvIu50EB2J0hs+rGo1hPTDtZn/r63hcQzUhIWQVmwP+NOzhpUcdnQj3/ -pn6BAJcxyYO2xDoUIncq586k8XVqshEl9xVwJMKhDDk84m/WQZg8i8szgI/muFsm -3vilMgIISrTMYeFIZWAy8rYfKLDMlmAtPRXYqyqOdTsLqz2X3RDMRHMXf1Vf8V31 -ug== ------END X509 CRL----- -----BEGIN CERTIFICATE----- MIICxjCCAa4CCQCETsDmKRzISDANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpp bnZhbGlkX3NpZ25hdHVyZSBDUkwgdGVzdDAeFw0xNzA4MDYwMTM5MzRaFw0xNzA5 diff --git a/vectors/cryptography_vectors/x509/custom/valid_signature_crl.pem b/vectors/cryptography_vectors/x509/custom/valid_signature_crl.pem new file mode 100644 index 000000000000..3aba91308bf8 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/valid_signature_crl.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBfTBnAgEBMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNVBAMMGmludmFsaWRfc2ln +bmF0dXJlIENSTCB0ZXN0Fw0xNzA4MDYwMTQ4MjVaFw0xNzA5MDUwMTQ4MjVaoA4w +DDAKBgNVHRQEAwIBAzANBgkqhkiG9w0BAQsFAAOCAQEAFgGnFwwqviPvA0bfmnvI +c6oGIlq9Bmx/vSH6gwLCuGWn2BrKCWCIJNEtK4hrTfQRASb/uywHvhnByAE2lQlY +9FiefdvXgF5zEah/gV/2A0azvqfvOlPBLzreeoW3Q1fizmip3XN1fXiq8cXBpEYt +SRTJPzgbHvIu50EB2J0hs+rGo1hPTDtZn/r63hcQzUhIWQVmwP+NOzhpUcdnQj3/ +pn6BAJcxyYO2xDoUIncq586k8XVqshEl9xVwJMKhDDk84m/WQZg8i8szgI/muFsm +3vilMgIISrTMYeFIZWAy8rYfKLDMlmAtPRXYqyqOdTsLqz2X3RDMRHMXf1Vf8V31 +ug== +-----END X509 CRL----- diff --git a/vectors/cryptography_vectors/x509/ee-pss-sha1-cert.pem b/vectors/cryptography_vectors/x509/ee-pss-sha1-cert.pem new file mode 100644 index 000000000000..b504aea5813a --- /dev/null +++ b/vectors/cryptography_vectors/x509/ee-pss-sha1-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFDCCAfygAwIBAgIBAjANBgkqhkiG9w0BAQowADANMQswCQYDVQQDDAJDQTAg +Fw0xNzA0MjQyMTE5NDlaGA8yMTE3MDQyNTIxMTk0OVowEzERMA8GA1UEAwwIUFNT +LVNIQTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo/4lYYYWu3tss +D9Vz++K3qBt6dWAr1H08c3a1rt6TL38kkG3JHPSKOM2fooAWVsu0LLuT5Rcf/w3G +Q/4xNPgo2HXpo7uIgu+jcuJTYgVFTeAxl++qnRDSWA2eBp4yuxsIVl1lDz9mjsI2 +oBH/wFk1/Ukc3RxCMwZ4rgQ4I+XndWfTlK1aqUAfrFkQ9QzBZK1KxMY1U7OWaoIb +FYvRmavknm+UqtKW5Vf7jJFkijwkFsbSGb6CYBM7YrDtPh2zyvlr3zG5ep5LR2in +Kcc/SuIiJ7TvkGPX79ByST5brbkb1Ctvhmjd1XMSuEPJ3EEPoqNGT4tniIQPYf55 +NB9KiR+3AgMBAAGjdzB1MB0GA1UdDgQWBBTnm+IqrYpsOst2UeWOB5gil+FzojAf +BgNVHSMEGDAWgBS0ETPx1+Je91OeICIQT4YGvx/JXjAJBgNVHRMEAjAAMBMGA1Ud +JQQMMAoGCCsGAQUFBwMBMBMGA1UdEQQMMAqCCFBTUy1TSEExMA0GCSqGSIb3DQEB +CjAAA4IBAQCC4qIOu7FVYMvRx13IrvzviF+RFRRfAD5NZSPFw5+riLMeRlA4Pdw/ +vCctNIpqjDaSFu8BRTUuyHPXSIvPo0Rl64TsfQNHP1Ut1/8XCecYCEBx/ROJHbM5 +YjoHMCAy+mR3f4BK1827Mp5U/wRJ6ljvE5EbALQ06ZEuIO6zqEAO6AROUCjWSyFd +z9fkEHS0XmploIywH4QXR7X+ueWOE3n76x+vziM4qoGsYxy0sxePfTWM1DscT1Kt +l5skZdZEKo6J8m8ImxfmtLutky2/tw5cdeWbovX3xfipabjPqpzO9Tf9aa4iblJa +AEQwRss+D6ixFO1rNKs1fjFva7A+9lrO +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/ocsp/ocsp-army.inapplicable-req.der b/vectors/cryptography_vectors/x509/ocsp/ocsp-army.inapplicable-req.der new file mode 100644 index 000000000000..56d9bccd2a2f Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/ocsp-army.inapplicable-req.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/ocsp-army.revoked-req.der b/vectors/cryptography_vectors/x509/ocsp/ocsp-army.revoked-req.der new file mode 100644 index 000000000000..ad1e22de9ed1 Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/ocsp-army.revoked-req.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/ocsp-army.valid-req.der b/vectors/cryptography_vectors/x509/ocsp/ocsp-army.valid-req.der new file mode 100644 index 000000000000..9310b13d9515 Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/ocsp-army.valid-req.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/req-acceptable-responses.der b/vectors/cryptography_vectors/x509/ocsp/req-acceptable-responses.der new file mode 100644 index 000000000000..0afa906d2f55 Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/req-acceptable-responses.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/req-duplicate-ext.der b/vectors/cryptography_vectors/x509/ocsp/req-duplicate-ext.der new file mode 100644 index 000000000000..d92324dfe9b2 Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/req-duplicate-ext.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/req-ext-unknown-oid.der b/vectors/cryptography_vectors/x509/ocsp/req-ext-unknown-oid.der new file mode 100644 index 000000000000..2283aae03494 Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/req-ext-unknown-oid.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/resp-response-type-unknown-oid.der b/vectors/cryptography_vectors/x509/ocsp/resp-response-type-unknown-oid.der new file mode 100644 index 000000000000..4ce88f5327ea Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/resp-response-type-unknown-oid.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/resp-successful-no-response-bytes.der b/vectors/cryptography_vectors/x509/ocsp/resp-successful-no-response-bytes.der new file mode 100644 index 000000000000..60ac1f76854f Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/resp-successful-no-response-bytes.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/resp-unknown-extension.der b/vectors/cryptography_vectors/x509/ocsp/resp-unknown-extension.der new file mode 100644 index 000000000000..2d127d74ae39 Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/resp-unknown-extension.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/resp-unknown-hash-alg.der b/vectors/cryptography_vectors/x509/ocsp/resp-unknown-hash-alg.der new file mode 100644 index 000000000000..aad0cb5ad33e Binary files /dev/null and b/vectors/cryptography_vectors/x509/ocsp/resp-unknown-hash-alg.der differ diff --git a/vectors/cryptography_vectors/x509/ocsp/resp-unknown-response-status.der b/vectors/cryptography_vectors/x509/ocsp/resp-unknown-response-status.der new file mode 100644 index 000000000000..e96966ef14cf --- /dev/null +++ b/vectors/cryptography_vectors/x509/ocsp/resp-unknown-response-status.der @@ -0,0 +1,2 @@ +0 + \ No newline at end of file diff --git a/vectors/cryptography_vectors/x509/requests/bad-version.pem b/vectors/cryptography_vectors/x509/requests/bad-version.pem new file mode 100644 index 000000000000..32a33cc06279 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/bad-version.pem @@ -0,0 +1,7 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIHJMHECAQEwDzENMAsGA1UEAwwEVGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEH +A0IABJjsayyAQod1J7UJYNT8AH4WWxLdKV0ozhrIz6hCzBAze7AqXWOSH8G+1EWC +pSfL3oMQNtBdJS0kpXXaUqEAgTSgADAKBggqhkjOPQQDAgNIADBFAiAUXVaEYATg +4Cc917T73KBImxh6xyhsA5pKuYpq1S4m9wIhAK+G93HR4ur7Ghel6+zUTvIAsj9e +rsn4lSYsqI4OI4ei +-----END CERTIFICATE REQUEST----- diff --git a/vectors/cryptography_vectors/x509/requests/basic_constraints.pem b/vectors/cryptography_vectors/x509/requests/basic_constraints.pem index 7169cda76531..c3eeef55b76c 100644 --- a/vectors/cryptography_vectors/x509/requests/basic_constraints.pem +++ b/vectors/cryptography_vectors/x509/requests/basic_constraints.pem @@ -1,11 +1,11 @@ Certificate Request: Data: - Version: 2 (0x2) - Subject: C=US, ST=Texas, L=Austin, O=PyCA, CN=cryptography.io + Version: 1 (0x0) + Subject: C = US, ST = Texas, L = Austin, O = PyCA, CN = cryptography.io Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public Key: (2048 bit) - Modulus (2048 bit): + RSA Public-Key: (2048 bit) + Modulus: 00:a4:1a:ae:63:a2:ad:23:0a:b7:a2:f0:0e:50:fd: 96:e1:02:96:05:07:72:c8:96:a7:d6:a9:f6:19:fd: 61:98:9a:ca:98:5c:41:69:0c:f2:f8:27:f2:c4:7d: @@ -30,23 +30,23 @@ Certificate Request: X509v3 Basic Constraints: critical CA:TRUE, pathlen:1 Signature Algorithm: sha1WithRSAEncryption - 0a:8a:70:98:1c:68:65:04:bb:6b:0c:93:90:03:e8:94:21:08: - 1d:af:e6:59:2a:27:b1:f7:80:c3:aa:0a:dd:8b:07:67:7e:cf: - ac:99:c7:c9:70:d8:f2:13:32:25:b9:03:7d:b7:37:da:f4:d6: - 43:00:be:80:fd:7d:6d:05:f7:a0:e8:3e:69:8a:b7:44:46:3c: - 58:87:28:72:1e:eb:31:50:26:25:39:0e:57:85:b5:28:a2:6a: - 0d:f5:88:70:f4:bc:81:d6:87:4e:f2:ca:64:1b:86:d5:04:2d: - 1e:d6:32:00:23:04:8b:b0:9a:a9:8c:5b:60:2d:9e:ea:57:7a: - d2:e3:b4:f0:f4:0c:08:54:af:91:e3:b4:61:51:91:7e:60:4f: - 0a:6e:db:65:38:1a:4f:35:07:e7:08:0d:0a:39:3e:7b:4a:bf: - 03:f6:6e:5b:f4:47:95:53:22:21:b9:91:db:0e:76:f1:0f:6f: - 82:a5:0f:b7:65:cf:19:12:9e:67:4e:5f:c1:b2:a7:02:d7:e4: - 6a:55:de:35:52:32:4d:45:ab:b3:fc:82:3d:6d:65:9c:be:6c: - 81:9a:10:9a:22:f8:75:de:9c:f4:61:de:6c:82:3a:5f:51:f4: - 7b:b7:14:68:0b:ac:2b:16:76:46:5e:3c:bb:03:dd:dc:12:17: - 70:06:4b:3c + 0a:8a:70:98:1c:68:65:04:bb:6b:0c:93:90:03:e8:94:21:08: + 1d:af:e6:59:2a:27:b1:f7:80:c3:aa:0a:dd:8b:07:67:7e:cf: + ac:99:c7:c9:70:d8:f2:13:32:25:b9:03:7d:b7:37:da:f4:d6: + 43:00:be:80:fd:7d:6d:05:f7:a0:e8:3e:69:8a:b7:44:46:3c: + 58:87:28:72:1e:eb:31:50:26:25:39:0e:57:85:b5:28:a2:6a: + 0d:f5:88:70:f4:bc:81:d6:87:4e:f2:ca:64:1b:86:d5:04:2d: + 1e:d6:32:00:23:04:8b:b0:9a:a9:8c:5b:60:2d:9e:ea:57:7a: + d2:e3:b4:f0:f4:0c:08:54:af:91:e3:b4:61:51:91:7e:60:4f: + 0a:6e:db:65:38:1a:4f:35:07:e7:08:0d:0a:39:3e:7b:4a:bf: + 03:f6:6e:5b:f4:47:95:53:22:21:b9:91:db:0e:76:f1:0f:6f: + 82:a5:0f:b7:65:cf:19:12:9e:67:4e:5f:c1:b2:a7:02:d7:e4: + 6a:55:de:35:52:32:4d:45:ab:b3:fc:82:3d:6d:65:9c:be:6c: + 81:9a:10:9a:22:f8:75:de:9c:f4:61:de:6c:82:3a:5f:51:f4: + 7b:b7:14:68:0b:ac:2b:16:76:46:5e:3c:bb:03:dd:dc:12:17: + 70:06:4b:3c -----BEGIN CERTIFICATE REQUEST----- -MIICwTCCAakCAQIwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD +MIICwTCCAakCAQAwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD VQQHDAZBdXN0aW4xDTALBgNVBAoMBFB5Q0ExGDAWBgNVBAMMD2NyeXB0b2dyYXBo eS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKQarmOirSMKt6Lw DlD9luEClgUHcsiWp9ap9hn9YZiayphcQWkM8vgn8sR9/m0InveZLPetRZeXw+tM diff --git a/vectors/cryptography_vectors/x509/requests/challenge-multi-valued.der b/vectors/cryptography_vectors/x509/requests/challenge-multi-valued.der new file mode 100644 index 000000000000..d7d6833a18b0 Binary files /dev/null and b/vectors/cryptography_vectors/x509/requests/challenge-multi-valued.der differ diff --git a/vectors/cryptography_vectors/x509/requests/ec_sha256_old_header.pem b/vectors/cryptography_vectors/x509/requests/ec_sha256_old_header.pem new file mode 100644 index 000000000000..f9c932180ff8 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/ec_sha256_old_header.pem @@ -0,0 +1,10 @@ +-----BEGIN NEW CERTIFICATE REQUEST----- +MIIBTzCB1gIBADBXMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8xDTALBgNVBAoM +BFB5Q0ExCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVz +dGluMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3hm1FMCzw66bOY6j4mtegWvc+RAs +rY8S/gL55MkkhySzkpftdYLgTYsypVEDjQkIaAOm0/uRoaEWfsAhWLAO+tOck5ZG +L6zP8P+vcVWBKQnTcmvVn94AHP9LubL1r4y6oAAwCgYIKoZIzj0EAwIDaAAwZQIw +LBqffejBeHMy0jB6iGtHalnxcrmw4lAmLzI4sbRe4RK7brNbD7VqEjuSlushLf/D +AjEAlM9EDJXFKCfVVq5tdlAOMAglXUfCn37ngu11WOUb/XaqRd9tmZ7VxGM0f+I4 +LRdR +-----END NEW CERTIFICATE REQUEST----- diff --git a/vectors/cryptography_vectors/x509/requests/freeipa-bad-critical.pem b/vectors/cryptography_vectors/x509/requests/freeipa-bad-critical.pem new file mode 100644 index 000000000000..ca83b5398ae1 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/freeipa-bad-critical.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDqzCCApMCAQAwLzERMA8GA1UEChMISVBBLlRFU1QxGjAYBgNVBAMTEXJlcGxp +Y2ExLmlwYS50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5TzQ +ZpKgWu4v4LmX44xRCgA2DcvY4D7Lw/Zyr3uch54gvAUWi2oi/PYVixJQNVHfZIck +grPRO+gtMaFsXuIHgWHjy2TdFNOV5Uav3W07fl28nngmAWd8Sq4W+i7vnBMp62sz +tE0nomTUe/52D+V3pggCzqVlvFvAg8IqxyavDQ974I13V2SuvJVJ7EnlaCnNZPRX +L1ICnszKfIhoLWH8cbBaxHCjZkHEInu87qb9OHNpevrz5OJMX2HG14Ic15I52l0Y +VTBQQr1AQFGDNpSqt+NeoCSiY9F7nhEXdkgvhk3rMV2nk2XJut9YIsEJYo4SK0pB +TxdSUYHsyZqrP/cdFwIDAQABoIIBNTAlBgkqhkiG9w0BCRQxGB4WAFMAZQByAHYA +ZQByAC0AQwBlAHIAdDCCAQoGCSqGSIb3DQEJDjGB/DCB+TCBkgYDVR0RAQEABIGH +MIGEghFyZXBsaWNhMS5pcGEudGVzdKAvBgorBgEEAYI3FAIDoCEMH2xkYXAvcmVw +bGljYTEuaXBhLnRlc3RASVBBLlRFU1SgPgYGKwYBBQICoDQwMqAKGwhJUEEuVEVT +VKEkMCKgAwIBAaEbMBkbBGxkYXAbEXJlcGxpY2ExLmlwYS50ZXN0MAwGA1UdEwEB +/wQCMAAwIAYDVR0OAQEABBYEFPtLvk2RcgKwKfIo0Cp8Pvp7Xu3wMDIGCSsGAQQB +gjcUAgEBAAQiHiAAYwBhAEkAUABBAHMAZQByAHYAaQBjAGUAQwBlAHIAdDANBgkq +hkiG9w0BAQsFAAOCAQEA1jEX9uXSAvDjP6ZRxT5Wo2DWy4yqJx5+tO21jrpRgCKu +owUhwyzEFiA/WDQ/vy9XGqvcRaRpkdbwrcmefvUCgprOBeNjR1F2aKTHngaH4WbW +d4BI0lR0Z1WZuvL2fRGDvOCQAGNVyGvtxV+15olWq7386fEe3PAHF9osXpcH97Ki +fL1+eG2Vkaqo4yylUGme/Rin4vGzxkjGYE+O/ugxtgil5VPs0nrJx0bFWaMLK9yE +rv9O1V3JSKoLn+yAKxrYQuMBl1nqpAj9P4NWdFsGl3Ubpn4vwitwaq9pkEu0K1Z+ +CP5FXOyFsgEGKncL4gub8IQC720B25A8YowGTk3BNw== +-----END CERTIFICATE REQUEST----- + diff --git a/vectors/cryptography_vectors/x509/requests/long-form-attribute.pem b/vectors/cryptography_vectors/x509/requests/long-form-attribute.pem new file mode 100644 index 000000000000..a8bc156c0a9b --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/long-form-attribute.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICZDCCAUwCAQAwDTELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCb+ec0zYAYLzk/MDdDJYvzdvEO2ZUrBYM6z1r8NedwpJfxUWqC +hvK1cpc9EbQeCwS1eooTIGoNveeCrwL+pWdmf1sh6gz7SsxdN/07nyhSM8M6Xkec ++tGrjyi1H/N1afwWXox3WcvBNbxu3Df5RKLDb0yt9aqhmJylbl/tbvgJesXymwmp +Rc1vXL0fOedUtuAJ3xQ15M0pgLF8qDn4lySJz25x76pMYPeN5/a7x+SR/jj81kep +VaVpuh/2hePV5uwUX3uWoj5sAkrBCifi4NPge0Npd6KeKVvXytLOymH/4+WvV719 +wCO+MyrkhpdHSakJDTIaQIxsqVeVVKdPLAPJAgMBAAGgEjAQBgkqhkiG9w0BCQcx +A38gADANBgkqhkiG9w0BAQsFAAOCAQEAMmgeSa8szbjPFD/4vcPBr/vBEROFGgL8 +mX3o5pF9gpr7nRjhLKBkgJvlRm6Ma3Xvdfc/r5Hp2ZBTA7sZZYhyeezGfCQN/Qhd +a1v+sCwG58IjvGfCSS7Y5tGlEBQ4MDf0Q7PYPSxaNUEBH7vo+M7U+nFuNSmyWlt6 +SFBSkohZkWoVSGx3KsAO+SAHYZ7JtqsAS/dm7Dflp8KxeDg7wzGBDQRpGF4CpI1V +QjGSJQXSEdD+J7mtvBEOD34abRfV6zOUGzOOo3NWE6wNpYgt0A7gVlzSYpdwqjBd +vACfXR2r/mu+4KkAvYh8WwCiTcYgGjl2pT1bO4hEmcJ0RSWy/fGD8Q== +-----END CERTIFICATE REQUEST----- diff --git a/vectors/cryptography_vectors/x509/requests/two_basic_constraints.pem b/vectors/cryptography_vectors/x509/requests/two_basic_constraints.pem index da23c06eb550..dc8236fc807f 100644 --- a/vectors/cryptography_vectors/x509/requests/two_basic_constraints.pem +++ b/vectors/cryptography_vectors/x509/requests/two_basic_constraints.pem @@ -1,11 +1,11 @@ Certificate Request: Data: - Version: 2 (0x2) - Subject: C=US, ST=Texas, L=Austin, O=PyCA, CN=cryptography.io + Version: 1 (0x0) + Subject: C = US, ST = Texas, L = Austin, O = PyCA, CN = cryptography.io Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public Key: (2048 bit) - Modulus (2048 bit): + RSA Public-Key: (2048 bit) + Modulus: 00:cc:72:54:2d:83:be:73:f5:9a:60:3f:b8:bd:78: 7d:f4:3d:6e:31:38:a9:26:72:86:19:14:87:0d:f4: 68:97:19:2f:d7:7c:80:45:ad:38:27:59:db:57:76: @@ -32,23 +32,23 @@ Certificate Request: X509v3 Basic Constraints: critical CA:FALSE Signature Algorithm: sha1WithRSAEncryption - b8:00:de:3c:28:bf:56:9a:a7:8f:50:a3:86:a3:02:91:8b:97: - 1c:b8:73:81:c2:fd:85:d7:6f:ba:b1:c3:18:8a:17:d9:66:cd: - b9:9a:9c:1f:c8:0b:88:33:b7:4e:97:b2:60:43:ea:13:57:13: - 17:7c:23:7d:22:6e:65:b0:0a:bc:dc:12:ec:b3:85:2f:1b:c9: - ef:9c:19:f3:15:fd:78:89:a6:d1:2d:b8:bf:b6:17:b8:dc:b5: - 7a:e6:2a:4d:2c:da:01:10:31:96:12:13:49:08:1b:d9:ba:97: - 54:e4:21:b8:50:92:9d:1f:30:f0:a2:de:99:8e:da:0e:1f:84: - d4:22:2a:f6:d4:3b:43:81:25:ca:2a:e2:17:f6:ef:2f:db:df: - 67:dc:0f:1b:36:ac:46:b4:39:3b:d6:17:1a:12:fb:5f:1d:28: - db:9f:66:38:64:b7:43:ab:84:49:11:3b:ae:f1:30:cf:79:7e: - a6:52:ff:91:cb:9c:53:09:44:89:83:cf:04:7b:3c:12:7b:8f: - 56:e7:48:9a:e5:2a:f3:1f:93:ec:07:5f:1d:f1:6d:59:ed:5e: - f6:6a:be:63:60:02:f4:65:34:fb:dc:0a:1b:b3:99:b5:4b:4f: - 66:55:35:d3:79:85:48:7e:ca:0e:06:0f:92:00:27:93:79:ce: - f7:2f:ad:2b + b8:00:de:3c:28:bf:56:9a:a7:8f:50:a3:86:a3:02:91:8b:97: + 1c:b8:73:81:c2:fd:85:d7:6f:ba:b1:c3:18:8a:17:d9:66:cd: + b9:9a:9c:1f:c8:0b:88:33:b7:4e:97:b2:60:43:ea:13:57:13: + 17:7c:23:7d:22:6e:65:b0:0a:bc:dc:12:ec:b3:85:2f:1b:c9: + ef:9c:19:f3:15:fd:78:89:a6:d1:2d:b8:bf:b6:17:b8:dc:b5: + 7a:e6:2a:4d:2c:da:01:10:31:96:12:13:49:08:1b:d9:ba:97: + 54:e4:21:b8:50:92:9d:1f:30:f0:a2:de:99:8e:da:0e:1f:84: + d4:22:2a:f6:d4:3b:43:81:25:ca:2a:e2:17:f6:ef:2f:db:df: + 67:dc:0f:1b:36:ac:46:b4:39:3b:d6:17:1a:12:fb:5f:1d:28: + db:9f:66:38:64:b7:43:ab:84:49:11:3b:ae:f1:30:cf:79:7e: + a6:52:ff:91:cb:9c:53:09:44:89:83:cf:04:7b:3c:12:7b:8f: + 56:e7:48:9a:e5:2a:f3:1f:93:ec:07:5f:1d:f1:6d:59:ed:5e: + f6:6a:be:63:60:02:f4:65:34:fb:dc:0a:1b:b3:99:b5:4b:4f: + 66:55:35:d3:79:85:48:7e:ca:0e:06:0f:92:00:27:93:79:ce: + f7:2f:ad:2b -----BEGIN CERTIFICATE REQUEST----- -MIICyTCCAbECAQIwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD +MIICyTCCAbECAQAwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD VQQHDAZBdXN0aW4xDTALBgNVBAoMBFB5Q0ExGDAWBgNVBAMMD2NyeXB0b2dyYXBo eS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMxyVC2DvnP1mmA/ uL14ffQ9bjE4qSZyhhkUhw30aJcZL9d8gEWtOCdZ21d2pfOxXjRfQ2PlJAoPxqs5 diff --git a/vectors/cryptography_vectors/x509/requests/unsupported_extension.pem b/vectors/cryptography_vectors/x509/requests/unsupported_extension.pem index d96097c31057..68a9d870d7b0 100644 --- a/vectors/cryptography_vectors/x509/requests/unsupported_extension.pem +++ b/vectors/cryptography_vectors/x509/requests/unsupported_extension.pem @@ -1,11 +1,11 @@ Certificate Request: Data: - Version: 2 (0x2) - Subject: C=US, ST=Texas, L=Austin, O=PyCA, CN=cryptography.io + Version: 1 (0x0) + Subject: C = US, ST = Texas, L = Austin, O = PyCA, CN = cryptography.io Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public Key: (2048 bit) - Modulus (2048 bit): + RSA Public-Key: (2048 bit) + Modulus: 00:b6:64:25:bd:fc:ba:bf:7b:ee:da:a6:25:79:75: 59:59:cb:bc:da:eb:22:66:97:93:4d:f0:67:39:45: 01:5c:58:0a:17:88:e4:05:14:c8:3f:33:39:5f:a0: @@ -30,23 +30,23 @@ Certificate Request: 1.2.3.4: value Signature Algorithm: sha1WithRSAEncryption - 16:5f:86:90:13:fd:63:e6:c9:ca:74:68:b4:6e:e6:c5:c3:46: - c1:26:bc:64:2b:fc:ef:be:ab:eb:8b:a9:de:8d:4e:a8:f9:f0: - 3e:b0:0b:8c:e4:f8:0b:28:5b:13:0c:46:f8:3b:55:cb:cc:cb: - ed:6a:4f:16:3a:4b:e9:65:2d:3c:1a:a5:1f:a8:07:ab:22:ee: - 91:60:f1:06:76:0c:6e:8f:7b:25:36:4b:d6:60:04:77:e6:35: - 10:4f:eb:fc:2a:c3:71:e5:cb:9f:94:bd:6c:44:08:79:fb:b2: - a0:f5:f2:c0:79:b0:c4:22:ec:81:29:b3:97:e5:2f:1f:47:c5: - 1a:3f:be:50:c8:f4:29:9a:94:1d:19:a9:e2:d6:06:ca:07:43: - 6c:f1:e4:7e:fb:b8:70:0c:5b:41:c4:10:84:29:39:49:17:09: - d1:21:89:d7:c8:e5:6c:48:66:98:ac:8b:33:ab:da:1f:51:a9: - 2f:4c:39:6d:48:d9:7b:34:7f:b5:1e:9e:b8:87:8b:21:13:41: - d4:53:64:c1:16:e0:a8:c1:6f:dc:be:8f:67:ad:e6:30:79:af: - bf:7e:ff:64:99:50:d8:4c:58:66:9c:da:d1:53:06:2e:d3:82: - e3:2d:b3:65:71:6e:6a:67:cf:e1:96:4f:f7:ac:0b:2e:6e:28: - a4:df:f5:e6 + 16:5f:86:90:13:fd:63:e6:c9:ca:74:68:b4:6e:e6:c5:c3:46: + c1:26:bc:64:2b:fc:ef:be:ab:eb:8b:a9:de:8d:4e:a8:f9:f0: + 3e:b0:0b:8c:e4:f8:0b:28:5b:13:0c:46:f8:3b:55:cb:cc:cb: + ed:6a:4f:16:3a:4b:e9:65:2d:3c:1a:a5:1f:a8:07:ab:22:ee: + 91:60:f1:06:76:0c:6e:8f:7b:25:36:4b:d6:60:04:77:e6:35: + 10:4f:eb:fc:2a:c3:71:e5:cb:9f:94:bd:6c:44:08:79:fb:b2: + a0:f5:f2:c0:79:b0:c4:22:ec:81:29:b3:97:e5:2f:1f:47:c5: + 1a:3f:be:50:c8:f4:29:9a:94:1d:19:a9:e2:d6:06:ca:07:43: + 6c:f1:e4:7e:fb:b8:70:0c:5b:41:c4:10:84:29:39:49:17:09: + d1:21:89:d7:c8:e5:6c:48:66:98:ac:8b:33:ab:da:1f:51:a9: + 2f:4c:39:6d:48:d9:7b:34:7f:b5:1e:9e:b8:87:8b:21:13:41: + d4:53:64:c1:16:e0:a8:c1:6f:dc:be:8f:67:ad:e6:30:79:af: + bf:7e:ff:64:99:50:d8:4c:58:66:9c:da:d1:53:06:2e:d3:82: + e3:2d:b3:65:71:6e:6a:67:cf:e1:96:4f:f7:ac:0b:2e:6e:28: + a4:df:f5:e6 -----BEGIN CERTIFICATE REQUEST----- -MIICuzCCAaMCAQIwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD +MIICuzCCAaMCAQAwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD VQQHDAZBdXN0aW4xDTALBgNVBAoMBFB5Q0ExGDAWBgNVBAMMD2NyeXB0b2dyYXBo eS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZkJb38ur977tqm JXl1WVnLvNrrImaXk03wZzlFAVxYCheI5AUUyD8zOV+gyvzdKD6x0Q2HwWUiIdNK diff --git a/vectors/cryptography_vectors/x509/requests/unsupported_extension_critical.pem b/vectors/cryptography_vectors/x509/requests/unsupported_extension_critical.pem index 2ae17d8e0388..aabe882ec536 100644 --- a/vectors/cryptography_vectors/x509/requests/unsupported_extension_critical.pem +++ b/vectors/cryptography_vectors/x509/requests/unsupported_extension_critical.pem @@ -1,11 +1,11 @@ Certificate Request: Data: - Version: 2 (0x2) - Subject: C=US, ST=Texas, L=Austin, O=PyCA, CN=cryptography.io + Version: 1 (0x0) + Subject: C = US, ST = Texas, L = Austin, O = PyCA, CN = cryptography.io Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public Key: (2048 bit) - Modulus (2048 bit): + RSA Public-Key: (2048 bit) + Modulus: 00:ce:ed:18:2c:b8:63:f6:65:50:1e:ec:7e:7b:86: 56:25:3e:3d:5d:86:6e:9a:d1:56:b0:79:b7:85:c9: 14:23:4d:ff:10:c7:68:f2:00:b6:57:39:a6:5d:3f: @@ -30,23 +30,23 @@ Certificate Request: 1.2.3.4: critical value Signature Algorithm: sha1WithRSAEncryption - 2c:70:0f:a6:d0:0d:70:24:a8:94:ad:4b:1d:50:46:19:7c:c0: - a8:fb:01:84:3b:3b:7e:b0:6f:6b:d9:86:81:a3:d4:03:e9:d7: - 0c:f6:ff:c6:43:00:88:59:7b:bc:8f:6d:3d:46:4d:a1:0b:40: - ba:7e:13:4e:4f:1d:02:35:e4:5b:30:a0:a8:fc:4d:49:a5:1b: - 11:19:57:25:58:57:03:09:55:56:cb:50:94:54:f9:15:a3:de: - ab:96:0d:b8:98:9d:0f:c7:16:e1:d6:0b:3b:7a:a2:53:07:d2: - 3c:f7:89:62:66:a4:34:39:c9:03:35:2b:a5:27:69:94:7d:56: - dc:72:8c:bc:3a:33:15:86:f8:c3:19:bb:c2:1d:51:3e:a9:1c: - 5c:8b:7a:63:18:1b:78:57:f4:14:be:39:90:38:d1:b6:8d:e1: - 45:63:1e:e1:32:54:3e:52:e9:5d:4d:d5:3c:65:b1:21:e3:00: - 88:f4:28:f7:34:f4:ac:08:54:59:4d:7b:b5:f4:84:d0:66:df: - 98:10:a3:38:bd:2c:e2:fa:87:7c:3f:c8:36:e6:a5:e1:b9:00: - 7d:c0:3a:40:69:b2:df:f9:c0:af:9f:e3:c6:48:a6:b6:69:0f: - e2:9e:36:dd:e8:ee:02:a1:10:1e:78:e6:c6:c3:b4:12:21:2d: - 70:4c:c0:b4 + 2c:70:0f:a6:d0:0d:70:24:a8:94:ad:4b:1d:50:46:19:7c:c0: + a8:fb:01:84:3b:3b:7e:b0:6f:6b:d9:86:81:a3:d4:03:e9:d7: + 0c:f6:ff:c6:43:00:88:59:7b:bc:8f:6d:3d:46:4d:a1:0b:40: + ba:7e:13:4e:4f:1d:02:35:e4:5b:30:a0:a8:fc:4d:49:a5:1b: + 11:19:57:25:58:57:03:09:55:56:cb:50:94:54:f9:15:a3:de: + ab:96:0d:b8:98:9d:0f:c7:16:e1:d6:0b:3b:7a:a2:53:07:d2: + 3c:f7:89:62:66:a4:34:39:c9:03:35:2b:a5:27:69:94:7d:56: + dc:72:8c:bc:3a:33:15:86:f8:c3:19:bb:c2:1d:51:3e:a9:1c: + 5c:8b:7a:63:18:1b:78:57:f4:14:be:39:90:38:d1:b6:8d:e1: + 45:63:1e:e1:32:54:3e:52:e9:5d:4d:d5:3c:65:b1:21:e3:00: + 88:f4:28:f7:34:f4:ac:08:54:59:4d:7b:b5:f4:84:d0:66:df: + 98:10:a3:38:bd:2c:e2:fa:87:7c:3f:c8:36:e6:a5:e1:b9:00: + 7d:c0:3a:40:69:b2:df:f9:c0:af:9f:e3:c6:48:a6:b6:69:0f: + e2:9e:36:dd:e8:ee:02:a1:10:1e:78:e6:c6:c3:b4:12:21:2d: + 70:4c:c0:b4 -----BEGIN CERTIFICATE REQUEST----- -MIICvjCCAaYCAQIwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD +MIICvjCCAaYCAQAwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD VQQHDAZBdXN0aW4xDTALBgNVBAoMBFB5Q0ExGDAWBgNVBAMMD2NyeXB0b2dyYXBo eS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7tGCy4Y/ZlUB7s fnuGViU+PV2GbprRVrB5t4XJFCNN/xDHaPIAtlc5pl0/7VBnzb3a+2ipD3mpDnkj diff --git a/vectors/cryptography_vectors/x509/scottishpower-bitstring-dn.pem b/vectors/cryptography_vectors/x509/scottishpower-bitstring-dn.pem new file mode 100644 index 000000000000..178fb0a2c7b0 --- /dev/null +++ b/vectors/cryptography_vectors/x509/scottishpower-bitstring-dn.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBqjCCAVCgAwIBAgIQVHD6IGvy5J65YtdaBbMWLTAKBggqhkjOPQQDAjAaMQsw +CQYDVQQLEwIwNzELMAkGA1UEAxMCVTEwHhcNMTYxMDE5MDAwMDAwWhcNMjYxMDE4 +MjM1OTU5WjA5MRYwFAYDVQQDDA1TY290dGlzaFBvd2VyMQswCQYDVQQLDAIwMjES +MBAGA1UELQMJAHCz1R8wXwABMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvcpe +CGVYsjDGVH8bfnqiV0f7zLT6EHWq0Cp3CDM55mLMEhqApxqiExuk4+vuLRYItver +e6MDZxIduNlhv2+4caNZMFcwDgYDVR0PAQH/BAQDAgeAMBEGA1UdDgQKBAhIY5WA +x6zvpjAdBgNVHSABAf8EEzARMA8GDSqGOgABhI+5DwECAQQwEwYDVR0jBAwwCoAI +RV6x1kMmgNgwCgYIKoZIzj0EAwIDSAAwRQIgYxMeqRLszP2Z1P0e1pMt5sb/DMir ++MvjqA35il7Hgx8CIQC4hpUTmQjB/ALfGC9huk+Gx8tZ6Xiz0fqT7vZZ5J4ntw== +-----END CERTIFICATE----- diff --git a/vectors/pyproject.toml b/vectors/pyproject.toml new file mode 100644 index 000000000000..af479e33054e --- /dev/null +++ b/vectors/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "cryptography_vectors" +version = "41.0.4" +authors = [ + {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} +] +description = "Test vectors for the cryptography package." +license = {text = "Apache-2.0 OR BSD-3-Clause"} + +[project.urls] +homepage = "https://github.com/pyca/cryptography" + +[tool.setuptools] +zip-safe = false +include-package-data = true + +[tool.distutils.bdist_wheel] +universal = true diff --git a/vectors/setup.cfg b/vectors/setup.cfg deleted file mode 100644 index 2a9acf13daa9..000000000000 --- a/vectors/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/vectors/setup.py b/vectors/setup.py deleted file mode 100644 index 482c01b35a8f..000000000000 --- a/vectors/setup.py +++ /dev/null @@ -1,32 +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. - -from __future__ import absolute_import, division, print_function - -import os - -from setuptools import find_packages, setup - - -base_dir = os.path.dirname(__file__) - -about = {} -with open(os.path.join(base_dir, "cryptography_vectors", "__about__.py")) as f: - exec (f.read(), about) - - -setup( - name=about["__title__"], - version=about["__version__"], - description=about["__summary__"], - license=about["__license__"], - url=about["__uri__"], - author=about["__author__"], - author_email=about["__email__"], - packages=find_packages(), - zip_safe=False, - include_package_data=True, -)