diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml index 2dbeca46e270..409f3daa0a6e 100644 --- a/.github/actions/cache/action.yml +++ b/.github/actions/cache/action.yml @@ -13,9 +13,10 @@ runs: steps: - name: Normalize key id: normalized-key - run: echo "key=$(echo "${{ inputs.key }}" | tr -d ',')" >> $GITHUB_OUTPUT + run: echo "key=$(echo "${KEY}" | tr -d ',')" >> $GITHUB_OUTPUT shell: bash - - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + env: + KEY: "${{ inputs.key }}" + - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 with: - key: ${{ steps.normalized-key.outputs.key }}-3 - workspaces: "./src/rust/ -> target" + key: ${{ steps.normalized-key.outputs.key }}-4 diff --git a/.github/actions/fetch-vectors/action.yml b/.github/actions/fetch-vectors/action.yml index b567db8a316a..d8c2ada012ab 100644 --- a/.github/actions/fetch-vectors/action.yml +++ b/.github/actions/fetch-vectors/action.yml @@ -9,12 +9,12 @@ runs: with: repository: "C2SP/wycheproof" path: "wycheproof" - # Latest commit on the wycheproof master branch, as of Apr 09, 2024. - ref: "cd27d6419bedd83cbd24611ec54b6d4bfdb0cdca" # wycheproof-ref + # Latest commit on the wycheproof master branch, as of May 02, 2025. + ref: "df4e933efef449fc88af0c06e028d425d84a9495" # wycheproof-ref - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: "C2SP/x509-limbo" path: "x509-limbo" - # Latest commit on the x509-limbo main branch, as of Nov 27, 2024. - ref: "793e65108940143e97abff5250aecd02f1d5316d" # x509-limbo-ref + # Latest commit on the x509-limbo main branch, as of May 13, 2025. + ref: "44b61a40371d90b50c4310464ad0e45c8ba08331" # x509-limbo-ref diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml index c1fa04df3208..9147232da0aa 100644 --- a/.github/actions/upload-coverage/action.yml +++ b/.github/actions/upload-coverage/action.yml @@ -13,7 +13,7 @@ runs: fi id: coverage-uuid shell: bash - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }} path: | diff --git a/.github/workflows/build_openssl.sh b/.github/bin/build_openssl.sh similarity index 79% rename from .github/workflows/build_openssl.sh rename to .github/bin/build_openssl.sh index 72b06e0b8f3e..84c869f4a516 100755 --- a/.github/workflows/build_openssl.sh +++ b/.github/bin/build_openssl.sh @@ -2,18 +2,6 @@ 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 @@ -24,18 +12,14 @@ if [[ "${TYPE}" == "openssl" ]]; then 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 + + # modify the shlib version to a unique one to make sure the dynamic + # linker doesn't load the system one. + sed -i "s/^SHLIB_VERSION=.*/SHLIB_VERSION=100/" VERSION.dat # 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) @@ -45,7 +29,7 @@ if [[ "${TYPE}" == "openssl" ]]; then 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 + if [[ "${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}" @@ -77,4 +61,14 @@ elif [[ "${TYPE}" == "boringssl" ]]; then rm -rf "${OSSL_PATH}/bin" popd rm -rf boringssl/ +elif [[ "${TYPE}" == "aws-lc" ]]; then + git clone https://github.com/aws/aws-lc.git + pushd aws-lc + git checkout "${VERSION}" + cmake -B build -DCMAKE_INSTALL_PREFIX="${OSSL_PATH}" + make -C build -j"$(nproc)" install + # delete binaries we don't need + rm -rf "${OSSL_PATH:?}/bin" + popd # aws-lc + rm -rf aws-lc/ fi diff --git a/.github/compare_benchmarks.py b/.github/bin/compare_benchmarks.py similarity index 100% rename from .github/compare_benchmarks.py rename to .github/bin/compare_benchmarks.py diff --git a/.github/bin/merge_rust_coverage.py b/.github/bin/merge_rust_coverage.py new file mode 100644 index 000000000000..7628d54c354a --- /dev/null +++ b/.github/bin/merge_rust_coverage.py @@ -0,0 +1,100 @@ +# 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 collections.abc +import sys + +import coverage + + +class RustCoveragePlugin(coverage.CoveragePlugin): + def __init__( + self, + coverage_data: collections.abc.Mapping[ + str, collections.abc.Mapping[int, int] + ], + ) -> None: + super().__init__() + self._data = coverage_data + + def file_reporter(self, filename: str) -> coverage.FileReporter: + return RustCoverageFileReporter(filename, self._data[filename]) + + +class RustCoverageFileReporter(coverage.FileReporter): + def __init__( + self, filename: str, coverage_data: collections.abc.Mapping[int, int] + ) -> None: + super().__init__(filename) + self._data = coverage_data + + def lines(self) -> set[int]: + return set(self._data) + + def arcs(self) -> set[tuple[int, int]]: + return {(-1, line) for line in self._data} + + +def main(*lcov_paths: str): + coverage_data = coverage.CoverageData(suffix="rust") + + # {filename: {line_number: count}} + raw_data = collections.defaultdict(lambda: collections.defaultdict(int)) + current_file = None + for p in lcov_paths: + with open(p) as f: + for line in f: + line = line.strip() + if line == "end_of_record": + assert current_file is not None + current_file = None + continue + + prefix, suffix = line.split(":", 1) + match prefix: + case "SF": + current_file = raw_data[suffix] + case "DA": + assert current_file is not None + line_number, count = suffix.split(",") + current_file[int(line_number)] += int(count) + case ( + "BRF" + | "BRH" + | "FN" + | "FNDA" + | "FNF" + | "FNH" + | "LF" + | "LH" + ): + # These are various forms of metadata and summary stats + # that we don't need. + pass + case _: + raise NotImplementedError(prefix) + + covered_lines = { + file_name: {(-1, line) for line, c in lines.items() if c > 0} + for file_name, lines in raw_data.items() + } + coverage_data.add_arcs(covered_lines) + coverage_data.add_file_tracers( + {file_name: "None.RustCoveragePlugin" for file_name in covered_lines} + ) + coverage_data.write() + + cov = coverage.Coverage( + plugins=[lambda reg: reg.add_file_tracer(RustCoveragePlugin(raw_data))] + ) + cov.combine() + coverage_percent = cov.report(show_missing=True) + if coverage_percent < 100: + print("+++ Combined coverage under 100% +++") + cov.html_report() + sys.exit(1) + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0411a7d15804..bb2ed8430d4d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,4 +1,6 @@ version: 2 +enable-beta-ecosystems: true + updates: - package-ecosystem: "github-actions" directories: @@ -21,7 +23,7 @@ updates: - dependency-type: all open-pull-requests-limit: 1024 - - package-ecosystem: pip + - package-ecosystem: uv directories: - "/" - "/.github/requirements/" diff --git a/.github/downstream.d/certbot-josepy.sh b/.github/downstream.d/certbot-josepy.sh index c27568ffe4f1..f172dd0088a3 100755 --- a/.github/downstream.d/certbot-josepy.sh +++ b/.github/downstream.d/certbot-josepy.sh @@ -6,6 +6,7 @@ case "${1}" in cd josepy git rev-parse HEAD curl -sSL https://install.python-poetry.org | python3 - + "${HOME}/.local/bin/poetry" self add poetry-plugin-export "${HOME}/.local/bin/poetry" export -f constraints.txt --dev --without-hashes -o constraints.txt pip install -e . pytest -c constraints.txt ;; diff --git a/.github/downstream.d/mitmproxy.sh b/.github/downstream.d/mitmproxy.sh index df7669aa9336..86ce98a1b1a3 100755 --- a/.github/downstream.d/mitmproxy.sh +++ b/.github/downstream.d/mitmproxy.sh @@ -2,10 +2,11 @@ case "${1}" in install) + pip install uv git clone --depth=1 https://github.com/mitmproxy/mitmproxy cd mitmproxy git rev-parse HEAD - pip install -e ".[dev]" + uv pip install --system --group dev -e . ;; run) cd mitmproxy diff --git a/.github/downstream.d/sigstore.sh b/.github/downstream.d/sigstore.sh new file mode 100755 index 000000000000..4f146a482393 --- /dev/null +++ b/.github/downstream.d/sigstore.sh @@ -0,0 +1,20 @@ +#!/bin/bash -ex + +case "${1}" in + install) + # NOTE: placed in /tmp to avoid inscrutable pytest failures + # with 'unrecognized arguments: --benchmark-disable' + git clone --depth=1 https://github.com/sigstore/sigstore-python /tmp/sigstore-python + cd /tmp/sigstore-python + git rev-parse HEAD + pip install -e ".[test]" + ;; + run) + cd /tmp/sigstore-python + # Run only the unit tests, and skip any that require network access. + pytest test/unit --skip-online + ;; + *) + exit 1 + ;; +esac diff --git a/.github/requirements/build-requirements.txt b/.github/requirements/build-requirements.txt index 875330958ca0..7d9d439638cc 100644 --- a/.github/requirements/build-requirements.txt +++ b/.github/requirements/build-requirements.txt @@ -1,10 +1,6 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --generate-hashes build-requirements.in -# -cffi==1.17.1 ; platform_python_implementation != "PyPy" \ +# This file was autogenerated by uv via the following command: +# uv pip compile --universal --python-version 3.9 --allow-unsafe --generate-hashes build-requirements.in -o build-requirements.txt +cffi==1.17.1 ; platform_python_implementation != 'PyPy' \ --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ @@ -73,36 +69,64 @@ cffi==1.17.1 ; platform_python_implementation != "PyPy" \ --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # via -r build-requirements.in -flit-core==3.10.1 \ - --hash=sha256:66e5b87874a0d6e39691f0e22f09306736b633548670ad3c09ec9db03c5662f7 \ - --hash=sha256:cb31a76e8b31ad3351bb89e531f64ef2b05d1e65bd939183250bf81ddf4922a8 +flit-core==3.12.0 \ + --hash=sha256:18f63100d6f94385c6ed57a72073443e1a71a4acb4339491615d0f16d6ff01b2 \ + --hash=sha256:e7a0304069ea895172e3c7bb703292e992c5d1555dd1233ab7b5621b5b69e62c # via -r build-requirements.in -maturin==1.7.5 \ - --hash=sha256:0d2d04ab5f47c1bc2b075a5d8255d9a72921e8dceebf9f9e9884f09d67f7cdd6 \ - --hash=sha256:5563d61cfa2fcd7d1552022df6566300f229fa3aed62020c93a750fa3dca9a99 \ - --hash=sha256:71cbcfd4a74aac3eafe99a1cd73d83af8049f572986ff4e0e5e4d8fec9c66a93 \ - --hash=sha256:742cd76a50104fdd832b010a205199e9b02333879f750c0cfca6c93e9472623f \ - --hash=sha256:76a78284a96c24cd2d0ac3eac865315b4b0be7a443463fd5b3ebea3c6f147703 \ - --hash=sha256:9044e5e2eb68bbf8ad86c4ffeab365b78b54bf342ba346dc93775531d3a4e647 \ - --hash=sha256:c1002ca9a23c45123af752d353f6b221151a6eab2b5b65d57a79298b7d8ca6d4 \ - --hash=sha256:c38e585555be525ebc2602ea7189c7ef3e1c3001c94893e5bc71f934468ff124 \ - --hash=sha256:c441fe54945fe8077f17cb116834980391169cf712b63631d8380c8c3de781a1 \ - --hash=sha256:e31c4d25b56346c7872417d58cca81e52387a37469cdb79f7225bae9ad75daf9 \ - --hash=sha256:e773ade7a1383c24eaf6b665340a91278c80ab544c18687aa69e9661b289cf48 \ - --hash=sha256:f05ccbdfe96ad58d70dba9c3eed090726db8ccbaf07ec03852113ca2fec6d84b \ - --hash=sha256:f6c80fa7d67f58fd2cecbcdf309e2c3c5cd6f965216191de73af6cf947ef2ab8 +maturin==1.8.6 \ + --hash=sha256:0e0dc2e0bfaa2e1bd238e0236cf8a2b7e2250ccaa29c1aa8d0e61fa664b0289d \ + --hash=sha256:1bf4c743dd2b24448e82b8c96251597818956ddf848e1d16b59356512c7e58d8 \ + --hash=sha256:24f66624db69b895b134a8f1592efdf04cd223c9b3b65243ad32080477936d14 \ + --hash=sha256:4dd2e2f005ca63ac7ef0dddf2d65324ee480277a11544dcc4e7e436af68034dd \ + --hash=sha256:4ea89cf76048bc760e12b36b608fc3f5ef4f7359c0895e9afe737be34041d948 \ + --hash=sha256:5c0ff7ad43883920032b63c94c76dcdd5758710d7b72b68db69e7826c40534ac \ + --hash=sha256:62a65f70ebaadd6eb6083f5548413744f2ef12400445778e08d41d4facf15bbe \ + --hash=sha256:6bc9281b90cd37e2a7985f2e5d6e3d35a1d64cf6e4d04ce5ed25603d162995b9 \ + --hash=sha256:b0637604774e2c50ab48a0e9023fe2f071837ecbc817c04ec28e1cfcc25224c2 \ + --hash=sha256:b1e786ec9b5f7315c7e3fcc62b0715f9d99ffe477b06d0d62655a71e6a51a67b \ + --hash=sha256:bec5948c6475954c8089b17fae349966258756bb2ca05e54099e476a08070795 \ + --hash=sha256:ca30fdb158a24cf312f3e53072a6e987182c103fa613efea2d28b5f52707d04a \ + --hash=sha256:dade5edfaf508439ff6bbc7be4f207e04c0999c47d9ef7e1bae16258e76b1518 # via -r build-requirements.in -pycparser==2.22 \ +pycparser==2.22 ; platform_python_implementation != 'PyPy' \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -tomli==2.1.0 \ - --hash=sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8 \ - --hash=sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391 - # via maturin - -# The following packages are considered to be unsafe in a requirements file: -setuptools==73.0.1 \ - --hash=sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e \ - --hash=sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193 +setuptools==80.7.1 \ + --hash=sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009 \ + --hash=sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552 # via -r build-requirements.in +tomli==2.2.1 ; python_full_version < '3.11' \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via maturin diff --git a/.github/requirements/uv-requirements.in b/.github/requirements/uv-requirements.in new file mode 100644 index 000000000000..60cc5e6a1a7c --- /dev/null +++ b/.github/requirements/uv-requirements.in @@ -0,0 +1 @@ +uv diff --git a/.github/requirements/uv-requirements.txt b/.github/requirements/uv-requirements.txt index 6a799fcaa391..4947076cbb17 100644 --- a/.github/requirements/uv-requirements.txt +++ b/.github/requirements/uv-requirements.txt @@ -1,21 +1,22 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --universal -p 3.8 --generate-hashes - -uv==0.5.4 \ - --hash=sha256:05b45c7eefb178dcdab0d49cd642fb7487377d00727102a8d6d306cc034c0d83 \ - --hash=sha256:2118bb99cbc9787cb5e5cc4a507201e25a3fe88a9f389e8ffb84f242d96038c2 \ - --hash=sha256:30ce031e36c54d4ba791d743d992d0a4fd8d70480db781d30a2f6f5125f39194 \ - --hash=sha256:4432215deb8d5c1ccab17ee51cb80f5de1a20865ee02df47532f87442a3d6a58 \ - --hash=sha256:493aedc3c758bbaede83ecc8d5f7e6a9279ebec151c7f756aa9ea898c73f8ddb \ - --hash=sha256:69079e900bd26b0f65069ac6fa684c74662ed87121c076f2b1cbcf042539034c \ - --hash=sha256:8d7a4a3df943a7c16cd032ccbaab8ed21ff64f4cb090b3a0a15a8b7502ccd876 \ - --hash=sha256:928ed95fefe4e1338d0a7ad2f6b635de59e2ec92adaed4a267f7501a3b252263 \ - --hash=sha256:a79a0885df364b897da44aae308e6ed9cca3a189d455cf1c205bd6f7b03daafa \ - --hash=sha256:ca72e6a4c3c6b8b5605867e16a7f767f5c99b7f526de6bbb903c60eb44fd1e01 \ - --hash=sha256:cd7a5a3a36f975a7678f27849a2d49bafe7272143d938e9b6f3bf28392a3ba00 \ - --hash=sha256:dd2df2ba823e6684230ab4c581f2320be38d7f46de11ce21d2dbba631470d7b6 \ - --hash=sha256:df3cb58b7da91f4fc647d09c3e96006cd6c7bd424a81ce2308a58593c6887c39 \ - --hash=sha256:ed5659cde099f39995f4cb793fd939d2260b4a26e4e29412c91e7537f53d8d25 \ - --hash=sha256:f07e5e0df40a09154007da41b76932671333f9fecb0735c698b19da25aa08927 \ - --hash=sha256:f40c6c6c3a1b398b56d3a8b28f7b455ac1ce4cbb1469f8d35d3bbc804d83daa4 \ - --hash=sha256:f511faf719b797ef0f14688f1abe20b3fd126209cf58512354d1813249745119 \ - --hash=sha256:f806af0ee451a81099c449c4cff0e813056fdf7dd264f3d3a8fd321b17ff9efc +# uv pip compile --universal --python-version 3.8 --generate-hashes uv-requirements.in -o uv-requirements.txt +uv==0.7.4 \ + --hash=sha256:06f259352d741f447a1fa4bb34348e251e3c0ee2dd43c9da6fdec6ab3ef9c80f \ + --hash=sha256:0b4d60c9a350cc8f58097f550256d110c73d605857f2fefa6bb86c49992dec70 \ + --hash=sha256:0cc0eee98197f3cd77a4cbf3fd22cebdcea9e77d3f21e74698548ed008ef54a1 \ + --hash=sha256:22080e86f65d6ce2e7b349ccc31f36fd841bbc565c210edd839c13a967488ed8 \ + --hash=sha256:40b73b40d9bdc95e6f04472bd4264ab12a2a8f379df99329c9f670d6647715b2 \ + --hash=sha256:425a53513a89aefc3819adad7fc06c963220e700f997d86569b4be3ab00f0d5b \ + --hash=sha256:5f077ef9e857321e6c585e7c97b4310b10edbd21de709449bbef26f932093bb2 \ + --hash=sha256:62368eaf4ac1647bdbbf88fa503c24ccd1d41cde4345be9ecb003cb2dc468490 \ + --hash=sha256:6555b82afa27e6061ea12405027bf3f72b02da59c706e82fb1ce36710b1c431f \ + --hash=sha256:6a77cc279eda15e3ffe570b850a84bc574762aaf29e6aa04c897928ce8af11de \ + --hash=sha256:6a93abb21b67b52de8f9a720b6f650cdc20b3b664623f7ab4881185b8bc36eb7 \ + --hash=sha256:7e68a2412931f3d22d1bf115e3351baadb109a598f66ed97f3cc482382af595c \ + --hash=sha256:81babf4715a2475bf8eadf5fa85971cd12401d13c98df83975e842f8eaff2573 \ + --hash=sha256:9cd8efc3a42f88e88cdc8e69b3147b7cb39fac1fd933b465e1b07dc879506aae \ + --hash=sha256:a8e841406af442ae104b569b0f420c915da51c853de14e8feaf503c9faedecb4 \ + --hash=sha256:b2bfa5fb120d11d381ac69ab82dc67c9be7b10770243cc50a2739828dc230811 \ + --hash=sha256:c73ad2342c50c8ee970af52ef96643bbc6109f5e969ae92c867f3e6876c3c1e5 \ + --hash=sha256:f2f633b3a780db64b83dc472135154e2776e4e8a6583e3c2190aa30188a137b2 + # via -r uv-requirements.in diff --git a/.github/workflows/auto-close-stale.yml b/.github/workflows/auto-close-stale.yml index d982491e0352..23af18c95018 100644 --- a/.github/workflows/auto-close-stale.yml +++ b/.github/workflows/auto-close-stale.yml @@ -13,7 +13,7 @@ jobs: pull-requests: "write" steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: only-labels: waiting-on-reporter days-before-stale: 3 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 2a3f2357b7ef..1dd242699613 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -2,15 +2,14 @@ name: Benchmark on: pull_request: paths: - - '.github/workflows/benchmark.yml' - - 'src/**' - - 'tests/**' + - ".github/workflows/benchmark.yml" + - "src/**" + - "tests/**" workflow_dispatch: inputs: base_commit: description: The base commit to compare against - permissions: contents: read @@ -44,7 +43,7 @@ jobs: - name: Setup python id: setup-python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.11" @@ -63,4 +62,4 @@ jobs: run: .venv-pr/bin/pytest --benchmark-enable --benchmark-only ./cryptography-pr/tests/bench/ --benchmark-json=bench-pr.json --x509-limbo-root=x509-limbo/ - name: Compare results - run: python ./cryptography-pr/.github/compare_benchmarks.py bench-base.json bench-pr.json | tee -a $GITHUB_STEP_SUMMARY + run: python ./cryptography-pr/.github/bin/compare_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-awslc-bump.yml similarity index 63% rename from .github/workflows/boring-open-version-bump.yml rename to .github/workflows/boring-open-awslc-bump.yml index 2a5fac7d494d..9a20183c528d 100644 --- a/.github/workflows/boring-open-version-bump.yml +++ b/.github/workflows/boring-open-awslc-bump.yml @@ -1,4 +1,4 @@ -name: Bump BoringSSL and/or OpenSSL +name: Bump BoringSSL, OpenSSL, AWS-LC permissions: contents: read @@ -19,7 +19,7 @@ jobs: persist-credentials: true - id: check-sha-boring run: | - SHA=$(git ls-remote https://boringssl.googlesource.com/boringssl refs/heads/master | cut -f1) + SHA=$(git ls-remote https://boringssl.googlesource.com/boringssl refs/heads/main | 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 @@ -38,11 +38,23 @@ jobs: 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 + - id: check-tag-aws-lc + run: | + # Get the latest tag from AWS-LC repository + LATEST_TAG=$(git ls-remote --tags https://github.com/aws/aws-lc.git | grep -o 'refs/tags/v[0-9\.]*$' | sort -V | tail -1 | sed 's|refs/tags/||') + CURRENT_TAG=$(grep aws-lc .github/workflows/ci.yml | grep VERSION | grep -o 'v[0-9\.]*') + + if [ "$LATEST_TAG" != "$CURRENT_TAG" ]; then + echo "NEW_TAG=${LATEST_TAG}" >> $GITHUB_OUTPUT + echo "COMMIT_MSG<> $GITHUB_OUTPUT + echo -e "## AWS-LC\n[Tag: ${LATEST_TAG}](https://github.com/aws/aws-lc/releases/tag/${LATEST_TAG})\n\n[Diff](https://github.com/aws/aws-lc/compare/${CURRENT_TAG}...${LATEST_TAG}) between the previously used tag and the new tag." >> $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/Latest commit on the BoringSSL main branch.*/Latest commit on the BoringSSL main branch, as of ${CURRENT_DATE}./" .github/workflows/ci.yml sed -E -i "s/TYPE: \"boringssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"boringssl\", VERSION: \"${COMMIT_SHA}\"/" .github/workflows/ci.yml git status if: steps.check-sha-boring.outputs.COMMIT_SHA @@ -58,21 +70,32 @@ jobs: if: steps.check-sha-openssl.outputs.COMMIT_SHA env: COMMIT_SHA: ${{ steps.check-sha-openssl.outputs.COMMIT_SHA }} + - name: Update AWS-LC + run: | + set -xe + CURRENT_DATE=$(date "+%b %d, %Y") + sed -E -i "s/Latest tag of AWS-LC main branch, as of .*/Latest tag of AWS-LC main branch, as of ${CURRENT_DATE}./" .github/workflows/ci.yml + sed -E -i "s/TYPE: \"aws-lc\", VERSION: \"v[0-9\.]*\"/TYPE: \"aws-lc\", VERSION: \"${NEW_TAG}\"/" .github/workflows/ci.yml + git status + if: steps.check-tag-aws-lc.outputs.NEW_TAG + env: + NEW_TAG: ${{ steps.check-tag-aws-lc.outputs.NEW_TAG }} - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 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 + if: steps.check-sha-boring.outputs.COMMIT_SHA || steps.check-sha-openssl.outputs.COMMIT_SHA || steps.check-tag-aws-lc.outputs.NEW_TAG - name: Create Pull Request - uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: branch: "bump-openssl-boringssl" - commit-message: "Bump BoringSSL and/or OpenSSL in CI" - title: "Bump BoringSSL and/or OpenSSL in CI" + commit-message: "Bump BoringSSL, OpenSSL, AWS-LC in CI" + title: "Bump BoringSSL, OpenSSL, AWS-LC in CI" author: "pyca-boringbot[bot] " body: | ${{ steps.check-sha-boring.outputs.COMMIT_MSG }} ${{ steps.check-sha-openssl.outputs.COMMIT_MSG }} + ${{ steps.check-tag-aws-lc.outputs.COMMIT_MSG }} token: ${{ steps.generate-token.outputs.token }} - if: steps.check-sha-boring.outputs.COMMIT_SHA || steps.check-sha-openssl.outputs.COMMIT_SHA + if: steps.check-sha-boring.outputs.COMMIT_SHA || steps.check-sha-openssl.outputs.COMMIT_SHA || steps.check-tag-aws-lc.outputs.NEW_TAG diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36bfa53c512a..6dc66496ce43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,6 @@ on: branches: - main - '*.*.x' - tags: - - '*.*' - - '*.*.*' permissions: contents: read @@ -27,37 +24,39 @@ jobs: fail-fast: false matrix: PYTHON: - - {VERSION: "3.12", NOXSESSION: "flake"} - - {VERSION: "3.12", NOXSESSION: "rust"} - - {VERSION: "3.12", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3"}} - - {VERSION: "3.13", NOXSESSION: "tests"} + - {VERSION: "3.8", NOXSESSION: "flake"} + - {VERSION: "3.13", NOXSESSION: "flake"} + - {VERSION: "3.13", NOXSESSION: "rust"} + - {VERSION: "3.12", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.5.0"}} - {VERSION: "3.14-dev", NOXSESSION: "tests"} - {VERSION: "pypy-3.10", NOXSESSION: "tests-nocoverage"} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.0.15"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.7"}} - - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3"}} - - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.3.2"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} - - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.1.7"}} - - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.2.3"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.4.0"}} - - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "3.9.2"}} - - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "4.0.0"}} - - {VERSION: "3.12", NOXSESSION: "tests-randomorder"} - # Latest commit on the BoringSSL master branch, as of Nov 27, 2024. - - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "boringssl", VERSION: "fcef13a49852397a0d39c00be8d7bc2ba1ab6fb9"}} - # Latest commit on the OpenSSL master branch, as of Nov 26, 2024. - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "b9886a6f3483e0525596d3b3956416282038da82"}} + - {VERSION: "pypy-3.11", NOXSESSION: "tests-nocoverage"} + - {VERSION: "3.13", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.5.0", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} + - {VERSION: "3.13", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.5.0", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "0"}} + - {VERSION: "3.13", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.5.0", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} + - {VERSION: "3.13", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.5.0"}} + - {VERSION: "3.13", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.0.16"}} + - {VERSION: "3.13", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.3.3"}} + - {VERSION: "3.13", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.4.1"}} + - {VERSION: "3.13", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.5.0"}} + - {VERSION: "3.13", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "4.0.0"}} + - {VERSION: "3.13", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "4.1.0"}} + # Latest commit on the BoringSSL main branch, as of May 17, 2025. + - {VERSION: "3.13", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "boringssl", VERSION: "a934ee9e1fe4397e682f9f18b1f4f061d7400f9d"}} + # Latest tag of AWS-LC main branch, as of May 13, 2025. + - {VERSION: "3.13", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "aws-lc", VERSION: "v1.51.2"}} + # Latest commit on the OpenSSL master branch, as of May 17, 2025. + - {VERSION: "3.13", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "10bd6fa8ca93b4cf53f005f110c827ed923c89a4"}} # Builds with various Rust versions. Includes MSRV and next # potential future MSRV. - # - 1.70: crates.io sparse protocol by default # - 1.77: offset_of! in std (for pyo3) # - 1.80: LazyLock in std - - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "1.65.0"} - - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "beta"} - - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "nightly"} - - {VERSION: "3.12", NOXSESSION: "tests-rust-debug"} + # - 1.83: const context Option::unwrap() + - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "1.74.0"} + - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "1.80.0"} + - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "beta"} + - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "nightly"} + - {VERSION: "3.13", NOXSESSION: "tests-rust-debug"} timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -66,14 +65,14 @@ jobs: persist-credentials: false - name: Setup python id: setup-python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.PYTHON.VERSION }} cache: pip cache-dependency-path: ci-constraints-requirements.txt timeout-minutes: 3 - name: Setup rust - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 with: toolchain: ${{ matrix.PYTHON.RUST }} components: rustfmt,clippy @@ -98,7 +97,7 @@ jobs: CONFIG_FLAGS: ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }} if: matrix.PYTHON.OPENSSL - name: Load OpenSSL cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 id: ossl-cache timeout-minutes: 2 with: @@ -106,10 +105,10 @@ jobs: # 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 }}-14 + key: "${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.OPENSSL_HASH }}-${{ hashFiles('.github/bin/build_openssl.sh') }}-0" if: matrix.PYTHON.OPENSSL - name: Build custom OpenSSL/LibreSSL - run: .github/workflows/build_openssl.sh + run: .github/bin/build_openssl.sh env: TYPE: ${{ matrix.PYTHON.OPENSSL.TYPE }} VERSION: ${{ matrix.PYTHON.OPENSSL.VERSION }} @@ -121,7 +120,7 @@ jobs: echo "RUSTFLAGS=-Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib -Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib64" >> $GITHUB_ENV if: matrix.PYTHON.OPENSSL - run: sudo apt-get install -y bindgen - if: matrix.PYTHON.OPENSSL.TYPE == 'boringssl' + if: matrix.PYTHON.OPENSSL.TYPE == 'boringssl' || matrix.PYTHON.OPENSSL.TYPE == 'aws-lc' - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 @@ -137,7 +136,8 @@ jobs: nox -v --install-only env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} - CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + # Needed until https://github.com/PyO3/pyo3/issues/5093 + PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 - name: Tests run: | nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo ${{ matrix.PYTHON.NOXARGS }} @@ -145,7 +145,6 @@ jobs: 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) }} - uses: ./.github/actions/upload-coverage @@ -170,9 +169,13 @@ jobs: - {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: "centos-stream10", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "centos-stream10-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} + + - {IMAGE: "ubuntu-rolling:aarch64", NOXSESSION: "tests", RUNNER: "ubuntu-24.04-arm"} + - {IMAGE: "alpine:aarch64", NOXSESSION: "tests", RUNNER: "ubuntu-24.04-arm"} - - {IMAGE: "ubuntu-rolling:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} - - {IMAGE: "alpine:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} + - {IMAGE: "ubuntu-rolling:armv7l", NOXSESSION: "tests", RUNNER: "ubuntu-24.04-arm"} timeout-minutes: 15 env: RUSTUP_HOME: /root/.rustup @@ -208,7 +211,6 @@ jobs: - run: /venv/bin/python -m pip install -c ci-constraints-requirements.txt 'nox' 'nox[uv]; python_version >= "3.8"' 'tomli; python_version < "3.11"' - 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 }} @@ -248,7 +250,7 @@ jobs: key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.PYTHON.VERSION }} - name: Setup python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.PYTHON.VERSION }} cache: pip @@ -262,7 +264,7 @@ jobs: timeout-minutes: 2 uses: ./.github/actions/fetch-vectors - - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 + - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 # v9 with: repo: pyca/infra workflow: build-macos-openssl.yml @@ -279,7 +281,6 @@ jobs: nox -v --install-only env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} - CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} MACOSX_DEPLOYMENT_TARGET: "10.13" - name: Tests run: nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo @@ -308,7 +309,7 @@ jobs: persist-credentials: false - name: Setup python id: setup-python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} @@ -323,7 +324,7 @@ jobs: key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.WINDOWS.ARCH }}-${{ steps.setup-python.outputs.python-version }} - run: python -m pip install -c ci-constraints-requirements.txt "nox" "nox[uv]; python_version >= '3.8'" "tomli; python_version < '3.11'" - - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 + - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 # v9 with: repo: pyca/infra workflow: build-windows-openssl.yml @@ -345,13 +346,11 @@ jobs: 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 --x509-limbo-root=x509-limbo env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} COLUMNS: 80 - CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - uses: ./.github/actions/upload-coverage @@ -371,6 +370,7 @@ jobs: - certbot-josepy - mitmproxy - scapy + - sigstore PYTHON: - '3.12' name: "Downstream tests for ${{ matrix.DOWNSTREAM }}" @@ -384,7 +384,7 @@ jobs: uses: ./.github/actions/cache timeout-minutes: 2 - name: Setup python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.PYTHON }} cache: pip @@ -392,8 +392,6 @@ jobs: timeout-minutes: 3 - run: ./.github/downstream.d/${{ matrix.DOWNSTREAM }}.sh install - run: pip install . - env: - 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 @@ -430,9 +428,9 @@ jobs: jobs: ${{ toJSON(needs) }} - name: Setup python if: ${{ always() }} - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: - python-version: '3.12' + python-version: '3.13' cache: pip cache-dependency-path: ci-constraints-requirements.txt timeout-minutes: 3 @@ -440,7 +438,7 @@ jobs: if: ${{ always() }} - name: Download coverage data if: ${{ always() }} - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: coverage-data-* merge-multiple: true @@ -449,50 +447,21 @@ jobs: 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 + echo "## Coverage" >> $GITHUB_STEP_SUMMARY + python .github/bin/merge_rust_coverage.py *.lcov > 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 + echo "🚨 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: | - 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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: _html-report + name: _html-coverage-report path: htmlcov if-no-files-found: ignore if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} - - name: Upload rust HTML report. - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: _html-rust-report - path: rust-coverage - if-no-files-found: ignore - if: ${{ failure() && steps.combinerustcoverage.outcome == 'failure' }} diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml index 1faf3bcbc2db..a519d7a51cad 100644 --- a/.github/workflows/linkcheck.yml +++ b/.github/workflows/linkcheck.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: Setup python id: setup-python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: 3.11 - name: Cache rust and pip diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index cc2470ceb0ba..405713e2d35f 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -43,7 +43,7 @@ jobs: echo "PYPI_URL=https://test.pypi.org/legacy/" >> $GITHUB_ENV if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'testpypi' - - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 + - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 # v9 with: path: tmpdist/ run_id: ${{ github.event.inputs.run_id || github.event.workflow_run.id }} @@ -52,7 +52,7 @@ jobs: find tmpdist/ -type f -name 'cryptography*' -exec mv {} dist/ \; - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: repository-url: ${{ env.PYPI_URL }} skip-existing: true diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index 813a9c10e835..b0e16f0695f2 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -34,7 +34,7 @@ jobs: ref: ${{ github.event.inputs.version || github.ref }} persist-credentials: false - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.13" timeout-minutes: 3 @@ -44,11 +44,11 @@ jobs: run: uv build --build-constraint=$BUILD_REQUIREMENTS_PATH --require-hashes --sdist - name: Make sdist and wheel (vectors) run: uv build --build-constraint=$BUILD_REQUIREMENTS_PATH --require-hashes vectors/ - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: "cryptography-sdist" path: dist/cryptography* - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: "vectors-sdist-wheel" path: vectors/dist/cryptography* @@ -66,30 +66,47 @@ jobs: matrix: PYTHON: - { VERSION: "cp311-cp311", ABI_VERSION: 'py37' } - - { VERSION: "cp311-cp311", ABI_VERSION: 'py39' } + - { VERSION: "cp311-cp311", ABI_VERSION: 'py311' } - { VERSION: "pp310-pypy310_pp73" } + - { VERSION: "pp311-pypy311_pp73" } MANYLINUX: - { 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: "manylinux_2_34_x86_64", CONTAINER: "cryptography-manylinux_2_34:x86_64", RUNNER: "ubuntu-latest"} - { NAME: "musllinux_1_2_x86_64", CONTAINER: "cryptography-musllinux_1_2: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: "manylinux_2_34_aarch64", CONTAINER: "cryptography-manylinux_2_34:aarch64", RUNNER: [self-hosted, Linux, ARM64]} - - { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: "ubuntu-24.04-arm" } + - { NAME: "manylinux_2_28_aarch64", CONTAINER: "cryptography-manylinux_2_28:aarch64", RUNNER: "ubuntu-24.04-arm" } + - { NAME: "manylinux_2_34_aarch64", CONTAINER: "cryptography-manylinux_2_34:aarch64", RUNNER: "ubuntu-24.04-arm" } + - { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: "ubuntu-24.04-arm" } + + - { NAME: "manylinux_2_31_armv7l", CONTAINER: "cryptography-manylinux_2_31:armv7l", RUNNER: "ubuntu-24.04-arm" } exclude: # There are no readily available musllinux PyPy distributions - PYTHON: { VERSION: "pp310-pypy310_pp73" } MANYLINUX: { NAME: "musllinux_1_2_x86_64", CONTAINER: "cryptography-musllinux_1_2:x86_64", RUNNER: "ubuntu-latest"} - PYTHON: { VERSION: "pp310-pypy310_pp73" } - MANYLINUX: { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + MANYLINUX: { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: "ubuntu-24.04-arm" } + - PYTHON: { VERSION: "pp311-pypy311_pp73" } + MANYLINUX: { NAME: "musllinux_1_2_x86_64", CONTAINER: "cryptography-musllinux_1_2:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp311-pypy311_pp73" } + MANYLINUX: { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: "ubuntu-24.04-arm" } # We also don't build pypy wheels for anything except the latest manylinux - PYTHON: { VERSION: "pp310-pypy310_pp73" } MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} - PYTHON: { VERSION: "pp310-pypy310_pp73" } - MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64]} + MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: "ubuntu-24.04-arm" } + - PYTHON: { VERSION: "pp311-pypy311_pp73" } + MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp311-pypy311_pp73" } + MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: "ubuntu-24.04-arm" } + + # No PyPy on armv7l either + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "manylinux_2_31_armv7l", CONTAINER: "cryptography-manylinux_2_31:armv7l", RUNNER: "ubuntu-24.04-arm" } + - PYTHON: { VERSION: "pp311-pypy311_pp73" } + MANYLINUX: { NAME: "manylinux_2_31_armv7l", CONTAINER: "cryptography-manylinux_2_31:armv7l", RUNNER: "ubuntu-24.04-arm" } name: "${{ matrix.PYTHON.VERSION }} for ${{ matrix.MANYLINUX.NAME }}" steps: - name: Ridiculous-er workaround for static node20 @@ -114,7 +131,7 @@ jobs: ${{ env.BUILD_REQUIREMENTS_PATH }} sparse-checkout-cone-mode: false - - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: cryptography-sdist - run: mkdir tmpwheelhouse @@ -146,7 +163,7 @@ jobs: - run: | echo "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'))" | uv run - - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.MANYLINUX.NAME }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION }}" path: wheelhouse/ @@ -171,7 +188,7 @@ jobs: # build against _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' - VERSION: '3.11' - ABI_VERSION: 'py39' + ABI_VERSION: 'py311' # 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' @@ -187,6 +204,11 @@ jobs: DEPLOYMENT_TARGET: '10.13' _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' ARCHFLAGS: '-arch x86_64' + - VERSION: 'pypy-3.11' + BIN_PATH: 'pypy3' + DEPLOYMENT_TARGET: '10.13' + _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: - name: Get build-requirements.txt from repository @@ -201,17 +223,17 @@ jobs: sparse-checkout-cone-mode: false - name: Setup python run: | - curl "$PYTHON_DOWNLOAD_URL" -o python.pkg + curl --max-time 30 --retry 5 "$PYTHON_DOWNLOAD_URL" -o python.pkg sudo installer -pkg python.pkg -target / env: PYTHON_DOWNLOAD_URL: ${{ matrix.PYTHON.DOWNLOAD_URL }} if: contains(matrix.PYTHON.VERSION, 'pypy') == false - name: Setup pypy - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.PYTHON.VERSION }} if: contains(matrix.PYTHON.VERSION, 'pypy') - - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 + - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 # v9 with: repo: pyca/infra workflow: build-macos-openssl.yml @@ -220,12 +242,12 @@ jobs: name: openssl-macos-universal2 path: "../openssl-macos-universal2/" github_token: ${{ secrets.GITHUB_TOKEN }} - - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + - uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 with: toolchain: stable # Add the arm64 target in addition to the native arch (x86_64) target: aarch64-apple-darwin - - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: cryptography-sdist @@ -256,7 +278,7 @@ jobs: - run: | echo "CRYPTOGRAPHY_WHEEL_NAME=$(basename $(ls wheelhouse/cryptography*.whl))" >> $GITHUB_ENV - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: "${{ env.CRYPTOGRAPHY_WHEEL_NAME }}" path: wheelhouse/ @@ -272,12 +294,15 @@ jobs: - {ARCH: 'x64', WINDOWS: 'win64', RUST_TRIPLE: 'x86_64-pc-windows-msvc'} PYTHON: - {VERSION: "3.11", "ABI_VERSION": "py37"} - - {VERSION: "3.11", "ABI_VERSION": "py39"} + - {VERSION: "3.11", "ABI_VERSION": "py311"} - {VERSION: "pypy-3.10"} + - {VERSION: "pypy-3.11"} 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.10"} + - WINDOWS: {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} + PYTHON: {VERSION: "pypy-3.11"} name: "${{ matrix.PYTHON.VERSION }} ${{ matrix.WINDOWS.WINDOWS }} ${{ matrix.PYTHON.ABI_VERSION }}" steps: - name: Get build-requirements.txt from repository @@ -291,21 +316,21 @@ jobs: ${{ env.UV_REQUIREMENTS_PATH }} sparse-checkout-cone-mode: false - - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: cryptography-sdist - name: Setup python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} - - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + - uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 with: toolchain: stable target: ${{ matrix.WINDOWS.RUST_TRIPLE }} - - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 + - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 # v9 with: repo: pyca/infra workflow: build-windows-openssl.yml @@ -339,7 +364,7 @@ jobs: run: | echo "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'))" | uv run - - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION }}" path: wheelhouse\ diff --git a/.github/workflows/x509-limbo-version-bump.yml b/.github/workflows/x509-limbo-version-bump.yml index 94c7ec8926f7..d9dff8b9e117 100644 --- a/.github/workflows/x509-limbo-version-bump.yml +++ b/.github/workflows/x509-limbo-version-bump.yml @@ -64,7 +64,7 @@ jobs: private_key: ${{ secrets.BORINGBOT_PRIVATE_KEY }} if: steps.check-sha-x509-limbo.outputs.COMMIT_SHA || steps.check-sha-wycheproof.outputs.COMMIT_SHA - name: Create Pull Request - uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: branch: "bump-vectors" commit-message: "Bump x509-limbo and/or wycheproof in CI" diff --git a/.readthedocs.yml b/.readthedocs.yml index 7ef04db29181..f97891f9c3c9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -6,15 +6,16 @@ sphinx: # The config file overrides the UI settings: # https://github.com/pyca/cryptography/issues/5863#issuecomment-817828152 builder: dirhtml + configuration: docs/conf.py formats: - pdf build: - os: "ubuntu-22.04" + os: "ubuntu-24.04" tools: - python: "3.11" - rust: "1.70" + python: "3.13" + rust: "latest" python: install: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2cc482613bd8..28d97a84860d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,143 @@ Changelog ========= +.. _v45-0-3: + +45.0.3 - 2025-05-25 +~~~~~~~~~~~~~~~~~~~ + +* Fixed decrypting PKCS#8 files encrypted with long salts (this impacts keys + encrypted by Bouncy Castle). +* Fixed decrypting PKCS#8 files encrypted with DES-CBC-MD5. While wildly + insecure, this remains prevalent. + +.. _v45-0-2: + +45.0.2 - 2025-05-17 +~~~~~~~~~~~~~~~~~~~ + +* Fixed using ``mypy`` with ``cryptography`` on older versions of Python. + +.. _v45-0-1: + +45.0.1 - 2025-05-17 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.5.0. + +.. _v45-0-0: + +45.0.0 - 2025-05-17 (YANKED) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Support for Python 3.7 is deprecated and will be removed in the next + ``cryptography`` release. +* Updated the minimum supported Rust version (MSRV) to 1.74.0, from 1.65.0. +* Added support for serialization of PKCS#12 Java truststores in + :func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_java_truststore` +* Added :meth:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id.derive_phc_encoded` and + :meth:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id.verify_phc_encoded` methods + to support password hashing in the PHC string format +* Added support for PKCS7 decryption and encryption using AES-256 as the + content algorithm, in addition to AES-128. +* **BACKWARDS INCOMPATIBLE:** Made SSH private key loading more consistent with + other private key loading: + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key` + now raises a ``TypeError`` if the key is unencrypted but a password is + provided (previously no exception was raised), and raises a ``TypeError`` if + the key is encrypted but no password is provided (previously a ``ValueError`` + was raised). +* Added ``__copy__`` to the + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, and + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey` + abstract base classes. +* We significantly refactored how private key loading ( + :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key` + and + :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`) + works. This is intended to be backwards compatible for all well-formed keys, + therefore if you discover a key that now raises an exception, please file a + bug with instructions for reproducing. +* Added ``unsafe_skip_rsa_key_validation`` keyword-argument to + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key`. +* Added :class:`~cryptography.hazmat.primitives.hashes.XOFHash` to support + repeated :meth:`~cryptography.hazmat.primitives.hashes.XOFHash.squeeze` + operations on extendable output functions. +* Added + :meth:`~cryptography.x509.ocsp.OCSPResponseBuilder.add_response_by_hash` + method to allow creating OCSP responses using certificate hash values rather + than full certificates. +* Extended the :mod:`X.509 path validation ` API to + support user-configured extension policies via the + :meth:`PolicyBuilder.extension_policies ` method. +* Deprecated the ``subject``, ``verification_time`` and ``max_chain_depth`` + properties on :class:`~cryptography.x509.verification.ClientVerifier` and + :class:`~cryptography.x509.verification.ServerVerifier` in favor of a new ``policy`` property. + These properties will be removed in the next release of ``cryptography``. +* **BACKWARDS INCOMPATIBLE:** The + :meth:`VerifiedClient.subject ` + property can now be `None` since a custom extension policy may allow certificates + without a Subject Alternative Name extension. +* Changed the behavior when the OpenSSL 3 legacy provider fails to load. + Instead of raising an exception, a warning is now emitted. The + ``CRYPTOGRAPHY_OPENSSL_NO_LEGACY`` environment variable can still be used to + disable the legacy provider at runtime. +* Added support for the ``CRYPTOGRAPHY_BUILD_OPENSSL_NO_LEGACY`` environment + variable during build time, which prevents the library from ever attempting + to load the legacy provider. +* Added support for the :class:`~cryptography.x509.PrivateKeyUsagePeriod` X.509 extension. + This extension defines the period during which the private key corresponding + to the certificate's public key may be used. +* Added support for compiling against `aws-lc`_. +* Parsing X.509 structures now more strictly enforces that ``Name`` structures + do not have malformed ASN.1. +* We now publish ``py311`` wheels that utilize the faster ``pyo3::buffer::PyBuffer`` + interface, resulting in significantly improved performance for operations + involving small buffers. +* Added :func:`~cryptography.hazmat.primitives.serialization.ssh_key_fingerprint` + for computing fingerprints of SSH public keys. +* Added support for deterministic ECDSA signing via the new keyword-only argument + ``ecdsa_deterministic`` in :meth:`~cryptography.x509.CertificateBuilder.sign`, + :meth:`~cryptography.x509.CertificateRevocationListBuilder.sign` + and :meth:`~cryptography.x509.CertificateSigningRequestBuilder.sign`. + +.. _v44-0-3: + +44.0.3 - 2025-05-02 +~~~~~~~~~~~~~~~~~~~ + +* Fixed compilation when using LibreSSL 4.1.0. + +.. _v44-0-2: + +44.0.2 - 2025-03-01 +~~~~~~~~~~~~~~~~~~~ + +* We now build wheels for PyPy 3.11. + +.. _v44-0-1: + +44.0.1 - 2025-02-11 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.4.1. +* We now build ``armv7l`` ``manylinux`` wheels and publish them to PyPI. +* We now build ``manylinux_2_34`` wheels and publish them to PyPI. + .. _v44-0-0: 44.0.0 - 2024-11-27 @@ -25,7 +162,7 @@ Changelog when using OpenSSL 3.2.0+. * Added support for the :class:`~cryptography.x509.Admissions` certificate extension. * Added basic support for PKCS7 decryption (including S/MIME 3.2) via - :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_der`, + :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_der`, :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_pem`, and :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_smime`. @@ -2556,3 +2693,4 @@ Changelog .. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic .. _`main`: https://github.com/pyca/cryptography/ .. _`cffi`: https://cffi.readthedocs.io/ +.. _`aws-lc`: https://github.com/aws/aws-lc diff --git a/Cargo.lock b/Cargo.lock index 32aebbdfad24..d378f76c127c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "asn1" -version = "0.20.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8b84b4ea1de2bf1dcd2a759737ddb328fb6695b2a95eb7e44fed67e3406f32" +checksum = "2d9c3502a6f1b50a2c69b97b71638a81ad3b21b9874604880401b9b2b0bf758f" dependencies = [ "asn1_derive", "itoa", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "asn1_derive" -version = "0.20.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a200809d0138620b3dba989f1d08d0620e76248bc1e62a2ec1b2df5eb1ee08ad" +checksum = "1766ebcb519d8dd186d60dfa912571edcaa2c1f995e2e56643a261a87df69a61" dependencies = [ "proc-macro2", "quote", @@ -37,15 +37,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "cc" -version = "1.2.1" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "shlex", ] @@ -65,6 +65,13 @@ dependencies = [ "pyo3", ] +[[package]] +name = "cryptography-crypto" +version = "0.1.0" +dependencies = [ + "openssl", +] + [[package]] name = "cryptography-keepalive" version = "0.1.0" @@ -78,6 +85,7 @@ version = "0.1.0" dependencies = [ "asn1", "cfg-if", + "cryptography-crypto", "cryptography-x509", "openssl", "openssl-sys", @@ -99,8 +107,10 @@ name = "cryptography-rust" version = "0.1.0" dependencies = [ "asn1", + "base64", "cfg-if", "cryptography-cffi", + "cryptography-crypto", "cryptography-keepalive", "cryptography-key-parsing", "cryptography-openssl", @@ -112,6 +122,7 @@ dependencies = [ "openssl-sys", "pem", "pyo3", + "pyo3-build-config", "self_cell", ] @@ -156,21 +167,21 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indoc" -version = "2.0.5" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "libc" -version = "0.2.166" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "memoffset" @@ -183,15 +194,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags", "cfg-if", @@ -215,9 +226,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", @@ -227,41 +238,40 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64", ] [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.23.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54b3d09cbdd1f8c20650b28e7b09e338881482f4aa908a5f61a00c98fba2690" +checksum = "f239d656363bcee73afef85277f1b281e8ac6212a1d42aa90e55b90ed43c47a4" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -275,9 +285,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.23.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3015cf985888fe66cfb63ce0e321c603706cd541b7aec7ddd35c281390af45d8" +checksum = "755ea671a1c34044fa165247aaf6f419ca39caa6003aee791a0df2713d8f1b6d" dependencies = [ "once_cell", "target-lexicon", @@ -285,9 +295,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.23.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fca7cd8fd809b5ac4eefb89c1f98f7a7651d3739dfb341ca6980090f554c270" +checksum = "fc95a2e67091e44791d4ea300ff744be5293f394f1bafd9f78c080814d35956e" dependencies = [ "libc", "pyo3-build-config", @@ -295,9 +305,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.23.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e657fa5379a79151b6ff5328d9216a84f55dc93b17b08e7c3609a969b73aa0" +checksum = "a179641d1b93920829a62f15e87c0ed791b6c8db2271ba0fd7c2686090510214" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -307,9 +317,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.23.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295548d5ffd95fd1981d2d3cf4458831b21d60af046b729b6fd143b0ba7aee2f" +checksum = "9dff85ebcaab8c441b0e3f7ae40a6963ecea8a9f5e74f647e33fcf5ec9a1e89e" dependencies = [ "heck", "proc-macro2", @@ -320,18 +330,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "self_cell" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "shlex" @@ -341,9 +351,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.89" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -352,21 +362,21 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unindent" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "vcpkg" diff --git a/Cargo.toml b/Cargo.toml index 26ecfa4ed6c4..04adc669f97b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "src/rust/", "src/rust/cryptography-cffi", + "src/rust/cryptography-crypto", "src/rust/cryptography-keepalive", "src/rust/cryptography-key-parsing", "src/rust/cryptography-openssl", @@ -16,11 +17,15 @@ authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV -rust-version = "1.65.0" +rust-version = "1.74.0" +license = "Apache-2.0 OR BSD-3-Clause" [workspace.dependencies] -asn1 = { version = "0.20.0", default-features = false } -pyo3 = { version = "0.23.2", features = ["abi3"] } +asn1 = { version = "0.21.3", default-features = false } +pyo3 = { version = "0.25", features = ["abi3"] } +pyo3-build-config = { version = "0.25" } +openssl = "0.10.72" +openssl-sys = "0.9.108" [profile.release] overflow-checks = true diff --git a/ci-constraints-requirements.txt b/ci-constraints-requirements.txt index 3331ce04c01c..52f678a76e5e 100644 --- a/ci-constraints-requirements.txt +++ b/ci-constraints-requirements.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --universal -p 3.7 --extra=docs --extra=docstest --extra=pep8test --extra=test --extra=test-randomorder --extra=nox --extra=sdist --unsafe-package=cffi --unsafe-package=pycparser --unsafe-package=setuptools --unsafe-package=cryptography-vectors pyproject.toml +# uv pip compile --universal --python-version 3.7 --all-extras --unsafe-package=cffi --unsafe-package=pycparser --unsafe-package=setuptools --unsafe-package=cryptography-vectors --unsafe-package=bcrypt pyproject.toml -o ci-constraints-requirements.txt alabaster==0.7.13 ; python_full_version < '3.9' # via sphinx alabaster==0.7.16 ; python_full_version == '3.9.*' @@ -8,11 +8,13 @@ alabaster==1.0.0 ; python_full_version >= '3.10' # via sphinx argcomplete==3.1.2 ; python_full_version < '3.8' # via nox -argcomplete==3.5.1 ; python_full_version >= '3.8' +argcomplete==3.6.2 ; python_full_version >= '3.8' + # via nox +attrs==25.3.0 ; python_full_version >= '3.8' # via nox babel==2.14.0 ; python_full_version < '3.8' # via sphinx -babel==2.16.0 ; python_full_version >= '3.8' +babel==2.17.0 ; python_full_version >= '3.8' # via sphinx bleach==6.0.0 ; python_full_version < '3.8' # via readme-renderer @@ -22,17 +24,19 @@ build==1.2.2.post1 ; python_full_version >= '3.8' # via # cryptography (pyproject.toml) # check-sdist -certifi==2024.8.30 +certifi==2025.4.26 # via # cryptography (pyproject.toml) # requests -charset-normalizer==3.4.0 +charset-normalizer==3.4.2 # via requests check-sdist==1.2.0 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) -click==8.1.7 +click==8.1.8 ; python_full_version < '3.10' + # via cryptography (pyproject.toml) +click==8.2.0 ; python_full_version >= '3.10' # via cryptography (pyproject.toml) -colorama==0.4.6 ; (platform_system != 'Windows' and sys_platform == 'win32') or platform_system == 'Windows' or os_name == 'nt' +colorama==0.4.6 ; os_name == 'nt' or sys_platform == 'win32' # via # build # click @@ -45,8 +49,10 @@ coverage==7.2.7 ; python_full_version < '3.8' # via pytest-cov coverage==7.6.1 ; python_full_version == '3.8.*' # via pytest-cov -coverage==7.6.8 ; python_full_version >= '3.9' +coverage==7.8.0 ; python_full_version >= '3.9' # via pytest-cov +dependency-groups==1.3.1 ; python_full_version >= '3.8' + # via nox distlib==0.3.9 # via virtualenv docutils==0.19 ; python_full_version < '3.8' @@ -63,7 +69,7 @@ docutils==0.21.2 ; python_full_version >= '3.9' # readme-renderer # sphinx # sphinx-rtd-theme -exceptiongroup==1.2.2 ; python_full_version < '3.11' +exceptiongroup==1.3.0 ; python_full_version < '3.11' # via pytest execnet==2.0.2 ; python_full_version < '3.8' # via pytest-xdist @@ -71,7 +77,9 @@ execnet==2.1.1 ; python_full_version >= '3.8' # via pytest-xdist filelock==3.12.2 ; python_full_version < '3.8' # via virtualenv -filelock==3.16.1 ; python_full_version >= '3.8' +filelock==3.16.1 ; python_full_version == '3.8.*' + # via virtualenv +filelock==3.18.0 ; python_full_version >= '3.9' # via virtualenv idna==3.10 # via requests @@ -89,16 +97,23 @@ importlib-metadata==6.7.0 ; python_full_version < '3.8' # sphinx # sphinxcontrib-spelling # virtualenv -importlib-metadata==8.5.0 ; python_full_version >= '3.8' and python_full_version < '3.10.2' +importlib-metadata==8.5.0 ; python_full_version == '3.8.*' + # via + # build + # pytest-randomly + # sphinx +importlib-metadata==8.7.0 ; python_full_version >= '3.9' and python_full_version < '3.10.2' # via # build # pytest-randomly # sphinx importlib-resources==6.4.5 ; python_full_version == '3.8.*' # via check-sdist -iniconfig==2.0.0 +iniconfig==2.0.0 ; python_full_version < '3.8' + # via pytest +iniconfig==2.1.0 ; python_full_version >= '3.8' # via pytest -jinja2==3.1.4 +jinja2==3.1.6 # via sphinx markupsafe==2.1.5 ; python_full_version < '3.9' # via jinja2 @@ -106,15 +121,19 @@ markupsafe==3.0.2 ; python_full_version >= '3.9' # via jinja2 mypy==1.4.1 ; python_full_version < '3.8' # via cryptography (pyproject.toml) -mypy==1.13.0 ; python_full_version >= '3.8' +mypy==1.14.1 ; python_full_version == '3.8.*' # via cryptography (pyproject.toml) -mypy-extensions==1.0.0 +mypy==1.15.0 ; python_full_version >= '3.9' + # via cryptography (pyproject.toml) +mypy-extensions==1.0.0 ; python_full_version < '3.8' + # via mypy +mypy-extensions==1.1.0 ; python_full_version >= '3.8' # via mypy -nh3==0.2.18 ; python_full_version >= '3.8' +nh3==0.2.21 ; python_full_version >= '3.8' # via readme-renderer nox==2024.4.15 ; python_full_version < '3.8' # via cryptography (pyproject.toml) -nox==2024.10.9 ; python_full_version >= '3.8' +nox==2025.5.1 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) packaging==24.0 ; python_full_version < '3.8' # via @@ -122,9 +141,10 @@ packaging==24.0 ; python_full_version < '3.8' # nox # pytest # sphinx -packaging==24.2 ; python_full_version >= '3.8' +packaging==25.0 ; python_full_version >= '3.8' # via # build + # dependency-groups # nox # pytest # sphinx @@ -132,11 +152,15 @@ pathspec==0.12.1 ; python_full_version >= '3.8' # via check-sdist platformdirs==4.0.0 ; python_full_version < '3.8' # via virtualenv -platformdirs==4.3.6 ; python_full_version >= '3.8' +platformdirs==4.3.6 ; python_full_version == '3.8.*' + # via virtualenv +platformdirs==4.3.8 ; python_full_version >= '3.9' # via virtualenv pluggy==1.2.0 ; python_full_version < '3.8' # via pytest -pluggy==1.5.0 ; python_full_version >= '3.8' +pluggy==1.5.0 ; python_full_version == '3.8.*' + # via pytest +pluggy==1.6.0 ; python_full_version >= '3.9' # via pytest pretend==1.0.9 # via cryptography (pyproject.toml) @@ -150,7 +174,7 @@ pygments==2.17.2 ; python_full_version < '3.8' # via # readme-renderer # sphinx -pygments==2.18.0 ; python_full_version >= '3.8' +pygments==2.19.1 ; python_full_version >= '3.8' # via # readme-renderer # sphinx @@ -163,7 +187,7 @@ pytest==7.4.4 ; python_full_version < '3.8' # pytest-cov # pytest-randomly # pytest-xdist -pytest==8.3.3 ; python_full_version >= '3.8' +pytest==8.3.5 ; python_full_version >= '3.8' # via # cryptography (pyproject.toml) # pytest-benchmark @@ -178,7 +202,7 @@ pytest-cov==4.1.0 ; python_full_version < '3.8' # via cryptography (pyproject.toml) pytest-cov==5.0.0 ; python_full_version == '3.8.*' # via cryptography (pyproject.toml) -pytest-cov==6.0.0 ; python_full_version >= '3.9' +pytest-cov==6.1.1 ; python_full_version >= '3.9' # via cryptography (pyproject.toml) pytest-randomly==3.12.0 ; python_full_version < '3.8' # via cryptography (pyproject.toml) @@ -190,7 +214,7 @@ pytest-xdist==3.5.0 ; python_full_version < '3.8' # via cryptography (pyproject.toml) pytest-xdist==3.6.1 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) -pytz==2024.2 ; python_full_version < '3.9' +pytz==2025.2 ; python_full_version < '3.9' # via babel readme-renderer==37.3 ; python_full_version < '3.8' # via cryptography (pyproject.toml) @@ -201,12 +225,16 @@ readme-renderer==44.0 ; python_full_version >= '3.9' requests==2.31.0 ; python_full_version < '3.8' # via sphinx requests==2.32.3 ; python_full_version >= '3.8' + # via + # sphinx + # sphinxcontrib-spelling +roman-numerals-py==3.1.0 ; python_full_version >= '3.11' # via sphinx -ruff==0.8.0 +ruff==0.11.10 # via cryptography (pyproject.toml) -six==1.16.0 ; python_full_version < '3.8' +six==1.17.0 ; python_full_version < '3.8' # via bleach -snowballstemmer==2.2.0 +snowballstemmer==3.0.1 # via sphinx sphinx==5.3.0 ; python_full_version < '3.8' # via @@ -215,21 +243,33 @@ sphinx==5.3.0 ; python_full_version < '3.8' sphinx==7.1.2 ; python_full_version == '3.8.*' # via # cryptography (pyproject.toml) + # sphinx-inline-tabs # sphinx-rtd-theme # sphinxcontrib-jquery # sphinxcontrib-spelling sphinx==7.4.7 ; python_full_version == '3.9.*' # via # cryptography (pyproject.toml) + # sphinx-inline-tabs + # sphinx-rtd-theme + # sphinxcontrib-jquery + # sphinxcontrib-spelling +sphinx==8.1.3 ; python_full_version == '3.10.*' + # via + # cryptography (pyproject.toml) + # sphinx-inline-tabs # sphinx-rtd-theme # sphinxcontrib-jquery # sphinxcontrib-spelling -sphinx==8.1.3 ; python_full_version >= '3.10' +sphinx==8.2.3 ; python_full_version >= '3.11' # via # cryptography (pyproject.toml) + # sphinx-inline-tabs # sphinx-rtd-theme # sphinxcontrib-jquery # sphinxcontrib-spelling +sphinx-inline-tabs==2023.4.21 ; python_full_version >= '3.8' + # via cryptography (pyproject.toml) sphinx-rtd-theme==3.0.2 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) sphinxcontrib-applehelp==1.0.2 ; python_full_version < '3.8' @@ -260,7 +300,9 @@ sphinxcontrib-serializinghtml==1.1.5 ; python_full_version < '3.9' # via sphinx sphinxcontrib-serializinghtml==2.0.0 ; python_full_version >= '3.9' # via sphinx -sphinxcontrib-spelling==8.0.0 +sphinxcontrib-spelling==8.0.0 ; python_full_version < '3.10' + # via cryptography (pyproject.toml) +sphinxcontrib-spelling==8.0.1 ; python_full_version >= '3.10' # via cryptography (pyproject.toml) tomli==2.0.1 ; python_full_version < '3.8' # via @@ -269,11 +311,12 @@ tomli==2.0.1 ; python_full_version < '3.8' # mypy # nox # pytest -tomli==2.1.0 ; python_full_version >= '3.8' and python_full_version <= '3.11' +tomli==2.2.1 ; python_full_version >= '3.8' and python_full_version <= '3.11' # via # build # check-sdist # coverage + # dependency-groups # mypy # nox # pytest @@ -282,21 +325,26 @@ typed-ast==1.5.5 ; python_full_version < '3.8' # via mypy typing-extensions==4.7.1 ; python_full_version < '3.8' # via + # exceptiongroup # importlib-metadata # mypy # nox # platformdirs -typing-extensions==4.12.2 ; python_full_version >= '3.8' - # via mypy +typing-extensions==4.13.2 ; python_full_version >= '3.8' + # via + # exceptiongroup + # mypy urllib3==2.0.7 ; python_full_version < '3.8' # via requests -urllib3==2.2.3 ; python_full_version >= '3.8' +urllib3==2.2.3 ; python_full_version == '3.8.*' + # via requests +urllib3==2.4.0 ; python_full_version >= '3.9' # via requests -uv==0.5.4 ; python_full_version >= '3.8' +uv==0.7.4 ; python_full_version >= '3.8' # via nox virtualenv==20.26.6 ; python_full_version < '3.8' # via nox -virtualenv==20.28.0 ; python_full_version >= '3.8' +virtualenv==20.31.2 ; python_full_version >= '3.8' # via nox webencodings==0.5.1 ; python_full_version < '3.8' # via bleach @@ -313,3 +361,4 @@ zipp==3.21.0 ; python_full_version >= '3.9' and python_full_version < '3.10.2' # cffi # pycparser # cryptography-vectors +# bcrypt diff --git a/docs/conf.py b/docs/conf.py index 1a00ac736683..6de55ed3bf87 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -47,6 +47,7 @@ "sphinx.ext.linkcode", "cryptography-docs", "sphinx_rtd_theme", + "sphinx_inline_tabs", ] if spelling is not None: @@ -71,7 +72,7 @@ # General information about the project. project = "Cryptography" -copyright = "2013-2024, Individual Contributors" +copyright = "2013-2025, 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 diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index b5097cbb1b77..6c5a7e738853 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -47,6 +47,26 @@ Asymmetric ciphers * RSA OAEP with custom label from the `BoringSSL evp tests`_. * Ed448 test vectors from :rfc:`8032`. * Deterministic ECDSA (:rfc:`6979`) from `OpenSSL's RFC 6979 test vectors`_. +* ``asymmetric/PKCS8/rsa-40bitrc2.pem`` a PKCS8 encoded RSA key from GnuTLS + encrypted with ``pbeWithSHAAnd40BitRC2-CBC``. The password is ``baz``. +* ``asymmetric/PKCS8/rsa-rc2-cbc.pem`` a PKCS8 encoded RSA key from GnuTLS + encrypted with ``RC2-CBC``. The password is ``Red Hat Enterprise Linux 7.4``. +* ``asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha224.pem`` a PKCS8 + encoded RSA key from Mbed-TLS using ``sha224`` as the PRF for PBKDF2. + The password is ``PolarSSLTest``. +* ``asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha384.pem`` a PKCS8 + encoded RSA key from Mbed-TLS using ``sha384`` as the PRF for PBKDF2. + The password is ``PolarSSLTest``. +* ``asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha512.pem`` a PKCS8 + encoded RSA key from Mbed-TLS using ``sha512`` as the PRF for PBKDF2. + The password is ``PolarSSLTest``. +* ``asymmetric/PKCS8/rsa-aes-192-cbc.pem`` a PKCS8 encoded RSA key from Mbed-TLS + encrypted with ``AES-192-CBC``. The password is ``PolarSSLTest``. +* ``asymmetric/PKCS8/ed25519-scrypt.pem`` a PKCS8 encoded Ed25519 key from + RustCrypto using scrypt as the KDF. The password is ``hunter42``. +* ``asymmetric/PKCS8/rsa-rc2-cbc-effective-key-length.pem`` a PKCS8 encoded key + encrypted with ``RC2-CBC`` with the ``effectiveKeyLength`` parameter set to + 258. This is an invalid key. Custom asymmetric vectors @@ -121,6 +141,42 @@ Custom asymmetric vectors * ``asymmetric/EC/explicit_parameters_wap_wsg_idm_ecid_wtls11_private_key.pem`` - Contains an EC private key with over the ``wap-wsg-idm-ecid-wtls11`` curve, encoded with explicit parameters. +* ``asymmetric/EC/secp256k1-explicit-no-seed.pem`` - An unencrypted PKCS8 private + key with the ``secp256k1`` curve explicitly encoded (``secp256k1`` does not have + a seed). +* ``asymmetric/EC/secp256k1-pub-explicit-no-seed.pem`` - A public key with the + ``secp256k1`` curve explicitly encoded. This is the public key for the + private key ``asymmetric/EC/secp256k1-explicit-no-seed.pem``. +* ``asymmetric/EC/secp256r1-explicit-no-seed.pem`` - An unencrypted PKCS8 private + key with the ``secp256r1`` curve explicitly encoded and with the seed omitted. +* ``asymmetric/EC/secp256r1-pub-explicit-no-seed.pem`` - A public key with the + ``secp256r1`` curve explicitly encoded and with the seed omitted. This is the + public key for the private key ``asymmetric/EC/secp256r1-explicit-no-seed.pem``. +* ``asymmetric/EC/secp256r1-explicit-seed.pem`` - An unencrypted PKCS8 private + key with the ``secp256r1`` curve explicitly encoded. +* ``asymmetric/EC/secp256r1-pub-explicit-seed.pem`` - A public key with the + ``secp256r1`` curve explicitly encoded. This is the public key for the + private key ``asymmetric/EC/secp256r1-explicit-seed.pem``. +* ``asymmetric/EC/secp384r1-explicit-no-seed.pem`` - An unencrypted PKCS8 private + key with the ``secp384r1`` curve explicitly encoded and with the seed omitted. +* ``asymmetric/EC/secp384r1-pub-explicit-no-seed.pem`` - A public key with the + ``secp384r1`` curve explicitly encoded and with the seed omitted. This is the + public key for the private key ``asymmetric/EC/secp384r1-explicit-no-seed.pem``. +* ``asymmetric/EC/secp384r1-explicit-seed.pem`` - An unencrypted PKCS8 private + key with the ``secp384r1`` curve explicitly encoded. +* ``asymmetric/EC/secp384r1-pub-explicit-seed.pem`` - A public key with the + ``secp384r1`` curve explicitly encoded. This is the public key for the + private key ``asymmetric/EC/secp384r1-explicit-seed.pem``. +* ``asymmetric/EC/secp521r1-explicit-no-seed.pem`` - An unencrypted PKCS8 private + key with the ``secp521r1`` curve explicitly encoded and with the seed omitted. +* ``asymmetric/EC/secp521r1-pub-explicit-no-seed.pem`` - A public key with the + ``secp521r1`` curve explicitly encoded and with the seed omitted. This is the + public key for the private key ``asymmetric/EC/secp521r1-explicit-no-seed.pem``. +* ``asymmetric/EC/secp521r1-explicit-seed.pem`` - An unencrypted PKCS8 private + key with the ``secp521r1`` curve explicitly encoded. +* ``asymmetric/EC/secp521r1-pub-explicit-seed.pem`` - A public key with the + ``secp521r1`` curve explicitly encoded. This is the public key for the + private key ``asymmetric/EC/secp521r1-explicit-seed.pem``. * ``asymmetric/EC/secp128r1_private_key.pem`` - Contains an EC private key on the curve ``secp128r1``. * ``asymmetric/EC/sect163k1-spki.pem`` - Contains an EC SPKI on the curve @@ -187,7 +243,53 @@ Custom asymmetric vectors a ring and the generator of the multiplicative subgroup is actually nilpotent with low degree. Taken from BoringSSL (see ``TEST(DSATest, NilpotentGenerator)``). - +* ``asymmetric/PKCS8/ec-invalid-private-scalar.pem`` - Contains a PKCS8 encoded + PEM with a ``secp256r1`` OID and an invalid (very large) private scalar. +* ``asymmetric/PKCS8/invalid-version.der`` - Contains a PKCS8 encoded DER with + an invalid version field. +* ``asymmetric/PKCS8/unknown-oid.der`` - Contains a PKCS8 encoded DER with an + unknown OID. +* ``asymmetric/Traditional_OpenSSL_Serialization/rsa-wrong-version.pem`` - An + RSA key, encoded as a "traditional" ``RSA PRIVATE KEY`` PEM block, with an + invalid version number. +* ``asymmetric/Traditional_OpenSSL_Serialization/dsa-wrong-version.pem`` - A + DSA key, encoded as a "traditional" ``DSA PRIVATE KEY`` PEM block, with an + invalid version number. +* ``asymmetric/PKCS8/ec-inconsistent-curve.pem`` - A PKCS8 encoded EC key where + the the curve OID in the parameters does not match the curve OID in the key. +* ``asymmetric/PKCS8/ec-inconsistent-curve2.pem`` - A PKCS8 encoded EC key + where the the curve OID in the parameters does not match the curve OID in + the key (the OIDs are reversed from ``ec-inconsistent-curve.pem``). +* ``asymmetric/EC/ec-missing-curve.pem`` - A PKCS#1 encoded EC key where the + curve OID is missing. +* ``asymmetric/PKCS8/ec-consistent-curve.pem`` - A PKCS8 encoded EC key where + the the curve OID in the parameters is the same as the curve OID in the key + (encoding the curve OID twice is duplicative, as the inner curve is + optional). +* ``asymmetric/PKCS8/ec-invalid-version.pem`` - A PKCS8 encoded EC key with an + invalid elliptic curve version field. +* ``asymmetric/PKCS8/enc-rsa-3des.pem`` - A PKCS8 encoded RSA key encrypted + with 3DES, with the password "password". +* ``asymmetric/PKCS8/enc-unknown-algorithm.pem`` - A PKCS8 encoded key with an + unknown encryption algorithm. +* ``asymmetric/PKCS8/enc-unknown-pbkdf2-prf.pem`` - A PKCS8 encoded key + encrypted using PBKDF2 with an unknown PRF. +* ``asymmetric/PKCS8/enc-unknown-kdf.pem`` - A PKCS8 encoded key encrypted + using an unknown KDF. +* ``asymmetric/Traditional_OpenSSL_Serialization/key1-no-dek-info.pem`` - An + RSA key in an encrypted PEM with no ``DEK-Info`` header. +* ``asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-dek-info.pem`` + - An RSA key in an encrypted PEM with a malformed ``DEK-Info`` header (no + comma). +* ``asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-iv.pem`` - An + RSA key in an encrypted PEM with a malformed IV (not valid hex). +* ``asymmetric/Traditional_OpenSSL_Serialization/key1-short-iv.pem`` - An + RSA key in an encrypted PEM with an IV that's too short (less than 8 bytes). +* ``asymmetric/PKCS8/rsa-pbewithmd5anddescbc.pem`` - A PKCS8 encoded RSA key + encrypted using the ``pbeWithMD5AndDES-CBC`` algorithm with the password + ``hunter2``. +* ``asymmetric/PKCS8/rsa-pbe-3des-long-salt.pem`` - A PKCS8 encoded RSA key + encrypted with a 20 byte salt with the password ``password``. Key exchange ~~~~~~~~~~~~ @@ -560,6 +662,14 @@ Custom X.509 Vectors * ``admissions_extension_authority_not_provided.pem`` - A certificate containing the ``Admissions`` extension with no admissions and no admission authority, signed by ``x509/custom/ca/rsa_ca.pem`` CA. +* ``no_sans.pem`` - Leaf certificate issued by ``x509/custom/ca/rsa_ca.pem`` + with no SAN extension. +* ``private_key_usage_period_both_dates.pem`` - A certificate containing + PrivateKeyUsagePeriod with both ``notBefore`` and ``notAfter`` fields set. +* ``private_key_usage_period_only_not_before.pem`` - A certificate containing + PrivateKeyUsagePeriod with only ``notBefore`` field set. +* ``private_key_usage_period_only_not_after.pem`` - A certificate containing + PrivateKeyUsagePeriod with only ``notAfter`` field set. Custom X.509 Request Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -687,6 +797,8 @@ Custom X.509 Certificate Revocation List Vectors * ``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. +* ``crl_issuer_invalid_printable_string.der`` - A CRL where the ``issuer`` + field contains an invalid ``PRINTABLE STRING`` value. X.509 OCSP Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~ @@ -866,6 +978,10 @@ Custom PKCS12 Test Vectors certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``☹`` and ``ï``, respectively, encrypted via AES 256 CBC with the password ``cryptography``. +* ``pkcs12/java-truststore.p12`` - A PKCS12 file containing two certs + (``x509/custom/dsa_selfsigned_ca.pem`` and ``x509/letsencryptx3.pem``) with + the first having a friendly name of `cert1`. Both have Java truststore + attributes with ANY_EXTENDED_KEY_USAGE. Custom PKCS7 Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -876,14 +992,14 @@ Custom PKCS7 Test Vectors * ``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. -* ``pkcs7/enveloped-aes-256-cbc.pem`` - A PEM encoded PKCS7 file with - enveloped data, with content encrypted using AES-256-CBC, under the public key of - ``x509/custom/ca/rsa_ca.pem``. -* ``pkcs7/enveloped-rsa-oaep.pem``- A PEM encoded PKCS7 file with - enveloped data, with key encrypted using RSA-OAEP, under the public key of +* ``pkcs7/enveloped-triple-des.pem`` - A PEM encoded PKCS7 file with + enveloped data, with content encrypted using DES EDE3 CBC (also called + Triple DES), under the public key of ``x509/custom/ca/rsa_ca.pem``. +* ``pkcs7/enveloped-rsa-oaep.pem``- A PEM encoded PKCS7 file with + enveloped data, with key encrypted using RSA-OAEP, under the public key of ``x509/custom/ca/rsa_ca.pem``. -* ``pkcs7/enveloped-no-content.der``- A DER encoded PKCS7 file with - enveloped data, without encrypted content, with key encrypted under the +* ``pkcs7/enveloped-no-content.der``- A DER encoded PKCS7 file with + enveloped data, without encrypted content, with key encrypted under the public key of ``x509/custom/ca/rsa_ca.pem``. Custom OpenSSH Test Vectors diff --git a/docs/fernet.rst b/docs/fernet.rst index 80e06db9341a..c257e75e3c17 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -10,7 +10,8 @@ has support for implementing key rotation via :class:`MultiFernet`. .. class:: Fernet(key) - This class provides both encryption and decryption facilities. + This class provides both encryption and decryption facilities. This class + exhibits :term:`thread safety`. .. doctest:: @@ -221,7 +222,8 @@ Using passwords with Fernet It is possible to use passwords with Fernet. To do this, you need to run the password through a key derivation function such as -:class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`, bcrypt or +:class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`, +:class:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id` or :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt`. .. doctest:: @@ -237,7 +239,7 @@ password through a key derivation function such as ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=480000, + ... iterations=1_200_000, ... ) >>> key = base64.urlsafe_b64encode(kdf.derive(password)) >>> f = Fernet(key) @@ -251,8 +253,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 480,000 iterations, which is what `Django -recommends as of December 2022`_. +tolerate. A good default is at least 1,200,000 iterations, which is what `Django +recommends as of January 2025`_. Implementation -------------- @@ -280,5 +282,5 @@ 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 +.. _`Django recommends as of January 2025`: 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 3c2272a4da7c..b500d71bc741 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -107,6 +107,15 @@ Glossary 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. + + thread safety + All immutable objects in ``cryptography`` are safe to use in + multi-threaded environments. This means they can be shared across + threads without requiring additional synchronization. Mutable objects, + such as hash contexts, can also be shared, but concurrent modification + may lead to exceptions or incorrect results. When working with + cryptographic operations in a multi-threaded application, ensure that + any mutable objects are used in a thread-safe manner. .. _`hardware security module`: https://en.wikipedia.org/wiki/Hardware_security_module .. _`idna`: https://pypi.org/project/idna/ diff --git a/docs/hazmat/decrepit/ciphers.rst b/docs/hazmat/decrepit/ciphers.rst index 8ae0178df2f1..5a3b2a50f396 100644 --- a/docs/hazmat/decrepit/ciphers.rst +++ b/docs/hazmat/decrepit/ciphers.rst @@ -4,7 +4,7 @@ Decrepit Symmetric algorithms ============================= -.. module:: cryptography.hazmat.decrepit.ciphers +.. module:: cryptography.hazmat.decrepit.ciphers.algorithms This module contains decrepit symmetric encryption algorithms. These are algorithms that should not be used unless necessary for backwards diff --git a/docs/hazmat/primitives/aead.rst b/docs/hazmat/primitives/aead.rst index 9c80c3a62049..e30bc6c5fdb2 100644 --- a/docs/hazmat/primitives/aead.rst +++ b/docs/hazmat/primitives/aead.rst @@ -90,6 +90,13 @@ also support providing integrity for associated data which is not encrypted. .. versionadded:: 2.0 + .. note:: + + This class only supports 128-bit tags. If you need tag truncation + (which is generally **not recommended**) you should use the + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` class + with :class:`~cryptography.hazmat.primitives.ciphers.Cipher`. + The AES-GCM construction is composed of the :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` block cipher utilizing Galois Counter Mode (GCM). diff --git a/docs/hazmat/primitives/asymmetric/cloudhsm.rst b/docs/hazmat/primitives/asymmetric/cloudhsm.rst new file mode 100644 index 000000000000..8934133a228a --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/cloudhsm.rst @@ -0,0 +1,169 @@ +.. hazmat:: + +Cloud KMS and HSM Asymmetric Keys +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. testsetup:: + + """ + We need to have this exist so the doctest below allows us to + test that we're satisfying the base class requirements. + """ + class Response: + def __init__(self, signature): + self.signature = signature + + class SomeCloudClient: + def __init__(self, creds): + pass + + def sign(self, key_id, algorithm, message): + return Response(b"\x00" * (self.key_size(key_id) // 8)) + + def key_size(self, key_id): + return 2048 + +``cryptography`` provides a set of abstract base classes for asymmetric keys +that can be used to integrate with cloud key management services, HSMs, and other ways of managing keys that are not in-memory. +A minimal example with a hypothetical cloud key management service for an RSA +key is provided below, but this works for all asymmetric types. You must provide +all methods of the base class, but many methods can be stubs with no implementation +if you only need a subset of functionality. + +.. doctest:: + + >>> import typing + >>> from cryptography.hazmat.primitives.asymmetric import rsa, utils + >>> from cryptography.hazmat.primitives import hashes, serialization + >>> from cryptography.hazmat.primitives.asymmetric.padding import AsymmetricPadding, PKCS1v15 + >>> + >>> class CloudRSAPrivateKey(rsa.RSAPrivateKey): + ... def __init__(self, creds, key_id): + ... self._creds = creds + ... self._cloud_client = SomeCloudClient(creds) + ... self._key_id = key_id + ... + ... def sign( + ... self, + ... data: bytes, + ... padding: AsymmetricPadding, + ... algorithm: typing.Union[utils.Prehashed, hashes.HashAlgorithm], + ... ) -> bytes: + ... """ + ... Signs data using the cloud KMS. You'll need to define a mapping + ... between the way your cloud provider represents padding and algorithms + ... and the way cryptography represents them. + ... """ + ... + ... # Hash the data if necessary + ... if not isinstance(algorithm, utils.Prehashed): + ... h = hashes.Hash(algorithm) + ... h.update(data) + ... digest = h.finalize() + ... hash_alg = algorithm + ... else: + ... digest = data + ... hash_alg = algorithm._algorithm + ... # Map cryptography padding/algorithm to KMS signing algorithm + ... kms_algorithm = self._map_to_kms_algorithm(padding, hash_alg) + ... + ... # Call KMS API to sign the digest + ... response = self._cloud_client.sign( + ... key_id=self._key_id, + ... algorithm=kms_algorithm, + ... message=digest, + ... ) + ... + ... return response.signature + ... + ... def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: + ... raise NotImplementedError() + ... + ... def _map_to_kms_algorithm( + ... self, + ... padding: AsymmetricPadding, + ... algorithm: hashes.HashAlgorithm + ... ) -> bytes: + ... """ + ... Maps the cryptography padding and algorithm to the corresponding KMS signing algorithm. + ... This is specific to your implementation. + ... """ + ... if isinstance(padding, PKCS1v15) and isinstance(algorithm, hashes.SHA256): + ... return b"RSA_PKCS1_V1_5_SHA_256" + ... else: + ... raise NotImplementedError() + ... + ... @property + ... def key_size(self) -> int: + ... return self._cloud_client.key_size(self._key_id) + ... + ... def public_key(self) -> rsa.RSAPublicKey: + ... raise NotImplementedError() + ... + ... def private_numbers(self) -> rsa.RSAPrivateNumbers: + ... """ + ... This method typically can't be implemented for cloud KMS keys + ... as the private key material is not accessible. + ... """ + ... raise NotImplementedError() + ... + ... def private_bytes( + ... self, + ... encoding: serialization.Encoding, + ... format: serialization.PrivateFormat, + ... encryption_algorithm: serialization.KeySerializationEncryption, + ... ) -> bytes: + ... """ + ... This method typically can't be implemented for cloud KMS keys + ... as the private key material is not accessible. + ... """ + ... raise NotImplementedError() + ... + ... def __copy__(self) -> "CloudRSAPrivateKey": + ... return self + ... + >>> cloud_private_key = CloudRSAPrivateKey("creds", "key_id") + >>> sig = cloud_private_key.sign(b"message", PKCS1v15(), hashes.SHA256()) + >>> isinstance(sig, bytes) + True + +This key can then be used with other parts of ``cryptography``, such as the X.509 APIs. +In the example below we assume that we are using our cloud private key to sign +a leaf certificate (not self-signed). + +.. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.x509.oid import NameOID + >>> import datetime + >>> one_day = datetime.timedelta(1, 0, 0) + >>> leaf_private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... ) + >>> leaf_public_key = leaf_private_key.public_key() + >>> builder = x509.CertificateBuilder() + >>> builder = builder.subject_name(x509.Name([ + ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'), + ... ])) + >>> builder = builder.issuer_name(x509.Name([ + ... x509.NameAttribute(NameOID.COMMON_NAME, 'My Cloud CA'), + ... ])) + >>> builder = builder.not_valid_before(datetime.datetime.today() - one_day) + >>> builder = builder.not_valid_after(datetime.datetime.today() + (one_day * 30)) + >>> builder = builder.serial_number(x509.random_serial_number()) + >>> builder = builder.public_key(leaf_public_key) + >>> builder = builder.add_extension( + ... x509.SubjectAlternativeName( + ... [x509.DNSName('cryptography.io')] + ... ), + ... critical=False + ... ) + >>> builder = builder.add_extension( + ... x509.BasicConstraints(ca=False, path_length=None), critical=True, + ... ) + >>> certificate = builder.sign( + ... private_key=cloud_private_key, algorithm=hashes.SHA256(), + ... ) + >>> isinstance(certificate, x509.Certificate) + True diff --git a/docs/hazmat/primitives/asymmetric/dh.rst b/docs/hazmat/primitives/asymmetric/dh.rst index 361aa6dff82b..012685abfb62 100644 --- a/docs/hazmat/primitives/asymmetric/dh.rst +++ b/docs/hazmat/primitives/asymmetric/dh.rst @@ -314,6 +314,8 @@ Numbers :returns: A new instance of :class:`DHParameters`. + :raises ValueError: If the parameters are invalid. + .. class:: DHPrivateNumbers(x, public_numbers) .. versionadded:: 0.8 diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index a22a64be5c41..3a15278a8d54 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -500,6 +500,14 @@ Key Interfaces Size (in :term:`bits`) of a secret scalar for the curve (as generated by :func:`generate_private_key`). + .. attribute:: group_order + + .. versionadded:: 45 + + :type: int + + The order of the curve's group. + .. class:: EllipticCurveSignatureAlgorithm diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst index 136dd324b57e..b0dd09aff24f 100644 --- a/docs/hazmat/primitives/asymmetric/index.rst +++ b/docs/hazmat/primitives/asymmetric/index.rst @@ -33,6 +33,7 @@ private key is able to decrypt it. dsa serialization utils + cloudhsm .. _`proof of identity`: https://en.wikipedia.org/wiki/Public-key_infrastructure diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index d712b2226459..54190ae2dd38 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -373,8 +373,8 @@ Padding .. warning:: - Our implementation of PKCS1 v1.5 decryption is not constant time. See - :doc:`/limitations` for details. + When used with OpenSSL 3.2 or older, 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) diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index 6d1130cbc729..fb49c7d14fb7 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -426,6 +426,37 @@ DSA keys look almost identical but begin with ``ssh-dss`` rather than :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that is not supported. + +.. function:: ssh_key_fingerprint(key, hash_algorithm) + + .. versionadded:: 45.0.0 + + Computes the fingerprint of an SSH public key. The fingerprint is the raw + bytes of the hash, depending on your use you may need to encode the data as + base64 or hex. + + :param key: The public key to compute the fingerprint for. + :type key: One of :data:`SSHPublicKeyTypes` + + :param hash_algorithm: The hash algorithm to use, either ``MD5()`` or + ``SHA256()``. + + :return: The key fingerprint. + :rtype: bytes + + .. code-block:: pycon + + >>> from cryptography.hazmat.primitives.serialization import load_ssh_public_key, ssh_key_fingerprint + >>> key_data = b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhVNvf1vigXfagQXKjdKN5zEF12KWVMVdDrU3sVLhgd user@example.com" + >>> public_key = load_ssh_public_key(key_data) + >>> md5_fingerprint = ssh_key_fingerprint(public_key, hashes.MD5()) + >>> md5_fingerprint + b'\x95\xf6\xc0\xe3so\xaen\xcc\x98\xbb\xf4\xd8BJ\x15' + >>> sha256_fingerprint = ssh_key_fingerprint(public_key, hashes.SHA256()) + >>> sha256_fingerprint + b'R\x0f*!\x99f9\x9a\xcd\x98[\xe8-&\xbah\xa6x\x96\x87\xb3\xf9\xe0\x9b\xb1,\xcc\xbdt\xd4\xc3\xb7' + + OpenSSH Private Key ~~~~~~~~~~~~~~~~~~~ @@ -456,7 +487,7 @@ An example ECDSA key in OpenSSH format:: :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`. -.. function:: load_ssh_private_key(data, password) +.. function:: load_ssh_private_key(data, password, *, unsafe_skip_rsa_key_validation=False) .. versionadded:: 3.0 @@ -474,6 +505,19 @@ 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 unsafe_skip_rsa_key_validation: + + .. versionadded:: 45.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 :data:`SSHPrivateKeyTypes` depending on the contents of ``data``. @@ -933,12 +977,54 @@ file suffix. ... b"friendlyname", key, cert, None, encryption ... ) -.. class:: PKCS12Certificate +.. function:: serialize_java_truststore(pkcs12_certs, encryption_algorithm) + + .. versionadded:: 45.0.0 + + .. warning:: + + 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 containing provided certificates. Java expects an + internal flag to denote truststore usage, which this function adds. + + :param certs: A set of certificates to also include in the structure. + :type certs: + + A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` + instances. + + :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 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, pkcs12 + >>> cert = x509.load_pem_x509_certificate(ca_cert) + >>> p12 = pkcs12.serialize_java_truststore( + ... [pkcs12.PKCS12Certificate(cert, b"friendlyname")], BestAvailableEncryption(b"password") + ... ) + +.. class:: PKCS12Certificate(cert, friendly_name=None) .. versionadded:: 36.0.0 Represents additional data provided for a certificate in a PKCS12 file. + :param cert: The certificate to associate with the additional data. + :type cert: :class:`~cryptography.x509.Certificate` + + :param friendly_name: An optional friendly name for the certificate. + :type friendly_name: bytes or None + .. attribute:: certificate A :class:`~cryptography.x509.Certificate` instance. @@ -1268,10 +1354,13 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.serialization import pkcs7 + >>> from cryptography.hazmat.primitives.ciphers import algorithms >>> cert = x509.load_pem_x509_certificate(ca_cert_rsa) >>> options = [pkcs7.PKCS7Options.Text] >>> pkcs7.PKCS7EnvelopeBuilder().set_data( ... b"data to encrypt" + ... ).set_content_encryption_algorithm( + ... algorithms.AES128 ... ).add_recipient( ... cert ... ).encrypt( @@ -1284,6 +1373,14 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :param data: The data to be encrypted. :type data: :term:`bytes-like` + .. method:: set_content_encryption_algorithm(content_encryption_algorithm) + + :param content_encryption_algorithm: the content encryption algorithm to use. + Only AES is supported, with a key size of 128 or 256 bits. + :type content_encryption_algorithm: + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES128` + or :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES256` + .. method:: add_recipient(certificate) Add a recipient for the message. Recipients will be able to use their private keys @@ -1350,10 +1447,10 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, associated with the certificate provided. Only private RSA keys are supported. :param options: A list of - :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For this operation only :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported. - + :returns bytes: The decrypted message. :raises ValueError: If the recipient certificate does not match any of the encrypted keys in the @@ -1363,10 +1460,10 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, with another algorithm than RSA with PKCS1 v1.5 padding. :raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with - another algorithm than AES-128-CBC. + another algorithm than AES (with key sizes 128 and 256), with CBC mode. :raises ValueError: If the PKCS7 data does not contain encrypted content. - + :raises ValueError: If the PKCS7 data is not of the enveloped data type. .. function:: pkcs7_decrypt_pem(data, certificate, private_key, options) @@ -1405,10 +1502,10 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, associated with the certificate provided. Only private RSA keys are supported. :param options: A list of - :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For this operation only :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported. - + :returns bytes: The decrypted message. :raises ValueError: If the PEM data does not have the PKCS7 tag. @@ -1420,10 +1517,10 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, with another algorithm than RSA with PKCS1 v1.5 padding. :raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with - another algorithm than AES-128-CBC. + another algorithm than AES (with key sizes 128 and 256), with CBC mode. :raises ValueError: If the PKCS7 data does not contain encrypted content. - + :raises ValueError: If the PKCS7 data is not of the enveloped data type. .. function:: pkcs7_decrypt_smime(data, certificate, private_key, options) @@ -1463,10 +1560,10 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, associated with the certificate provided. Only private RSA keys are supported. :param options: A list of - :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For this operation only :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported. - + :returns bytes: The decrypted message. :raises ValueError: If the S/MIME data is not one of the correct content types. @@ -1478,10 +1575,10 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, with another algorithm than RSA with PKCS1 v1.5 padding. :raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with - another algorithm than AES-128-CBC. + another algorithm than AES (with key sizes 128 and 256), with CBC mode. :raises ValueError: If the PKCS7 data does not contain encrypted content. - + :raises ValueError: If the PKCS7 data is not of the enveloped data type. @@ -1494,7 +1591,7 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, .. attribute:: Text For signing, the text option adds ``text/plain`` headers to an S/MIME message when - serializing to + serializing to :attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME`. This option is disallowed with ``DER`` serialization. For envelope creation, it adds ``text/plain`` headers to the encrypted content, regardless diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst index c1c29cad27c6..1bf8ccf81d22 100644 --- a/docs/hazmat/primitives/cryptographic-hashes.rst +++ b/docs/hazmat/primitives/cryptographic-hashes.rst @@ -35,7 +35,7 @@ Message digests (Hashing) :param algorithm: A :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` - instance such as those described in + instance such as those described :ref:`below `. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the @@ -44,14 +44,14 @@ Message digests (Hashing) .. method:: update(data) :param bytes data: The bytes to be hashed. - :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`.finalize`. :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: copy() Copy this :class:`Hash` instance, usually so that you may call - :meth:`finalize` to get an intermediate digest value while we continue - to call :meth:`update` on the original instance. + :meth:`.finalize` to get an intermediate digest value while we continue + to call :meth:`.update` on the original instance. :return: A new instance of :class:`Hash` that can be updated and finalized independently of the original instance. @@ -62,11 +62,70 @@ Message digests (Hashing) Finalize the current context and return the message digest as bytes. After ``finalize`` has been called this object can no longer be used - and :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise an + and :meth:`.update`, :meth:`.copy`, and :meth:`.finalize` will raise an :class:`~cryptography.exceptions.AlreadyFinalized` exception. :return bytes: The message digest as bytes. +.. class:: XOFHash(algorithm) + + An extendable output function (XOF) is a cryptographic hash function that + can produce an arbitrary amount of output for a given input. The output + can be obtained by repeatedly calling :meth:`.squeeze` with the desired + length. + + .. doctest:: + + >>> import sys + >>> from cryptography.hazmat.primitives import hashes + >>> digest = hashes.XOFHash(hashes.SHAKE128(digest_size=sys.maxsize)) + >>> digest.update(b"abc") + >>> digest.update(b"123") + >>> digest.squeeze(16) + b'\x18\xd6\xbd\xeb5u\x83[@\xfa%/\xdc\xca\x9f\x1b' + >>> digest.squeeze(16) + b'\xc2\xeb\x12\x05\xc3\xf9Bu\x88\xe0\xda\x80FvAV' + + :param algorithm: A + :class:`~cryptography.hazmat.primitives.hashes.ExtendableOutputFunction` + instance such as those described + :ref:`below `. The ``digest_size`` + passed is the maximum number of bytes that can be squeezed from the XOF + when using this class. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``algorithm`` is unsupported. + + .. method:: update(data) + + :param bytes data: The bytes to be hashed. + :raises cryptography.exceptions.AlreadyFinalized: If already squeezed. + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. + + .. method:: copy() + + Copy this :class:`XOFHash` instance, usually so that you may call + :meth:`.squeeze` to get an intermediate digest value while we continue + to call :meth:`.update` on the original instance. + + :return: A new instance of :class:`XOFHash` that can be updated + and squeezed independently of the original instance. If + you copy an instance that has already been squeezed, the copy will + also be in a squeezed state. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`.squeeze`. + + .. method:: squeeze(length) + + :param int length: The number of bytes to squeeze. + + After :meth:`.squeeze` has been called this object can no longer be updated + and :meth:`.update`, will raise an + :class:`~cryptography.exceptions.AlreadyFinalized` exception. + + :return bytes: ``length`` bytes of output from the extendable output function (XOF). + :raises ValueError: If the maximum number of bytes that can be squeezed + has been exceeded. + .. _cryptographic-hash-algorithms: @@ -176,36 +235,6 @@ than SHA-2 so at this time most users should choose SHA-2. SHA3/512 is a cryptographic hash function from the SHA-3 family and is standardized by NIST. It produces a 512-bit message digest. -.. class:: SHAKE128(digest_size) - - .. versionadded:: 2.5 - - SHAKE128 is an extendable output function (XOF) based on the same core - permutations as SHA3. It allows the caller to obtain an arbitrarily long - digest length. Longer lengths, however, do not increase security or - collision resistance and lengths shorter than 128 bit (16 bytes) will - decrease it. - - :param int digest_size: The length of output desired. Must be greater than - zero. - - :raises ValueError: If the ``digest_size`` is invalid. - -.. class:: SHAKE256(digest_size) - - .. versionadded:: 2.5 - - SHAKE256 is an extendable output function (XOF) based on the same core - permutations as SHA3. It allows the caller to obtain an arbitrarily long - digest length. Longer lengths, however, do not increase security or - collision resistance and lengths shorter than 256 bit (32 bytes) will - decrease it. - - :param int digest_size: The length of output desired. Must be greater than - zero. - - :raises ValueError: If the ``digest_size`` is invalid. - SHA-1 ~~~~~ @@ -250,6 +279,52 @@ SM3 `draft-sca-cfrg-sm3`_.) This hash should be used for compatibility purposes where required and is not otherwise recommended for use. +.. _extendable-output-functions: + +Extendable Output Functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: SHAKE128(digest_size) + + .. versionadded:: 2.5 + + SHAKE128 is an extendable output function (XOF) based on the same core + permutations as SHA3. It allows the caller to obtain an arbitrarily long + digest length. Longer lengths, however, do not increase security or + collision resistance and lengths shorter than 128 bit (16 bytes) will + decrease it. + + This class can be used with :class:`Hash` or :class:`XOFHash`. When used + in :class:`Hash` :meth:`~cryptography.hazmat.primitives.hashes.Hash.finalize` + will return ``digest_size`` bytes. When used in :class:`XOFHash` this + defines the total number of bytes allowed to be squeezed. + + :param int digest_size: The length of output desired. Must be greater than + zero. + + :raises ValueError: If the ``digest_size`` is invalid. + +.. class:: SHAKE256(digest_size) + + .. versionadded:: 2.5 + + SHAKE256 is an extendable output function (XOF) based on the same core + permutations as SHA3. It allows the caller to obtain an arbitrarily long + digest length. Longer lengths, however, do not increase security or + collision resistance and lengths shorter than 256 bit (32 bytes) will + decrease it. + + This class can be used with :class:`Hash` or :class:`XOFHash`. When used + in :class:`Hash` :meth:`~cryptography.hazmat.primitives.hashes.Hash.finalize` + will return ``digest_size`` bytes. When used in :class:`XOFHash` this + defines the total number of bytes allowed to be squeezed. + + :param int digest_size: The length of output desired. Must be greater than + zero. + + :raises ValueError: If the ``digest_size`` is invalid. + + Interfaces ~~~~~~~~~~ @@ -269,6 +344,10 @@ Interfaces The size of the resulting digest in bytes. +.. class:: ExtendableOutputFunction + + An interface applied to hashes that act as extendable output functions (XOFs). + The currently supported XOFs are :class:`SHAKE128` and :class:`SHAKE256`. .. class:: HashContext diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 113b1bf7f87d..ced988855f84 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -130,6 +130,67 @@ Argon2id checking whether the password a user provides matches the stored derived key. + .. method:: derive_phc_encoded(key_material) + + .. versionadded:: 45.0.0 + + :param key_material: The input key material. + :type key_material: :term:`bytes-like` + :return str: A PHC-formatted string containing the parameters, salt, and derived key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + any method is + called more than + once. + + This method generates and returns a new key from the supplied password, + formatting the result as a string according to the Password Hashing + Competition (PHC) format. The returned string includes the algorithm, + all parameters, the salt, and the derived key in a standardized format: + ``$argon2id$v=19$m=,t=,p=$$`` + + This format is suitable for password storage and is compatible with other + Argon2id implementations that support the PHC format. + + .. classmethod:: verify_phc_encoded(key_material, phc_encoded, secret=None) + + .. versionadded:: 45.0.0 + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive_phc_encoded`. + :param str phc_encoded: A PHC-formatted string as returned by + :meth:`derive_phc_encoded`. + :param bytes secret: Optional secret data; used for keyed hashing. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the key in the encoded string + or when the format of the + encoded string is invalid. + + This class method verifies whether the supplied ``key_material`` matches + the key contained in the PHC-formatted string. It extracts the parameters + from the string, recomputes the key with those parameters, and compares + the result to the key in the string. + + This is useful for validating a password against a stored PHC-formatted + hash string. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives.kdf.argon2 import Argon2id + >>> salt = os.urandom(16) + >>> # Create an Argon2id instance and derive a PHC-formatted string + >>> kdf = Argon2id( + ... salt=salt, + ... length=32, + ... iterations=1, + ... lanes=4, + ... memory_cost=64 * 1024, + ... ) + >>> encoded = kdf.derive_phc_encoded(b"my great password") + >>> # later, verify the password + >>> Argon2id.verify_phc_encoded(b"my great password", encoded) + PBKDF2 ------ @@ -162,7 +223,7 @@ PBKDF2 ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=480000, + ... iterations=1_200_000, ... ) >>> key = kdf.derive(b"my great password") >>> # verify @@ -170,7 +231,7 @@ PBKDF2 ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=480000, + ... iterations=1_200_000, ... ) >>> kdf.verify(b"my great password", key) @@ -665,8 +726,8 @@ HKDF called more than once. - Derives a new key from the input key material by performing both the - extract and expand operations. + Derives a new key from the input key material by only performing the + expand operation. .. method:: verify(key_material, expected_key) diff --git a/docs/index.rst b/docs/index.rst index 7086f80ee6e3..75a77f57c975 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,11 +24,21 @@ Challenges`_. Installation ------------ -You can install ``cryptography`` with ``pip``: -.. code-block:: console +To install ``cryptography``: + +.. tab:: ``pip`` + + .. code-block:: console + + $ pip install cryptography + +.. tab:: ``uv`` + + .. code-block:: console + + $ uv add cryptography - $ pip install cryptography See :doc:`Installation ` for more information. diff --git a/docs/installation.rst b/docs/installation.rst index 8e5af7dd54c3..66509f320612 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,11 +1,19 @@ Installation ============ -You can install ``cryptography`` with ``pip``: +You can install ``cryptography``: -.. code-block:: console +.. tab:: ``pip`` - $ pip install cryptography + .. code-block:: console + + $ pip install cryptography + +.. tab:: ``uv`` + + .. code-block:: console + + $ uv add cryptography If this does not work please **upgrade your pip** first, as that is the single most common cause of installation problems. @@ -17,11 +25,12 @@ Currently we test ``cryptography`` on Python 3.7+ and PyPy3 7.3.11+ on these operating systems. * x86-64 RHEL 8.x -* x86-64 CentOS 9 Stream +* x86-64 CentOS Stream 9, 10 * x86-64 Fedora (latest) * x86-64 macOS 13 Ventura and ARM64 macOS 14 Sonoma * x86-64 Ubuntu 20.04, 22.04, 24.04, rolling * ARM64 Ubuntu rolling +* ARMv7l Ubuntu rolling * x86-64 Debian Bullseye (11.x), Bookworm (12.x), Trixie (13.x), and Sid (unstable) * x86-64 and ARM64 Alpine (latest) @@ -32,13 +41,14 @@ OpenSSL releases in addition to distribution provided releases from the above supported platforms: * ``OpenSSL 3.0-latest`` -* ``OpenSSL 3.1-latest`` * ``OpenSSL 3.2-latest`` * ``OpenSSL 3.3-latest`` +* ``OpenSSL 3.4-latest`` +* ``OpenSSL 3.5-latest`` -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. +We also test against the latest commit of BoringSSL, the latest ``aws-lc`` release, +and versions of LibreSSL that are receiving security support at the time of a +given ``cryptography`` release. Building cryptography on Windows @@ -99,49 +109,46 @@ 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 -~~~~~~ +.. tab:: Alpine -.. warning:: + .. warning:: - The Rust available by default in Alpine < 3.17 is older than the minimum - supported version. See the :ref:`Rust installation instructions - ` for information about installing a newer Rust. + The Rust available by default in Alpine < 3.19 is older than the minimum + supported version. See the :ref:`Rust installation instructions + ` for information about installing a newer Rust. -.. code-block:: console + .. code-block:: console - $ sudo apk add gcc musl-dev python3-dev libffi-dev openssl-dev cargo pkgconfig + $ 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``. + If you get an error with ``openssl-dev`` you may have to use ``libressl-dev``. -Debian/Ubuntu -~~~~~~~~~~~~~ +.. tab:: Debian/Ubuntu -.. warning:: + .. warning:: - The Rust available in Debian versions prior to Bookworm are older than the - minimum supported version. See the :ref:`Rust installation instructions - ` for information about installing a newer Rust. + The Rust available in Debian versions prior to Trixie are older than the + minimum supported version. See the :ref:`Rust installation instructions + ` for information about installing a newer Rust. -.. code-block:: console + .. code-block:: console - $ sudo apt-get install build-essential libssl-dev libffi-dev \ - python3-dev cargo pkg-config + $ sudo apt-get install build-essential libssl-dev libffi-dev \ + python3-dev cargo pkg-config -Fedora/RHEL/CentOS -~~~~~~~~~~~~~~~~~~ +.. tab:: Fedora/RHEL/CentOS -.. warning:: + .. warning:: - For RHEL and CentOS you must be on version 8.8 or newer for the command - below to install a sufficiently new Rust. If your Rust is less than 1.65.0 - please see the :ref:`Rust installation instructions ` - for information about installing a newer Rust. + For RHEL and CentOS you must be on version 8.10 or newer for the command + below to install a sufficiently new Rust. If your Rust is less than 1.74.0 + please see the :ref:`Rust installation instructions ` + for information about installing a newer Rust. -.. code-block:: console + .. code-block:: console - $ sudo dnf install redhat-rpm-config gcc libffi-devel python3-devel \ - openssl-devel cargo pkg-config + $ sudo dnf install redhat-rpm-config gcc libffi-devel python3-devel \ + openssl-devel cargo pkg-config Building @@ -312,7 +319,7 @@ Rust a Rust toolchain. Building ``cryptography`` requires having a working Rust toolchain. The current -minimum supported Rust version is 1.65.0. **This is newer than the Rust some +minimum supported Rust version is 1.74.0. **This is newer than the Rust some package managers ship**, so users may need to install with the instructions below. diff --git a/docs/limitations.rst b/docs/limitations.rst index 3f43c743c729..ae0ed68d51b1 100644 --- a/docs/limitations.rst +++ b/docs/limitations.rst @@ -32,15 +32,13 @@ succeeded, even by timing variability. 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. +In OpenSSL 3.2.0 and newer, this is automatically mitigated by OpenSSL (by +returning a random value and never raising an exception). If you are using +cryptography with an older version of OpenSSL, such attacks are still possible. -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. +Regardless of OpenSSL version, we recommend not implementing or using online +protocols that use RSA PKCS1 v1.5 decryption, as 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 index d4e69f4c86f6..6343619a7960 100644 --- a/docs/openssl.rst +++ b/docs/openssl.rst @@ -41,5 +41,11 @@ 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). +Additionally, the ``CRYPTOGRAPHY_BUILD_OPENSSL_NO_LEGACY`` environment variable +can be set during the build process to prevent the library from ever attempting +to load the legacy provider. + +If loading the legacy provider is not disabled and the legacy provider fails to +load, a warning is emitted. .. _`OpenSSL`: https://www.openssl.org/ diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 8cbe187e3e3f..831369fe90eb 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -28,6 +28,7 @@ committers conda CPython Cryptanalysis +criticalities crypto cryptographic cryptographically @@ -51,6 +52,7 @@ Diffie disambiguating Django Docstrings +duplicative El Encodings endian @@ -83,6 +85,7 @@ kibibytes Koblitz Lange logins +Mbed metadata MGF Monterey @@ -103,6 +106,7 @@ RHEL parsers Parsers PEM +PHC pickleable plaintext Poly @@ -119,6 +123,7 @@ pytest relicensed responder runtime +RustCrypto Schneier scrypt serializer @@ -135,9 +140,12 @@ testability Thawte timestamp timestamps +TLS toolchain totient Trixie +truststore +truststores tunable Ubuntu unencrypted diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst index beaa3537cc2c..58878cc7a2c5 100644 --- a/docs/x509/ocsp.rst +++ b/docs/x509/ocsp.rst @@ -157,7 +157,7 @@ Creating Requests .. 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 + 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 @@ -249,7 +249,8 @@ Creating Responses .. method:: add_response(cert, issuer, algorithm, cert_status, this_update, next_update, revocation_time, revocation_reason) This method adds status information about the certificate that was - requested to the response. + requested to the response. You can call this method or ``add_response_by_hash`` + only once. :param cert: The :class:`~cryptography.x509.Certificate` whose validity is being checked. @@ -269,17 +270,72 @@ Creating Responses :param cert_status: An item from the :class:`~cryptography.x509.ocsp.OCSPCertStatus` enumeration. - :param this_update: A naïve :class:`datetime.datetime` object - representing the most recent time in UTC at which the status being - indicated is known by the responder to be correct. + :param this_update: A :class:`datetime.datetime` object representing + the most recent time at which the status being indicated is known + by the responder to be correct. If it does not have a timezone, it + is assumed to be in UTC. + + :param next_update: A :class:`datetime.datetime` object or ``None``. + The time at or before which newer information will be available + about the status of the certificate. If it does not have a + timezone, it is assumed to be in UTC. + + :param revocation_time: A :class:`datetime.datetime` object or ``None`` + if the ``cert`` is not revoked. The time at which the certificate + was revoked. If it does not have a timezone, it is assumed to be in + UTC. + + :param revocation_reason: An item from the + :class:`~cryptography.x509.ReasonFlags` enumeration or ``None`` if + the ``cert`` is not revoked. + + .. method:: add_response_by_hash(issuer_name_hash, issuer_key_hash, serial_number, algorithm, cert_status, this_update, next_update, revocation_time, revocation_reason) + + .. versionadded:: 45.0.0 + + This method adds status information about the certificate that was + requested to the response using the hash values directly, rather than requiring + the full certificates. You can call this method or ``add_response`` + 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. + + :param cert_status: An item from the + :class:`~cryptography.x509.ocsp.OCSPCertStatus` enumeration. + + :param this_update: A :class:`datetime.datetime` object representing + the most recent time at which the status being indicated is known + by the responder to be correct. If it does not have a timezone, it + is assumed to be in UTC. - :param next_update: A naïve :class:`datetime.datetime` object or - ``None``. The time in UTC at or before which newer information will - be available about the status of the certificate. + :param next_update: A :class:`datetime.datetime` object or ``None``. + The time at or before which newer information will be available + about the status of the certificate. If it does not have a + timezone, it is assumed to be in UTC. - :param revocation_time: A naïve :class:`datetime.datetime` object or - ``None`` if the ``cert`` is not revoked. The time in UTC at which - the certificate was revoked. + :param revocation_time: A :class:`datetime.datetime` object or ``None`` + if the ``cert`` is not revoked. The time at which the certificate + was revoked. If it does not have a timezone, it is assumed to be in + UTC. :param revocation_reason: An item from the :class:`~cryptography.x509.ReasonFlags` enumeration or ``None`` if diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index a9f655085bb6..74d6da68bad4 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -223,6 +223,9 @@ Loading Certificates :returns: An instance of :class:`~cryptography.x509.Certificate`. + :raises ValueError: If a certificate cannot be parsed from the provided + data. + Loading Certificate Revocation Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -883,6 +886,11 @@ X.509 Certificate Builder .. versionadded:: 1.0 + .. note:: + All methods, except :meth:`sign`, return a **new** CertificateBuilder + instance with the corresponding updated value. They do not modify the + existing builder in place. + .. doctest:: >>> from cryptography import x509 @@ -929,6 +937,8 @@ X.509 Certificate Builder :param name: The :class:`~cryptography.x509.Name` that describes the issuer (CA). + :return: A new :class:`CertificateBuilder` with the updated issuer name. + .. method:: subject_name(name) Sets the subject's distinguished name. @@ -936,6 +946,8 @@ X.509 Certificate Builder :param name: The :class:`~cryptography.x509.Name` that describes the subject. + :return: A new :class:`CertificateBuilder` with the updated subject name. + .. method:: public_key(public_key) Sets the subject's public key. @@ -943,6 +955,8 @@ X.509 Certificate Builder :param public_key: The subject's public key. This can be one of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. + :return: A new :class:`CertificateBuilder` with the updated public key. + .. method:: serial_number(serial_number) Sets the certificate's serial number (an integer). The CA's policy @@ -958,6 +972,8 @@ X.509 Certificate Builder revocation checking). Users should consider using :func:`~cryptography.x509.random_serial_number` when possible. + :return: A new :class:`CertificateBuilder` with the updated serial number. + .. method:: not_valid_before(time) Sets the certificate's activation time. This is the time from which @@ -968,6 +984,8 @@ X.509 Certificate Builder activation time for the certificate. The certificate may not be trusted clients if it is used before this time. + :return: A new :class:`CertificateBuilder` with the updated activation time. + .. method:: not_valid_after(time) Sets the certificate's expiration time. This is the time from which @@ -978,6 +996,8 @@ X.509 Certificate Builder expiration time for the certificate. The certificate may not be trusted clients if it is used after this time. + :return: A new :class:`CertificateBuilder` with the updated expiration time. + .. method:: add_extension(extval, critical) Adds an X.509 extension to the certificate. @@ -988,7 +1008,9 @@ X.509 Certificate Builder :param critical: Set to ``True`` if the extension must be understood and handled by whoever reads the certificate. - .. method:: sign(private_key, algorithm, *, rsa_padding=None) + :return: A new :class:`CertificateBuilder` with the additional extension. + + .. method:: sign(private_key, algorithm, *, rsa_padding=None, ecdsa_deterministic=None) Sign the certificate using the CA's private key. @@ -1023,6 +1045,20 @@ X.509 Certificate Builder :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + :param ecdsa_deterministic: + + .. versionadded:: 45.0.0 + + This is a keyword-only argument. If ``private_key`` is an + ``EllipticCurvePrivateKey`` then this can be set to true to + use deterministic signing, as defined in :rfc:`6979`. This only + impacts the signing process, verification is not affected + (the verification process is the same for both + deterministic and non-deterministic signed messages). All other + key types **must** not pass a value other than ``None``. + + :type ecdsa_deterministic: ``None``, ``bool`` + :returns: :class:`~cryptography.x509.Certificate` @@ -1263,7 +1299,7 @@ X.509 Certificate Revocation List Builder obtained from an existing CRL or created with :class:`~cryptography.x509.RevokedCertificateBuilder`. - .. method:: sign(private_key, algorithm, *, rsa_padding=None) + .. method:: sign(private_key, algorithm, *, rsa_padding=None, ecdsa_deterministic=None) Sign this CRL using the CA's private key. @@ -1298,6 +1334,20 @@ X.509 Certificate Revocation List Builder :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + :param ecdsa_deterministic: + + .. versionadded:: 45.0.0 + + This is a keyword-only argument. If ``private_key`` is an + ``EllipticCurvePrivateKey`` then this can be set to true to + use deterministic signing, as defined in :rfc:`6979`. This only + impacts the signing process, verification is not affected + (the verification process is the same for both + deterministic and non-deterministic signed messages). All other + key types **must** not pass a value other than ``None``. + + :type ecdsa_deterministic: ``None``, ``bool`` + :returns: :class:`~cryptography.x509.CertificateRevocationList` X.509 Revoked Certificate Object @@ -1476,7 +1526,7 @@ X.509 CSR (Certificate Signing Request) Builder Object :returns: A new :class:`~cryptography.x509.CertificateSigningRequestBuilder`. - .. method:: sign(private_key, algorithm, *, rsa_padding=None) + .. method:: sign(private_key, algorithm, *, rsa_padding=None, ecdsa_deterministic=None) :param private_key: The private key that will be used to sign the request. When the request is @@ -1511,6 +1561,20 @@ X.509 CSR (Certificate Signing Request) Builder Object :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + :param ecdsa_deterministic: + + .. versionadded:: 45.0.0 + + This is a keyword-only argument. If ``private_key`` is an + ``EllipticCurvePrivateKey`` then this can be set to true to + use deterministic signing, as defined in :rfc:`6979`. This only + impacts the signing process, verification is not affected + (the verification process is the same for both + deterministic and non-deterministic signed messages). All other + key types **must** not pass a value other than ``None``. + + :type ecdsa_deterministic: ``None``, ``bool`` + :returns: A new :class:`~cryptography.x509.CertificateSigningRequest`. @@ -2488,6 +2552,44 @@ X.509 Extensions Returns :attr:`~cryptography.x509.oid.ExtensionOID.PRECERT_POISON`. +.. class:: PrivateKeyUsagePeriod(not_before, not_after) + :canonical: cryptography.x509.extensions.PrivateKeyUsagePeriod + + .. versionadded:: 45.0.0 + + This extension defines the period during which the private key corresponding + to the certificate's public key may be used. Either ``not_before`` or ``not_after`` + must be provided. + + :param not_before: A :class:`datetime.datetime` object or ``None``. Specifies + the earliest time the private key can be used. + :param not_after: A :class:`datetime.datetime` object or ``None``. Specifies + the latest time the private key can be used. + + .. attribute:: not_before + + A :class:`datetime.datetime` object or ``None``. Represents the earliest + time the private key can be used. + + .. attribute:: not_after + + A :class:`datetime.datetime` object or ``None``. Represents the latest + time the private key can be used. + + .. method:: public_bytes() + + Returns the encoded bytes of the extension. + + :return: A ``bytes`` object containing the encoded extension. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.PRIVATE_KEY_USAGE_PERIOD`. + + .. class:: SignedCertificateTimestamps(scts) :canonical: cryptography.x509.extensions.SignedCertificateTimestamps @@ -3737,7 +3839,17 @@ instances. The following common OIDs are available as constants. 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`. + Exchange (IKE). For more information see :rfc:`4945`. + + .. attribute:: BUNDLE_SECURITY + + .. versionadded:: 45.0.0 + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.35"``. This + is used to denote that a certificate is used by a Bundle Protocol + Node to secure data either in transit (e.g. via TLS/TCPCL) or at + rest (e.g. via BPSec). + For more information see :rfc:`9172` and :rfc:`9174`. .. attribute:: CERTIFICATE_TRANSPARENCY @@ -3749,6 +3861,70 @@ instances. The following common OIDs are available as constants. purposes. For more information see :rfc:`6962`. +.. class:: OtherNameFormOID + :canonical: cryptography.hazmat._oid.OtherNameFormOID + + .. versionadded:: 45.0.0 + + .. attribute:: PERMANENT_IDENTIFIER + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.8.3"``. + This is used to correlate multiple certificates which relate to + the same entity, as identified by this Other Name value. + The Other Name value is encoded as sequence of optional + UTF-8 value and optional OID assigner. + For more information see :rfc:`4043`. + + .. attribute:: HW_MODULE_NAME + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.8.4"``. + This is used to identify hardware module components when + protecting firmware packages. + The Other Name value is encoded as sequence of OID hardware-type + and octet-string serial number. + For more information see :rfc:`4108`. + + .. attribute:: DNS_SRV + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.8.7"``. + This is used to identify service names using qualified DNS name + of the form ``_Service.Name``. + The Other Name value is encoded as IA5 text. + For more information see :rfc:`4985`. + + .. attribute:: NAI_REALM + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.8.8"``. + This is used to identify realms for RADIUS dynamic peer discovery + using Network Access Identifier (NAI) values. + The Other Name value is encoded as UTF-8 text. + For more information see :rfc:`7585`. + + .. attribute:: SMTP_UTF8_MAILBOX + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.8.9"``. + This is used to identify an internationalized email address associated + with an entity. + The Other Name value is encoded as UTF-8 text. + For more information see :rfc:`9598`. + + .. attribute:: ACP_NODE_NAME + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.8.10"``. + This is used to identify a single node within an + Autonomic Control Plane (ACP). + The Other Name value is encoded as IA5 text. + For more information see :rfc:`8994`. + + .. attribute:: BUNDLE_EID + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.8.11"``. + This is used to contain the text form of an endpoint identifier (EID) + for the Bundle Protocol Version 7. + The Other Name value is encoded as IA5 text. + For more information see :rfc:`9171` and :rfc:`9174`. + + .. class:: AuthorityInformationAccessOID :canonical: cryptography.hazmat._oid.AuthorityInformationAccessOID @@ -3909,6 +4085,12 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"1.3.6.1.4.1.11129.2.4.3"``. + .. attribute:: PRIVATE_KEY_USAGE_PERIOD + + .. versionadded:: 45.0.0 + + Corresponds to the dotted string ``"2.5.29.16"``. + .. attribute:: SIGNED_CERTIFICATE_TIMESTAMPS .. versionadded:: 3.0 diff --git a/docs/x509/verification.rst b/docs/x509/verification.rst index 70aafd48f94c..649da8189456 100644 --- a/docs/x509/verification.rst +++ b/docs/x509/verification.rst @@ -111,7 +111,7 @@ the root of trust: .. versionadded:: 43.0.0 - .. versionchanged:: 44.0.0 + .. versionchanged:: 45.0.0 Made ``subjects`` optional with the addition of custom extension policies. .. attribute:: subjects @@ -133,6 +133,11 @@ the root of trust: .. versionadded:: 43.0.0 + .. versionchanged:: 45.0.0 + ``verification_time`` and ``max_chain_depth`` were deprecated and will be + removed in version 46.0.0. + The new ``policy`` property should be used to access these values instead. + A ClientVerifier verifies client certificates. It contains and describes various pieces of configurable path @@ -142,17 +147,11 @@ the root of trust: ClientVerifier instances cannot be constructed directly; :class:`PolicyBuilder` must be used. - .. attribute:: validation_time - - :type: :class:`datetime.datetime` + .. attribute:: policy - The verifier's validation time. + :type: :class:`Policy` - .. attribute:: max_chain_depth - - :type: :class:`int` - - The verifier's maximum intermediate CA chain depth. + The policy used by the verifier. Can be used to access verification time, maximum chain depth, etc. .. attribute:: store @@ -181,6 +180,12 @@ the root of trust: .. versionadded:: 42.0.0 + .. versionchanged:: 45.0.0 + ``subject``, ``verification_time`` and ``max_chain_depth`` were deprecated and will be + removed in version 46.0.0. + The new ``policy`` property should be used to access these values instead. + + A ServerVerifier verifies server certificates. It contains and describes various pieces of configurable path @@ -191,23 +196,11 @@ the root of trust: ServerVerifier instances cannot be constructed directly; :class:`PolicyBuilder` must be used. - .. attribute:: subject - - :type: :class:`Subject` - - The verifier's subject. - - .. attribute:: validation_time + .. attribute:: policy - :type: :class:`datetime.datetime` + :type: :class:`Policy` - The verifier's validation time. - - .. attribute:: max_chain_depth - - :type: :class:`int` - - The verifier's maximum intermediate CA chain depth. + The policy used by the verifier. Can be used to access verification time, maximum chain depth, etc. .. attribute:: store @@ -239,10 +232,14 @@ the root of trust: .. versionadded:: 42.0.0 + .. versionchanged:: 45.0.0 + Added the ``extension_policies`` method. + Removed the ``new_`` prefix from all parameter names. + A PolicyBuilder provides a builder-style interface for constructing a Verifier. - .. method:: time(new_time) + .. method:: time(time) Sets the verifier's verification time. @@ -250,19 +247,19 @@ the root of trust: when :meth:`build_server_verifier` or :meth:`build_client_verifier` is called. - :param new_time: The :class:`datetime.datetime` to use in the verifier + :param time: The :class:`datetime.datetime` to use in the verifier :returns: A new instance of :class:`PolicyBuilder` - .. method:: store(new_store) + .. method:: store(store) Sets the verifier's trust store. - :param new_store: The :class:`Store` to use in the verifier + :param store: The :class:`Store` to use in the verifier :returns: A new instance of :class:`PolicyBuilder` - .. method:: max_chain_depth(new_max_chain_depth) + .. method:: max_chain_depth(max_chain_depth) Sets the verifier's maximum chain building depth. @@ -272,10 +269,30 @@ the root of trust: one intermediate CA, and so forth. Note that self-issued intermediates don't count against the chain depth, per RFC 5280. - :param new_max_chain_depth: The maximum depth to allow in the verifier + :param max_chain_depth: The maximum depth to allow in the verifier :returns: A new instance of :class:`PolicyBuilder` + .. method:: extension_policies(*, ee_policy, ca_policy) + + .. versionadded:: 45.0.0 + + Sets the EE and CA extension policies for the verifier. + The default policies used are those returned by :meth:`ExtensionPolicy.webpki_defaults_ee` + and :meth:`ExtensionPolicy.webpki_defaults_ca`. + + .. warning:: + If the PolicyBuilder will be used to build a :class:`ServerVerifier`, the EE extension policy + `must require` the :class:`~cryptography.x509.SubjectAlternativeName` extension to be present. + All CA extension policies `must require` the :class:`~cryptography.x509.BasicConstraints` + extension to be present. + + :param ExtensionPolicy ca_policy: The CA extension policy to use. + :param ExtensionPolicy ee_policy: The EE extension policy to use. + + :returns: A new instance of :class:`PolicyBuilder` + + .. method:: build_server_verifier(subject) Builds a verifier for verifying server certificates. @@ -297,3 +314,184 @@ the root of trust: for server verification. :returns: An instance of :class:`ClientVerifier` + +.. class:: ExtensionPolicy + + .. versionadded:: 45.0.0 + + ExtensionPolicy provides a set of static methods to construct predefined + extension policies, and a builder-style interface for modifying them. + + .. note:: Calling any of the builder methods (:meth:`require_not_present`, :meth:`may_be_present`, or :meth:`require_present`) + multiple times with the same extension type will raise an exception. + + .. note:: Currently only the following extension types are supported in the ExtensionPolicy API: + :class:`~cryptography.x509.AuthorityInformationAccess`, + :class:`~cryptography.x509.AuthorityKeyIdentifier`, + :class:`~cryptography.x509.SubjectKeyIdentifier`, + :class:`~cryptography.x509.KeyUsage`, + :class:`~cryptography.x509.SubjectAlternativeName`, + :class:`~cryptography.x509.BasicConstraints`, + :class:`~cryptography.x509.NameConstraints`, + :class:`~cryptography.x509.ExtendedKeyUsage`. + + .. staticmethod:: permit_all() + + Creates an ExtensionPolicy that does not put any constraints on a certificate's extensions. + This can serve as a base for a fully custom extension policy. + + :returns: An instance of :class:`ExtensionPolicy` + + .. staticmethod:: webpki_defaults_ca() + + Creates an ExtensionPolicy for CA certificates, + based on CA/B Forum guidelines. + + This is the default CA extension policy used by :class:`PolicyBuilder`. + + :returns: An instance of :class:`ExtensionPolicy` + + .. staticmethod:: webpki_defaults_ee() + + Creates an ExtensionPolicy for EE certificates, + based on CA/B Forum guidelines. + + This is the default EE extension policy used by :class:`PolicyBuilder`. + + :returns: An instance of :class:`ExtensionPolicy` + + .. method:: require_not_present(extension_type) + + Specifies that the extension identified by `extension_type` must not be present (must be absent). + + :param type[ExtensionType] extension_type: The extension_type of the extension that must not be present. + + :returns: An instance of :class:`ExtensionPolicy` + + .. method:: may_be_present(extension_type, criticality, validator_cb) + + Specifies that the extension identified by `extension_type` is optional. + If it is present, it must conform to the given criticality constraint. + An optional validator callback may be provided. + + If a validator callback is provided, the callback will be invoked + when :meth:`ClientVerifier.verify` or :meth:`ServerVerifier.verify` is called on a verifier + that uses the extension policy. For details on the callback signature, see :type:`MaybeExtensionValidatorCallback`. + + :param type[ExtensionType] extension_type: A concrete class derived from :type:`~cryptography.x509.ExtensionType` + indicating which extension may be present. + :param Criticality criticality: The criticality of the extension + :param validator_cb: An optional Python callback to validate the extension value. + Must accept extensions of type `extension_type`. + :type validator_cb: :type:`MaybeExtensionValidatorCallback` or None + + :returns: An instance of :class:`ExtensionPolicy` + + .. method:: require_present(extension_type, criticality, validator_cb) + + Specifies that the extension identified by `extension_type`` must be present + and conform to the given criticality constraint. An optional validator callback may be provided. + + If a validator callback is provided, the callback will be invoked + when :meth:`ClientVerifier.verify` or :meth:`ServerVerifier.verify` is called on a verifier + that uses the extension policy. For details on the callback signature, see :type:`PresentExtensionValidatorCallback`. + + :param type[ExtensionType] extension_type: A concrete class derived from :type:`~cryptography.x509.ExtensionType` + indicating which extension is required to be present. + :param Criticality criticality: The criticality of the extension + :param validator_cb: An optional Python callback to validate the extension value. + Must accept extensions of type `extension_type`. + :type validator_cb: :type:`PresentExtensionValidatorCallback` or None + + :returns: An instance of :class:`ExtensionPolicy` + +.. class:: Criticality + + .. versionadded:: 45.0.0 + + An enumeration of criticality constraints for certificate extensions. + + .. attribute:: CRITICAL + + The extension must be marked as critical. + + .. attribute:: AGNOSTIC + + The extension may be marked either as critical or non-critical. + + .. attribute:: NON_CRITICAL + + The extension must not be marked as critical. + +.. class:: Policy + + .. versionadded:: 45.0.0 + + Represents a policy for certificate verification. Passed to extension validator callbacks and + accessible via :class:`ClientVerifier` and :class:`ServerVerifier`. + + .. attribute:: max_chain_depth + + The maximum chain depth (as described in :meth:`PolicyBuilder.max_chain_depth`). + + :type: int + + .. attribute:: subject + + The subject used during verification. + Will be None if the verifier is a :class:`ClientVerifier`. + + :type: x509.verification.Subject or None + + .. attribute:: validation_time + + The validation time. + + :type: datetime.datetime + + .. attribute:: extended_key_usage + + The Extended Key Usage required by the policy. + + :type: x509.ObjectIdentifier + + .. attribute:: minimum_rsa_modulus + + The minimum RSA modulus size required by the policy. + + :type: int + +.. type:: MaybeExtensionValidatorCallback + :canonical: Callable[[Policy, Certificate, Optional[ExtensionType]], None] + + .. versionadded:: 45.0.0 + + + A Python callback that validates an extension that may or may not be present. + If the extension is not present, the callback will be invoked with `ext` set to `None`. + + To fail the validation, the callback must raise an exception. + + :param Policy policy: The verification policy. + :param Certificate certificate: The certificate being verified. + :param ExtensionType or None extension: The extension value or `None` if the extension is not present. + + :returns: An extension validator callback must return `None`. + If the validation fails, the validator must raise an exception. + +.. type:: PresentExtensionValidatorCallback + :canonical: Callable[[Policy, Certificate, ExtensionType], None] + + .. versionadded:: 45.0.0 + + + A Python callback that validates an extension that must be present. + + To fail the validation, the callback must raise an exception. + + :param Policy policy: The verification policy. + :param Certificate certificate: The certificate being verified. + :param ExtensionType extension: The extension value. + + :returns: An extension validator callback must return `None`. + If the validation fails, the validator must raise an exception. diff --git a/noxfile.py b/noxfile.py index 93ac329a0001..77a49376a197 100644 --- a/noxfile.py +++ b/noxfile.py @@ -7,6 +7,7 @@ import glob import itertools import json +import os import pathlib import re import sys @@ -14,10 +15,10 @@ import nox -try: +if sys.version_info >= (3, 11): import tomllib -except ImportError: - import tomli as tomllib # type: ignore[import-not-found,no-redef] +else: + import tomli as tomllib nox.options.reuse_existing_virtualenvs = True nox.options.default_venv_backend = "uv|virtualenv" @@ -59,10 +60,11 @@ def tests(session: nox.Session) -> None: pathlib.Path(".") / ".rust-cov" / str(uuid.uuid4()) ).absolute() if session.name != "tests-nocoverage": + rustflags = os.environ.get("RUSTFLAGS", "") + assert rustflags is not None session.env.update( { - "RUSTFLAGS": "-Cinstrument-coverage " - + session.env.get("RUSTFLAGS", ""), + "RUSTFLAGS": f"-Cinstrument-coverage {rustflags}", "LLVM_PROFILE_FILE": str(prof_location / "cov-%p.profraw"), } ) @@ -86,6 +88,7 @@ def tests(session: nox.Session) -> None: cov_args = [ "--cov=cryptography", "--cov=tests", + "--cov-context=test", ] else: cov_args = [] @@ -213,15 +216,15 @@ def flake(session: nox.Session) -> None: @nox.session -@nox.session(name="rust-noclippy") def rust(session: nox.Session) -> None: prof_location = ( pathlib.Path(".") / ".rust-cov" / str(uuid.uuid4()) ).absolute() + rustflags = os.environ.get("RUSTFLAGS", "") + assert rustflags is not None session.env.update( { - "RUSTFLAGS": "-Cinstrument-coverage " - + session.env.get("RUSTFLAGS", ""), + "RUSTFLAGS": f"-Cinstrument-coverage {rustflags}", "LLVM_PROFILE_FILE": str(prof_location / "cov-%p.profraw"), } ) @@ -232,16 +235,15 @@ def rust(session: nox.Session) -> None: install(session, *pyproject_data["build-system"]["requires"]) session.run("cargo", "fmt", "--all", "--", "--check", external=True) - if session.name != "rust-noclippy": - session.run( - "cargo", - "clippy", - "--all", - "--", - "-D", - "warnings", - external=True, - ) + session.run( + "cargo", + "clippy", + "--all", + "--", + "-D", + "warnings", + external=True, + ) build_output = session.run( "cargo", @@ -271,7 +273,7 @@ def rust(session: nox.Session) -> None: @nox.session -def local(session): +def local(session: nox.Session): pyproject_data = load_pyproject_toml() install(session, "-e", "./vectors", verbose=False) install( @@ -345,11 +347,6 @@ def process_rust_coverage( rust_binaries: 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 ) diff --git a/pyproject.toml b/pyproject.toml index 949d68423064..d94a1f5f98e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,30 +2,30 @@ # These requirements must be kept sync with the requirements in # ./.github/requirements/build-requirements.{in,txt} requires = [ - "maturin>=1,<2", + "maturin>=1.8.6,<2", # Must be kept in sync with `project.dependencies` - "cffi>=1.12; platform_python_implementation != 'PyPy'", - # Needed because cffi imports distutils, and in Python 3.12, distutils has - # been removed from the stdlib, but installing setuptools puts it back. + "cffi>=1.14; platform_python_implementation != 'PyPy'", + # Used by cffi (which import distutils, and in Python 3.12, distutils has + # been removed from the stdlib, but installing setuptools puts it back) as + # well as our build.rs for the rust/cffi bridge. "setuptools!=74.0.0,!=74.1.0,!=74.1.1,!=74.1.2", ] build-backend = "maturin" [project] name = "cryptography" -version = "44.0.0" +version = "45.0.3" authors = [ - {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} + { 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"} +license = "Apache-2.0 OR BSD-3-Clause" +license-files = [ "LICENSE", "LICENSE.APACHE", "LICENSE.BSD" ] 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", @@ -49,7 +49,7 @@ classifiers = [ requires-python = ">=3.7,!=3.9.0,!=3.9.1" dependencies = [ # Must be kept in sync with `build-system.requires` - "cffi>=1.12; platform_python_implementation != 'PyPy'", + "cffi>=1.14; platform_python_implementation != 'PyPy'", ] [project.urls] @@ -65,7 +65,7 @@ ssh = ["bcrypt >=3.1.5"] # All the following are used for our own testing. nox = ["nox >=2024.04.15", "nox[uv] >=2024.03.02; python_version >= '3.8'"] test = [ - "cryptography_vectors==44.0.0", + "cryptography_vectors==45.0.3", "pytest >=7.4.0", "pytest-benchmark >=4.0", "pytest-cov >=2.10.1", @@ -74,11 +74,24 @@ test = [ "certifi >=2024", ] test-randomorder = ["pytest-randomly"] -docs = ["sphinx >=5.3.0", "sphinx-rtd-theme >=3.0.0; python_version >= '3.8'"] -docstest = ["pyenchant >=3", "readme-renderer >=30.0", "sphinxcontrib-spelling >=7.3.1"] +docs = [ + "sphinx >=5.3.0", + "sphinx-rtd-theme >=3.0.0; python_version >= '3.8'", + "sphinx-inline-tabs; python_version >= '3.8'", +] +docstest = [ + "pyenchant >=3", + "readme-renderer >=30.0", + "sphinxcontrib-spelling >=7.3.1", +] sdist = ["build >=1.0.0"] # `click` included because its needed to type check `release.py` -pep8test = ["ruff >=0.3.6", "mypy >=1.4", "check-sdist; python_version >= '3.8'", "click >=8.0.1"] +pep8test = [ + "ruff >=0.3.6", + "mypy >=1.4", + "check-sdist; python_version >= '3.8'", + "click >=8.0.1", +] [tool.maturin] python-source = "src" @@ -87,29 +100,27 @@ manifest-path = "src/rust/Cargo.toml" module-name = "cryptography.hazmat.bindings._rust" locked = true sdist-generator = "git" -features = ["pyo3/abi3-py37"] include = [ "CHANGELOG.rst", "CONTRIBUTING.rst", - "LICENSE", - "LICENSE.APACHE", - "LICENSE.BSD", "docs/**/*", - "src/_cffi_src/**/*.py", - "src/_cffi_src/**/*.c", - "src/_cffi_src/**/*.h", + { path = "src/_cffi_src/**/*.py", format = "sdist" }, + { path = "src/_cffi_src/**/*.c", format = "sdist" }, + { path = "src/_cffi_src/**/*.h", format = "sdist" }, - "**/Cargo.toml", - "**/Cargo.lock", - "src/rust/**/*.rs", + { path = "Cargo.toml", format = "sdist" }, + { path = "Cargo.lock", format = "sdist" }, + { path = "src/rust/**/Cargo.toml", format = "sdist" }, + { path = "src/rust/**/Cargo.lock", format = "sdist" }, + { path = "src/rust/**/*.rs", format = "sdist" }, "tests/**/*.py", ] exclude = [ "vectors/**/*", - "src/rust/target/**/*", + "target/**/*", "docs/_build/**/*", ".github/**/*", ".readthedocs.yml", @@ -133,20 +144,16 @@ warn_redundant_casts = true warn_unused_ignores = true warn_unused_configs = true strict_equality = true +strict_bytes = true [[tool.mypy.overrides]] -module = [ - "pretend" -] +module = ["pretend"] ignore_missing_imports = true [tool.coverage.run] branch = true relative_files = true -source = [ - "cryptography", - "tests/", -] +source = ["cryptography", "tests/"] [tool.coverage.paths] source = [ @@ -155,10 +162,7 @@ source = [ "*.nox\\*\\Lib\\site-packages\\cryptography", "*.nox/pypy/site-packages/cryptography", ] -tests = [ - "tests/", - "*tests\\", -] +tests = ["tests/", "*tests\\"] [tool.coverage.report] exclude_lines = [ @@ -167,6 +171,9 @@ exclude_lines = [ "if typing.TYPE_CHECKING", ] +[tool.coverage.html] +show_contexts = true + [tool.ruff] line-length = 79 @@ -184,13 +191,3 @@ git-only = [ ".gitattributes", ".gitignore", ] - -[tool.uv] -# These cover all Python versions, but by expressing multiple environments we -# force uv's resolver to pick the latest versions of packages for each version. -environments = [ - "python_version >= '3.10'", - "python_version >= '3.9' and python_version < '3.10'", - "python_version >= '3.8' and python_version < '3.9'", - "python_version < '3.8'", -] diff --git a/release.py b/release.py index 120a6c445738..74d74c969d58 100644 --- a/release.py +++ b/release.py @@ -5,11 +5,16 @@ import pathlib import re import subprocess +import sys import click -import tomllib from packaging.version import Version +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + def run(*args: str) -> None: print(f"[running] {list(args)}") diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py index b1278f36f025..39e8fef82064 100644 --- a/src/_cffi_src/openssl/asn1.py +++ b/src/_cffi_src/openssl/asn1.py @@ -39,7 +39,6 @@ /* ASN1 INTEGER */ void ASN1_INTEGER_free(ASN1_INTEGER *); -int ASN1_INTEGER_set(ASN1_INTEGER *, long); /* ASN1 TIME */ ASN1_TIME *ASN1_TIME_new(void); @@ -49,11 +48,6 @@ /* ASN1 GENERALIZEDTIME */ void ASN1_GENERALIZEDTIME_free(ASN1_GENERALIZEDTIME *); -/* ASN1 ENUMERATED */ -ASN1_ENUMERATED *ASN1_ENUMERATED_new(void); -void ASN1_ENUMERATED_free(ASN1_ENUMERATED *); -int ASN1_ENUMERATED_set(ASN1_ENUMERATED *, long); - 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 *); diff --git a/src/_cffi_src/openssl/bignum.py b/src/_cffi_src/openssl/bignum.py index 4c7a9593d51c..f14c96c0c1c5 100644 --- a/src/_cffi_src/openssl/bignum.py +++ b/src/_cffi_src/openssl/bignum.py @@ -35,7 +35,7 @@ """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_AWSLC static const long Cryptography_HAS_PRIME_CHECKS = 0; int (*BN_prime_checks_for_size)(int) = NULL; #else diff --git a/src/_cffi_src/openssl/bio.py b/src/_cffi_src/openssl/bio.py index 7cd94e37fd15..ee1c03e6abca 100644 --- a/src/_cffi_src/openssl/bio.py +++ b/src/_cffi_src/openssl/bio.py @@ -35,7 +35,8 @@ """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL \ + || CRYPTOGRAPHY_IS_AWSLC #if !defined(_WIN32) #include diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index e90a71b375ff..ada4a883b66d 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -42,6 +42,13 @@ #define CRYPTOGRAPHY_IS_BORINGSSL 0 #endif +#if defined(OPENSSL_IS_AWSLC) +#define CRYPTOGRAPHY_IS_AWSLC 1 +#else +#define CRYPTOGRAPHY_IS_AWSLC 0 +#endif + + #if OPENSSL_VERSION_NUMBER < 0x10101050 #error "pyca/cryptography MUST be linked with Openssl 1.1.1e or later" #endif diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py index f47e20327003..fe590b787979 100644 --- a/src/_cffi_src/openssl/engine.py +++ b/src/_cffi_src/openssl/engine.py @@ -28,8 +28,10 @@ int ENGINE_free(ENGINE *); const char *ENGINE_get_name(const ENGINE *); -// These bindings are unused by cryptography or pyOpenSSL but are present -// for advanced users who need them. +/* +These bindings are unused by cryptography or pyOpenSSL but are present +for advanced users who need them. +*/ int ENGINE_ctrl_cmd_string(ENGINE *, const char *, const char *, int); void ENGINE_load_builtin_engines(void); EVP_PKEY *ENGINE_load_private_key(ENGINE *, const char *, UI_METHOD *, void *); @@ -40,12 +42,16 @@ #ifdef OPENSSL_NO_ENGINE static const long Cryptography_HAS_ENGINE = 0; -#if CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_AWSLC typedef void UI_METHOD; #endif -/* Despite being OPENSSL_NO_ENGINE, BoringSSL/LibreSSL define these symbols. */ -#if !CRYPTOGRAPHY_IS_BORINGSSL && !CRYPTOGRAPHY_IS_LIBRESSL +/* +Despite being OPENSSL_NO_ENGINE, +BoringSSL/LibreSSL/AWS-LC define these symbols. +*/ +#if !CRYPTOGRAPHY_IS_BORINGSSL && !CRYPTOGRAPHY_IS_LIBRESSL \ + && !CRYPTOGRAPHY_IS_AWSLC int (*ENGINE_free)(ENGINE *) = NULL; void (*ENGINE_load_builtin_engines)(void) = NULL; #endif diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index a86e560a659c..d61a69b6b984 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -36,7 +36,7 @@ """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_AWSLC static const int EVP_F_EVP_ENCRYPTFINAL_EX = 0; static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH = 0; #endif diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py index f25c9bb52a66..2d66aaf7d7e0 100644 --- a/src/_cffi_src/openssl/evp.py +++ b/src/_cffi_src/openssl/evp.py @@ -11,10 +11,8 @@ TYPES = """ typedef ... EVP_CIPHER; typedef ... EVP_MD; -typedef ... EVP_MD_CTX; typedef ... EVP_PKEY; -typedef ... EVP_PKEY_CTX; static const int EVP_PKEY_RSA; static const int EVP_PKEY_DSA; static const int EVP_PKEY_DH; @@ -32,27 +30,12 @@ EVP_PKEY *EVP_PKEY_new(void); void EVP_PKEY_free(EVP_PKEY *); int EVP_PKEY_type(int); -int EVP_PKEY_size(EVP_PKEY *); RSA *EVP_PKEY_get1_RSA(EVP_PKEY *); -int EVP_SignInit(EVP_MD_CTX *, const EVP_MD *); -int EVP_SignUpdate(EVP_MD_CTX *, const void *, size_t); -int EVP_SignFinal(EVP_MD_CTX *, unsigned char *, unsigned int *, EVP_PKEY *); - -int EVP_VerifyInit(EVP_MD_CTX *, const EVP_MD *); -int EVP_VerifyUpdate(EVP_MD_CTX *, const void *, size_t); -int EVP_VerifyFinal(EVP_MD_CTX *, const unsigned char *, unsigned int, - EVP_PKEY *); - - -int EVP_PKEY_set1_RSA(EVP_PKEY *, RSA *); int EVP_PKEY_set1_DSA(EVP_PKEY *, DSA *); int EVP_PKEY_id(const 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 *); int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); diff --git a/src/_cffi_src/openssl/nid.py b/src/_cffi_src/openssl/nid.py index 9051977f0ab6..a462ab29ff65 100644 --- a/src/_cffi_src/openssl/nid.py +++ b/src/_cffi_src/openssl/nid.py @@ -12,7 +12,6 @@ static const int NID_undef; static const int NID_subject_alt_name; -static const int NID_crl_reason; """ FUNCTIONS = """ diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py index 04badc47af1b..eac5fd83c771 100644 --- a/src/_cffi_src/openssl/pem.py +++ b/src/_cffi_src/openssl/pem.py @@ -26,10 +26,6 @@ X509_REQ *PEM_read_bio_X509_REQ(BIO *, X509_REQ **, pem_password_cb *, void *); -X509_CRL *PEM_read_bio_X509_CRL(BIO *, X509_CRL **, pem_password_cb *, void *); - -int PEM_write_bio_X509_CRL(BIO *, X509_CRL *); - DH *PEM_read_bio_DHparams(BIO *, DH **, pem_password_cb *, void *); EVP_PKEY *PEM_read_bio_PUBKEY(BIO *, EVP_PKEY **, pem_password_cb *, void *); diff --git a/src/_cffi_src/openssl/rand.py b/src/_cffi_src/openssl/rand.py index 50fbeb279e45..53f5fa735e51 100644 --- a/src/_cffi_src/openssl/rand.py +++ b/src/_cffi_src/openssl/rand.py @@ -14,7 +14,6 @@ FUNCTIONS = """ void RAND_add(const void *, int, double); int RAND_status(void); -int RAND_bytes(unsigned char *, int); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py index 099ec4db13a6..a72db401efd5 100644 --- a/src/_cffi_src/openssl/ssl.py +++ b/src/_cffi_src/openssl/ssl.py @@ -12,6 +12,7 @@ static const long Cryptography_HAS_SSL_ST; static const long Cryptography_HAS_TLS_ST; static const long Cryptography_HAS_TLSv1_3_FUNCTIONS; +static const long Cryptography_HAS_TLSv1_3_HS_FUNCTIONS; static const long Cryptography_HAS_SIGALGS; static const long Cryptography_HAS_PSK; static const long Cryptography_HAS_PSK_TLSv1_3; @@ -477,7 +478,8 @@ /* in OpenSSL 1.1.0 the SSL_ST values were renamed to TLS_ST and several were removed */ -#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL \ + || CRYPTOGRAPHY_IS_AWSLC static const long Cryptography_HAS_SSL_ST = 1; #else static const long Cryptography_HAS_SSL_ST = 0; @@ -494,7 +496,8 @@ static const long TLS_ST_OK = 0; #endif -#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL \ + || CRYPTOGRAPHY_IS_AWSLC static const long Cryptography_HAS_DTLS_GET_DATA_MTU = 0; size_t (*DTLS_get_data_mtu)(SSL *) = NULL; #else @@ -589,9 +592,15 @@ #if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 0; +int (*SSL_CTX_set_ciphersuites)(SSL_CTX *, const char *) = NULL; +#else +static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 1; +#endif +#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_AWSLC +static const long Cryptography_HAS_TLSv1_3_HS_FUNCTIONS = 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; void (*SSL_CTX_set_post_handshake_auth)(SSL_CTX *, int) = NULL; void (*SSL_set_post_handshake_auth)(SSL *, int) = NULL; @@ -600,10 +609,10 @@ 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_FUNCTIONS = 1; +static const long Cryptography_HAS_TLSv1_3_HS_FUNCTIONS = 1; #endif -#if CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_AWSLC static const long Cryptography_HAS_SSL_COOKIE = 0; static const long SSL_OP_COOKIE_EXCHANGE = 0; @@ -623,7 +632,8 @@ #else static const long Cryptography_HAS_SSL_COOKIE = 1; #endif -#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL \ + || CRYPTOGRAPHY_IS_AWSLC static const long Cryptography_HAS_PSK_TLSv1_3 = 0; void (*SSL_CTX_set_psk_find_session_callback)(SSL_CTX *, int (*)( @@ -646,7 +656,7 @@ 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 +#if !CRYPTOGRAPHY_IS_BORINGSSL && !CRYPTOGRAPHY_IS_AWSLC int (*SSL_SESSION_set_protocol_version)(SSL_SESSION *, int) = NULL; #endif SSL_SESSION *(*Cryptography_SSL_SESSION_new)(void) = NULL; diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index 8527a85eeb9f..835527ab3e24 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -15,18 +15,15 @@ * Note that the result is an opaque type. */ typedef STACK_OF(X509) Cryptography_STACK_OF_X509; -typedef STACK_OF(X509_REVOKED) Cryptography_STACK_OF_X509_REVOKED; """ TYPES = """ typedef ... Cryptography_STACK_OF_X509; -typedef ... Cryptography_STACK_OF_X509_REVOKED; typedef ... X509_ALGOR; typedef ... X509_EXTENSION; typedef ... X509_EXTENSIONS; typedef ... X509_REQ; -typedef ... X509_REVOKED; typedef ... X509_CRL; typedef ... X509; @@ -78,25 +75,7 @@ int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int); ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *); -X509_REVOKED *X509_REVOKED_new(void); -void X509_REVOKED_free(X509_REVOKED *); - -int X509_REVOKED_set_serialNumber(X509_REVOKED *, ASN1_INTEGER *); - -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 *d2i_X509_CRL_bio(BIO *, X509_CRL **); -int X509_CRL_add0_revoked(X509_CRL *, X509_REVOKED *); -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 i2d_X509_CRL_bio(BIO *, X509_CRL *); void X509_CRL_free(X509_CRL *); /* ASN1 serialization */ @@ -128,11 +107,6 @@ int X509_EXTENSION_get_critical(const X509_EXTENSION *); -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 *); - const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *); long X509_get_version(X509 *); @@ -156,18 +130,6 @@ 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); - -X509_NAME *X509_CRL_get_issuer(X509_CRL *); -Cryptography_STACK_OF_X509_REVOKED *X509_CRL_get_REVOKED(X509_CRL *); - -int X509_CRL_set1_lastUpdate(X509_CRL *, const ASN1_TIME *); -int X509_CRL_set1_nextUpdate(X509_CRL *, const ASN1_TIME *); - -const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *); -const ASN1_TIME *X509_REVOKED_get0_revocationDate(const X509_REVOKED *); - void X509_ALGOR_get0(const ASN1_OBJECT **, int *, const void **, const X509_ALGOR *); """ diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py index 99fc2d1593c4..c430602486aa 100644 --- a/src/cryptography/__about__.py +++ b/src/cryptography/__about__.py @@ -10,8 +10,8 @@ "__version__", ] -__version__ = "44.0.0" +__version__ = "45.0.3" __author__ = "The Python Cryptographic Authority and individual contributors" -__copyright__ = f"Copyright 2013-2024 {__author__}" +__copyright__ = f"Copyright 2013-2025 {__author__}" diff --git a/src/cryptography/__init__.py b/src/cryptography/__init__.py index f37370e90a71..e8febfb8d554 100644 --- a/src/cryptography/__init__.py +++ b/src/cryptography/__init__.py @@ -19,8 +19,8 @@ if sys.version_info[:2] == (3, 7): warnings.warn( "Python 3.7 is no longer supported by the Python core team " - "and support for it is deprecated in cryptography. A future " - "release of cryptography will remove support for Python 3.7.", + "and support for it is deprecated in cryptography. The next release " + "of cryptography will remove support for Python 3.7.", utils.CryptographyDeprecationWarning, stacklevel=2, ) diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py index 868ecb277789..c6744ae384e3 100644 --- a/src/cryptography/fernet.py +++ b/src/cryptography/fernet.py @@ -9,6 +9,7 @@ import os import time import typing +from collections.abc import Iterable from cryptography import utils from cryptography.exceptions import InvalidSignature @@ -168,7 +169,7 @@ def _decrypt_data( class MultiFernet: - def __init__(self, fernets: typing.Iterable[Fernet]): + def __init__(self, fernets: Iterable[Fernet]): fernets = list(fernets) if not fernets: raise ValueError( diff --git a/src/cryptography/hazmat/_oid.py b/src/cryptography/hazmat/_oid.py index 8bd240d099a9..249b4dc857af 100644 --- a/src/cryptography/hazmat/_oid.py +++ b/src/cryptography/hazmat/_oid.py @@ -14,6 +14,7 @@ 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") + PRIVATE_KEY_USAGE_PERIOD = ObjectIdentifier("2.5.29.16") SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") @@ -155,6 +156,18 @@ class SignatureAlgorithmOID: } +class HashAlgorithmOID: + SHA1 = ObjectIdentifier("1.3.14.3.2.26") + SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.2.4") + SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.2.1") + SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.2.2") + SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.2.3") + SHA3_224 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.224") + SHA3_256 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.256") + SHA3_384 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.384") + SHA3_512 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.512") + + class PublicKeyAlgorithmOID: DSA = ObjectIdentifier("1.2.840.10040.4.1") EC_PUBLIC_KEY = ObjectIdentifier("1.2.840.10045.2.1") @@ -177,9 +190,20 @@ class ExtendedKeyUsageOID: 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") + BUNDLE_SECURITY = ObjectIdentifier("1.3.6.1.5.5.7.3.35") CERTIFICATE_TRANSPARENCY = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4") +class OtherNameFormOID: + PERMANENT_IDENTIFIER = ObjectIdentifier("1.3.6.1.5.5.7.8.3") + HW_MODULE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.4") + DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") + NAI_REALM = ObjectIdentifier("1.3.6.1.5.5.7.8.8") + SMTP_UTF8_MAILBOX = ObjectIdentifier("1.3.6.1.5.5.7.8.9") + ACP_NODE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.10") + BUNDLE_EID = ObjectIdentifier("1.3.6.1.5.5.7.8.11") + + 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") @@ -237,7 +261,7 @@ class AttributeOID: SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", - SignatureAlgorithmOID.RSASSA_PSS: "RSASSA-PSS", + SignatureAlgorithmOID.RSASSA_PSS: "rsassaPss", SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", @@ -257,10 +281,18 @@ class AttributeOID: SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" ), + HashAlgorithmOID.SHA1: "sha1", + HashAlgorithmOID.SHA224: "sha224", + HashAlgorithmOID.SHA256: "sha256", + HashAlgorithmOID.SHA384: "sha384", + HashAlgorithmOID.SHA512: "sha512", + HashAlgorithmOID.SHA3_224: "sha3_224", + HashAlgorithmOID.SHA3_256: "sha3_256", + HashAlgorithmOID.SHA3_384: "sha3_384", + HashAlgorithmOID.SHA3_512: "sha3_512", PublicKeyAlgorithmOID.DSA: "dsaEncryption", PublicKeyAlgorithmOID.EC_PUBLIC_KEY: "id-ecPublicKey", PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5: "rsaEncryption", - PublicKeyAlgorithmOID.RSASSA_PSS: "rsassaPss", PublicKeyAlgorithmOID.X25519: "X25519", PublicKeyAlgorithmOID.X448: "X448", ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", @@ -274,6 +306,7 @@ class AttributeOID: ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", ExtensionOID.KEY_USAGE: "keyUsage", + ExtensionOID.PRIVATE_KEY_USAGE_PERIOD: "privateKeyUsagePeriod", ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 78996848f391..361011a86c44 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -132,7 +132,19 @@ 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 - + if rust_openssl.CRYPTOGRAPHY_IS_AWSLC: + return isinstance( + algorithm, + ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA512_224, + hashes.SHA512_256, + ), + ) return self.hash_supported(algorithm) def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: @@ -169,14 +181,17 @@ def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: if isinstance(padding, PKCS1v15): return True elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): - # 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 + # FIPS 186-4 only allows salt length == digest length for PSS + # It is technically acceptable to set an explicit salt length + # equal to the digest length and this will incorrectly fail, but + # since we don't do that in the tests and this method is + # private, we'll ignore that until we need to do otherwise. + if ( + self._fips_enabled + and padding._salt_length != PSS.DIGEST_LENGTH ): - return True - else: - return self.hash_supported(padding._mgf._algorithm) + return False + return self.hash_supported(padding._mgf._algorithm) elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): return self._oaep_hash_supported( padding._mgf._algorithm @@ -236,7 +251,10 @@ def elliptic_curve_exchange_algorithm_supported( ) def dh_supported(self) -> bool: - return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + return ( + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) def dh_x942_serialization_supported(self) -> bool: return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 @@ -252,6 +270,7 @@ def x448_supported(self) -> bool: return ( not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC ) def ed25519_supported(self) -> bool: @@ -265,6 +284,7 @@ def ed448_supported(self) -> bool: return ( not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC ) def ecdsa_deterministic_supported(self) -> bool: @@ -279,7 +299,10 @@ def poly1305_supported(self) -> bool: return True def pkcs7_supported(self) -> bool: - return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + return ( + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) backend = Backend() diff --git a/src/cryptography/hazmat/bindings/_rust/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/__init__.pyi index 30b67d85597e..2f4eef4ead80 100644 --- a/src/cryptography/hazmat/bindings/_rust/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -5,21 +5,30 @@ import typing from cryptography.hazmat.primitives import padding - -def check_ansix923_padding(data: bytes) -> bool: ... +from cryptography.utils import Buffer class PKCS7PaddingContext(padding.PaddingContext): def __init__(self, block_size: int) -> None: ... - def update(self, data: bytes) -> bytes: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... def finalize(self) -> bytes: ... class PKCS7UnpaddingContext(padding.PaddingContext): def __init__(self, block_size: int) -> None: ... - def update(self, data: bytes) -> bytes: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... def finalize(self) -> bytes: ... class ObjectIdentifier: - def __init__(self, val: str) -> None: ... + def __init__(self, value: str) -> None: ... @property def dotted_string(self) -> str: ... @property diff --git a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi index e4321bec2ad2..103e96c1f117 100644 --- a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi +++ b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -3,7 +3,7 @@ # for complete details. import datetime -import typing +from collections.abc import Iterator from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization @@ -25,7 +25,7 @@ class OCSPRequest: class OCSPResponse: @property - def responses(self) -> typing.Iterator[OCSPSingleResponse]: ... + def responses(self) -> Iterator[OCSPSingleResponse]: ... @property def response_status(self) -> ocsp.OCSPResponseStatus: ... @property diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi index 320cef10250e..5fb3cb2403c0 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -47,9 +47,12 @@ __all__ = [ CRYPTOGRAPHY_IS_LIBRESSL: bool CRYPTOGRAPHY_IS_BORINGSSL: bool +CRYPTOGRAPHY_IS_AWSLC: bool CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: bool CRYPTOGRAPHY_OPENSSL_309_OR_GREATER: bool CRYPTOGRAPHY_OPENSSL_320_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_330_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_350_OR_GREATER: bool class Providers: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi index 047f49d819c1..831fcd1469d5 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi @@ -2,102 +2,106 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from collections.abc import Sequence + +from cryptography.utils import Buffer + class AESGCM: - def __init__(self, key: bytes) -> None: ... + def __init__(self, key: Buffer) -> None: ... @staticmethod - def generate_key(key_size: int) -> bytes: ... + def generate_key(bit_length: int) -> bytes: ... def encrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... def decrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... class ChaCha20Poly1305: - def __init__(self, key: bytes) -> None: ... + def __init__(self, key: Buffer) -> None: ... @staticmethod def generate_key() -> bytes: ... def encrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... def decrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... class AESCCM: - def __init__(self, key: bytes, tag_length: int = 16) -> None: ... + def __init__(self, key: Buffer, tag_length: int = 16) -> None: ... @staticmethod - def generate_key(key_size: int) -> bytes: ... + def generate_key(bit_length: int) -> bytes: ... def encrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... def decrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... class AESSIV: - def __init__(self, key: bytes) -> None: ... + def __init__(self, key: Buffer) -> None: ... @staticmethod - def generate_key(key_size: int) -> bytes: ... + def generate_key(bit_length: int) -> bytes: ... def encrypt( self, - data: bytes, - associated_data: list[bytes] | None, + data: Buffer, + associated_data: Sequence[Buffer] | None, ) -> bytes: ... def decrypt( self, - data: bytes, - associated_data: list[bytes] | None, + data: Buffer, + associated_data: Sequence[Buffer] | None, ) -> bytes: ... class AESOCB3: - def __init__(self, key: bytes) -> None: ... + def __init__(self, key: Buffer) -> None: ... @staticmethod - def generate_key(key_size: int) -> bytes: ... + def generate_key(bit_length: int) -> bytes: ... def encrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... def decrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... class AESGCMSIV: - def __init__(self, key: bytes) -> None: ... + def __init__(self, key: Buffer) -> None: ... @staticmethod - def generate_key(key_size: int) -> bytes: ... + def generate_key(bit_length: int) -> bytes: ... def encrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... def decrypt( self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, ) -> bytes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi index 759f3b591cba..a48fb017ff56 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi @@ -13,7 +13,7 @@ def create_encryption_ctx( ) -> ciphers.AEADEncryptionContext: ... @typing.overload def create_encryption_ctx( - algorithm: ciphers.CipherAlgorithm, mode: modes.Mode + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None ) -> ciphers.CipherContext: ... @typing.overload def create_decryption_ctx( @@ -21,7 +21,7 @@ def create_decryption_ctx( ) -> ciphers.AEADDecryptionContext: ... @typing.overload def create_decryption_ctx( - algorithm: ciphers.CipherAlgorithm, mode: modes.Mode + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None ) -> ciphers.CipherContext: ... def cipher_supported( algorithm: ciphers.CipherAlgorithm, mode: modes.Mode diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi index 5233f9a1d1c8..f85b3d1b2361 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi @@ -3,10 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.utils import Buffer class Ed25519PrivateKey: ... class Ed25519PublicKey: ... def generate_key() -> ed25519.Ed25519PrivateKey: ... -def from_private_bytes(data: bytes) -> ed25519.Ed25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> 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 index 7a06520380a0..c8ca0ecb156b 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi @@ -3,10 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import ed448 +from cryptography.utils import Buffer class Ed448PrivateKey: ... class Ed448PublicKey: ... def generate_key() -> ed448.Ed448PrivateKey: ... -def from_private_bytes(data: bytes) -> ed448.Ed448PrivateKey: ... +def from_private_bytes(data: Buffer) -> 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 index 56f317001629..6bfd295af889 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi @@ -5,6 +5,7 @@ import typing from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer class Hash(hashes.HashContext): def __init__( @@ -12,8 +13,16 @@ class Hash(hashes.HashContext): ) -> None: ... @property def algorithm(self) -> hashes.HashAlgorithm: ... - def update(self, data: bytes) -> None: ... + def update(self, data: Buffer) -> None: ... def finalize(self) -> bytes: ... def copy(self) -> Hash: ... def hash_supported(algorithm: hashes.HashAlgorithm) -> bool: ... + +class XOFHash: + def __init__(self, algorithm: hashes.ExtendableOutputFunction) -> None: ... + @property + def algorithm(self) -> hashes.ExtendableOutputFunction: ... + def update(self, data: Buffer) -> None: ... + def squeeze(self, length: int) -> bytes: ... + def copy(self) -> XOFHash: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi index e38d9b54d01b..3883d1b1a920 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi @@ -5,17 +5,18 @@ import typing from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer class HMAC(hashes.HashContext): def __init__( self, - key: bytes, + key: Buffer, algorithm: hashes.HashAlgorithm, backend: typing.Any = None, ) -> None: ... @property def algorithm(self) -> hashes.HashAlgorithm: ... - def update(self, data: bytes) -> None: ... + def update(self, data: Buffer) -> 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 index 4b90bb4f7744..9979e42db661 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi @@ -5,9 +5,10 @@ import typing from cryptography.hazmat.primitives.hashes import HashAlgorithm +from cryptography.utils import Buffer def derive_pbkdf2_hmac( - key_material: bytes, + key_material: Buffer, algorithm: HashAlgorithm, salt: bytes, iterations: int, @@ -24,7 +25,7 @@ class Scrypt: p: int, backend: typing.Any = None, ) -> None: ... - def derive(self, key_material: bytes) -> bytes: ... + def derive(self, key_material: Buffer) -> bytes: ... def verify(self, key_material: bytes, expected_key: bytes) -> None: ... class Argon2id: @@ -41,3 +42,8 @@ class Argon2id: ) -> None: ... def derive(self, key_material: bytes) -> bytes: ... def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi index 6815b7d9154b..404057e03740 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi @@ -8,16 +8,17 @@ from cryptography.hazmat.primitives.asymmetric.types import ( PrivateKeyTypes, PublicKeyTypes, ) +from cryptography.utils import Buffer def load_der_private_key( - data: bytes, + data: Buffer, password: bytes | None, backend: typing.Any = None, *, unsafe_skip_rsa_key_validation: bool = False, ) -> PrivateKeyTypes: ... def load_pem_private_key( - data: bytes, + data: Buffer, password: bytes | None, backend: typing.Any = None, *, diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi index 2e9b0a9e1254..45a2a39f6890 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi @@ -2,12 +2,14 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from cryptography.utils import Buffer + class Poly1305: - def __init__(self, key: bytes) -> None: ... + def __init__(self, key: Buffer) -> None: ... @staticmethod - def generate_tag(key: bytes, data: bytes) -> bytes: ... + def generate_tag(key: Buffer, data: Buffer) -> bytes: ... @staticmethod - def verify_tag(key: bytes, data: bytes, tag: bytes) -> None: ... - def update(self, data: bytes) -> None: ... + def verify_tag(key: Buffer, data: Buffer, tag: bytes) -> None: ... + def update(self, data: Buffer) -> 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 index da0f3ec588b9..38d2adddb101 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi @@ -3,10 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.utils import Buffer class X25519PrivateKey: ... class X25519PublicKey: ... def generate_key() -> x25519.X25519PrivateKey: ... -def from_private_bytes(data: bytes) -> x25519.X25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> 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 index e51cfebe15f6..3ac098091af5 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi @@ -3,10 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import x448 +from cryptography.utils import Buffer class X448PrivateKey: ... class X448PublicKey: ... def generate_key() -> x448.X448PrivateKey: ... -def from_private_bytes(data: bytes) -> x448.X448PrivateKey: ... +def from_private_bytes(data: Buffer) -> x448.X448PrivateKey: ... def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi index 40514c4623d5..b25becb6bdef 100644 --- a/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi +++ b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi @@ -3,6 +3,7 @@ # for complete details. import typing +from collections.abc import Iterable from cryptography import x509 from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes @@ -13,6 +14,7 @@ from cryptography.hazmat.primitives.serialization.pkcs12 import ( PKCS12KeyAndCertificates, PKCS12PrivateKeyTypes, ) +from cryptography.utils import Buffer class PKCS12Certificate: def __init__( @@ -24,8 +26,8 @@ class PKCS12Certificate: def certificate(self) -> x509.Certificate: ... def load_key_and_certificates( - data: bytes, - password: bytes | None, + data: Buffer, + password: Buffer | None, backend: typing.Any = None, ) -> tuple[ PrivateKeyTypes | None, @@ -37,10 +39,14 @@ def load_pkcs12( password: bytes | None, backend: typing.Any = None, ) -> PKCS12KeyAndCertificates: ... +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... def serialize_key_and_certificates( name: bytes | None, key: PKCS12PrivateKeyTypes | None, cert: x509.Certificate | None, - cas: typing.Iterable[x509.Certificate | PKCS12Certificate] | None, + cas: Iterable[x509.Certificate | PKCS12Certificate] | None, encryption_algorithm: KeySerializationEncryption, ) -> bytes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi index f9aa81ea0caf..358b135865a8 100644 --- a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi +++ b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -2,7 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import typing +from collections.abc import Iterable from cryptography import x509 from cryptography.hazmat.primitives import serialization @@ -15,31 +15,32 @@ def serialize_certificates( ) -> bytes: ... def encrypt_and_serialize( builder: pkcs7.PKCS7EnvelopeBuilder, + content_encryption_algorithm: pkcs7.ContentEncryptionAlgorithm, encoding: serialization.Encoding, - options: typing.Iterable[pkcs7.PKCS7Options], + options: Iterable[pkcs7.PKCS7Options], ) -> bytes: ... def sign_and_serialize( builder: pkcs7.PKCS7SignatureBuilder, encoding: serialization.Encoding, - options: typing.Iterable[pkcs7.PKCS7Options], + options: Iterable[pkcs7.PKCS7Options], ) -> bytes: ... def decrypt_der( data: bytes, certificate: x509.Certificate, private_key: rsa.RSAPrivateKey, - options: typing.Iterable[pkcs7.PKCS7Options], + options: Iterable[pkcs7.PKCS7Options], ) -> bytes: ... def decrypt_pem( data: bytes, certificate: x509.Certificate, private_key: rsa.RSAPrivateKey, - options: typing.Iterable[pkcs7.PKCS7Options], + options: Iterable[pkcs7.PKCS7Options], ) -> bytes: ... def decrypt_smime( data: bytes, certificate: x509.Certificate, private_key: rsa.RSAPrivateKey, - options: typing.Iterable[pkcs7.PKCS7Options], + options: Iterable[pkcs7.PKCS7Options], ) -> bytes: ... def load_pem_pkcs7_certificates( data: bytes, diff --git a/src/cryptography/hazmat/bindings/_rust/test_support.pyi b/src/cryptography/hazmat/bindings/_rust/test_support.pyi index ef9f779f2ee9..c6c6d0bbe129 100644 --- a/src/cryptography/hazmat/bindings/_rust/test_support.pyi +++ b/src/cryptography/hazmat/bindings/_rust/test_support.pyi @@ -5,6 +5,7 @@ from cryptography import x509 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import pkcs7 +from cryptography.utils import Buffer class TestCertificate: not_after_tag: int @@ -16,7 +17,7 @@ def test_parse_certificate(data: bytes) -> TestCertificate: ... def pkcs7_verify( encoding: serialization.Encoding, sig: bytes, - msg: bytes | None, + msg: Buffer | None, certs: list[x509.Certificate], options: list[pkcs7.PKCS7Options], ) -> None: ... diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi index b494fb61de3d..6ba9afb3da57 100644 --- a/src/cryptography/hazmat/bindings/_rust/x509.pyi +++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi @@ -4,6 +4,7 @@ import datetime import typing +from collections.abc import Iterator from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization @@ -44,18 +45,21 @@ def create_x509_certificate( private_key: PrivateKeyTypes, hash_algorithm: hashes.HashAlgorithm | None, rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, ) -> x509.Certificate: ... def create_x509_csr( builder: x509.CertificateSigningRequestBuilder, private_key: PrivateKeyTypes, hash_algorithm: hashes.HashAlgorithm | None, rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, ) -> x509.CertificateSigningRequest: ... def create_x509_crl( builder: x509.CertificateRevocationListBuilder, private_key: PrivateKeyTypes, hash_algorithm: hashes.HashAlgorithm | None, rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, ) -> x509.CertificateRevocationList: ... class Sct: @@ -108,7 +112,7 @@ class Certificate: @property def signature_algorithm_parameters( self, - ) -> None | PSS | PKCS1v15 | ECDSA: ... + ) -> PSS | PKCS1v15 | ECDSA | None: ... @property def extensions(self) -> x509.Extensions: ... @property @@ -129,7 +133,7 @@ class CertificateRevocationList: def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... def get_revoked_certificate_by_serial_number( self, serial_number: int - ) -> RevokedCertificate | None: ... + ) -> x509.RevokedCertificate | None: ... @property def signature_hash_algorithm( self, @@ -139,7 +143,7 @@ class CertificateRevocationList: @property def signature_algorithm_parameters( self, - ) -> None | PSS | PKCS1v15 | ECDSA: ... + ) -> PSS | PKCS1v15 | ECDSA | None: ... @property def issuer(self) -> x509.Name: ... @property @@ -162,7 +166,7 @@ class CertificateRevocationList: def __getitem__(self, idx: int) -> x509.RevokedCertificate: ... @typing.overload def __getitem__(self, idx: slice) -> list[x509.RevokedCertificate]: ... - def __iter__(self) -> typing.Iterator[x509.RevokedCertificate]: ... + def __iter__(self) -> Iterator[x509.RevokedCertificate]: ... def is_signature_valid( self, public_key: CertificateIssuerPublicKeyTypes ) -> bool: ... @@ -182,7 +186,7 @@ class CertificateSigningRequest: @property def signature_algorithm_parameters( self, - ) -> None | PSS | PKCS1v15 | ECDSA: ... + ) -> PSS | PKCS1v15 | ECDSA | None: ... @property def extensions(self) -> x509.Extensions: ... @property @@ -197,14 +201,73 @@ class CertificateSigningRequest: def get_attribute_for_oid(self, oid: x509.ObjectIdentifier) -> bytes: ... class PolicyBuilder: - def time(self, new_time: datetime.datetime) -> PolicyBuilder: ... - def store(self, new_store: Store) -> PolicyBuilder: ... - def max_chain_depth(self, new_max_chain_depth: int) -> PolicyBuilder: ... + def time(self, time: datetime.datetime) -> PolicyBuilder: ... + def store(self, store: Store) -> PolicyBuilder: ... + def max_chain_depth(self, max_chain_depth: int) -> PolicyBuilder: ... + def extension_policies( + self, *, ca_policy: ExtensionPolicy, ee_policy: ExtensionPolicy + ) -> PolicyBuilder: ... def build_client_verifier(self) -> ClientVerifier: ... def build_server_verifier( self, subject: x509.verification.Subject ) -> ServerVerifier: ... +class Policy: + @property + def max_chain_depth(self) -> int: ... + @property + def subject(self) -> x509.verification.Subject | None: ... + @property + def validation_time(self) -> datetime.datetime: ... + @property + def extended_key_usage(self) -> x509.ObjectIdentifier: ... + @property + def minimum_rsa_modulus(self) -> int: ... + +class Criticality: + CRITICAL: Criticality + AGNOSTIC: Criticality + NON_CRITICAL: Criticality + +T = typing.TypeVar("T", contravariant=True, bound=x509.ExtensionType) + +MaybeExtensionValidatorCallback = typing.Callable[ + [ + Policy, + x509.Certificate, + T | None, + ], + None, +] + +PresentExtensionValidatorCallback = typing.Callable[ + [Policy, x509.Certificate, T], + None, +] + +class ExtensionPolicy: + @staticmethod + def permit_all() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ca() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ee() -> ExtensionPolicy: ... + def require_not_present( + self, extension_type: type[x509.ExtensionType] + ) -> ExtensionPolicy: ... + def may_be_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: MaybeExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + def require_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: PresentExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + class VerifiedClient: @property def subjects(self) -> list[x509.GeneralName] | None: ... @@ -213,11 +276,13 @@ class VerifiedClient: class ClientVerifier: @property - def validation_time(self) -> datetime.datetime: ... + def policy(self) -> Policy: ... @property - def store(self) -> Store: ... + def validation_time(self) -> datetime.datetime: ... @property def max_chain_depth(self) -> int: ... + @property + def store(self) -> Store: ... def verify( self, leaf: x509.Certificate, @@ -225,14 +290,16 @@ class ClientVerifier: ) -> VerifiedClient: ... class ServerVerifier: + @property + def policy(self) -> Policy: ... @property def subject(self) -> x509.verification.Subject: ... @property def validation_time(self) -> datetime.datetime: ... @property - def store(self) -> Store: ... - @property def max_chain_depth(self) -> int: ... + @property + def store(self) -> Store: ... def verify( self, leaf: x509.Certificate, diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index 73c06f7d08ce..c90c916dcd08 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -64,8 +64,13 @@ def cryptography_has_custom_ext() -> list[str]: def cryptography_has_tlsv13_functions() -> list[str]: return [ - "SSL_VERIFY_POST_HANDSHAKE", "SSL_CTX_set_ciphersuites", + ] + + +def cryptography_has_tlsv13_hs_functions() -> list[str]: + return [ + "SSL_VERIFY_POST_HANDSHAKE", "SSL_verify_client_post_handshake", "SSL_CTX_set_post_handshake_auth", "SSL_set_post_handshake_auth", @@ -164,6 +169,9 @@ def cryptography_has_get_extms_support() -> list[str]: "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, + "Cryptography_HAS_TLSv1_3_HS_FUNCTIONS": ( + cryptography_has_tlsv13_hs_functions + ), "Cryptography_HAS_ENGINE": cryptography_has_engine, "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index d4dfeef485d1..7de2c65356bb 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -10,6 +10,7 @@ import types import typing import warnings +from collections.abc import Callable import cryptography from cryptography.exceptions import InternalError @@ -35,7 +36,7 @@ def _openssl_assert(ok: bool) -> None: def build_conditional_library( lib: typing.Any, - conditional_names: dict[str, typing.Callable[[], list[str]]], + conditional_names: dict[str, Callable[[], list[str]]], ) -> typing.Any: conditional_lib = types.ModuleType("lib") conditional_lib._original_lib = lib # type: ignore[attr-defined] diff --git a/src/cryptography/hazmat/decrepit/ciphers/algorithms.py b/src/cryptography/hazmat/decrepit/ciphers/algorithms.py index a7d4aa3c5d87..072a99143d6e 100644 --- a/src/cryptography/hazmat/decrepit/ciphers/algorithms.py +++ b/src/cryptography/hazmat/decrepit/ciphers/algorithms.py @@ -40,6 +40,11 @@ def key_size(self) -> int: return len(self.key) * 8 +# Not actually supported, marker for tests +class _DES: + key_size = 64 + + class Blowfish(BlockCipherAlgorithm): name = "Blowfish" block_size = 64 diff --git a/src/cryptography/hazmat/primitives/_cipheralgorithm.py b/src/cryptography/hazmat/primitives/_cipheralgorithm.py index 588a61698fdc..305a9fd3179c 100644 --- a/src/cryptography/hazmat/primitives/_cipheralgorithm.py +++ b/src/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -36,7 +36,7 @@ def key_size(self) -> int: class BlockCipherAlgorithm(CipherAlgorithm): - key: bytes + key: utils.Buffer @property @abc.abstractmethod @@ -46,7 +46,9 @@ def block_size(self) -> int: """ -def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: +def _verify_key_size( + algorithm: CipherAlgorithm, key: utils.Buffer +) -> utils.Buffer: # Verify that the key is instance of bytes utils._check_byteslike("key", key) diff --git a/src/cryptography/hazmat/primitives/_serialization.py b/src/cryptography/hazmat/primitives/_serialization.py index 46157721970b..e998865cdd97 100644 --- a/src/cryptography/hazmat/primitives/_serialization.py +++ b/src/cryptography/hazmat/primitives/_serialization.py @@ -126,8 +126,7 @@ def key_cert_algorithm( ) -> KeySerializationEncryptionBuilder: if self._format is not PrivateFormat.PKCS12: raise TypeError( - "key_cert_algorithm only supported with " - "PrivateFormat.PKCS12" + "key_cert_algorithm only supported with PrivateFormat.PKCS12" ) if self._key_cert_algorithm is not None: raise ValueError("key_cert_algorithm already set") diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py index 31c9748a91cd..1822e99dcda8 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -81,6 +81,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> DHPublicKey: + """ + Returns a copy. + """ + DHPublicKeyWithSerialization = DHPublicKey DHPublicKey.register(rust_openssl.dh.DHPublicKey) @@ -130,6 +136,12 @@ def private_bytes( Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> DHPrivateKey: + """ + Returns a copy. + """ + 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 6dd34c0e09b0..21d78ba95e03 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -10,6 +10,7 @@ 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.utils import Buffer class DSAParameters(metaclass=abc.ABCMeta): @@ -53,7 +54,7 @@ def parameters(self) -> DSAParameters: @abc.abstractmethod def sign( self, - data: bytes, + data: Buffer, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> bytes: """ @@ -77,6 +78,12 @@ def private_bytes( Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> DSAPrivateKey: + """ + Returns a copy. + """ + DSAPrivateKeyWithSerialization = DSAPrivateKey DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey) @@ -115,8 +122,8 @@ def public_bytes( @abc.abstractmethod def verify( self, - signature: bytes, - data: bytes, + signature: Buffer, + data: Buffer, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> None: """ @@ -129,6 +136,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> DSAPublicKey: + """ + Returns a copy. + """ + DSAPublicKeyWithSerialization = DSAPublicKey DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index da1fbea13a6e..a13d9827f44a 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -52,6 +52,13 @@ def key_size(self) -> int: Bit size of a secret scalar for the curve. """ + @property + @abc.abstractmethod + def group_order(self) -> int: + """ + The order of the curve's group. + """ + class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): @property @@ -97,7 +104,7 @@ def key_size(self) -> int: @abc.abstractmethod def sign( self, - data: bytes, + data: utils.Buffer, signature_algorithm: EllipticCurveSignatureAlgorithm, ) -> bytes: """ @@ -121,6 +128,12 @@ def private_bytes( Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePrivateKey: + """ + Returns a copy. + """ + EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey EllipticCurvePrivateKey.register(rust_openssl.ec.ECPrivateKey) @@ -160,8 +173,8 @@ def public_bytes( @abc.abstractmethod def verify( self, - signature: bytes, - data: bytes, + signature: utils.Buffer, + data: utils.Buffer, signature_algorithm: EllipticCurveSignatureAlgorithm, ) -> None: """ @@ -188,6 +201,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePublicKey: + """ + Returns a copy. + """ + EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey EllipticCurvePublicKey.register(rust_openssl.ec.ECPublicKey) @@ -199,96 +218,121 @@ def __eq__(self, other: object) -> bool: class SECT571R1(EllipticCurve): name = "sect571r1" key_size = 570 + group_order = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47 # noqa: E501 class SECT409R1(EllipticCurve): name = "sect409r1" key_size = 409 + group_order = 0x10000000000000000000000000000000000000000000000000001E2AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173 # noqa: E501 class SECT283R1(EllipticCurve): name = "sect283r1" key_size = 283 + group_order = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307 # noqa: E501 class SECT233R1(EllipticCurve): name = "sect233r1" key_size = 233 + group_order = 0x1000000000000000000000000000013E974E72F8A6922031D2603CFE0D7 class SECT163R2(EllipticCurve): name = "sect163r2" key_size = 163 + group_order = 0x40000000000000000000292FE77E70C12A4234C33 class SECT571K1(EllipticCurve): name = "sect571k1" key_size = 571 + group_order = 0x20000000000000000000000000000000000000000000000000000000000000000000000131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001 # noqa: E501 class SECT409K1(EllipticCurve): name = "sect409k1" key_size = 409 + group_order = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF # noqa: E501 class SECT283K1(EllipticCurve): name = "sect283k1" key_size = 283 + group_order = 0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61 # noqa: E501 class SECT233K1(EllipticCurve): name = "sect233k1" key_size = 233 + group_order = 0x8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF class SECT163K1(EllipticCurve): name = "sect163k1" key_size = 163 + group_order = 0x4000000000000000000020108A2E0CC0D99F8A5EF class SECP521R1(EllipticCurve): name = "secp521r1" key_size = 521 + group_order = 0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409 # noqa: E501 class SECP384R1(EllipticCurve): name = "secp384r1" key_size = 384 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973 # noqa: E501 class SECP256R1(EllipticCurve): name = "secp256r1" key_size = 256 + group_order = ( + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + ) class SECP256K1(EllipticCurve): name = "secp256k1" key_size = 256 + group_order = ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + ) class SECP224R1(EllipticCurve): name = "secp224r1" key_size = 224 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D class SECP192R1(EllipticCurve): name = "secp192r1" key_size = 192 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831 class BrainpoolP256R1(EllipticCurve): name = "brainpoolP256r1" key_size = 256 + group_order = ( + 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7 + ) class BrainpoolP384R1(EllipticCurve): name = "brainpoolP384r1" key_size = 384 + group_order = 0x8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565 # noqa: E501 class BrainpoolP512R1(EllipticCurve): name = "brainpoolP512r1" key_size = 512 + group_order = 0xAADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069 # noqa: E501 _CURVE_TYPES: dict[str, EllipticCurve] = { diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py index 3a26185d7dbc..e576dc9f42f1 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -9,6 +9,7 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class Ed25519PublicKey(metaclass=abc.ABCMeta): @@ -42,7 +43,7 @@ def public_bytes_raw(self) -> bytes: """ @abc.abstractmethod - def verify(self, signature: bytes, data: bytes) -> None: + def verify(self, signature: Buffer, data: Buffer) -> None: """ Verify the signature. """ @@ -53,6 +54,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> Ed25519PublicKey: + """ + Returns a copy. + """ + Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) @@ -71,7 +78,7 @@ def generate(cls) -> Ed25519PrivateKey: return rust_openssl.ed25519.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> Ed25519PrivateKey: + def from_private_bytes(cls, data: Buffer) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -107,10 +114,16 @@ def private_bytes_raw(self) -> bytes: """ @abc.abstractmethod - def sign(self, data: bytes) -> bytes: + def sign(self, data: Buffer) -> bytes: """ Signs the data. """ + @abc.abstractmethod + def __copy__(self) -> Ed25519PrivateKey: + """ + Returns a copy. + """ + 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 78c82c4a3c45..89db20984fd6 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -9,6 +9,7 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class Ed448PublicKey(metaclass=abc.ABCMeta): @@ -42,7 +43,7 @@ def public_bytes_raw(self) -> bytes: """ @abc.abstractmethod - def verify(self, signature: bytes, data: bytes) -> None: + def verify(self, signature: Buffer, data: Buffer) -> None: """ Verify the signature. """ @@ -53,6 +54,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> Ed448PublicKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "ed448"): Ed448PublicKey.register(rust_openssl.ed448.Ed448PublicKey) @@ -72,7 +79,7 @@ def generate(cls) -> Ed448PrivateKey: return rust_openssl.ed448.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> Ed448PrivateKey: + def from_private_bytes(cls, data: Buffer) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -90,7 +97,7 @@ def public_key(self) -> Ed448PublicKey: """ @abc.abstractmethod - def sign(self, data: bytes) -> bytes: + def sign(self, data: Buffer) -> bytes: """ Signs the data. """ @@ -113,6 +120,12 @@ def private_bytes_raw(self) -> bytes: Equivalent to private_bytes(Raw, Raw, NoEncryption()). """ + @abc.abstractmethod + def __copy__(self) -> Ed448PrivateKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "x448"): Ed448PrivateKey.register(rust_openssl.ed448.Ed448PrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index 905068e3b8cc..2e192aaaa749 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -63,6 +63,12 @@ def private_bytes( Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> RSAPrivateKey: + """ + Returns a copy. + """ + RSAPrivateKeyWithSerialization = RSAPrivateKey RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey) @@ -127,6 +133,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> RSAPublicKey: + """ + Returns a copy. + """ + RSAPublicKeyWithSerialization = RSAPublicKey RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey) @@ -223,6 +235,8 @@ def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: no more than two factors. This function is adapted from code in PyCrypto. """ # reject invalid values early + if d <= 1 or e <= 1: + raise ValueError("d, e can't be <= 1") if 17 != pow(17, e * d, n): raise ValueError("n, d, e don't match") # See 8.2.2(i) in Handbook of Applied Cryptography. diff --git a/src/cryptography/hazmat/primitives/asymmetric/x25519.py b/src/cryptography/hazmat/primitives/asymmetric/x25519.py index 0cfa36e346ad..a4993766b076 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -9,6 +9,7 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class X25519PublicKey(metaclass=abc.ABCMeta): @@ -47,6 +48,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> X25519PublicKey: + """ + Returns a copy. + """ + X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) @@ -64,7 +71,7 @@ def generate(cls) -> X25519PrivateKey: return rust_openssl.x25519.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> X25519PrivateKey: + def from_private_bytes(cls, data: Buffer) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -105,5 +112,11 @@ def exchange(self, peer_public_key: X25519PublicKey) -> bytes: Performs a key exchange operation using the provided peer's public key. """ + @abc.abstractmethod + def __copy__(self) -> X25519PrivateKey: + """ + Returns a copy. + """ + 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 86086ab44855..c6fd71ba5003 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x448.py @@ -9,6 +9,7 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class X448PublicKey(metaclass=abc.ABCMeta): @@ -47,6 +48,12 @@ def __eq__(self, other: object) -> bool: Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> X448PublicKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "x448"): X448PublicKey.register(rust_openssl.x448.X448PublicKey) @@ -66,7 +73,7 @@ def generate(cls) -> X448PrivateKey: return rust_openssl.x448.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> X448PrivateKey: + def from_private_bytes(cls, data: Buffer) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -107,6 +114,12 @@ def exchange(self, peer_public_key: X448PublicKey) -> bytes: Performs a key exchange operation using the provided peer's public key. """ + @abc.abstractmethod + def __copy__(self) -> X448PrivateKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "x448"): X448PrivateKey.register(rust_openssl.x448.X448PrivateKey) diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py index f9fa8a587ea5..9d650045b0fe 100644 --- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -36,7 +36,7 @@ class AES(BlockCipherAlgorithm): # 512 added to support AES-256-XTS, which uses 512-bit keys key_sizes = frozenset([128, 192, 256, 512]) - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @property @@ -50,7 +50,7 @@ class AES128(BlockCipherAlgorithm): key_sizes = frozenset([128]) key_size = 128 - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @@ -60,7 +60,7 @@ class AES256(BlockCipherAlgorithm): key_sizes = frozenset([256]) key_size = 256 - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @@ -69,7 +69,7 @@ class Camellia(BlockCipherAlgorithm): block_size = 128 key_sizes = frozenset([128, 192, 256]) - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @property @@ -152,7 +152,7 @@ class ChaCha20(CipherAlgorithm): name = "ChaCha20" key_sizes = frozenset([256]) - def __init__(self, key: bytes, nonce: bytes): + def __init__(self, key: utils.Buffer, nonce: utils.Buffer): self.key = _verify_key_size(self, key) utils._check_byteslike("nonce", nonce) @@ -162,7 +162,7 @@ def __init__(self, key: bytes, nonce: bytes): self._nonce = nonce @property - def nonce(self) -> bytes: + def nonce(self) -> utils.Buffer: return self._nonce @property diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index ebfa8052c8da..24fceea236cb 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -10,18 +10,19 @@ from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm from cryptography.hazmat.primitives.ciphers import modes +from cryptography.utils import Buffer class CipherContext(metaclass=abc.ABCMeta): @abc.abstractmethod - def update(self, data: bytes) -> bytes: + def update(self, data: Buffer) -> bytes: """ Processes the provided bytes through the cipher and returns the results as bytes. """ @abc.abstractmethod - def update_into(self, data: bytes, buf: bytes) -> int: + def update_into(self, data: Buffer, buf: Buffer) -> int: """ Processes the provided bytes and writes the resulting data into the provided buffer. Returns the number of bytes written. @@ -44,7 +45,7 @@ def reset_nonce(self, nonce: bytes) -> None: class AEADCipherContext(CipherContext, metaclass=abc.ABCMeta): @abc.abstractmethod - def authenticate_additional_data(self, data: bytes) -> None: + def authenticate_additional_data(self, data: Buffer) -> None: """ Authenticates the provided bytes. """ @@ -134,9 +135,9 @@ def decryptor(self): typing.Union[ modes.ModeWithNonce, modes.ModeWithTweak, - None, modes.ECB, modes.ModeWithInitializationVector, + None, ] ] diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index 1dd2cc1e80c3..36c555c68a90 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -34,7 +34,7 @@ def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: """ The value of the initialization vector for this mode as bytes. """ @@ -43,7 +43,7 @@ def initialization_vector(self) -> bytes: class ModeWithTweak(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def tweak(self) -> bytes: + def tweak(self) -> utils.Buffer: """ The value of the tweak for this mode as bytes. """ @@ -52,7 +52,7 @@ def tweak(self) -> bytes: class ModeWithNonce(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def nonce(self) -> bytes: + def nonce(self) -> utils.Buffer: """ The value of the nonce for this mode as bytes. """ @@ -83,7 +83,7 @@ def _check_iv_length( def _check_nonce_length( - nonce: bytes, name: str, algorithm: CipherAlgorithm + nonce: utils.Buffer, name: str, algorithm: CipherAlgorithm ) -> None: if not isinstance(algorithm, BlockCipherAlgorithm): raise UnsupportedAlgorithm( @@ -109,12 +109,12 @@ def _check_iv_and_key_length( class CBC(ModeWithInitializationVector): name = "CBC" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -123,7 +123,7 @@ def initialization_vector(self) -> bytes: class XTS(ModeWithTweak): name = "XTS" - def __init__(self, tweak: bytes): + def __init__(self, tweak: utils.Buffer): utils._check_byteslike("tweak", tweak) if len(tweak) != 16: @@ -132,7 +132,7 @@ def __init__(self, tweak: bytes): self._tweak = tweak @property - def tweak(self) -> bytes: + def tweak(self) -> utils.Buffer: return self._tweak def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: @@ -158,12 +158,12 @@ class ECB(Mode): class OFB(ModeWithInitializationVector): name = "OFB" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -172,12 +172,12 @@ def initialization_vector(self) -> bytes: class CFB(ModeWithInitializationVector): name = "CFB" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -186,12 +186,12 @@ def initialization_vector(self) -> bytes: class CFB8(ModeWithInitializationVector): name = "CFB8" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -200,12 +200,12 @@ def initialization_vector(self) -> bytes: class CTR(ModeWithNonce): name = "CTR" - def __init__(self, nonce: bytes): + def __init__(self, nonce: utils.Buffer): utils._check_byteslike("nonce", nonce) self._nonce = nonce @property - def nonce(self) -> bytes: + def nonce(self) -> utils.Buffer: return self._nonce def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: @@ -220,7 +220,7 @@ class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): def __init__( self, - initialization_vector: bytes, + initialization_vector: utils.Buffer, tag: bytes | None = None, min_tag_length: int = 16, ): @@ -250,7 +250,7 @@ def tag(self) -> bytes | None: return self._tag @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py index b819e399287e..4b55ec33dbff 100644 --- a/src/cryptography/hazmat/primitives/hashes.py +++ b/src/cryptography/hazmat/primitives/hashes.py @@ -7,6 +7,7 @@ import abc from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.utils import Buffer __all__ = [ "MD5", @@ -30,6 +31,7 @@ "Hash", "HashAlgorithm", "HashContext", + "XOFHash", ] @@ -66,7 +68,7 @@ def algorithm(self) -> HashAlgorithm: """ @abc.abstractmethod - def update(self, data: bytes) -> None: + def update(self, data: Buffer) -> None: """ Processes the provided bytes through the hash. """ @@ -87,6 +89,8 @@ def copy(self) -> HashContext: Hash = rust_openssl.hashes.Hash HashContext.register(Hash) +XOFHash = rust_openssl.hashes.XOFHash + class ExtendableOutputFunction(metaclass=abc.ABCMeta): """ diff --git a/src/cryptography/hazmat/primitives/kdf/concatkdf.py b/src/cryptography/hazmat/primitives/kdf/concatkdf.py index 96d9d4c0df5e..1b928415c5c1 100644 --- a/src/cryptography/hazmat/primitives/kdf/concatkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -5,6 +5,7 @@ from __future__ import annotations import typing +from collections.abc import Callable from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidKey @@ -29,9 +30,9 @@ def _common_args_checks( def _concatkdf_derive( - key_material: bytes, + key_material: utils.Buffer, length: int, - auxfn: typing.Callable[[], hashes.HashContext], + auxfn: Callable[[], hashes.HashContext], otherinfo: bytes, ) -> bytes: utils._check_byteslike("key_material", key_material) @@ -69,7 +70,7 @@ def __init__( def _hash(self) -> hashes.Hash: return hashes.Hash(self._algorithm) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized self._used = True @@ -111,7 +112,7 @@ def __init__( def _hmac(self) -> hmac.HMAC: return hmac.HMAC(self._salt, self._algorithm) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized self._used = True diff --git a/src/cryptography/hazmat/primitives/kdf/hkdf.py b/src/cryptography/hazmat/primitives/kdf/hkdf.py index ee562d2f4433..ce11c54baa31 100644 --- a/src/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/hkdf.py @@ -32,12 +32,12 @@ def __init__( self._hkdf_expand = HKDFExpand(self._algorithm, length, info) - def _extract(self, key_material: bytes) -> bytes: + def _extract(self, key_material: utils.Buffer) -> bytes: h = hmac.HMAC(self._salt, self._algorithm) h.update(key_material) return h.finalize() - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: utils._check_byteslike("key_material", key_material) return self._hkdf_expand.derive(self._extract(key_material)) @@ -74,7 +74,7 @@ def __init__( self._used = False - def _expand(self, key_material: bytes) -> bytes: + def _expand(self, key_material: utils.Buffer) -> bytes: output = [b""] counter = 1 @@ -88,7 +88,7 @@ def _expand(self, key_material: bytes) -> bytes: return b"".join(output)[: self._length] - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: utils._check_byteslike("key_material", key_material) if self._used: raise AlreadyFinalized diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py index 802b484c72ae..d6c3ff383bb6 100644 --- a/src/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -5,6 +5,7 @@ from __future__ import annotations import typing +from collections.abc import Callable from cryptography import utils from cryptography.exceptions import ( @@ -36,7 +37,7 @@ class CounterLocation(utils.Enum): class _KBKDFDeriver: def __init__( self, - prf: typing.Callable, + prf: Callable, mode: Mode, length: int, rlen: int, @@ -120,7 +121,9 @@ def _valid_byte_length(value: int) -> bool: return False return True - def derive(self, key_material: bytes, prf_output_size: int) -> bytes: + def derive( + self, key_material: utils.Buffer, prf_output_size: int + ) -> bytes: if self._used: raise AlreadyFinalized @@ -227,7 +230,7 @@ def __init__( def _prf(self, key_material: bytes) -> hmac.HMAC: return hmac.HMAC(key_material, self._algorithm) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: return self._deriver.derive(key_material, self._algorithm.digest_size) def verify(self, key_material: bytes, expected_key: bytes) -> None: @@ -280,7 +283,7 @@ def _prf(self, _: bytes) -> cmac.CMAC: return cmac.CMAC(self._cipher) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: self._cipher = self._algorithm(key_material) assert self._cipher is not None diff --git a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py index 82689ebca4ae..d539f1317556 100644 --- a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -43,7 +43,7 @@ def __init__( self._salt = salt self._iterations = iterations - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized("PBKDF2 instances can only be used once.") self._used = True diff --git a/src/cryptography/hazmat/primitives/kdf/x963kdf.py b/src/cryptography/hazmat/primitives/kdf/x963kdf.py index 6e38366a996f..63870cdc1f5f 100644 --- a/src/cryptography/hazmat/primitives/kdf/x963kdf.py +++ b/src/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -35,7 +35,7 @@ def __init__( self._sharedinfo = sharedinfo self._used = False - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized self._used = True diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index b2a3f1cfffaa..f9cd1f13c469 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -5,20 +5,19 @@ from __future__ import annotations import abc -import typing from cryptography import utils -from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.bindings._rust import ( + ANSIX923PaddingContext, + ANSIX923UnpaddingContext, PKCS7PaddingContext, PKCS7UnpaddingContext, - check_ansix923_padding, ) class PaddingContext(metaclass=abc.ABCMeta): @abc.abstractmethod - def update(self, data: bytes) -> bytes: + def update(self, data: utils.Buffer) -> bytes: """ Pads the provided bytes and returns any available data as bytes. """ @@ -38,74 +37,6 @@ def _byte_padding_check(block_size: int) -> None: raise ValueError("block_size must be a multiple of 8.") -def _byte_padding_update( - buffer_: bytes | None, data: bytes, block_size: int -) -> tuple[bytes, bytes]: - if buffer_ is None: - raise AlreadyFinalized("Context was already finalized.") - - utils._check_byteslike("data", data) - - buffer_ += bytes(data) - - finished_blocks = len(buffer_) // (block_size // 8) - - result = buffer_[: finished_blocks * (block_size // 8)] - buffer_ = buffer_[finished_blocks * (block_size // 8) :] - - return buffer_, result - - -def _byte_padding_pad( - buffer_: bytes | None, - block_size: int, - paddingfn: typing.Callable[[int], bytes], -) -> bytes: - if buffer_ is None: - raise AlreadyFinalized("Context was already finalized.") - - pad_size = block_size // 8 - len(buffer_) - return buffer_ + paddingfn(pad_size) - - -def _byte_unpadding_update( - buffer_: bytes | None, data: bytes, block_size: int -) -> tuple[bytes, bytes]: - if buffer_ is None: - raise AlreadyFinalized("Context was already finalized.") - - utils._check_byteslike("data", data) - - buffer_ += bytes(data) - - finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0) - - result = buffer_[: finished_blocks * (block_size // 8)] - buffer_ = buffer_[finished_blocks * (block_size // 8) :] - - return buffer_, result - - -def _byte_unpadding_check( - buffer_: bytes | None, - 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_) - - if not valid: - raise ValueError("Invalid padding bytes.") - - pad_size = buffer_[-1] - return buffer_[:-pad_size] - - class PKCS7: def __init__(self, block_size: int): _byte_padding_check(block_size) @@ -128,56 +59,11 @@ def __init__(self, block_size: int): self.block_size = block_size def padder(self) -> PaddingContext: - return _ANSIX923PaddingContext(self.block_size) + return ANSIX923PaddingContext(self.block_size) def unpadder(self) -> PaddingContext: - return _ANSIX923UnpaddingContext(self.block_size) - - -class _ANSIX923PaddingContext(PaddingContext): - _buffer: bytes | None - - 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: bytes) -> bytes: - self._buffer, result = _byte_padding_update( - self._buffer, data, self.block_size - ) - return result + return ANSIX923UnpaddingContext(self.block_size) - def _padding(self, size: int) -> bytes: - return bytes([0]) * (size - 1) + bytes([size]) - def finalize(self) -> bytes: - result = _byte_padding_pad( - self._buffer, self.block_size, self._padding - ) - self._buffer = None - return result - - -class _ANSIX923UnpaddingContext(PaddingContext): - _buffer: bytes | None - - 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: bytes) -> bytes: - self._buffer, result = _byte_unpadding_update( - self._buffer, data, self.block_size - ) - return result - - def finalize(self) -> bytes: - result = _byte_unpadding_check( - self._buffer, - self.block_size, - check_ansix923_padding, - ) - self._buffer = None - return result +PaddingContext.register(ANSIX923PaddingContext) +PaddingContext.register(ANSIX923UnpaddingContext) diff --git a/src/cryptography/hazmat/primitives/serialization/__init__.py b/src/cryptography/hazmat/primitives/serialization/__init__.py index 07b2264b9a51..62283cc70fd6 100644 --- a/src/cryptography/hazmat/primitives/serialization/__init__.py +++ b/src/cryptography/hazmat/primitives/serialization/__init__.py @@ -33,6 +33,7 @@ load_ssh_private_key, load_ssh_public_identity, load_ssh_public_key, + ssh_key_fingerprint, ) __all__ = [ @@ -60,4 +61,5 @@ "load_ssh_private_key", "load_ssh_public_identity", "load_ssh_public_key", + "ssh_key_fingerprint", ] diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py index 549e1f992d39..58884ff61a79 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -5,6 +5,7 @@ from __future__ import annotations import typing +from collections.abc import Iterable from cryptography import x509 from cryptography.hazmat.bindings._rust import pkcs12 as rust_pkcs12 @@ -26,6 +27,7 @@ "PKCS12PrivateKeyTypes", "load_key_and_certificates", "load_pkcs12", + "serialize_java_truststore", "serialize_key_and_certificates", ] @@ -118,11 +120,29 @@ def __repr__(self) -> str: ] +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: serialization.KeySerializationEncryption, +) -> bytes: + if not certs: + raise ValueError("You must supply at least one cert") + + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): + raise TypeError( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + + return rust_pkcs12.serialize_java_truststore(certs, encryption_algorithm) + + def serialize_key_and_certificates( name: bytes | None, key: PKCS12PrivateKeyTypes | None, cert: x509.Certificate | None, - cas: typing.Iterable[_PKCS12CATypes] | None, + cas: Iterable[_PKCS12CATypes] | None, encryption_algorithm: serialization.KeySerializationEncryption, ) -> bytes: if key is not None and not isinstance( diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py index 882e345f2e7f..456dc5b0831c 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -10,12 +10,16 @@ import email.policy import io import typing +from collections.abc import Iterable from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa +from cryptography.hazmat.primitives.ciphers import ( + algorithms, +) from cryptography.utils import _check_byteslike load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates @@ -35,6 +39,10 @@ rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey ] +ContentEncryptionAlgorithm = typing.Union[ + typing.Type[algorithms.AES128], typing.Type[algorithms.AES256] +] + class PKCS7Options(utils.Enum): Text = "Add text/plain MIME type" @@ -48,7 +56,7 @@ class PKCS7Options(utils.Enum): class PKCS7SignatureBuilder: def __init__( self, - data: bytes | None = None, + data: utils.Buffer | None = None, signers: list[ tuple[ x509.Certificate, @@ -63,7 +71,7 @@ def __init__( self._signers = signers self._additional_certs = additional_certs - def set_data(self, data: bytes) -> PKCS7SignatureBuilder: + def set_data(self, data: utils.Buffer) -> PKCS7SignatureBuilder: _check_byteslike("data", data) if self._data is not None: raise ValueError("data may only be set once") @@ -126,7 +134,7 @@ def add_certificate( def sign( self, encoding: serialization.Encoding, - options: typing.Iterable[PKCS7Options], + options: Iterable[PKCS7Options], backend: typing.Any = None, ) -> bytes: if len(self._signers) == 0: @@ -184,6 +192,8 @@ def __init__( *, _data: bytes | None = None, _recipients: list[x509.Certificate] | None = None, + _content_encryption_algorithm: ContentEncryptionAlgorithm + | None = None, ): from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, @@ -197,13 +207,18 @@ def __init__( ) self._data = _data self._recipients = _recipients if _recipients is not None else [] + self._content_encryption_algorithm = _content_encryption_algorithm def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder: _check_byteslike("data", data) if self._data is not None: raise ValueError("data may only be set once") - return PKCS7EnvelopeBuilder(_data=data, _recipients=self._recipients) + return PKCS7EnvelopeBuilder( + _data=data, + _recipients=self._recipients, + _content_encryption_algorithm=self._content_encryption_algorithm, + ) def add_recipient( self, @@ -221,17 +236,42 @@ def add_recipient( *self._recipients, certificate, ], + _content_encryption_algorithm=self._content_encryption_algorithm, + ) + + def set_content_encryption_algorithm( + self, content_encryption_algorithm: ContentEncryptionAlgorithm + ) -> PKCS7EnvelopeBuilder: + if self._content_encryption_algorithm is not None: + raise ValueError("Content encryption algo may only be set once") + if content_encryption_algorithm not in { + algorithms.AES128, + algorithms.AES256, + }: + raise TypeError("Only AES128 and AES256 are supported") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=self._recipients, + _content_encryption_algorithm=content_encryption_algorithm, ) def encrypt( self, encoding: serialization.Encoding, - options: typing.Iterable[PKCS7Options], + options: Iterable[PKCS7Options], ) -> bytes: if len(self._recipients) == 0: raise ValueError("Must have at least one recipient") if self._data is None: raise ValueError("You must add data to encrypt") + + # The default content encryption algorithm is AES-128, which the S/MIME + # v3.2 RFC specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7) + content_encryption_algorithm = ( + self._content_encryption_algorithm or algorithms.AES128 + ) + options = list(options) if not all(isinstance(x, PKCS7Options) for x in options): raise ValueError("options must be from the PKCS7Options enum") @@ -260,7 +300,9 @@ def encrypt( "Cannot use Binary and Text options at the same time" ) - return rust_pkcs7.encrypt_and_serialize(self, encoding, options) + return rust_pkcs7.encrypt_and_serialize( + self, content_encryption_algorithm, encoding, options + ) pkcs7_decrypt_der = rust_pkcs7.decrypt_der diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index c01afb0ccdc9..c08b98121f2c 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -169,20 +169,20 @@ def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes: def _ssh_pem_encode( - data: bytes, + data: utils.Buffer, 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: bytes, block_len: int) -> None: +def _check_block_size(data: utils.Buffer, 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: bytes) -> None: +def _check_empty(data: utils.Buffer) -> None: """All data should have been parsed.""" if data: raise ValueError("Corrupt data: unparsed data") @@ -196,7 +196,9 @@ def _init_cipher( ) -> Cipher[modes.CBC | modes.CTR | modes.GCM]: """Generate key + iv and return cipher.""" if not password: - raise ValueError("Key is password-protected.") + raise TypeError( + "Key is password-protected, but password was not provided." + ) ciph = _SSH_CIPHERS[ciphername] seed = _bcrypt_kdf( @@ -251,14 +253,14 @@ def _to_mpint(val: int) -> bytes: class _FragList: """Build recursive structure without data copy.""" - flist: list[bytes] + flist: list[utils.Buffer] - def __init__(self, init: list[bytes] | None = None) -> None: + def __init__(self, init: list[utils.Buffer] | None = None) -> None: self.flist = [] if init: self.flist.extend(init) - def put_raw(self, val: bytes) -> None: + def put_raw(self, val: utils.Buffer) -> None: """Add plain bytes""" self.flist.append(val) @@ -329,7 +331,7 @@ def load_public( return public_key, data def load_private( - self, data: memoryview, pubfields + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool ) -> tuple[rsa.RSAPrivateKey, memoryview]: """Make RSA private key from data.""" n, data = _get_mpint(data) @@ -347,7 +349,9 @@ def load_private( private_numbers = rsa.RSAPrivateNumbers( p, q, d, dmp1, dmq1, iqmp, public_numbers ) - private_key = private_numbers.private_key() + private_key = private_numbers.private_key( + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation + ) return private_key, data def encode_public( @@ -403,7 +407,7 @@ def load_public( return public_key, data def load_private( - self, data: memoryview, pubfields + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool ) -> tuple[dsa.DSAPrivateKey, memoryview]: """Make DSA private key from data.""" (p, q, g, y), data = self.get_public(data) @@ -483,7 +487,7 @@ def load_public( return public_key, data def load_private( - self, data: memoryview, pubfields + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool ) -> tuple[ec.EllipticCurvePrivateKey, memoryview]: """Make ECDSA private key from data.""" (curve_name, point), data = self.get_public(data) @@ -543,7 +547,7 @@ def load_public( return public_key, data def load_private( - self, data: memoryview, pubfields + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool ) -> tuple[ed25519.Ed25519PrivateKey, memoryview]: """Make Ed25519 private key from data.""" (point,), data = self.get_public(data) @@ -612,6 +616,13 @@ def load_public( _, data = load_application(data) return public_key, data + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ssh-ed25519 private keys cannot be loaded" + ) + class _SSHFormatSKECDSA: """ @@ -631,6 +642,13 @@ def load_public( _, data = load_application(data) return public_key, data + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ecdsa-sha2-nistp256 private keys cannot be loaded" + ) + _KEY_FORMATS = { _SSH_RSA: _SSHFormatRSA(), @@ -644,7 +662,7 @@ def load_public( } -def _lookup_kformat(key_type: bytes): +def _lookup_kformat(key_type: utils.Buffer): """Return valid format or throw error""" if not isinstance(key_type, bytes): key_type = memoryview(key_type).tobytes() @@ -662,9 +680,11 @@ def _lookup_kformat(key_type: bytes): def load_ssh_private_key( - data: bytes, + data: utils.Buffer, password: bytes | None, backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, ) -> SSHPrivateKeyTypes: """Load private key from OpenSSH custom encoding.""" utils._check_byteslike("data", data) @@ -696,7 +716,7 @@ def load_ssh_private_key( pubfields, pubdata = kformat.get_public(pubdata) _check_empty(pubdata) - if (ciphername, kdfname) != (_NONE, _NONE): + if ciphername != _NONE or kdfname != _NONE: ciphername_bytes = ciphername.tobytes() if ciphername_bytes not in _SSH_CIPHERS: raise UnsupportedAlgorithm( @@ -731,6 +751,10 @@ def load_ssh_private_key( # should be no output from finalize _check_empty(dec.finalize()) else: + if password: + raise TypeError( + "Password was given but private key is not encrypted." + ) # load secret data edata, data = _get_sshstr(data) _check_empty(data) @@ -745,7 +769,11 @@ def load_ssh_private_key( 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) + private_key, edata = kformat.load_private( + edata, + pubfields, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, + ) # We don't use the comment _, edata = _get_sshstr(edata) @@ -1001,7 +1029,7 @@ def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm: def _load_ssh_public_identity( - data: bytes, + data: utils.Buffer, _legacy_dsa_allowed=False, ) -> SSHCertificate | SSHPublicKeyTypes: utils._check_byteslike("data", data) @@ -1121,8 +1149,30 @@ def _parse_exts_opts(exts_opts: memoryview) -> dict[bytes, bytes]: return result +def ssh_key_fingerprint( + key: SSHPublicKeyTypes, + hash_algorithm: hashes.MD5 | hashes.SHA256, +) -> bytes: + if not isinstance(hash_algorithm, (hashes.MD5, hashes.SHA256)): + raise TypeError("hash_algorithm must be either MD5 or SHA256") + + key_type = _get_ssh_key_type(key) + kformat = _lookup_kformat(key_type) + + f_pub = _FragList() + f_pub.put_sshstr(key_type) + kformat.encode_public(key, f_pub) + + ssh_binary_data = f_pub.tobytes() + + # Hash the binary data + hash_obj = hashes.Hash(hash_algorithm) + hash_obj.update(ssh_binary_data) + return hash_obj.finalize() + + def load_ssh_public_key( - data: bytes, backend: typing.Any = None + data: utils.Buffer, backend: typing.Any = None ) -> SSHPublicKeyTypes: cert_or_key = _load_ssh_public_identity(data, _legacy_dsa_allowed=True) public_key: SSHPublicKeyTypes diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py index 855a5d212ea3..21fb000499c4 100644 --- a/src/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -11,6 +11,7 @@ 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.utils import Buffer HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512] @@ -44,7 +45,7 @@ def _generate_uri( class HOTP: def __init__( self, - key: bytes, + key: Buffer, length: int, algorithm: HOTPHashTypes, backend: typing.Any = None, @@ -84,7 +85,7 @@ def _dynamic_truncate(self, counter: int) -> int: try: ctx.update(counter.to_bytes(length=8, byteorder="big")) except OverflowError: - raise ValueError(f"Counter must be between 0 and {2 ** 64 - 1}.") + raise ValueError(f"Counter must be between 0 and {2**64 - 1}.") hmac_value = ctx.finalize() diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py index b9ed7349a14e..10c725cc0ab0 100644 --- a/src/cryptography/hazmat/primitives/twofactor/totp.py +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -13,12 +13,13 @@ HOTPHashTypes, _generate_uri, ) +from cryptography.utils import Buffer class TOTP: def __init__( self, - key: bytes, + key: Buffer, length: int, algorithm: HOTPHashTypes, time_step: int, diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 706d0ae4cbd7..7de7e4dfc06b 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -9,6 +9,7 @@ import types import typing import warnings +from collections.abc import Callable, Sequence # We use a UserWarning subclass, instead of DeprecationWarning, because CPython @@ -26,6 +27,17 @@ class CryptographyDeprecationWarning(UserWarning): DeprecatedIn41 = CryptographyDeprecationWarning DeprecatedIn42 = CryptographyDeprecationWarning DeprecatedIn43 = CryptographyDeprecationWarning +DeprecatedIn45 = CryptographyDeprecationWarning + + +# If you're wondering why we don't use `Buffer`, it's because `Buffer` would +# be more accurately named: Bufferable. It means something which has an +# `__buffer__`. Which means you can't actually treat the result as a buffer +# (and do things like take a `len()`). +if sys.version_info >= (3, 9): + Buffer = typing.Union[bytes, bytearray, memoryview] +else: + Buffer = typing.ByteString def _check_bytes(name: str, value: bytes) -> None: @@ -33,7 +45,7 @@ def _check_bytes(name: str, value: bytes) -> None: raise TypeError(f"{name} must be bytes") -def _check_byteslike(name: str, value: bytes) -> None: +def _check_byteslike(name: str, value: Buffer) -> None: try: memoryview(value) except TypeError: @@ -81,7 +93,7 @@ def __delattr__(self, attr: str) -> None: delattr(self._module, attr) - def __dir__(self) -> typing.Sequence[str]: + def __dir__(self) -> Sequence[str]: return ["_module", *dir(self._module)] @@ -102,7 +114,7 @@ def deprecated( return dv -def cached_property(func: typing.Callable) -> property: +def cached_property(func: Callable) -> property: cached_name = f"_cached_{func}" sentinel = object() diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 8a89d67f151e..318eecc96e1d 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -66,6 +66,7 @@ PolicyInformation, PrecertificateSignedCertificateTimestamps, PrecertPoison, + PrivateKeyUsagePeriod, ProfessionInfo, ReasonFlags, SignedCertificateTimestamps, @@ -115,6 +116,7 @@ OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME OID_KEY_USAGE = ExtensionOID.KEY_USAGE +OID_PRIVATE_KEY_USAGE_PERIOD = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS @@ -233,6 +235,7 @@ "PolicyInformation", "PrecertPoison", "PrecertificateSignedCertificateTimestamps", + "PrivateKeyUsagePeriod", "ProfessionInfo", "PublicKeyAlgorithmOID", "RFC822Name", diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 25b317af626f..1be612be607b 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -9,6 +9,7 @@ import os import typing import warnings +from collections.abc import Iterable from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 @@ -131,7 +132,7 @@ def __hash__(self) -> int: class Attributes: def __init__( self, - attributes: typing.Iterable[Attribute], + attributes: Iterable[Attribute], ) -> None: self._attributes = list(attributes) @@ -330,6 +331,7 @@ def sign( backend: typing.Any = None, *, rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, ) -> CertificateSigningRequest: """ Signs the request using the requestor's private key. @@ -343,8 +345,18 @@ def sign( if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + return rust_x509.create_x509_csr( - self, private_key, algorithm, rsa_padding + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, ) @@ -510,8 +522,7 @@ def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder: time = _convert_to_naive_utc_time(time) if time < _EARLIEST_UTC_TIME: raise ValueError( - "The not valid after date must be on or after" - " 1950 January 1." + "The not valid after date must be on or after 1950 January 1." ) if ( self._not_valid_before is not None @@ -560,6 +571,7 @@ def sign( backend: typing.Any = None, *, rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, ) -> Certificate: """ Signs the certificate using the CA's private key. @@ -588,8 +600,18 @@ def sign( if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + return rust_x509.create_x509_certificate( - self, private_key, algorithm, rsa_padding + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, ) @@ -717,6 +739,7 @@ def sign( backend: typing.Any = None, *, rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, ) -> CertificateRevocationList: if self._issuer_name is None: raise ValueError("A CRL must have an issuer name") @@ -733,8 +756,18 @@ def sign( if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + return rust_x509.create_x509_crl( - self, private_key, algorithm, rsa_padding + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, ) diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index fc3e7730eca0..dfa472d38061 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -9,6 +9,7 @@ import hashlib import ipaddress import typing +from collections.abc import Iterable, Iterator from cryptography import utils from cryptography.hazmat.bindings._rust import asn1 @@ -109,9 +110,7 @@ def public_bytes(self) -> bytes: class Extensions: - def __init__( - self, extensions: typing.Iterable[Extension[ExtensionType]] - ) -> None: + def __init__(self, extensions: Iterable[Extension[ExtensionType]]) -> None: self._extensions = list(extensions) def get_extension_for_oid( @@ -182,7 +181,7 @@ class AuthorityKeyIdentifier(ExtensionType): def __init__( self, key_identifier: bytes | None, - authority_cert_issuer: typing.Iterable[GeneralName] | None, + authority_cert_issuer: Iterable[GeneralName] | None, authority_cert_serial_number: int | None, ) -> None: if (authority_cert_issuer is None) != ( @@ -323,9 +322,7 @@ def public_bytes(self) -> bytes: class AuthorityInformationAccess(ExtensionType): oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS - def __init__( - self, descriptions: typing.Iterable[AccessDescription] - ) -> None: + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( @@ -356,9 +353,7 @@ def public_bytes(self) -> bytes: class SubjectInformationAccess(ExtensionType): oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS - def __init__( - self, descriptions: typing.Iterable[AccessDescription] - ) -> None: + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( @@ -456,8 +451,7 @@ def path_length(self) -> int | None: def __repr__(self) -> str: return ( - f"" + f"" ) def __eq__(self, other: object) -> bool: @@ -506,7 +500,7 @@ class CRLDistributionPoints(ExtensionType): oid = ExtensionOID.CRL_DISTRIBUTION_POINTS def __init__( - self, distribution_points: typing.Iterable[DistributionPoint] + self, distribution_points: Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( @@ -543,7 +537,7 @@ class FreshestCRL(ExtensionType): oid = ExtensionOID.FRESHEST_CRL def __init__( - self, distribution_points: typing.Iterable[DistributionPoint] + self, distribution_points: Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( @@ -579,10 +573,10 @@ def public_bytes(self) -> bytes: class DistributionPoint: def __init__( self, - full_name: typing.Iterable[GeneralName] | None, + full_name: Iterable[GeneralName] | None, relative_name: RelativeDistinguishedName | None, reasons: frozenset[ReasonFlags] | None, - crl_issuer: typing.Iterable[GeneralName] | None, + crl_issuer: Iterable[GeneralName] | None, ) -> None: if full_name and relative_name: raise ValueError( @@ -824,12 +818,11 @@ def public_bytes(self) -> bytes: class CertificatePolicies(ExtensionType): oid = ExtensionOID.CERTIFICATE_POLICIES - def __init__(self, policies: typing.Iterable[PolicyInformation]) -> None: + def __init__(self, policies: Iterable[PolicyInformation]) -> None: policies = list(policies) if not all(isinstance(x, PolicyInformation) for x in policies): raise TypeError( - "Every item in the policies list must be a " - "PolicyInformation" + "Every item in the policies list must be a PolicyInformation" ) self._policies = policies @@ -856,7 +849,7 @@ class PolicyInformation: def __init__( self, policy_identifier: ObjectIdentifier, - policy_qualifiers: typing.Iterable[str | UserNotice] | None, + policy_qualifiers: Iterable[str | UserNotice] | None, ) -> None: if not isinstance(policy_identifier, ObjectIdentifier): raise TypeError("policy_identifier must be an ObjectIdentifier") @@ -956,7 +949,7 @@ class NoticeReference: def __init__( self, organization: str | None, - notice_numbers: typing.Iterable[int], + notice_numbers: Iterable[int], ) -> None: self._organization = organization notice_numbers = list(notice_numbers) @@ -995,7 +988,7 @@ def notice_numbers(self) -> list[int]: class ExtendedKeyUsage(ExtensionType): oid = ExtensionOID.EXTENDED_KEY_USAGE - def __init__(self, usages: typing.Iterable[ObjectIdentifier]) -> None: + def __init__(self, usages: Iterable[ObjectIdentifier]) -> None: usages = list(usages) if not all(isinstance(x, ObjectIdentifier) for x in usages): raise TypeError( @@ -1063,7 +1056,7 @@ def public_bytes(self) -> bytes: class TLSFeature(ExtensionType): oid = ExtensionOID.TLS_FEATURE - def __init__(self, features: typing.Iterable[TLSFeatureType]) -> None: + def __init__(self, features: Iterable[TLSFeatureType]) -> None: features = list(features) if ( not all(isinstance(x, TLSFeatureType) for x in features) @@ -1273,13 +1266,78 @@ def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) +class PrivateKeyUsagePeriod(ExtensionType): + oid = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD + + def __init__( + self, + not_before: datetime.datetime | None, + not_after: datetime.datetime | None, + ) -> None: + if ( + not isinstance(not_before, datetime.datetime) + and not_before is not None + ): + raise TypeError("not_before must be a datetime.datetime or None") + + if ( + not isinstance(not_after, datetime.datetime) + and not_after is not None + ): + raise TypeError("not_after must be a datetime.datetime or None") + + if not_before is None and not_after is None: + raise ValueError( + "At least one of not_before and not_after must not be None" + ) + + if ( + not_before is not None + and not_after is not None + and not_before > not_after + ): + raise ValueError("not_before must be before not_after") + + self._not_before = not_before + self._not_after = not_after + + @property + def not_before(self) -> datetime.datetime | None: + return self._not_before + + @property + def not_after(self) -> datetime.datetime | None: + return self._not_after + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrivateKeyUsagePeriod): + return NotImplemented + + return ( + self.not_before == other.not_before + and self.not_after == other.not_after + ) + + def __hash__(self) -> int: + return hash((self.not_before, self.not_after)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + class NameConstraints(ExtensionType): oid = ExtensionOID.NAME_CONSTRAINTS def __init__( self, - permitted_subtrees: typing.Iterable[GeneralName] | None, - excluded_subtrees: typing.Iterable[GeneralName] | None, + permitted_subtrees: Iterable[GeneralName] | None, + excluded_subtrees: Iterable[GeneralName] | None, ) -> None: if permitted_subtrees is not None: permitted_subtrees = list(permitted_subtrees) @@ -1327,11 +1385,11 @@ def __eq__(self, other: object) -> bool: and self.permitted_subtrees == other.permitted_subtrees ) - def _validate_tree(self, tree: typing.Iterable[GeneralName]) -> None: + def _validate_tree(self, tree: Iterable[GeneralName]) -> None: self._validate_ip_name(tree) self._validate_dns_name(tree) - def _validate_ip_name(self, tree: typing.Iterable[GeneralName]) -> None: + def _validate_ip_name(self, tree: Iterable[GeneralName]) -> None: if any( isinstance(name, IPAddress) and not isinstance( @@ -1344,7 +1402,7 @@ def _validate_ip_name(self, tree: typing.Iterable[GeneralName]) -> None: " IPv6Network object" ) - def _validate_dns_name(self, tree: typing.Iterable[GeneralName]) -> None: + def _validate_dns_name(self, tree: Iterable[GeneralName]) -> None: if any( isinstance(name, DNSName) and "*" in name.value for name in tree ): @@ -1437,7 +1495,7 @@ def __hash__(self) -> int: class GeneralNames: - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: general_names = list(general_names) if not all(isinstance(x, GeneralName) for x in general_names): raise TypeError( @@ -1519,7 +1577,7 @@ def __hash__(self) -> int: class SubjectAlternativeName(ExtensionType): oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @@ -1591,7 +1649,7 @@ def public_bytes(self) -> bytes: class IssuerAlternativeName(ExtensionType): oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @@ -1663,7 +1721,7 @@ def public_bytes(self) -> bytes: class CertificateIssuer(ExtensionType): oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @@ -1802,9 +1860,7 @@ class PrecertificateSignedCertificateTimestamps(ExtensionType): def __init__( self, - signed_certificate_timestamps: typing.Iterable[ - SignedCertificateTimestamp - ], + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( @@ -1845,9 +1901,7 @@ class SignedCertificateTimestamps(ExtensionType): def __init__( self, - signed_certificate_timestamps: typing.Iterable[ - SignedCertificateTimestamp - ], + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( @@ -1915,7 +1969,7 @@ def public_bytes(self) -> bytes: class OCSPAcceptableResponses(ExtensionType): oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES - def __init__(self, responses: typing.Iterable[ObjectIdentifier]) -> None: + def __init__(self, responses: Iterable[ObjectIdentifier]) -> None: responses = list(responses) if any(not isinstance(r, ObjectIdentifier) for r in responses): raise TypeError("All responses must be ObjectIdentifiers") @@ -1934,7 +1988,7 @@ def __hash__(self) -> int: def __repr__(self) -> str: return f"" - def __iter__(self) -> typing.Iterator[ObjectIdentifier]: + def __iter__(self) -> Iterator[ObjectIdentifier]: return iter(self._responses) def public_bytes(self) -> bytes: @@ -1946,7 +2000,7 @@ class IssuingDistributionPoint(ExtensionType): def __init__( self, - full_name: typing.Iterable[GeneralName] | None, + full_name: Iterable[GeneralName] | None, relative_name: RelativeDistinguishedName | None, only_contains_user_certs: bool, only_contains_ca_certs: bool, @@ -2224,8 +2278,8 @@ class ProfessionInfo: def __init__( self, naming_authority: NamingAuthority | None, - profession_items: typing.Iterable[str], - profession_oids: typing.Iterable[ObjectIdentifier] | None, + profession_items: Iterable[str], + profession_oids: Iterable[ObjectIdentifier] | None, registration_number: str | None, add_profession_info: bytes | None, ) -> None: @@ -2328,7 +2382,7 @@ def __init__( self, admission_authority: GeneralName | None, naming_authority: NamingAuthority | None, - profession_infos: typing.Iterable[ProfessionInfo], + profession_infos: Iterable[ProfessionInfo], ) -> None: if admission_authority is not None and not isinstance( admission_authority, GeneralName @@ -2398,7 +2452,7 @@ class Admissions(ExtensionType): def __init__( self, authority: GeneralName | None, - admissions: typing.Iterable[Admission], + admissions: Iterable[Admission], ) -> None: if authority is not None and not isinstance(authority, GeneralName): raise TypeError("authority must be a GeneralName") @@ -2459,10 +2513,7 @@ def value(self) -> bytes: return self._value def __repr__(self) -> str: - return ( - f"" - ) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, UnrecognizedExtension): diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index 1b6b89d12a97..5f827818c254 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -9,6 +9,7 @@ import sys import typing import warnings +from collections.abc import Iterable, Iterator from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 @@ -114,11 +115,20 @@ def sub(m): return _RFC4514NameParser._PAIR_RE.sub(sub, val) -class NameAttribute: +NameAttributeValueType = typing.TypeVar( + "NameAttributeValueType", + typing.Union[str, bytes], + str, + bytes, + covariant=True, +) + + +class NameAttribute(typing.Generic[NameAttributeValueType]): def __init__( self, oid: ObjectIdentifier, - value: str | bytes, + value: NameAttributeValueType, _type: _ASN1Type | None = None, *, _validate: bool = True, @@ -166,15 +176,15 @@ def __init__( raise TypeError("_type must be from the _ASN1Type enum") self._oid = oid - self._value = value - self._type = _type + self._value: NameAttributeValueType = value + self._type: _ASN1Type = _type @property def oid(self) -> ObjectIdentifier: return self._oid @property - def value(self) -> str | bytes: + def value(self) -> NameAttributeValueType: return self._value @property @@ -216,7 +226,7 @@ def __repr__(self) -> str: class RelativeDistinguishedName: - def __init__(self, attributes: typing.Iterable[NameAttribute]): + def __init__(self, attributes: Iterable[NameAttribute]): attributes = list(attributes) if not attributes: raise ValueError("a relative distinguished name cannot be empty") @@ -231,8 +241,9 @@ def __init__(self, attributes: typing.Iterable[NameAttribute]): raise ValueError("duplicate attributes are not allowed") def get_attributes_for_oid( - self, oid: ObjectIdentifier - ) -> list[NameAttribute]: + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: return [i for i in self if i.oid == oid] def rfc4514_string( @@ -258,7 +269,7 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: return hash(self._attribute_set) - def __iter__(self) -> typing.Iterator[NameAttribute]: + def __iter__(self) -> Iterator[NameAttribute]: return iter(self._attributes) def __len__(self) -> int: @@ -270,16 +281,16 @@ def __repr__(self) -> str: class Name: @typing.overload - def __init__(self, attributes: typing.Iterable[NameAttribute]) -> None: ... + def __init__(self, attributes: Iterable[NameAttribute]) -> None: ... @typing.overload def __init__( - self, attributes: typing.Iterable[RelativeDistinguishedName] + self, attributes: Iterable[RelativeDistinguishedName] ) -> None: ... def __init__( self, - attributes: typing.Iterable[NameAttribute | RelativeDistinguishedName], + attributes: Iterable[NameAttribute | RelativeDistinguishedName], ) -> None: attributes = list(attributes) if all(isinstance(x, NameAttribute) for x in attributes): @@ -324,8 +335,9 @@ def rfc4514_string( ) def get_attributes_for_oid( - self, oid: ObjectIdentifier - ) -> list[NameAttribute]: + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: return [i for i in self if i.oid == oid] @property @@ -346,7 +358,7 @@ def __hash__(self) -> int: # for you, consider optimizing! return hash(tuple(self._attributes)) - def __iter__(self) -> typing.Iterator[NameAttribute]: + def __iter__(self) -> Iterator[NameAttribute]: for rdn in self._attributes: yield from rdn diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py index 5a011c412ad3..f61ed80b82bd 100644 --- a/src/cryptography/x509/ocsp.py +++ b/src/cryptography/x509/ocsp.py @@ -5,7 +5,7 @@ from __future__ import annotations import datetime -import typing +from collections.abc import Iterable from cryptography import utils, x509 from cryptography.hazmat.bindings._rust import ocsp @@ -13,11 +13,7 @@ from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPrivateKeyTypes, ) -from cryptography.x509.base import ( - _EARLIEST_UTC_TIME, - _convert_to_naive_utc_time, - _reject_duplicate_extension, -) +from cryptography.x509.base import _reject_duplicate_extension class OCSPResponderEncoding(utils.Enum): @@ -59,8 +55,8 @@ class OCSPCertStatus(utils.Enum): class _SingleResponse: def __init__( self, - cert: x509.Certificate, - issuer: x509.Certificate, + resp: tuple[x509.Certificate, x509.Certificate] | None, + resp_hash: tuple[bytes, bytes, int] | None, algorithm: hashes.HashAlgorithm, cert_status: OCSPCertStatus, this_update: datetime.datetime, @@ -68,11 +64,6 @@ def __init__( revocation_time: datetime.datetime | None, revocation_reason: x509.ReasonFlags | None, ): - if not isinstance(cert, x509.Certificate) or not isinstance( - issuer, x509.Certificate - ): - raise TypeError("cert and issuer must be a Certificate") - _verify_algorithm(algorithm) if not isinstance(this_update, datetime.datetime): raise TypeError("this_update must be a datetime object") @@ -81,8 +72,8 @@ def __init__( ): raise TypeError("next_update must be a datetime object or None") - self._cert = cert - self._issuer = issuer + self._resp = resp + self._resp_hash = resp_hash self._algorithm = algorithm self._this_update = this_update self._next_update = next_update @@ -106,13 +97,6 @@ def __init__( if not isinstance(revocation_time, datetime.datetime): raise TypeError("revocation_time must be a datetime object") - revocation_time = _convert_to_naive_utc_time(revocation_time) - if revocation_time < _EARLIEST_UTC_TIME: - raise ValueError( - "The revocation_time must be on or after" - " 1950 January 1." - ) - if revocation_reason is not None and not isinstance( revocation_reason, x509.ReasonFlags ): @@ -243,9 +227,60 @@ def add_response( if self._response is not None: raise ValueError("Only one response per OCSPResponse.") + if not isinstance(cert, x509.Certificate) or not isinstance( + issuer, x509.Certificate + ): + raise TypeError("cert and issuer must be a Certificate") + + singleresp = _SingleResponse( + (cert, issuer), + None, + algorithm, + cert_status, + this_update, + next_update, + revocation_time, + revocation_reason, + ) + return OCSPResponseBuilder( + singleresp, + self._responder_id, + self._certs, + self._extensions, + ) + + def add_response_by_hash( + self, + issuer_name_hash: bytes, + issuer_key_hash: bytes, + serial_number: int, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ) -> OCSPResponseBuilder: + if self._response is not None: + raise ValueError("Only one response per OCSPResponse.") + + if not isinstance(serial_number, int): + raise TypeError("serial_number must be an integer") + + utils._check_bytes("issuer_name_hash", issuer_name_hash) + utils._check_bytes("issuer_key_hash", issuer_key_hash) + _verify_algorithm(algorithm) + 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" + ) + singleresp = _SingleResponse( - cert, - issuer, + None, + (issuer_name_hash, issuer_key_hash, serial_number), algorithm, cert_status, this_update, @@ -280,7 +315,7 @@ def responder_id( ) def certificates( - self, certs: typing.Iterable[x509.Certificate] + self, certs: Iterable[x509.Certificate] ) -> OCSPResponseBuilder: if self._certs is not None: raise ValueError("certificates may only be set once") diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index d4e409e0a2a0..520fc7ab018c 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -14,6 +14,7 @@ NameOID, ObjectIdentifier, OCSPExtensionOID, + OtherNameFormOID, PublicKeyAlgorithmOID, SignatureAlgorithmOID, SubjectInformationAccessOID, @@ -29,6 +30,7 @@ "NameOID", "OCSPExtensionOID", "ObjectIdentifier", + "OtherNameFormOID", "PublicKeyAlgorithmOID", "SignatureAlgorithmOID", "SubjectInformationAccessOID", diff --git a/src/cryptography/x509/verification.py b/src/cryptography/x509/verification.py index b83650681237..2db4324d9615 100644 --- a/src/cryptography/x509/verification.py +++ b/src/cryptography/x509/verification.py @@ -11,6 +11,9 @@ __all__ = [ "ClientVerifier", + "Criticality", + "ExtensionPolicy", + "Policy", "PolicyBuilder", "ServerVerifier", "Store", @@ -25,4 +28,7 @@ ClientVerifier = rust_x509.ClientVerifier ServerVerifier = rust_x509.ServerVerifier PolicyBuilder = rust_x509.PolicyBuilder +Policy = rust_x509.Policy +ExtensionPolicy = rust_x509.ExtensionPolicy +Criticality = rust_x509.Criticality VerificationError = rust_x509.VerificationError diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 9eb165a96f14..c18583075509 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -5,24 +5,30 @@ authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true +license.workspace = true [dependencies] +base64 = "0.22" once_cell = "1" cfg-if = "1" pyo3.workspace = true asn1.workspace = true cryptography-cffi = { path = "cryptography-cffi" } +cryptography-crypto = { path = "cryptography-crypto" } cryptography-keepalive = { path = "cryptography-keepalive" } cryptography-key-parsing = { path = "cryptography-key-parsing" } cryptography-x509 = { path = "cryptography-x509" } cryptography-x509-verification = { path = "cryptography-x509-verification" } cryptography-openssl = { path = "cryptography-openssl" } pem = { version = "3", default-features = false } -openssl = "0.10.68" -openssl-sys = "0.9.104" +openssl.workspace = true +openssl-sys.workspace = true foreign-types-shared = "0.1" self_cell = "1" +[build-dependencies] +pyo3-build-config.workspace = true + [features] extension-module = ["pyo3/extension-module"] default = ["extension-module"] @@ -32,4 +38,4 @@ name = "cryptography_rust" crate-type = ["cdylib"] [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_309_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_OSSLCONF, values("OPENSSL_NO_IDEA", "OPENSSL_NO_CAST", "OPENSSL_NO_BF", "OPENSSL_NO_CAMELLIA", "OPENSSL_NO_SEED", "OPENSSL_NO_SM4"))'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_309_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_330_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_IS_AWSLC)', 'cfg(CRYPTOGRAPHY_BUILD_OPENSSL_NO_LEGACY)', 'cfg(CRYPTOGRAPHY_OSSLCONF, values("OPENSSL_NO_IDEA", "OPENSSL_NO_CAST", "OPENSSL_NO_BF", "OPENSSL_NO_CAMELLIA", "OPENSSL_NO_SEED", "OPENSSL_NO_SM4", "OPENSSL_NO_RC4"))'] } diff --git a/src/rust/build.rs b/src/rust/build.rs index 2d94d8da7ba3..c8c4be98d720 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -6,6 +6,8 @@ use std::env; #[allow(clippy::unusual_byte_groupings)] fn main() { + pyo3_build_config::use_pyo3_cfgs(); + if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); @@ -18,6 +20,12 @@ fn main() { if version >= 0x3_02_00_00_0 { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_320_OR_GREATER"); } + if version >= 0x3_03_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_330_OR_GREATER"); + } + if version >= 0x3_05_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_350_OR_GREATER"); + } } if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { @@ -28,6 +36,14 @@ fn main() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); } + if env::var("DEP_OPENSSL_AWSLC").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_AWSLC"); + } + + if env::var("CRYPTOGRAPHY_BUILD_OPENSSL_NO_LEGACY").is_ok_and(|v| !v.is_empty() && v != "0") { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_BUILD_OPENSSL_NO_LEGACY"); + } + if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { for var in vars.split(',') { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OSSLCONF=\"{var}\""); diff --git a/src/rust/cryptography-cffi/Cargo.toml b/src/rust/cryptography-cffi/Cargo.toml index 9408de8b4415..401f4221ce3d 100644 --- a/src/rust/cryptography-cffi/Cargo.toml +++ b/src/rust/cryptography-cffi/Cargo.toml @@ -5,13 +5,14 @@ authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true +license.workspace = true [dependencies] pyo3.workspace = true -openssl-sys = "0.9.104" +openssl-sys.workspace = true [build-dependencies] -cc = "1.2.1" +cc = "1.2.23" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(python_implementation, values("CPython", "PyPy"))'] } diff --git a/src/rust/cryptography-cffi/build.rs b/src/rust/cryptography-cffi/build.rs index 1243a8187a97..398513b7a15f 100644 --- a/src/rust/cryptography-cffi/build.rs +++ b/src/rust/cryptography-cffi/build.rs @@ -71,7 +71,7 @@ fn main() { // This is because we don't want a potentially random build path to end up in the binary because // CFFI generated code uses the __FILE__ macro in its debug messages. if let Some(out_dir_str) = Path::new(&out_dir).to_str() { - build.flag_if_supported(format!("-fmacro-prefix-map={}=.", out_dir_str).as_str()); + build.flag_if_supported(format!("-fmacro-prefix-map={out_dir_str}=.").as_str()); } for python_include in env::split_paths(&python_includes) { diff --git a/src/rust/cryptography-crypto/Cargo.toml b/src/rust/cryptography-crypto/Cargo.toml new file mode 100644 index 000000000000..979931e2b368 --- /dev/null +++ b/src/rust/cryptography-crypto/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cryptography-crypto" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true +license.workspace = true + +[dependencies] +openssl.workspace = true diff --git a/src/rust/cryptography-crypto/src/encoding.rs b/src/rust/cryptography-crypto/src/encoding.rs new file mode 100644 index 000000000000..fc9908556430 --- /dev/null +++ b/src/rust/cryptography-crypto/src/encoding.rs @@ -0,0 +1,52 @@ +// 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 fn hex_decode(v: &str) -> Option> { + if v.len() % 2 != 0 { + return None; + } + + let mut b = Vec::with_capacity(v.len() / 2); + let v = v.as_bytes(); + for i in (0..v.len()).step_by(2) { + let high = match v[i] { + b @ b'0'..=b'9' => b - b'0', + b @ b'a'..=b'f' => b - b'a' + 10, + b @ b'A'..=b'F' => b - b'A' + 10, + _ => return None, + }; + + let low = match v[i + 1] { + b @ b'0'..=b'9' => b - b'0', + b @ b'a'..=b'f' => b - b'a' + 10, + b @ b'A'..=b'F' => b - b'A' + 10, + _ => return None, + }; + + b.push((high << 4) | low); + } + + Some(b) +} + +#[cfg(test)] +mod tests { + use super::hex_decode; + + #[test] + fn test_hex_decode() { + for (text, expected) in [ + ("", Some(vec![])), + ("00", Some(vec![0])), + ("0", None), + ("12-0", None), + ("120-", None), + ("ab", Some(vec![0xAB])), + ("AB", Some(vec![0xAB])), + ("ABCD", Some(vec![0xAB, 0xCD])), + ] { + assert_eq!(hex_decode(text), expected); + } + } +} diff --git a/src/rust/cryptography-crypto/src/lib.rs b/src/rust/cryptography-crypto/src/lib.rs new file mode 100644 index 000000000000..80b0e7e8b86e --- /dev/null +++ b/src/rust/cryptography-crypto/src/lib.rs @@ -0,0 +1,7 @@ +// 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 encoding; +pub mod pbkdf1; +pub mod pkcs12; diff --git a/src/rust/cryptography-crypto/src/pbkdf1.rs b/src/rust/cryptography-crypto/src/pbkdf1.rs new file mode 100644 index 000000000000..7a5bad23e423 --- /dev/null +++ b/src/rust/cryptography-crypto/src/pbkdf1.rs @@ -0,0 +1,135 @@ +// 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. + +/// This is the OpenSSL KDF that's used in decrypting PEM blocks. It is a +/// generalization of PBKDF1. +pub fn openssl_kdf( + hash_alg: openssl::hash::MessageDigest, + password: &[u8], + salt: [u8; 8], + length: usize, +) -> Result, openssl::error::ErrorStack> { + let mut key = Vec::with_capacity(length); + + while key.len() < length { + let mut h = openssl::hash::Hasher::new(hash_alg)?; + + if !key.is_empty() { + h.update(&key[key.len() - hash_alg.size()..])?; + } + + h.update(password)?; + h.update(&salt)?; + + let digest = h.finish()?; + let size = digest.len().min(length - key.len()); + key.extend_from_slice(&digest[..size]); + } + + Ok(key) +} + +/// PBKDF1 as defined in RFC 2898 for PKCS#5 v1.5 PBE algorithms +pub fn pbkdf1( + hash_alg: openssl::hash::MessageDigest, + password: &[u8], + salt: [u8; 8], + iterations: u64, + length: usize, +) -> Result, openssl::error::ErrorStack> { + if length > hash_alg.size() || iterations == 0 { + return Err(openssl::error::ErrorStack::get()); + } + + let mut h = openssl::hash::Hasher::new(hash_alg)?; + h.update(password)?; + h.update(&salt)?; + let mut t = h.finish()?; + + // Apply hash function for specified iterations + for _ in 1..iterations { + let mut h = openssl::hash::Hasher::new(hash_alg)?; + h.update(&t)?; + t = h.finish()?; + } + + // Return the first `length` bytes + Ok(t[..length].to_vec()) +} + +#[cfg(test)] +mod tests { + use super::{openssl_kdf, pbkdf1}; + + #[test] + fn test_openssl_kdf() { + for (md, password, salt, expected) in [ + ( + openssl::hash::MessageDigest::md5(), + b"password123" as &[u8], + [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], + &[ + 0x31, 0xcc, 0x91, 0x39, 0x80, 0x69, 0x98, 0xb2, 0xaa, 0xc3, 0x66, 0xcf, 0x40, + 0x1b, 0x49, 0xdc, 0x0d, 0x37, 0xbd, 0x5c, 0x22, 0x52, 0xc7, 0xcb, + ][..], + ), + ( + openssl::hash::MessageDigest::md5(), + b"diffpassword", + [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22], + &[ + 0x89, 0x9b, 0x70, 0x37, 0xdb, 0x29, 0x42, 0x69, 0xdb, 0x4d, 0x67, 0xb9, 0x81, + 0x67, 0xa7, 0x59, 0xfd, 0xec, 0x5d, 0x0f, 0x57, 0x52, 0xef, 0x04, + ], + ), + ( + openssl::hash::MessageDigest::md5(), + b"secret_key", + [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + &[ + 0xa1, 0x98, 0x00, 0xf9, 0xab, 0x97, 0x36, 0xa1, 0x83, 0x4a, 0x19, 0x76, 0x47, + 0x25, 0xda, 0x9b, + ], + ), + ( + openssl::hash::MessageDigest::md5(), + b"another_password", + [0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00], + &[ + 0xc2, 0x09, 0x35, 0x72, 0xe5, 0x62, 0xd4, 0xba, 0x90, 0x4a, 0x5f, 0x46, 0x7f, + 0x27, 0xd2, 0x6c, + ], + ), + ( + openssl::hash::MessageDigest::md5(), + b"very_long_and_complex_password", + [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef], + &[ + 0x1c, 0x5d, 0xdf, 0xa2, 0xca, 0xca, 0x41, 0xa4, 0xc9, 0xb4, 0x31, 0xd2, 0x9f, + 0x04, 0x46, 0x81, 0xd2, 0x2b, 0x4e, 0x40, 0x06, 0x41, 0x5d, 0x37, 0x20, 0xef, + 0x01, 0x1d, 0xc7, 0x8a, 0x16, 0xb5, + ], + ), + ( + openssl::hash::MessageDigest::md5(), + b"different_secure_password", + [0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10], + &[ + 0xca, 0x08, 0xce, 0xb2, 0x46, 0x8f, 0xdc, 0x72, 0x83, 0xc7, 0x0b, 0x8a, 0xd0, + 0x94, 0x54, 0x2b, 0x22, 0xd5, 0x1f, 0xf2, 0x5d, 0x0c, 0x1d, 0x99, 0xf3, 0x2f, + 0x54, 0xa8, 0x68, 0x95, 0x13, 0xbd, + ], + ), + ] { + let key = openssl_kdf(md, password, salt, expected.len()).unwrap(); + assert_eq!(key, expected); + } + } + + #[test] + fn test_pbkdf1() { + assert!(pbkdf1(openssl::hash::MessageDigest::md5(), b"abc", [0; 8], 1, 20).is_err()); + assert!(pbkdf1(openssl::hash::MessageDigest::md5(), b"abc", [0; 8], 0, 8).is_err()); + } +} diff --git a/src/rust/cryptography-crypto/src/pkcs12.rs b/src/rust/cryptography-crypto/src/pkcs12.rs new file mode 100644 index 000000000000..147e86110f8c --- /dev/null +++ b/src/rust/cryptography-crypto/src/pkcs12.rs @@ -0,0 +1,140 @@ +// 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 KDF_ENCRYPTION_KEY_ID: u8 = 1; +pub const KDF_IV_ID: u8 = 2; +pub const KDF_MAC_KEY_ID: u8 = 3; + +pub fn kdf( + pass: &str, + salt: &[u8], + id: u8, + rounds: u64, + key_len: usize, + hash_alg: openssl::hash::MessageDigest, +) -> Result, openssl::error::ErrorStack> { + // Encode the password as big-endian UTF-16 with NUL trailer + let pass = pass + .encode_utf16() + .chain([0]) + .flat_map(|v| v.to_be_bytes()) + .collect::>(); + + // Comments are borrowed from BoringSSL. + // In the spec, |block_size| is called "v", but measured in bits. + let block_size = hash_alg.block_size(); + + // 1. Construct a string, D (the "diversifier"), by concatenating v/8 copies + // of ID. + let d = vec![id; block_size]; + + // 2. Concatenate copies of the salt together to create a string S of length + // v(ceiling(s/v)) bits (the final copy of the salt may be truncated to + // create S). Note that if the salt is the empty string, then so is S. + // + // 3. Concatenate copies of the password together to create a string P of + // length v(ceiling(p/v)) bits (the final copy of the password may be + // truncated to create P). Note that if the password is the empty string, + // then so is P. + // + // 4. Set I=S||P to be the concatenation of S and P. + let s_len = block_size * salt.len().div_ceil(block_size); + let p_len = block_size * pass.len().div_ceil(block_size); + + let mut init_key = vec![0; s_len + p_len]; + for i in 0..s_len { + init_key[i] = salt[i % salt.len()]; + } + for i in 0..p_len { + init_key[i + s_len] = pass[i % pass.len()]; + } + + let mut result = vec![0; key_len]; + let mut pos = 0; + loop { + // A. Set A_i=H^r(D||I). (i.e., the r-th hash of D||I, + // H(H(H(... H(D||I)))) + + let mut h = openssl::hash::Hasher::new(hash_alg)?; + h.update(&d)?; + h.update(&init_key)?; + let mut a = h.finish()?; + + for _ in 1..rounds { + let mut h = openssl::hash::Hasher::new(hash_alg)?; + h.update(&a)?; + a = h.finish()?; + } + + let to_add = a.len().min(result.len() - pos); + result[pos..pos + to_add].copy_from_slice(&a[..to_add]); + pos += to_add; + if pos == result.len() { + break; + } + + // B. Concatenate copies of A_i to create a string B of length v bits (the + // final copy of A_i may be truncated to create B). + let mut b = vec![0; block_size]; + for i in 0..block_size { + b[i] = a[i % a.len()]; + } + + // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit blocks, + // where k=ceiling(s/v)+ceiling(p/v), modify I by setting I_j=(I_j+B+1) mod + // 2^v for each j. + assert!(init_key.len() % block_size == 0); + let mut j = 0; + while j < init_key.len() { + let mut carry = 1u16; + let mut k = block_size - 1; + loop { + carry += init_key[k + j] as u16 + b[k] as u16; + init_key[j + k] = carry as u8; + carry >>= 8; + if k == 0 { + break; + } + k -= 1; + } + j += block_size; + } + } + + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::{kdf, KDF_ENCRYPTION_KEY_ID, KDF_IV_ID, KDF_MAC_KEY_ID}; + + #[test] + fn test_pkcs12_kdf() { + for (password, salt, id, rounds, key_len, hash, expected_key) in [ + // From https://github.com/RustCrypto/formats/blob/master/pkcs12/tests/kdf.rs + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7F\xa3\x17~[\x07h\xa3\x11\x8b\xf8c" as &[u8]), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97sI\xdbn&\xcc\xc9\x98\xd9\xe8\xf8=l"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84G\x02\xc2\xc1\xf3\xb4c!\xe2RJM"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97s"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9d"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05'"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"dr\xc0\xeb\xad?\xabA#\xe8\xb5\xedx4\xde!\xee\xb2\x01\x87\xb3\xef\xf7\x8a}\x1c\xdf\xfa@4\x85\x1d"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"?\x91\x13\xf0\\0\xa9\x96\xc4\xa5\x16@\x9b\xda\xc9\xd0e\xf4B\x96\xcc\xd5+\xb7]\xe3\xfc\xfd\xbe+\xf10"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 100, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r"), + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 200, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r\x9c\xea`\x14\xd7\xfeb\xa2\xed\x92m\xc3ka0\x7f\x11\x9dd\xed\xbc\xebZ\x9cX\x13;\xbfu\xba\x0b\xef\x00\n\x1aQ\x80\xe4\xb1\xde}\x89\xc8\x95(\xbc\xb7\x89\x9a\x1eF\xfdM\xa0\xd9\xde\x8f\x8ee\xe8\xd0\xd7u\xe3=\x12G\xe7mYj401a\xb2\x19\xf3\x9a\xfd\xa4H\xbfQ\x8a(5\xfc^(\xf0\xb5Z\x1ba7\xa2\xc7\x0c\xf7"), + + ("ge@äheim", b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha512(), b"\xb1J\x9f\x01\xbf\xd9\xdc\xe4\xc9\xd6m/\xe9\x93~_\xd9\xf1\xaf\xa5\x9e7\no\xa4\xfc\x81\xc1\xcc\x8e\xc8\xee"), + + // From https://cs.opensource.google/go/x/crypto/+/master:pkcs12/pbkdf_test.go + ("sesame", b"\xff\xff\xff\xff\xff\xff\xff\xff", KDF_ENCRYPTION_KEY_ID, 2048, 24, openssl::hash::MessageDigest::sha1(), b"\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1"), + ] { + let result = kdf(password, salt, id, rounds, key_len, hash).map_err(|_| ()).unwrap(); + assert_eq!(result, expected_key); + } + } +} diff --git a/src/rust/cryptography-keepalive/Cargo.toml b/src/rust/cryptography-keepalive/Cargo.toml index baf8d9342119..82a1a64f662f 100644 --- a/src/rust/cryptography-keepalive/Cargo.toml +++ b/src/rust/cryptography-keepalive/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true +license.workspace = true [dependencies] pyo3.workspace = true diff --git a/src/rust/cryptography-keepalive/src/lib.rs b/src/rust/cryptography-keepalive/src/lib.rs index 9542f9efc24c..c55d439547ef 100644 --- a/src/rust/cryptography-keepalive/src/lib.rs +++ b/src/rust/cryptography-keepalive/src/lib.rs @@ -4,10 +4,11 @@ #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] -use pyo3::pybacked::{PyBackedBytes, PyBackedStr}; use std::cell::UnsafeCell; use std::ops::Deref; +use pyo3::pybacked::{PyBackedBytes, PyBackedStr}; + pub struct KeepAlive { values: UnsafeCell>, } diff --git a/src/rust/cryptography-key-parsing/Cargo.toml b/src/rust/cryptography-key-parsing/Cargo.toml index 9b96b736c405..0489f47c5f4a 100644 --- a/src/rust/cryptography-key-parsing/Cargo.toml +++ b/src/rust/cryptography-key-parsing/Cargo.toml @@ -5,13 +5,15 @@ authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true +license.workspace = true [dependencies] asn1.workspace = true cfg-if = "1" -openssl = "0.10.68" -openssl-sys = "0.9.104" +openssl.workspace = true +openssl-sys.workspace = true +cryptography-crypto = { path = "../cryptography-crypto" } cryptography-x509 = { path = "../cryptography-x509" } [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_OSSLCONF, values("OPENSSL_NO_RC2"))', 'cfg(CRYPTOGRAPHY_IS_AWSLC)'] } diff --git a/src/rust/cryptography-key-parsing/build.rs b/src/rust/cryptography-key-parsing/build.rs index cd318b35ff35..895d9c091a0b 100644 --- a/src/rust/cryptography-key-parsing/build.rs +++ b/src/rust/cryptography-key-parsing/build.rs @@ -12,4 +12,14 @@ fn main() { if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); } + + if env::var("DEP_OPENSSL_AWSLC").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_AWSLC"); + } + + if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { + for var in vars.split(',') { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OSSLCONF=\"{var}\""); + } + } } diff --git a/src/rust/cryptography-key-parsing/src/dsa.rs b/src/rust/cryptography-key-parsing/src/dsa.rs new file mode 100644 index 000000000000..0b0461370dd2 --- /dev/null +++ b/src/rust/cryptography-key-parsing/src/dsa.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. + +use crate::KeyParsingResult; + +#[derive(asn1::Asn1Read)] +struct DsaPrivateKey<'a> { + version: u8, + p: asn1::BigUint<'a>, + q: asn1::BigUint<'a>, + g: asn1::BigUint<'a>, + pub_key: asn1::BigUint<'a>, + priv_key: asn1::BigUint<'a>, +} + +pub fn parse_pkcs1_private_key( + data: &[u8], +) -> KeyParsingResult> { + let dsa_private_key = asn1::parse_single::>(data)?; + if dsa_private_key.version != 0 { + return Err(crate::KeyParsingError::InvalidKey); + } + let p = openssl::bn::BigNum::from_slice(dsa_private_key.p.as_bytes())?; + let q = openssl::bn::BigNum::from_slice(dsa_private_key.q.as_bytes())?; + let g = openssl::bn::BigNum::from_slice(dsa_private_key.g.as_bytes())?; + let priv_key = openssl::bn::BigNum::from_slice(dsa_private_key.priv_key.as_bytes())?; + let pub_key = openssl::bn::BigNum::from_slice(dsa_private_key.pub_key.as_bytes())?; + let dsa = openssl::dsa::Dsa::from_private_components(p, q, g, priv_key, pub_key)?; + Ok(openssl::pkey::PKey::from_dsa(dsa)?) +} diff --git a/src/rust/cryptography-key-parsing/src/ec.rs b/src/rust/cryptography-key-parsing/src/ec.rs new file mode 100644 index 000000000000..fd6a195f4bfe --- /dev/null +++ b/src/rust/cryptography-key-parsing/src/ec.rs @@ -0,0 +1,107 @@ +// 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 cryptography_x509::common::EcParameters; + +use crate::{KeyParsingError, KeyParsingResult}; + +// From RFC 5915 Section 3 +#[derive(asn1::Asn1Read)] +pub(crate) struct EcPrivateKey<'a> { + pub(crate) version: u8, + pub(crate) private_key: &'a [u8], + #[explicit(0)] + pub(crate) parameters: Option>, + #[explicit(1)] + pub(crate) public_key: Option>, +} + +pub(crate) fn ec_params_to_group( + params: &EcParameters<'_>, +) -> KeyParsingResult { + match params { + EcParameters::NamedCurve(curve_oid) => { + let curve_nid = match curve_oid { + &cryptography_x509::oid::EC_SECP192R1 => openssl::nid::Nid::X9_62_PRIME192V1, + &cryptography_x509::oid::EC_SECP224R1 => openssl::nid::Nid::SECP224R1, + &cryptography_x509::oid::EC_SECP256R1 => openssl::nid::Nid::X9_62_PRIME256V1, + &cryptography_x509::oid::EC_SECP384R1 => openssl::nid::Nid::SECP384R1, + &cryptography_x509::oid::EC_SECP521R1 => openssl::nid::Nid::SECP521R1, + + &cryptography_x509::oid::EC_SECP256K1 => openssl::nid::Nid::SECP256K1, + + &cryptography_x509::oid::EC_SECT233R1 => openssl::nid::Nid::SECT233R1, + &cryptography_x509::oid::EC_SECT283R1 => openssl::nid::Nid::SECT283R1, + &cryptography_x509::oid::EC_SECT409R1 => openssl::nid::Nid::SECT409R1, + &cryptography_x509::oid::EC_SECT571R1 => openssl::nid::Nid::SECT571R1, + + &cryptography_x509::oid::EC_SECT163R2 => openssl::nid::Nid::SECT163R2, + + &cryptography_x509::oid::EC_SECT163K1 => openssl::nid::Nid::SECT163K1, + &cryptography_x509::oid::EC_SECT233K1 => openssl::nid::Nid::SECT233K1, + &cryptography_x509::oid::EC_SECT283K1 => openssl::nid::Nid::SECT283K1, + &cryptography_x509::oid::EC_SECT409K1 => openssl::nid::Nid::SECT409K1, + &cryptography_x509::oid::EC_SECT571K1 => openssl::nid::Nid::SECT571K1, + + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] + &cryptography_x509::oid::EC_BRAINPOOLP256R1 => openssl::nid::Nid::BRAINPOOL_P256R1, + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] + &cryptography_x509::oid::EC_BRAINPOOLP384R1 => openssl::nid::Nid::BRAINPOOL_P384R1, + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] + &cryptography_x509::oid::EC_BRAINPOOLP512R1 => openssl::nid::Nid::BRAINPOOL_P512R1, + + _ => return Err(KeyParsingError::UnsupportedEllipticCurve(curve_oid.clone())), + }; + + Ok(openssl::ec::EcGroup::from_curve_name(curve_nid) + .map_err(|_| KeyParsingError::UnsupportedEllipticCurve(curve_oid.clone()))?) + } + EcParameters::ImplicitCurve(_) | EcParameters::SpecifiedCurve(_) => { + Err(KeyParsingError::ExplicitCurveUnsupported) + } + } +} + +pub fn parse_pkcs1_private_key( + data: &[u8], + ec_params: Option>, +) -> KeyParsingResult> { + let ec_private_key = asn1::parse_single::>(data)?; + if ec_private_key.version != 1 { + return Err(crate::KeyParsingError::InvalidKey); + } + + let group = match (ec_params, ec_private_key.parameters) { + (Some(outer_params), Some(inner_params)) => { + if outer_params != inner_params { + return Err(crate::KeyParsingError::InvalidKey); + } + ec_params_to_group(&outer_params)? + } + (Some(outer_params), None) => ec_params_to_group(&outer_params)?, + (None, Some(inner_params)) => ec_params_to_group(&inner_params)?, + (None, None) => return Err(crate::KeyParsingError::InvalidKey), + }; + + let private_number = openssl::bn::BigNum::from_slice(ec_private_key.private_key)?; + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let public_point = if let Some(point_bytes) = ec_private_key.public_key { + openssl::ec::EcPoint::from_bytes(&group, point_bytes.as_bytes(), &mut bn_ctx) + .map_err(|_| crate::KeyParsingError::InvalidKey)? + } else { + let mut public_point = openssl::ec::EcPoint::new(&group)?; + public_point + .mul_generator(&group, &private_number, &bn_ctx) + .map_err(|_| crate::KeyParsingError::InvalidKey)?; + public_point + }; + + let ec_key = + openssl::ec::EcKey::from_private_components(&group, &private_number, &public_point) + .map_err(|_| KeyParsingError::InvalidKey)?; + ec_key + .check_key() + .map_err(|_| KeyParsingError::InvalidKey)?; + Ok(openssl::pkey::PKey::from_ec_key(ec_key)?) +} diff --git a/src/rust/cryptography-key-parsing/src/lib.rs b/src/rust/cryptography-key-parsing/src/lib.rs index c97bc3f754c6..8d54f3adf718 100644 --- a/src/rust/cryptography-key-parsing/src/lib.rs +++ b/src/rust/cryptography-key-parsing/src/lib.rs @@ -6,6 +6,9 @@ #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] #![allow(unknown_lints, clippy::result_large_err)] +pub mod dsa; +pub mod ec; +pub mod pkcs8; pub mod rsa; pub mod spki; @@ -16,6 +19,9 @@ pub enum KeyParsingError { UnsupportedEllipticCurve(asn1::ObjectIdentifier), Parse(asn1::ParseError), OpenSSL(openssl::error::ErrorStack), + UnsupportedEncryptionAlgorithm(asn1::ObjectIdentifier), + EncryptedKeyWithoutPassword, + IncorrectPassword, } impl From for KeyParsingError { diff --git a/src/rust/cryptography-key-parsing/src/pkcs8.rs b/src/rust/cryptography-key-parsing/src/pkcs8.rs new file mode 100644 index 000000000000..06634ae51a0f --- /dev/null +++ b/src/rust/cryptography-key-parsing/src/pkcs8.rs @@ -0,0 +1,316 @@ +// 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 cryptography_x509::common::{ + AlgorithmIdentifier, AlgorithmParameters, PbeParams, Pkcs12PbeParams, +}; +use cryptography_x509::csr::Attributes; +use cryptography_x509::pkcs8::EncryptedPrivateKeyInfo; + +use crate::{ec, rsa, KeyParsingError, KeyParsingResult}; + +// RFC 5208 Section 5 +#[derive(asn1::Asn1Read)] +struct PrivateKeyInfo<'a> { + version: u8, + algorithm: AlgorithmIdentifier<'a>, + private_key: &'a [u8], + #[implicit(0)] + _attributes: Option>, +} + +pub fn parse_private_key( + data: &[u8], +) -> KeyParsingResult> { + let k = asn1::parse_single::>(data)?; + if k.version != 0 { + return Err(crate::KeyParsingError::InvalidKey); + } + match k.algorithm.params { + AlgorithmParameters::Rsa(_) | AlgorithmParameters::RsaPss(_) => { + rsa::parse_pkcs1_private_key(k.private_key) + } + AlgorithmParameters::Ec(ec_params) => { + ec::parse_pkcs1_private_key(k.private_key, Some(ec_params)) + } + + AlgorithmParameters::Dsa(dsa_params) => { + let private_key_bytes = + asn1::parse_single::>(k.private_key)?.as_bytes(); + let dsa_private_key = openssl::bn::BigNum::from_slice(private_key_bytes)?; + let p = openssl::bn::BigNum::from_slice(dsa_params.p.as_bytes())?; + let q = openssl::bn::BigNum::from_slice(dsa_params.q.as_bytes())?; + let g = openssl::bn::BigNum::from_slice(dsa_params.g.as_bytes())?; + + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let mut pub_key = openssl::bn::BigNum::new()?; + pub_key.mod_exp(&g, &dsa_private_key, &p, &mut bn_ctx)?; + + let dsa = + openssl::dsa::Dsa::from_private_components(p, q, g, dsa_private_key, pub_key)?; + Ok(openssl::pkey::PKey::from_dsa(dsa)?) + } + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + AlgorithmParameters::Dh(dh_params) => { + let private_key_bytes = + asn1::parse_single::>(k.private_key)?.as_bytes(); + let dh_private_key = openssl::bn::BigNum::from_slice(private_key_bytes)?; + let p = openssl::bn::BigNum::from_slice(dh_params.p.as_bytes())?; + let g = openssl::bn::BigNum::from_slice(dh_params.g.as_bytes())?; + let q = openssl::bn::BigNum::from_slice(dh_params.q.as_bytes())?; + + let dh = openssl::dh::Dh::from_params(p, g, q)?; + let dh = dh.set_private_key(dh_private_key)?; + Ok(openssl::pkey::PKey::from_dh(dh)?) + } + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + AlgorithmParameters::DhKeyAgreement(dh_params) => { + let private_key_bytes = + asn1::parse_single::>(k.private_key)?.as_bytes(); + let dh_private_key = openssl::bn::BigNum::from_slice(private_key_bytes)?; + let p = openssl::bn::BigNum::from_slice(dh_params.p.as_bytes())?; + let g = openssl::bn::BigNum::from_slice(dh_params.g.as_bytes())?; + + let dh = openssl::dh::Dh::from_pqg(p, None, g)?; + let dh = dh.set_private_key(dh_private_key)?; + Ok(openssl::pkey::PKey::from_dh(dh)?) + } + + AlgorithmParameters::X25519 => { + let key_bytes = asn1::parse_single(k.private_key)?; + Ok(openssl::pkey::PKey::private_key_from_raw_bytes( + key_bytes, + openssl::pkey::Id::X25519, + )?) + } + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] + AlgorithmParameters::X448 => { + let key_bytes = asn1::parse_single(k.private_key)?; + Ok(openssl::pkey::PKey::private_key_from_raw_bytes( + key_bytes, + openssl::pkey::Id::X448, + )?) + } + AlgorithmParameters::Ed25519 => { + let key_bytes = asn1::parse_single(k.private_key)?; + Ok(openssl::pkey::PKey::private_key_from_raw_bytes( + key_bytes, + openssl::pkey::Id::ED25519, + )?) + } + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] + AlgorithmParameters::Ed448 => { + let key_bytes = asn1::parse_single(k.private_key)?; + Ok(openssl::pkey::PKey::private_key_from_raw_bytes( + key_bytes, + openssl::pkey::Id::ED448, + )?) + } + + _ => Err(KeyParsingError::UnsupportedKeyType( + k.algorithm.oid().clone(), + )), + } +} + +fn pkcs12_pbe_decrypt( + data: &[u8], + password: &[u8], + cipher: openssl::symm::Cipher, + hash: openssl::hash::MessageDigest, + params: &Pkcs12PbeParams<'_>, +) -> KeyParsingResult> { + let Ok(password) = std::str::from_utf8(password) else { + return Err(KeyParsingError::IncorrectPassword); + }; + let key = cryptography_crypto::pkcs12::kdf( + password, + params.salt, + cryptography_crypto::pkcs12::KDF_ENCRYPTION_KEY_ID, + params.iterations, + cipher.key_len(), + hash, + )?; + let iv = cryptography_crypto::pkcs12::kdf( + password, + params.salt, + cryptography_crypto::pkcs12::KDF_IV_ID, + params.iterations, + cipher.block_size(), + hash, + )?; + + openssl::symm::decrypt(cipher, &key, Some(&iv), data) + .map_err(|_| KeyParsingError::IncorrectPassword) +} + +fn pkcs5_pbe_decrypt( + data: &[u8], + password: &[u8], + cipher: openssl::symm::Cipher, + hash: openssl::hash::MessageDigest, + params: &PbeParams, +) -> KeyParsingResult> { + // PKCS#5 v1.5 uses PBKDF1 with iteration count + // For PKCS#5 PBE, we need key + IV length + let key_iv_len = cipher.key_len() + cipher.iv_len().unwrap(); + let key_iv = cryptography_crypto::pbkdf1::pbkdf1( + hash, + password, + params.salt, + params.iterations, + key_iv_len, + )?; + + let key = &key_iv[..cipher.key_len()]; + let iv = &key_iv[cipher.key_len()..]; + + openssl::symm::decrypt(cipher, key, Some(iv), data) + .map_err(|_| KeyParsingError::IncorrectPassword) +} + +pub fn parse_encrypted_private_key( + data: &[u8], + password: Option<&[u8]>, +) -> KeyParsingResult> { + let epki = asn1::parse_single::>(data)?; + let password = match password { + None | Some(b"") => return Err(KeyParsingError::EncryptedKeyWithoutPassword), + Some(p) => p, + }; + + let plaintext = match epki.encryption_algorithm.params { + AlgorithmParameters::PbeWithMd5AndDesCbc(params) => pkcs5_pbe_decrypt( + epki.encrypted_data, + password, + openssl::symm::Cipher::des_cbc(), + openssl::hash::MessageDigest::md5(), + ¶ms, + )?, + AlgorithmParameters::PbeWithShaAnd3KeyTripleDesCbc(params) => pkcs12_pbe_decrypt( + epki.encrypted_data, + password, + openssl::symm::Cipher::des_ede3_cbc(), + openssl::hash::MessageDigest::sha1(), + ¶ms, + )?, + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_RC2"))] + AlgorithmParameters::PbeWithShaAnd40BitRc2Cbc(params) => pkcs12_pbe_decrypt( + epki.encrypted_data, + password, + openssl::symm::Cipher::rc2_40_cbc(), + openssl::hash::MessageDigest::sha1(), + ¶ms, + )?, + AlgorithmParameters::Pbes2(params) => { + let (cipher, iv) = match params.encryption_scheme.params { + AlgorithmParameters::DesEde3Cbc(ref iv) => { + (openssl::symm::Cipher::des_ede3_cbc(), &iv[..]) + } + AlgorithmParameters::Aes128Cbc(ref iv) => { + (openssl::symm::Cipher::aes_128_cbc(), &iv[..]) + } + AlgorithmParameters::Aes192Cbc(ref iv) => { + (openssl::symm::Cipher::aes_192_cbc(), &iv[..]) + } + AlgorithmParameters::Aes256Cbc(ref iv) => { + (openssl::symm::Cipher::aes_256_cbc(), &iv[..]) + } + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_RC2"))] + AlgorithmParameters::Rc2Cbc(ref params) => { + // A version of 58 == 128 bits effective key length. The + // default is 32. See RFC 8018 B.2.3. + if params.version.unwrap_or(32) != 58 { + return Err(KeyParsingError::InvalidKey); + } + (openssl::symm::Cipher::rc2_cbc(), ¶ms.iv[..]) + } + _ => { + return Err(KeyParsingError::UnsupportedEncryptionAlgorithm( + params.encryption_scheme.oid().clone(), + )) + } + }; + + let key = match params.key_derivation_func.params { + AlgorithmParameters::Pbkdf2(pbkdf2_params) => { + let mut key = vec![0; cipher.key_len()]; + let md = match pbkdf2_params.prf.params { + AlgorithmParameters::HmacWithSha1(_) => { + openssl::hash::MessageDigest::sha1() + } + AlgorithmParameters::HmacWithSha224(_) => { + openssl::hash::MessageDigest::sha224() + } + AlgorithmParameters::HmacWithSha256(_) => { + openssl::hash::MessageDigest::sha256() + } + AlgorithmParameters::HmacWithSha384(_) => { + openssl::hash::MessageDigest::sha384() + } + AlgorithmParameters::HmacWithSha512(_) => { + openssl::hash::MessageDigest::sha512() + } + _ => { + return Err(KeyParsingError::UnsupportedEncryptionAlgorithm( + pbkdf2_params.prf.oid().clone(), + )) + } + }; + openssl::pkcs5::pbkdf2_hmac( + password, + pbkdf2_params.salt, + pbkdf2_params + .iteration_count + .try_into() + .map_err(|_| KeyParsingError::InvalidKey)?, + md, + &mut key, + ) + .unwrap(); + key + } + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] + AlgorithmParameters::Scrypt(scrypt_params) => { + let mut key = vec![0; cipher.key_len()]; + openssl::pkcs5::scrypt( + password, + scrypt_params.salt, + scrypt_params.cost_parameter, + scrypt_params.block_size, + scrypt_params.parallelization_parameter, + (usize::MAX / 2).try_into().unwrap(), + &mut key, + )?; + key + } + _ => { + return Err(KeyParsingError::UnsupportedEncryptionAlgorithm( + params.key_derivation_func.oid().clone(), + )) + } + }; + + openssl::symm::decrypt(cipher, &key, Some(iv), epki.encrypted_data) + .map_err(|_| KeyParsingError::IncorrectPassword)? + } + _ => { + return Err(KeyParsingError::UnsupportedEncryptionAlgorithm( + epki.encryption_algorithm.oid().clone(), + )) + } + }; + + parse_private_key(&plaintext) +} diff --git a/src/rust/cryptography-key-parsing/src/rsa.rs b/src/rust/cryptography-key-parsing/src/rsa.rs index bf33a492352e..2de5a0be3401 100644 --- a/src/rust/cryptography-key-parsing/src/rsa.rs +++ b/src/rust/cryptography-key-parsing/src/rsa.rs @@ -10,6 +10,22 @@ pub struct Pkcs1RsaPublicKey<'a> { e: asn1::BigUint<'a>, } +// RFC 8017, Section A.1.2 +#[derive(asn1::Asn1Read)] +pub(crate) struct RsaPrivateKey<'a> { + pub(crate) version: u8, + pub(crate) n: asn1::BigUint<'a>, + pub(crate) e: asn1::BigUint<'a>, + pub(crate) d: asn1::BigUint<'a>, + pub(crate) p: asn1::BigUint<'a>, + pub(crate) q: asn1::BigUint<'a>, + pub(crate) dmp1: asn1::BigUint<'a>, + pub(crate) dmq1: asn1::BigUint<'a>, + pub(crate) iqmp: asn1::BigUint<'a>, + // We don't support these, so don't bother to parse the inner fields. + pub(crate) other_prime_infos: Option, 1>>, +} + pub fn parse_pkcs1_public_key( data: &[u8], ) -> KeyParsingResult> { @@ -21,3 +37,22 @@ pub fn parse_pkcs1_public_key( let rsa = openssl::rsa::Rsa::from_public_components(n, e)?; Ok(openssl::pkey::PKey::from_rsa(rsa)?) } + +pub fn parse_pkcs1_private_key( + data: &[u8], +) -> KeyParsingResult> { + let rsa_private_key = asn1::parse_single::>(data)?; + if rsa_private_key.version != 0 || rsa_private_key.other_prime_infos.is_some() { + return Err(crate::KeyParsingError::InvalidKey); + } + let n = openssl::bn::BigNum::from_slice(rsa_private_key.n.as_bytes())?; + let e = openssl::bn::BigNum::from_slice(rsa_private_key.e.as_bytes())?; + let d = openssl::bn::BigNum::from_slice(rsa_private_key.d.as_bytes())?; + let p = openssl::bn::BigNum::from_slice(rsa_private_key.p.as_bytes())?; + let q = openssl::bn::BigNum::from_slice(rsa_private_key.q.as_bytes())?; + let dmp1 = openssl::bn::BigNum::from_slice(rsa_private_key.dmp1.as_bytes())?; + let dmq1 = openssl::bn::BigNum::from_slice(rsa_private_key.dmq1.as_bytes())?; + let iqmp = openssl::bn::BigNum::from_slice(rsa_private_key.iqmp.as_bytes())?; + let rsa_key = openssl::rsa::Rsa::from_private_components(n, e, d, p, q, dmp1, dmq1, iqmp)?; + Ok(openssl::pkey::PKey::from_rsa(rsa_key)?) +} diff --git a/src/rust/cryptography-key-parsing/src/spki.rs b/src/rust/cryptography-key-parsing/src/spki.rs index db4f69d94d10..31f150af8bf8 100644 --- a/src/rust/cryptography-key-parsing/src/spki.rs +++ b/src/rust/cryptography-key-parsing/src/spki.rs @@ -2,7 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use cryptography_x509::common::{AlgorithmParameters, EcParameters, SubjectPublicKeyInfo}; +use cryptography_x509::common::{AlgorithmParameters, SubjectPublicKeyInfo}; use crate::{KeyParsingError, KeyParsingResult}; @@ -12,80 +12,48 @@ pub fn parse_public_key( let k = asn1::parse_single::>(data)?; match k.algorithm.params { - AlgorithmParameters::Ec(ec_params) => match ec_params { - EcParameters::NamedCurve(curve_oid) => { - let curve_nid = match curve_oid { - cryptography_x509::oid::EC_SECP192R1 => openssl::nid::Nid::X9_62_PRIME192V1, - cryptography_x509::oid::EC_SECP224R1 => openssl::nid::Nid::SECP224R1, - cryptography_x509::oid::EC_SECP256R1 => openssl::nid::Nid::X9_62_PRIME256V1, - cryptography_x509::oid::EC_SECP384R1 => openssl::nid::Nid::SECP384R1, - cryptography_x509::oid::EC_SECP521R1 => openssl::nid::Nid::SECP521R1, - - cryptography_x509::oid::EC_SECP256K1 => openssl::nid::Nid::SECP256K1, - - cryptography_x509::oid::EC_SECT233R1 => openssl::nid::Nid::SECT233R1, - cryptography_x509::oid::EC_SECT283R1 => openssl::nid::Nid::SECT283R1, - cryptography_x509::oid::EC_SECT409R1 => openssl::nid::Nid::SECT409R1, - cryptography_x509::oid::EC_SECT571R1 => openssl::nid::Nid::SECT571R1, - - cryptography_x509::oid::EC_SECT163R2 => openssl::nid::Nid::SECT163R2, - - cryptography_x509::oid::EC_SECT163K1 => openssl::nid::Nid::SECT163K1, - cryptography_x509::oid::EC_SECT233K1 => openssl::nid::Nid::SECT233K1, - cryptography_x509::oid::EC_SECT283K1 => openssl::nid::Nid::SECT283K1, - cryptography_x509::oid::EC_SECT409K1 => openssl::nid::Nid::SECT409K1, - cryptography_x509::oid::EC_SECT571K1 => openssl::nid::Nid::SECT571K1, - - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] - cryptography_x509::oid::EC_BRAINPOOLP256R1 => { - openssl::nid::Nid::BRAINPOOL_P256R1 - } - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] - cryptography_x509::oid::EC_BRAINPOOLP384R1 => { - openssl::nid::Nid::BRAINPOOL_P384R1 - } - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] - cryptography_x509::oid::EC_BRAINPOOLP512R1 => { - openssl::nid::Nid::BRAINPOOL_P512R1 - } - - _ => return Err(KeyParsingError::UnsupportedEllipticCurve(curve_oid)), - }; - - let group = openssl::ec::EcGroup::from_curve_name(curve_nid) - .map_err(|_| KeyParsingError::UnsupportedEllipticCurve(curve_oid))?; - let mut bn_ctx = openssl::bn::BigNumContext::new()?; - let ec_point = openssl::ec::EcPoint::from_bytes( - &group, - k.subject_public_key.as_bytes(), - &mut bn_ctx, - ) - .map_err(|_| KeyParsingError::InvalidKey)?; - let ec_key = openssl::ec::EcKey::from_public_key(&group, &ec_point)?; - Ok(openssl::pkey::PKey::from_ec_key(ec_key)?) - } - EcParameters::ImplicitCurve(_) | EcParameters::SpecifiedCurve(_) => { - Err(KeyParsingError::ExplicitCurveUnsupported) - } - }, + AlgorithmParameters::Ec(ec_params) => { + let group = crate::ec::ec_params_to_group(&ec_params)?; + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let ec_point = openssl::ec::EcPoint::from_bytes( + &group, + k.subject_public_key.as_bytes(), + &mut bn_ctx, + ) + .map_err(|_| KeyParsingError::InvalidKey)?; + let ec_key = openssl::ec::EcKey::from_public_key(&group, &ec_point)?; + Ok(openssl::pkey::PKey::from_ec_key(ec_key)?) + } AlgorithmParameters::Ed25519 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::ED25519, - )?), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + ) + .map_err(|_| KeyParsingError::InvalidKey)?), + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] AlgorithmParameters::Ed448 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::ED448, - )?), + ) + .map_err(|_| KeyParsingError::InvalidKey)?), AlgorithmParameters::X25519 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::X25519, - )?), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + ) + .map_err(|_| KeyParsingError::InvalidKey)?), + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] AlgorithmParameters::X448 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::X448, - )?), + ) + .map_err(|_| KeyParsingError::InvalidKey)?), AlgorithmParameters::Rsa(_) | AlgorithmParameters::RsaPss(_) => { // RSA-PSS keys are treated the same as bare RSA keys. crate::rsa::parse_pkcs1_public_key(k.subject_public_key.as_bytes()) diff --git a/src/rust/cryptography-openssl/Cargo.toml b/src/rust/cryptography-openssl/Cargo.toml index 3d4c17ebaafd..5c7cd79d32eb 100644 --- a/src/rust/cryptography-openssl/Cargo.toml +++ b/src/rust/cryptography-openssl/Cargo.toml @@ -5,13 +5,14 @@ authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true +license.workspace = true [dependencies] cfg-if = "1" -openssl = "0.10.68" -ffi = { package = "openssl-sys", version = "0.9.101" } +openssl.workspace = true +openssl-sys.workspace = true foreign-types = "0.3" foreign-types-shared = "0.1" [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_IS_AWSLC)'] } diff --git a/src/rust/cryptography-openssl/build.rs b/src/rust/cryptography-openssl/build.rs index bed5a22111f1..9cad2b41e810 100644 --- a/src/rust/cryptography-openssl/build.rs +++ b/src/rust/cryptography-openssl/build.rs @@ -21,8 +21,16 @@ fn main() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); } - if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { - println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); + let is_boringssl = env::var("DEP_OPENSSL_BORINGSSL").is_ok(); + let is_awslc = env::var("DEP_OPENSSL_AWSLC").is_ok(); + + if is_boringssl || is_awslc { + let cfg_name = if is_boringssl { + "CRYPTOGRAPHY_IS_BORINGSSL" + } else { + "CRYPTOGRAPHY_IS_AWSLC" + }; + println!("cargo:rustc-cfg={cfg_name}"); if env::var_os("CARGO_CFG_UNIX").is_some() { match env::var("CARGO_CFG_TARGET_OS").as_deref() { Ok("macos") => println!("cargo:rustc-link-lib=c++"), diff --git a/src/rust/cryptography-openssl/src/aead.rs b/src/rust/cryptography-openssl/src/aead.rs index 42f0fd7f8041..dfd4dd20c4f1 100644 --- a/src/rust/cryptography-openssl/src/aead.rs +++ b/src/rust/cryptography-openssl/src/aead.rs @@ -2,11 +2,15 @@ // 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 openssl_sys as ffi; + +use crate::{cvt, cvt_p, OpenSSLResult}; pub enum AeadType { ChaCha20Poly1305, + Aes128GcmSiv, + Aes256GcmSiv, } foreign_types::foreign_type! { @@ -27,6 +31,10 @@ impl AeadCtx { let aead = match aead { // SAFETY: No preconditions. AeadType::ChaCha20Poly1305 => unsafe { ffi::EVP_aead_chacha20_poly1305() }, + // SAFETY: No preconditions. + AeadType::Aes128GcmSiv => unsafe { ffi::EVP_aead_aes_128_gcm_siv() }, + // SAFETY: No preconditions. + AeadType::Aes256GcmSiv => unsafe { ffi::EVP_aead_aes_256_gcm_siv() }, }; // SAFETY: We're passing a valid key and aead. diff --git a/src/rust/cryptography-openssl/src/cmac.rs b/src/rust/cryptography-openssl/src/cmac.rs index 2f4d22653111..e93790d874ea 100644 --- a/src/rust/cryptography-openssl/src/cmac.rs +++ b/src/rust/cryptography-openssl/src/cmac.rs @@ -5,6 +5,7 @@ use std::ptr; use foreign_types_shared::{ForeignType, ForeignTypeRef}; +use openssl_sys as ffi; use crate::hmac::DigestBytes; use crate::{cvt, cvt_p, OpenSSLResult}; diff --git a/src/rust/cryptography-openssl/src/fips.rs b/src/rust/cryptography-openssl/src/fips.rs index b14d2a5a659d..83ab169c1952 100644 --- a/src/rust/cryptography-openssl/src/fips.rs +++ b/src/rust/cryptography-openssl/src/fips.rs @@ -2,14 +2,26 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] -use crate::{cvt, OpenSSLResult}; #[cfg(all( CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, - not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) + not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )) ))] use std::ptr; +#[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC +)))] +use openssl_sys as ffi; + +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +use crate::{cvt, OpenSSLResult}; + pub fn is_enabled() -> bool { cfg_if::cfg_if! { if #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] { diff --git a/src/rust/cryptography-openssl/src/hmac.rs b/src/rust/cryptography-openssl/src/hmac.rs index 64abf83d40ae..3124356245ff 100644 --- a/src/rust/cryptography-openssl/src/hmac.rs +++ b/src/rust/cryptography-openssl/src/hmac.rs @@ -5,6 +5,7 @@ use std::ptr; use foreign_types_shared::{ForeignType, ForeignTypeRef}; +use openssl_sys as ffi; use crate::{cvt, cvt_p, OpenSSLResult}; @@ -22,9 +23,12 @@ unsafe impl Sync for Hmac {} unsafe impl Send for Hmac {} impl Hmac { - // On BoringSSL, the length is a size_t, so the length conversion is a + // On BoringSSL and AWS-LC, the length is a size_t, so the length conversion is a // no-op. - #[cfg_attr(CRYPTOGRAPHY_IS_BORINGSSL, allow(clippy::useless_conversion))] + #[cfg_attr( + any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC), + allow(clippy::useless_conversion) + )] pub fn new(key: &[u8], md: openssl::hash::MessageDigest) -> OpenSSLResult { // SAFETY: All FFI conditions are handled. unsafe { @@ -91,6 +95,8 @@ impl std::ops::Deref for DigestBytes { #[cfg(test)] mod tests { + use openssl_sys as ffi; + use super::DigestBytes; #[test] diff --git a/src/rust/cryptography-openssl/src/lib.rs b/src/rust/cryptography-openssl/src/lib.rs index d0fb6fff5c21..2cf0238e4990 100644 --- a/src/rust/cryptography-openssl/src/lib.rs +++ b/src/rust/cryptography-openssl/src/lib.rs @@ -4,12 +4,16 @@ #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] -#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] +#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] pub mod aead; pub mod cmac; pub mod fips; pub mod hmac; -#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] +#[cfg(any( + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_AWSLC +))] pub mod poly1305; pub type OpenSSLResult = Result; diff --git a/src/rust/cryptography-openssl/src/poly1305.rs b/src/rust/cryptography-openssl/src/poly1305.rs index e386bc2d7f4a..d19141a4261c 100644 --- a/src/rust/cryptography-openssl/src/poly1305.rs +++ b/src/rust/cryptography-openssl/src/poly1305.rs @@ -4,6 +4,8 @@ use std::mem::MaybeUninit; +use openssl_sys as ffi; + pub struct Poly1305State { // The state data must be allocated in the heap so that its address does not change. This is // because BoringSSL APIs that take a `poly1305_state*` ignore all the data before an aligned diff --git a/src/rust/cryptography-x509-verification/Cargo.toml b/src/rust/cryptography-x509-verification/Cargo.toml index 2cc2ff48829c..68042ab5167f 100644 --- a/src/rust/cryptography-x509-verification/Cargo.toml +++ b/src/rust/cryptography-x509-verification/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true +license.workspace = true [dependencies] asn1.workspace = true diff --git a/src/rust/cryptography-x509-verification/src/certificate.rs b/src/rust/cryptography-x509-verification/src/certificate.rs index ec1dd33a8085..4c9fc256b5f8 100644 --- a/src/rust/cryptography-x509-verification/src/certificate.rs +++ b/src/rust/cryptography-x509-verification/src/certificate.rs @@ -55,6 +55,7 @@ Xw4nMqk= type Key = (); type Err = (); type CertificateExtra = (); + type PolicyExtra = (); fn public_key(&self, _cert: &Certificate<'_>) -> Result { // Simulate failing to retrieve a public key. diff --git a/src/rust/cryptography-x509-verification/src/lib.rs b/src/rust/cryptography-x509-verification/src/lib.rs index 75ec6ce005da..b995a03505f2 100644 --- a/src/rust/cryptography-x509-verification/src/lib.rs +++ b/src/rust/cryptography-x509-verification/src/lib.rs @@ -16,21 +16,20 @@ use std::fmt::Display; use std::vec; use asn1::ObjectIdentifier; -use cryptography_x509::extensions::{DuplicateExtensionsError, Extensions}; -use cryptography_x509::{ - common::Asn1Read, - extensions::{NameConstraints, SubjectAlternativeName}, - name::GeneralName, - oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}, +use cryptography_x509::common::Asn1Read; +use cryptography_x509::extensions::{ + DuplicateExtensionsError, Extensions, NameConstraints, SubjectAlternativeName, }; -use types::{RFC822Constraint, RFC822Name}; +use cryptography_x509::name::GeneralName; +use cryptography_x509::oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}; use crate::certificate::cert_is_self_issued; use crate::ops::{CryptoOps, VerificationCertificate}; use crate::policy::Policy; use crate::trust_store::Store; -use crate::types::DNSName; -use crate::types::{DNSConstraint, IPAddress, IPConstraint}; +use crate::types::{ + DNSConstraint, DNSPattern, IPAddress, IPConstraint, RFC822Constraint, RFC822Name, +}; use crate::ApplyNameConstraintStatus::{Applied, Skipped}; pub enum ValidationErrorKind<'chain, B: CryptoOps> { @@ -50,7 +49,7 @@ pub struct ValidationError<'chain, B: CryptoOps> { } impl<'chain, B: CryptoOps> ValidationError<'chain, B> { - pub(crate) fn new(kind: ValidationErrorKind<'chain, B>) -> Self { + pub fn new(kind: ValidationErrorKind<'chain, B>) -> Self { ValidationError { kind, cert: None } } @@ -155,45 +154,51 @@ impl<'a, 'chain> NameChain<'a, 'chain> { budget.name_constraint_check()?; match (constraint, san) { - (GeneralName::DNSName(pattern), GeneralName::DNSName(name)) => { - match (DNSConstraint::new(pattern.0), DNSName::new(name.0)) { - (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), + (GeneralName::DNSName(constraint), GeneralName::DNSName(name)) => { + // NOTE: A DNS SAN can be a wildcard pattern instead of a normal DNS name. + // These are handled by matching unconditionally on the inner name, + // since a NC of `foo.com` will match both `foo.com` and any arbitrarily deep + // subdomain of `foo.com`, where a wildcard SAN like `*.foo.com` will only + // match exactly one subdomain of `foo.com`. Therefore, the NC's matching + // set is a strict superset of any possible wildcard SAN pattern. + match (DNSConstraint::new(constraint.0), DNSPattern::new(name.0)) { + (Some(constraint), Some(name)) => { + Ok(Applied(constraint.matches(name.inner_name()))) + } (_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "unsatisfiable DNS name constraint: malformed SAN {}", name.0 )))), (None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "malformed DNS name constraint: {}", - pattern.0 + constraint.0 )))), } } - (GeneralName::IPAddress(pattern), GeneralName::IPAddress(name)) => { + (GeneralName::IPAddress(constraint), GeneralName::IPAddress(name)) => { match ( - IPConstraint::from_bytes(pattern), + IPConstraint::from_bytes(constraint), IPAddress::from_bytes(name), ) { - (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), + (Some(constraint), Some(name)) => Ok(Applied(constraint.matches(&name))), (_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!( - "unsatisfiable IP name constraint: malformed SAN {:?}", - name, + "unsatisfiable IP name constraint: malformed SAN {name:?}", )))), (None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!( - "malformed IP name constraints: {:?}", - pattern + "malformed IP name constraints: {constraint:?}", )))), } } - (GeneralName::RFC822Name(pattern), GeneralName::RFC822Name(name)) => { - match (RFC822Constraint::new(pattern.0), RFC822Name::new(name.0)) { - (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), + (GeneralName::RFC822Name(constraint), GeneralName::RFC822Name(name)) => { + match (RFC822Constraint::new(constraint.0), RFC822Name::new(name.0)) { + (Some(constraint), Some(name)) => Ok(Applied(constraint.matches(&name))), (_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "unsatisfiable RFC822 name constraint: malformed SAN {:?}", name.0, )))), (None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "malformed RFC822 name constraints: {:?}", - pattern.0 + constraint.0 )))), } } @@ -456,7 +461,7 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { let leaf_extensions = leaf.certificate().extensions()?; self.policy - .permits_ee(leaf.certificate(), &leaf_extensions) + .permits_ee(leaf, &leaf_extensions) .map_err(|e| e.set_cert(leaf.clone()))?; let mut chain = self.build_chain_inner( diff --git a/src/rust/cryptography-x509-verification/src/ops.rs b/src/rust/cryptography-x509-verification/src/ops.rs index 05cca823fdc3..66698414479c 100644 --- a/src/rust/cryptography-x509-verification/src/ops.rs +++ b/src/rust/cryptography-x509-verification/src/ops.rs @@ -72,6 +72,9 @@ pub trait CryptoOps { /// Extra data that's passed around with the certificate. type CertificateExtra; + /// Extra data that's accessible alongside the PolicyDefinition. + type PolicyExtra; + /// Extracts the public key from the given `Certificate` in /// a `Key` format known by the cryptographic backend, or `None` /// if the key is malformed. @@ -90,9 +93,10 @@ pub trait CryptoOps { #[cfg(test)] pub(crate) mod tests { + use cryptography_x509::certificate::Certificate; + use super::VerificationCertificate; use crate::certificate::tests::PublicKeyErrorOps; - use cryptography_x509::certificate::Certificate; pub(crate) fn v1_cert_pem() -> pem::Pem { pem::parse( @@ -111,6 +115,10 @@ zl9HYIMxATFyqSiD9jsx .unwrap() } + pub(crate) fn epoch() -> asn1::DateTime { + asn1::DateTime::new(1970, 1, 1, 0, 0, 0).unwrap() + } + pub(crate) fn cert(cert_pem: &pem::Pem) -> Certificate<'_> { asn1::parse_single(cert_pem.contents()).unwrap() } diff --git a/src/rust/cryptography-x509-verification/src/policy/extension.rs b/src/rust/cryptography-x509-verification/src/policy/extension.rs index c5c751a7a96e..eb0f70ffb042 100644 --- a/src/rust/cryptography-x509-verification/src/policy/extension.rs +++ b/src/rust/cryptography-x509-verification/src/policy/extension.rs @@ -2,36 +2,179 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use std::sync::Arc; + +use cryptography_x509::extensions::{Extension, Extensions}; use cryptography_x509::oid::{ AUTHORITY_INFORMATION_ACCESS_OID, AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID, EXTENDED_KEY_USAGE_OID, KEY_USAGE_OID, NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID, SUBJECT_KEY_IDENTIFIER_OID, }; -use cryptography_x509::{ - certificate::Certificate, - extensions::{Extension, Extensions}, -}; -use crate::{ - ops::CryptoOps, policy::Policy, ValidationError, ValidationErrorKind, ValidationResult, -}; - -pub(crate) struct ExtensionPolicy { - pub(crate) authority_information_access: ExtensionValidator, - pub(crate) authority_key_identifier: ExtensionValidator, - pub(crate) subject_key_identifier: ExtensionValidator, - pub(crate) key_usage: ExtensionValidator, - pub(crate) subject_alternative_name: ExtensionValidator, - pub(crate) basic_constraints: ExtensionValidator, - pub(crate) name_constraints: ExtensionValidator, - pub(crate) extended_key_usage: ExtensionValidator, +use crate::ops::{CryptoOps, VerificationCertificate}; +use crate::policy::Policy; +use crate::{ValidationError, ValidationErrorKind, ValidationResult}; + +#[derive(Clone)] +pub struct ExtensionPolicy<'cb, B: CryptoOps> { + pub authority_information_access: ExtensionValidator<'cb, B>, + pub authority_key_identifier: ExtensionValidator<'cb, B>, + pub subject_key_identifier: ExtensionValidator<'cb, B>, + pub key_usage: ExtensionValidator<'cb, B>, + pub subject_alternative_name: ExtensionValidator<'cb, B>, + pub basic_constraints: ExtensionValidator<'cb, B>, + pub name_constraints: ExtensionValidator<'cb, B>, + pub extended_key_usage: ExtensionValidator<'cb, B>, } -impl ExtensionPolicy { +impl<'cb, B: CryptoOps + 'cb> ExtensionPolicy<'cb, B> { + pub fn new_permit_all() -> Self { + const fn make_permissive_validator<'cb, B: CryptoOps + 'cb>( + oid: asn1::ObjectIdentifier, + ) -> ExtensionValidator<'cb, B> { + ExtensionValidator::MaybePresent { + oid, + criticality: Criticality::Agnostic, + validator: None, + } + } + + ExtensionPolicy { + authority_information_access: make_permissive_validator( + AUTHORITY_INFORMATION_ACCESS_OID, + ), + authority_key_identifier: make_permissive_validator(AUTHORITY_KEY_IDENTIFIER_OID), + subject_key_identifier: make_permissive_validator(SUBJECT_KEY_IDENTIFIER_OID), + key_usage: make_permissive_validator(KEY_USAGE_OID), + subject_alternative_name: make_permissive_validator(SUBJECT_ALTERNATIVE_NAME_OID), + basic_constraints: make_permissive_validator(BASIC_CONSTRAINTS_OID), + name_constraints: make_permissive_validator(NAME_CONSTRAINTS_OID), + extended_key_usage: make_permissive_validator(EXTENDED_KEY_USAGE_OID), + } + } + + pub fn new_default_webpki_ca() -> Self { + // NOTE: Only those checks that we are fine with users disabling should + // be part of default ExtensionPolicies, since these are user-configurable. + // Any constraints that are mandatory should be put directly into `Policy`. + + ExtensionPolicy { + // 5280 4.2.2.1: Authority Information Access + authority_information_access: ExtensionValidator::maybe_present( + AUTHORITY_INFORMATION_ACCESS_OID, + Criticality::NonCritical, + Some(Arc::new(common::authority_information_access)), + ), + // 5280 4.2.1.1: Authority Key Identifier + authority_key_identifier: ExtensionValidator::maybe_present( + AUTHORITY_KEY_IDENTIFIER_OID, + Criticality::NonCritical, + Some(Arc::new(ca::authority_key_identifier)), + ), + // 5280 4.2.1.2: Subject Key Identifier + // NOTE: CABF requires SKI in CA certificates, but many older CAs lack it. + // We choose to be permissive here. + subject_key_identifier: ExtensionValidator::maybe_present( + SUBJECT_KEY_IDENTIFIER_OID, + Criticality::NonCritical, + None, + ), + // 5280 4.2.1.3: Key Usage + key_usage: ExtensionValidator::present( + KEY_USAGE_OID, + Criticality::Agnostic, + Some(Arc::new(ca::key_usage)), + ), + subject_alternative_name: ExtensionValidator::maybe_present( + SUBJECT_ALTERNATIVE_NAME_OID, + Criticality::Agnostic, + None, + ), + // 5280 4.2.1.9: Basic Constraints + basic_constraints: ExtensionValidator::present( + BASIC_CONSTRAINTS_OID, + Criticality::Critical, + None, // NOTE: Mandatory validation is done in `Policy::permits_ca` + ), + // 5280 4.2.1.10: Name Constraints + // NOTE: MUST be critical in 5280, but CABF relaxes to MAY. + name_constraints: ExtensionValidator::maybe_present( + NAME_CONSTRAINTS_OID, + Criticality::Agnostic, + Some(Arc::new(ca::name_constraints)), + ), + // 5280: 4.2.1.12: Extended Key Usage + // NOTE: CABF requires EKUs in many non-root CA certs, but validators widely + // ignore this requirement and treat a missing EKU as "any EKU". + // We choose to be permissive here. + extended_key_usage: ExtensionValidator::maybe_present( + EXTENDED_KEY_USAGE_OID, + Criticality::NonCritical, + Some(Arc::new(ca::extended_key_usage)), + ), + } + } + + pub fn new_default_webpki_ee() -> Self { + // NOTE: Only those checks that we are fine with users disabling should + // be part of default ExtensionPolicies, since these are user-configurable. + // Any constraints that are mandatory should be put directly into `Policy`. + + ExtensionPolicy { + // 5280 4.2.2.1: Authority Information Access + authority_information_access: ExtensionValidator::maybe_present( + AUTHORITY_INFORMATION_ACCESS_OID, + Criticality::NonCritical, + Some(Arc::new(common::authority_information_access)), + ), + // 5280 4.2.1.1.: Authority Key Identifier + authority_key_identifier: ExtensionValidator::present( + AUTHORITY_KEY_IDENTIFIER_OID, + Criticality::NonCritical, + None, + ), + subject_key_identifier: ExtensionValidator::maybe_present( + SUBJECT_KEY_IDENTIFIER_OID, + Criticality::Agnostic, + None, + ), + // 5280 4.2.1.3: Key Usage + key_usage: ExtensionValidator::maybe_present( + KEY_USAGE_OID, + Criticality::Agnostic, + Some(Arc::new(ee::key_usage)), + ), + // CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name + // This validator only handles the criticality checks. Matching + // SANs against the subject in the profile is handled by + // `Policy::permits_ee`. + subject_alternative_name: ExtensionValidator::present( + SUBJECT_ALTERNATIVE_NAME_OID, + Criticality::Agnostic, + Some(Arc::new(ee::subject_alternative_name)), + ), + // 5280 4.2.1.9: Basic Constraints + basic_constraints: ExtensionValidator::maybe_present( + BASIC_CONSTRAINTS_OID, + Criticality::Agnostic, + Some(Arc::new(ee::basic_constraints)), + ), + // 5280 4.2.1.10: Name Constraints + name_constraints: ExtensionValidator::not_present(NAME_CONSTRAINTS_OID), + // CA/B: 7.1.2.7.10: Subscriber Certificate Extended Key Usage + // NOTE: CABF requires EKUs in EE certs, while RFC 5280 does not. + extended_key_usage: ExtensionValidator::maybe_present( + EXTENDED_KEY_USAGE_OID, + Criticality::NonCritical, + Some(Arc::new(ee::extended_key_usage)), + ), + } + } + pub(crate) fn permits<'chain>( &self, policy: &Policy<'_, B>, - cert: &Certificate<'chain>, + cert: &VerificationCertificate<'chain, B>, extensions: &Extensions<'_>, ) -> ValidationResult<'chain, (), B> { let mut authority_information_access_seen = false; @@ -125,7 +268,8 @@ impl ExtensionPolicy { } /// Represents different criticality states for an extension. -pub(crate) enum Criticality { +#[derive(Clone)] +pub enum Criticality { /// The extension MUST be marked as critical. Critical, /// The extension MAY be marked as critical. @@ -146,58 +290,75 @@ impl Criticality { } } -type PresentExtensionValidatorCallback = for<'chain> fn( - &Policy<'_, B>, - &Certificate<'chain>, - &Extension<'_>, -) -> ValidationResult<'chain, (), B>; - -type MaybeExtensionValidatorCallback = for<'chain> fn( - &Policy<'_, B>, - &Certificate<'chain>, - Option<&Extension<'_>>, -) -> ValidationResult<'chain, (), B>; +pub type PresentExtensionValidatorCallback<'cb, B> = Arc< + dyn for<'chain> Fn( + &Policy<'_, B>, + &VerificationCertificate<'chain, B>, + &Extension<'_>, + ) -> ValidationResult<'chain, (), B> + + Send + + Sync + + 'cb, +>; + +pub type MaybeExtensionValidatorCallback<'cb, B> = Arc< + dyn for<'chain> Fn( + &Policy<'_, B>, + &VerificationCertificate<'chain, B>, + Option<&Extension<'_>>, + ) -> ValidationResult<'chain, (), B> + + Send + + Sync + + 'cb, +>; /// Represents different validation states for an extension. -pub(crate) enum ExtensionValidator { +#[derive(Clone)] +pub enum ExtensionValidator<'cb, B: CryptoOps> { /// The extension MUST NOT be present. - NotPresent, + NotPresent { oid: asn1::ObjectIdentifier }, /// The extension MUST be present. Present { + oid: asn1::ObjectIdentifier, /// The extension's criticality. criticality: Criticality, /// An optional validator over the extension's inner contents, with /// the surrounding `Policy` as context. - validator: Option>, + validator: Option>, }, /// The extension MAY be present; the interior validator is /// always called if supplied, including if the extension is not present. MaybePresent { + oid: asn1::ObjectIdentifier, criticality: Criticality, - validator: Option>, + validator: Option>, }, } -impl ExtensionValidator { - pub(crate) fn not_present() -> Self { - Self::NotPresent +impl<'cb, B: CryptoOps> ExtensionValidator<'cb, B> { + pub(crate) fn not_present(oid: asn1::ObjectIdentifier) -> Self { + Self::NotPresent { oid } } pub(crate) fn present( + oid: asn1::ObjectIdentifier, criticality: Criticality, - validator: Option>, + validator: Option>, ) -> Self { Self::Present { + oid, criticality, validator, } } pub(crate) fn maybe_present( + oid: asn1::ObjectIdentifier, criticality: Criticality, - validator: Option>, + validator: Option>, ) -> Self { Self::MaybePresent { + oid, criticality, validator, } @@ -206,28 +367,32 @@ impl ExtensionValidator { pub(crate) fn permits<'chain>( &self, policy: &Policy<'_, B>, - cert: &Certificate<'chain>, + cert: &VerificationCertificate<'chain, B>, extension: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { match (self, extension) { // Extension MUST NOT be present and isn't; OK. - (ExtensionValidator::NotPresent, None) => Ok(()), + (ExtensionValidator::NotPresent { .. }, None) => Ok(()), // Extension MUST NOT be present but is; NOT OK. - (ExtensionValidator::NotPresent, Some(extn)) => { + (ExtensionValidator::NotPresent { .. }, Some(extn)) => { Err(ValidationError::new(ValidationErrorKind::ExtensionError { oid: extn.extn_id.clone(), reason: "Certificate contains prohibited extension", })) } // Extension MUST be present but is not; NOT OK. - (ExtensionValidator::Present { .. }, None) => Err(ValidationError::new( - ValidationErrorKind::Other("Certificate is missing required extension".to_string()), - )), + (ExtensionValidator::Present { oid, .. }, None) => { + Err(ValidationError::new(ValidationErrorKind::ExtensionError { + oid: oid.clone(), + reason: "Certificate is missing required extension", + })) + } // Extension MUST be present and is; check it. ( ExtensionValidator::Present { criticality, validator, + .. }, Some(extn), ) => { @@ -239,13 +404,14 @@ impl ExtensionValidator { } // If a custom validator is supplied, apply it. - validator.map_or(Ok(()), |v| v(policy, cert, extn)) + validator.as_ref().map_or(Ok(()), |v| v(policy, cert, extn)) } // Extension MAY be present. ( ExtensionValidator::MaybePresent { criticality, validator, + .. }, extn, ) => { @@ -258,29 +424,22 @@ impl ExtensionValidator { })) } // If a custom validator is supplied, apply it. - _ => validator.map_or(Ok(()), |v| v(policy, cert, extn)), + _ => validator.as_ref().map_or(Ok(()), |v| v(policy, cert, extn)), } } } } } -pub(crate) mod ee { - use cryptography_x509::{ - certificate::Certificate, - extensions::{ - BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage, SubjectAlternativeName, - }, - }; +mod ee { + use cryptography_x509::extensions::{BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage}; - use crate::{ - ops::CryptoOps, - policy::{Policy, ValidationError, ValidationErrorKind, ValidationResult}, - }; + use crate::ops::{CryptoOps, VerificationCertificate}; + use crate::policy::{Policy, ValidationError, ValidationErrorKind, ValidationResult}; pub(crate) fn basic_constraints<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { @@ -297,11 +456,11 @@ pub(crate) mod ee { } pub(crate) fn subject_alternative_name<'chain, B: CryptoOps>( - policy: &Policy<'_, B>, - cert: &Certificate<'_>, + _: &Policy<'_, B>, + cert: &VerificationCertificate<'chain, B>, extn: &Extension<'_>, ) -> ValidationResult<'chain, (), B> { - match (cert.subject().is_empty(), extn.critical) { + match (cert.certificate().subject().is_empty(), extn.critical) { // If the subject is empty, the SAN MUST be critical. (true, false) => { return Err(ValidationError::new(ValidationErrorKind::Other( @@ -317,25 +476,15 @@ pub(crate) mod ee { _ => (), }; - // NOTE: We only verify the SAN against the policy's subject if the - // policy actually contains one. This enables both client and server - // profiles to use this validator, **with the expectation** that - // server profile construction requires a subject to be present. - if let Some(sub) = policy.subject.as_ref() { - let san: SubjectAlternativeName<'_> = extn.value()?; - if !sub.matches(&san) { - return Err(ValidationError::new(ValidationErrorKind::Other( - "leaf certificate has no matching subjectAltName".into(), - ))); - } - } + // NOTE: policy.subject is checked against SAN elsewhere (see `ExtensionPolicy::permits`) + // since we always want to check that, even if a custom ExtensionPolicy with a lax validator is used. Ok(()) } pub(crate) fn extended_key_usage<'chain, B: CryptoOps>( policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { @@ -361,7 +510,7 @@ pub(crate) mod ee { pub(crate) fn key_usage<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { @@ -378,25 +527,19 @@ pub(crate) mod ee { } } -pub(crate) mod ca { - use cryptography_x509::{ - certificate::Certificate, - common::Asn1Read, - extensions::{ - AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage, - NameConstraints, - }, - oid::EKU_ANY_KEY_USAGE_OID, +mod ca { + use cryptography_x509::common::Asn1Read; + use cryptography_x509::extensions::{ + AuthorityKeyIdentifier, ExtendedKeyUsage, Extension, KeyUsage, NameConstraints, }; + use cryptography_x509::oid::EKU_ANY_KEY_USAGE_OID; - use crate::{ - ops::CryptoOps, - policy::{Policy, ValidationError, ValidationErrorKind, ValidationResult}, - }; + use crate::ops::{CryptoOps, VerificationCertificate}; + use crate::policy::{Policy, ValidationError, ValidationErrorKind, ValidationResult}; pub(crate) fn authority_key_identifier<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { // CABF: AKI is required on all CA certificates *except* root CA certificates, @@ -439,7 +582,7 @@ pub(crate) mod ca { pub(crate) fn key_usage<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: &Extension<'_>, ) -> ValidationResult<'chain, (), B> { let key_usage: KeyUsage<'_> = extn.value()?; @@ -453,29 +596,9 @@ pub(crate) mod ca { Ok(()) } - pub(crate) fn basic_constraints<'chain, B: CryptoOps>( - _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, - extn: &Extension<'_>, - ) -> ValidationResult<'chain, (), B> { - let basic_constraints: BasicConstraints = extn.value()?; - - if !basic_constraints.ca { - return Err(ValidationError::new(ValidationErrorKind::Other( - "basicConstraints.cA must be asserted in a CA certificate".to_string(), - ))); - } - - // NOTE: basicConstraints.pathLength is checked as part of - // `Policy::permits_ca`, since we need the current chain building - // depth to check it. - - Ok(()) - } - pub(crate) fn name_constraints<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { @@ -507,7 +630,7 @@ pub(crate) mod ca { pub(crate) fn extended_key_usage<'chain, B: CryptoOps>( policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { @@ -528,21 +651,16 @@ pub(crate) mod ca { } } -pub(crate) mod common { - use cryptography_x509::{ - certificate::Certificate, - common::Asn1Read, - extensions::{Extension, SequenceOfAccessDescriptions}, - }; +mod common { + use cryptography_x509::common::Asn1Read; + use cryptography_x509::extensions::{Extension, SequenceOfAccessDescriptions}; - use crate::{ - ops::CryptoOps, - policy::{Policy, ValidationResult}, - }; + use crate::ops::{CryptoOps, VerificationCertificate}; + use crate::policy::{Policy, ValidationResult}; pub(crate) fn authority_information_access<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, extn: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { @@ -557,16 +675,17 @@ pub(crate) mod common { #[cfg(test)] mod tests { + use std::sync::Arc; + use asn1::{ObjectIdentifier, SimpleAsn1Writable}; - use cryptography_x509::certificate::Certificate; use cryptography_x509::extensions::{BasicConstraints, Extension}; use cryptography_x509::oid::BASIC_CONSTRAINTS_OID; use super::{Criticality, ExtensionValidator}; use crate::certificate::tests::PublicKeyErrorOps; - use crate::ops::tests::{cert, v1_cert_pem}; - use crate::ops::CryptoOps; - use crate::policy::{Policy, Subject, ValidationResult}; + use crate::ops::tests::{cert, epoch, v1_cert_pem}; + use crate::ops::{CryptoOps, VerificationCertificate}; + use crate::policy::{Policy, PolicyDefinition, Subject, ValidationResult}; use crate::types::DNSName; #[test] @@ -584,10 +703,6 @@ mod tests { assert!(criticality.permits(false)); } - fn epoch() -> asn1::DateTime { - asn1::DateTime::new(1970, 1, 1, 0, 0, 0).unwrap() - } - fn create_encoded_extension( oid: ObjectIdentifier, critical: bool, @@ -604,7 +719,7 @@ mod tests { fn present_extension_validator<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, _ext: &Extension<'_>, ) -> ValidationResult<'chain, (), B> { Ok(()) @@ -615,17 +730,25 @@ mod tests { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); + let verification_cert = VerificationCertificate::new(&cert, ()); let ops = PublicKeyErrorOps {}; - let policy = Policy::server( + let policy_def = PolicyDefinition::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, - ); + None, + None, + ) + .expect("failed to create policy definition"); + let policy = Policy::new(&policy_def, ()); // Test a policy that stipulates that a given extension MUST be present. - let extension_validator = - ExtensionValidator::present(Criticality::Critical, Some(present_extension_validator)); + let extension_validator = ExtensionValidator::present( + BASIC_CONSTRAINTS_OID, + Criticality::Critical, + Some(Arc::new(present_extension_validator)), + ); // Check the case where the extension is present. let bc = BasicConstraints { @@ -635,16 +758,18 @@ mod tests { let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, true, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator - .permits(&policy, &cert, Some(&raw_ext)) + .permits(&policy, &verification_cert, Some(&raw_ext)) .is_ok()); // Check the case where the extension isn't present. - assert!(extension_validator.permits(&policy, &cert, None).is_err()); + assert!(extension_validator + .permits(&policy, &verification_cert, None) + .is_err()); } fn maybe_extension_validator<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, - _cert: &Certificate<'_>, + _cert: &VerificationCertificate<'chain, B>, _ext: Option<&Extension<'_>>, ) -> ValidationResult<'chain, (), B> { Ok(()) @@ -655,18 +780,24 @@ mod tests { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); + let verification_cert = VerificationCertificate::new(&cert, ()); let ops = PublicKeyErrorOps {}; - let policy = Policy::server( + let policy_def = PolicyDefinition::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, - ); + None, + None, + ) + .expect("failed to create policy definition"); + let policy = Policy::new(&policy_def, ()); // Test a validator that stipulates that a given extension CAN be present. let extension_validator = ExtensionValidator::maybe_present( + BASIC_CONSTRAINTS_OID, Criticality::Critical, - Some(maybe_extension_validator), + Some(Arc::new(maybe_extension_validator)), ); // Check the case where the extension is present. @@ -677,11 +808,13 @@ mod tests { let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, true, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator - .permits(&policy, &cert, Some(&raw_ext)) + .permits(&policy, &verification_cert, Some(&raw_ext)) .is_ok()); // Check the case where the extension isn't present. - assert!(extension_validator.permits(&policy, &cert, None).is_ok()); + assert!(extension_validator + .permits(&policy, &verification_cert, None) + .is_ok()); } #[test] @@ -689,16 +822,21 @@ mod tests { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); + let verification_cert = VerificationCertificate::new(&cert, ()); let ops = PublicKeyErrorOps {}; - let policy = Policy::server( + let policy_def = PolicyDefinition::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, - ); + None, + None, + ) + .expect("failed to create policy definition"); + let policy = Policy::new(&policy_def, ()); // Test a validator that stipulates that a given extension MUST NOT be present. - let extension_validator = ExtensionValidator::not_present(); + let extension_validator = ExtensionValidator::not_present(BASIC_CONSTRAINTS_OID); // Check the case where the extension is present. let bc = BasicConstraints { @@ -708,11 +846,13 @@ mod tests { let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, true, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator - .permits(&policy, &cert, Some(&raw_ext)) + .permits(&policy, &verification_cert, Some(&raw_ext)) .is_err()); // Check the case where the extension isn't present. - assert!(extension_validator.permits(&policy, &cert, None).is_ok()); + assert!(extension_validator + .permits(&policy, &verification_cert, None) + .is_ok()); } #[test] @@ -721,16 +861,23 @@ mod tests { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; - let policy = Policy::server( + let policy_def = PolicyDefinition::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, - ); + None, + None, + ) + .expect("failed to create policy definition"); + let policy = Policy::new(&policy_def, ()); // Test a present policy that stipulates that a given extension MUST be critical. - let extension_validator = - ExtensionValidator::present(Criticality::Critical, Some(present_extension_validator)); + let extension_validator = ExtensionValidator::present( + BASIC_CONSTRAINTS_OID, + Criticality::Critical, + Some(Arc::new(present_extension_validator)), + ); // Mark the extension as non-critical despite our policy stipulating that it must be critical. let bc = BasicConstraints { @@ -740,7 +887,11 @@ mod tests { let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, false, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator - .permits(&policy, &cert, Some(&raw_ext)) + .permits( + &policy, + &VerificationCertificate::new(&cert, ()), + Some(&raw_ext) + ) .is_err()); } @@ -750,17 +901,22 @@ mod tests { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; - let policy = Policy::server( + let policy_def = PolicyDefinition::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, - ); + None, + None, + ) + .expect("failed to create policy definition"); + let policy = Policy::new(&policy_def, ()); // Test a maybe present validator that stipulates that a given extension MUST be critical. let extension_validator = ExtensionValidator::maybe_present( + BASIC_CONSTRAINTS_OID, Criticality::Critical, - Some(maybe_extension_validator), + Some(Arc::new(maybe_extension_validator)), ); // Mark the extension as non-critical despite our policy stipulating that it must be critical. @@ -771,7 +927,11 @@ mod tests { let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, false, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator - .permits(&policy, &cert, Some(&raw_ext)) + .permits( + &policy, + &VerificationCertificate::new(&cert, ()), + Some(&raw_ext) + ) .is_err()); } } diff --git a/src/rust/cryptography-x509-verification/src/policy/mod.rs b/src/rust/cryptography-x509-verification/src/policy/mod.rs index 935113fcdf3c..1d82e4b07ed8 100644 --- a/src/rust/cryptography-x509-verification/src/policy/mod.rs +++ b/src/rust/cryptography-x509-verification/src/policy/mod.rs @@ -5,7 +5,7 @@ mod extension; use std::collections::HashSet; -use std::ops::Range; +use std::ops::{Deref, Range}; use std::sync::Arc; use asn1::ObjectIdentifier; @@ -20,40 +20,43 @@ use cryptography_x509::extensions::{BasicConstraints, Extensions, SubjectAlterna use cryptography_x509::name::GeneralName; use cryptography_x509::oid::{ BASIC_CONSTRAINTS_OID, EC_SECP256R1, EC_SECP384R1, EC_SECP521R1, EKU_CLIENT_AUTH_OID, - EKU_SERVER_AUTH_OID, + EKU_SERVER_AUTH_OID, SUBJECT_ALTERNATIVE_NAME_OID, }; use once_cell::sync::Lazy; use crate::ops::CryptoOps; -use crate::policy::extension::{ca, common, ee, Criticality, ExtensionPolicy, ExtensionValidator}; +pub use crate::policy::extension::{ + Criticality, ExtensionPolicy, ExtensionValidator, MaybeExtensionValidatorCallback, + PresentExtensionValidatorCallback, +}; use crate::types::{DNSName, DNSPattern, IPAddress}; use crate::{ValidationError, ValidationErrorKind, ValidationResult, VerificationCertificate}; // RSA key constraints, as defined in CA/B 6.1.5. -static WEBPKI_MINIMUM_RSA_MODULUS: usize = 2048; +const WEBPKI_MINIMUM_RSA_MODULUS: usize = 2048; // SubjectPublicKeyInfo AlgorithmIdentifier constants, as defined in CA/B 7.1.3.1. // RSA -static SPKI_RSA: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const SPKI_RSA: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Rsa(Some(())), }; // SECP256R1 -static SPKI_SECP256R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const SPKI_SECP256R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP256R1)), }; // SECP384R1 -static SPKI_SECP384R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const SPKI_SECP384R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP384R1)), }; // SECP521R1 -static SPKI_SECP521R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const SPKI_SECP521R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP521R1)), }; @@ -73,19 +76,19 @@ pub static WEBPKI_PERMITTED_SPKI_ALGORITHMS: Lazy = AlgorithmIdentifier { +const RSASSA_PKCS1V15_SHA256: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaWithSha256(Some(())), }; // RSASSA‐PKCS1‐v1_5 with SHA‐384 -static RSASSA_PKCS1V15_SHA384: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const RSASSA_PKCS1V15_SHA384: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaWithSha384(Some(())), }; // RSASSA‐PKCS1‐v1_5 with SHA‐512 -static RSASSA_PKCS1V15_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const RSASSA_PKCS1V15_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaWithSha512(Some(())), }; @@ -124,19 +127,19 @@ static RSASSA_PSS_SHA512: Lazy> = Lazy::new(|| Algorithm }); // For P-256: the signature MUST use ECDSA with SHA‐256 -static ECDSA_SHA256: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const ECDSA_SHA256: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::EcDsaWithSha256(None), }; // For P-384: the signature MUST use ECDSA with SHA‐384 -static ECDSA_SHA384: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const ECDSA_SHA384: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::EcDsaWithSha384(None), }; // For P-521: the signature MUST use ECDSA with SHA‐512 -static ECDSA_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { +const ECDSA_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::EcDsaWithSha512(None), }; @@ -180,7 +183,7 @@ impl Subject<'_> { fn subject_alt_name_matches(&self, general_name: &GeneralName<'_>) -> bool { match (general_name, self) { (GeneralName::DNSName(pattern), Self::DNS(name)) => { - DNSPattern::new(pattern.0).map_or(false, |p| p.matches(name)) + DNSPattern::new(pattern.0).is_some_and(|p| p.matches(name)) } (GeneralName::IPAddress(addr), Self::IP(name)) => { IPAddress::from_bytes(addr) == Some(*name) @@ -196,8 +199,8 @@ impl Subject<'_> { } } -/// A `Policy` describes user-configurable aspects of X.509 path validation. -pub struct Policy<'a, B: CryptoOps> { +/// A `PolicyDefinition` describes user-configurable aspects of X.509 path validation. +pub struct PolicyDefinition<'a, B: CryptoOps> { pub ops: B, /// A top-level constraint on the length of intermediate CA paths @@ -230,19 +233,21 @@ pub struct Policy<'a, B: CryptoOps> { /// algorithm identifiers. pub permitted_signature_algorithms: Arc>>, - ca_extension_policy: ExtensionPolicy, - ee_extension_policy: ExtensionPolicy, + ca_extension_policy: ExtensionPolicy<'a, B>, + ee_extension_policy: ExtensionPolicy<'a, B>, } -impl<'a, B: CryptoOps> Policy<'a, B> { +impl<'a, B: CryptoOps + 'a> PolicyDefinition<'a, B> { fn new( ops: B, subject: Option>, time: asn1::DateTime, max_chain_depth: Option, extended_key_usage: ObjectIdentifier, - ) -> Self { - Self { + ca_extension_policy: Option>, + ee_extension_policy: Option>, + ) -> Result { + let retval = Self { ops, max_chain_depth: max_chain_depth.unwrap_or(DEFAULT_MAX_CHAIN_DEPTH), subject, @@ -251,93 +256,38 @@ impl<'a, B: CryptoOps> Policy<'a, B> { minimum_rsa_modulus: WEBPKI_MINIMUM_RSA_MODULUS, permitted_public_key_algorithms: Arc::clone(&*WEBPKI_PERMITTED_SPKI_ALGORITHMS), permitted_signature_algorithms: Arc::clone(&*WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS), - ca_extension_policy: ExtensionPolicy { - // 5280 4.2.2.1: Authority Information Access - authority_information_access: ExtensionValidator::maybe_present( - Criticality::NonCritical, - Some(common::authority_information_access), - ), - // 5280 4.2.1.1: Authority Key Identifier - authority_key_identifier: ExtensionValidator::maybe_present( - Criticality::NonCritical, - Some(ca::authority_key_identifier), - ), - // 5280 4.2.1.2: Subject Key Identifier - // NOTE: CABF requires SKI in CA certificates, but many older CAs lack it. - // We choose to be permissive here. - subject_key_identifier: ExtensionValidator::maybe_present( - Criticality::NonCritical, - None, - ), - // 5280 4.2.1.3: Key Usage - key_usage: ExtensionValidator::present(Criticality::Agnostic, Some(ca::key_usage)), - subject_alternative_name: ExtensionValidator::maybe_present( - Criticality::Agnostic, - None, - ), - // 5280 4.2.1.9: Basic Constraints - basic_constraints: ExtensionValidator::present( - Criticality::Critical, - Some(ca::basic_constraints), - ), - // 5280 4.2.1.10: Name Constraints - // NOTE: MUST be critical in 5280, but CABF relaxes to MAY. - name_constraints: ExtensionValidator::maybe_present( - Criticality::Agnostic, - Some(ca::name_constraints), - ), - // 5280: 4.2.1.12: Extended Key Usage - // NOTE: CABF requires EKUs in many non-root CA certs, but validators widely - // ignore this requirement and treat a missing EKU as "any EKU". - // We choose to be permissive here. - extended_key_usage: ExtensionValidator::maybe_present( - Criticality::NonCritical, - Some(ca::extended_key_usage), - ), - }, - ee_extension_policy: ExtensionPolicy { - // 5280 4.2.2.1: Authority Information Access - authority_information_access: ExtensionValidator::maybe_present( - Criticality::NonCritical, - Some(common::authority_information_access), - ), - // 5280 4.2.1.1.: Authority Key Identifier - authority_key_identifier: ExtensionValidator::present( - Criticality::NonCritical, - None, - ), - subject_key_identifier: ExtensionValidator::maybe_present( - Criticality::Agnostic, - None, - ), - // 5280 4.2.1.3: Key Usage - key_usage: ExtensionValidator::maybe_present( - Criticality::Agnostic, - Some(ee::key_usage), - ), - // CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name - // This validator handles both client and server cases by only matching against - // the SAN if the profile contains a subject, which it won't in the client - // validation case. - subject_alternative_name: ExtensionValidator::present( - Criticality::Agnostic, - Some(ee::subject_alternative_name), - ), - // 5280 4.2.1.9: Basic Constraints - basic_constraints: ExtensionValidator::maybe_present( - Criticality::Agnostic, - Some(ee::basic_constraints), - ), - // 5280 4.2.1.10: Name Constraints - name_constraints: ExtensionValidator::not_present(), - // CA/B: 7.1.2.7.10: Subscriber Certificate Extended Key Usage - // NOTE: CABF requires EKUs in EE certs, while RFC 5280 does not. - extended_key_usage: ExtensionValidator::maybe_present( - Criticality::NonCritical, - Some(ee::extended_key_usage), - ), - }, + ca_extension_policy: ca_extension_policy + .unwrap_or_else(ExtensionPolicy::new_default_webpki_ca), + ee_extension_policy: ee_extension_policy + .unwrap_or_else(ExtensionPolicy::new_default_webpki_ee), + }; + + // Even without the following checks, verification + // would fail, but we want to fail early and provide a more specific error message. + + if !matches!( + retval.ca_extension_policy.basic_constraints, + ExtensionValidator::Present { .. } + ) { + return Err( + "A CA extension policy must require the basicConstraints extension to be present.", + ); } + + // NOTE: If subject is set (server profile), we do not accept + // EE extension policies that allow the SAN extension to be absent. + if retval.subject.is_some() + && !matches!( + retval.ee_extension_policy.subject_alternative_name, + ExtensionValidator::Present { .. } + ) + { + return Err( + "An EE extension policy used for server verification must require the subjectAltName extension to be present.", + ); + } + + Ok(retval) } /// Create a new policy with suitable defaults for client certification @@ -346,13 +296,21 @@ impl<'a, B: CryptoOps> Policy<'a, B> { /// **IMPORTANT**: This is **not** the appropriate API for verifying /// website (i.e. server) certificates. For that, you **must** use /// [`Policy::server`]. - pub fn client(ops: B, time: asn1::DateTime, max_chain_depth: Option) -> Self { + pub fn client( + ops: B, + time: asn1::DateTime, + max_chain_depth: Option, + ca_extension_policy: Option>, + ee_extension_policy: Option>, + ) -> Result { Self::new( ops, None, time, max_chain_depth, EKU_CLIENT_AUTH_OID.clone(), + ca_extension_policy, + ee_extension_policy, ) } @@ -363,15 +321,38 @@ impl<'a, B: CryptoOps> Policy<'a, B> { subject: Subject<'a>, time: asn1::DateTime, max_chain_depth: Option, - ) -> Self { + ca_extension_policy: Option>, + ee_extension_policy: Option>, + ) -> Result { Self::new( ops, Some(subject), time, max_chain_depth, EKU_SERVER_AUTH_OID.clone(), + ca_extension_policy, + ee_extension_policy, ) } +} + +pub struct Policy<'a, B: CryptoOps> { + definition: &'a PolicyDefinition<'a, B>, + pub extra: B::PolicyExtra, +} + +impl<'a, B: CryptoOps> Deref for Policy<'a, B> { + type Target = PolicyDefinition<'a, B>; + + fn deref(&self) -> &Self::Target { + self.definition + } +} + +impl<'a, B: CryptoOps> Policy<'a, B> { + pub fn new(definition: &'a PolicyDefinition<'a, B>, extra: B::PolicyExtra) -> Self { + Self { definition, extra } + } fn permits_basic<'chain>(&self, cert: &Certificate<'_>) -> ValidationResult<'chain, (), B> { // CA/B 7.1.1: @@ -438,11 +419,11 @@ impl<'a, B: CryptoOps> Policy<'a, B> { /// Checks whether the given CA certificate is compatible with this policy. pub(crate) fn permits_ca<'chain>( &self, - cert: &Certificate<'chain>, + cert: &VerificationCertificate<'chain, B>, current_depth: u8, extensions: &Extensions<'_>, ) -> ValidationResult<'chain, (), B> { - self.permits_basic(cert)?; + self.permits_basic(cert.certificate())?; // 5280 4.1.2.6: Subject // CA certificates MUST have a subject populated with a non-empty distinguished name. @@ -450,24 +431,30 @@ impl<'a, B: CryptoOps> Policy<'a, B> { // and `ChainBuilder::potential_issuers` enforces subject/issuer matching, // meaning that an CA with an empty subject cannot occur in a built chain. - // NOTE: This conceptually belongs in `valid_issuer`, but is easier - // to test here. It's also conceptually an extension policy, but - // requires a bit of extra external state (`current_depth`) that isn't - // presently convenient to push into that layer. - // - // NOTE: BasicConstraints is required via `ca_extension_policies`, - // so we always take this branch. if let Some(bc) = extensions.get_extension(&BASIC_CONSTRAINTS_OID) { let bc: BasicConstraints = bc.value()?; + // NOTE: This conceptually belongs in `valid_issuer`, but is easier + // to test here. if bc .path_length - .map_or(false, |len| u64::from(current_depth) > len) + .is_some_and(|len| u64::from(current_depth) > len) { return Err(ValidationError::new(ValidationErrorKind::Other( "path length constraint violated".to_string(), ))); } + + if !bc.ca { + return Err(ValidationError::new(ValidationErrorKind::Other( + "basicConstraints.cA must be asserted in a CA certificate".to_string(), + ))); + } + } else { + return Err(ValidationError::new(ValidationErrorKind::ExtensionError { + oid: BASIC_CONSTRAINTS_OID, + reason: "missing required extension: CA certificate has no basicConstraints", + })); } self.ca_extension_policy.permits(self, cert, extensions)?; @@ -478,10 +465,19 @@ impl<'a, B: CryptoOps> Policy<'a, B> { /// Checks whether the given EE certificate is compatible with this policy. pub(crate) fn permits_ee<'chain>( &self, - cert: &Certificate<'chain>, - extensions: &Extensions<'_>, + cert: &VerificationCertificate<'chain, B>, + extensions: &Extensions<'chain>, ) -> ValidationResult<'chain, (), B> { - self.permits_basic(cert)?; + self.permits_basic(cert.certificate())?; + + if let Some(ref subject) = self.subject { + let san: Option> = + match &extensions.get_extension(&SUBJECT_ALTERNATIVE_NAME_OID) { + Some(ext) => Some(ext.value()?), + None => None, + }; + permits_subject_alternative_name(subject, &san)?; + } self.ee_extension_policy.permits(self, cert, extensions)?; @@ -509,7 +505,7 @@ impl<'a, B: CryptoOps> Policy<'a, B> { issuer_extensions: &Extensions<'_>, ) -> ValidationResult<'chain, (), B> { // The issuer needs to be a valid CA at the current depth. - self.permits_ca(issuer.certificate(), current_depth, issuer_extensions) + self.permits_ca(issuer, current_depth, issuer_extensions) .map_err(|e| e.set_cert(issuer.clone()))?; // CA/B 7.1.3.1 SubjectPublicKeyInfo @@ -595,16 +591,33 @@ fn permits_validity_date<'chain, B: CryptoOps>( Ok(()) } +fn permits_subject_alternative_name<'chain, B: CryptoOps>( + subject: &Subject<'_>, + san: &Option>, +) -> ValidationResult<'chain, (), B> { + let Some(san) = san else { + return Err(ValidationError::new(ValidationErrorKind::Other( + "missing required extension: leaf server certificate has no subjectAltName".into(), + ))); + }; + + if !subject.matches(san) { + return Err(ValidationError::new(ValidationErrorKind::Other( + "leaf certificate has no matching subjectAltName".into(), + ))); + } + + Ok(()) +} + #[cfg(test)] mod tests { use std::ops::Deref; use asn1::{DateTime, SequenceOfWriter}; use cryptography_x509::common::Time; - use cryptography_x509::{ - extensions::SubjectAlternativeName, - name::{GeneralName, UnvalidatedIA5String}, - }; + use cryptography_x509::extensions::SubjectAlternativeName; + use cryptography_x509::name::{GeneralName, UnvalidatedIA5String}; use super::{ permits_validity_date, ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512, RSASSA_PKCS1V15_SHA256, @@ -612,13 +625,11 @@ mod tests { RSASSA_PSS_SHA512, WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS, }; use crate::certificate::tests::PublicKeyErrorOps; - use crate::{ - policy::{ - Subject, SPKI_RSA, SPKI_SECP256R1, SPKI_SECP384R1, SPKI_SECP521R1, - WEBPKI_PERMITTED_SPKI_ALGORITHMS, - }, - types::{DNSName, IPAddress}, + use crate::policy::{ + Subject, SPKI_RSA, SPKI_SECP256R1, SPKI_SECP384R1, SPKI_SECP521R1, + WEBPKI_PERMITTED_SPKI_ALGORITHMS, }; + use crate::types::{DNSName, IPAddress}; #[test] fn test_webpki_permitted_spki_algorithms_canonical_encodings() { diff --git a/src/rust/cryptography-x509-verification/src/trust_store.rs b/src/rust/cryptography-x509-verification/src/trust_store.rs index c3b525930d9f..dc1260a8a08c 100644 --- a/src/rust/cryptography-x509-verification/src/trust_store.rs +++ b/src/rust/cryptography-x509-verification/src/trust_store.rs @@ -6,8 +6,7 @@ use std::collections::HashMap; use cryptography_x509::name::Name; -use crate::CryptoOps; -use crate::VerificationCertificate; +use crate::{CryptoOps, VerificationCertificate}; /// A `Store` represents the core state needed for X.509 path validation. pub struct Store<'a, B: CryptoOps> { diff --git a/src/rust/cryptography-x509-verification/src/types.rs b/src/rust/cryptography-x509-verification/src/types.rs index 0cd84489e089..d71dc3895838 100644 --- a/src/rust/cryptography-x509-verification/src/types.rs +++ b/src/rust/cryptography-x509-verification/src/types.rs @@ -8,7 +8,7 @@ use std::str::FromStr; use asn1::IA5String; // RFC 2822 3.2.4 -static ATEXT_CHARS: &str = "!#$%&'*+-/=?^_`{|}~"; +const ATEXT_CHARS: &str = "!#$%&'*+-/=?^_`{|}~"; /// Represents a DNS name can be used in X.509 name matching. /// @@ -138,6 +138,19 @@ impl<'a> DNSPattern<'a> { }, } } + + /// Returns the inner `DNSName` within this `DNSPattern`, e.g. + /// `foo.com` for `*.foo.com` or `example.com` for `example.com`. + /// + /// This API must not be used to bypass pattern matching; it exists + /// solely to enable checks that only require the inner name, such + /// as Name Constraint checks. + pub fn inner_name(&self) -> &DNSName<'a> { + match self { + DNSPattern::Exact(dnsname) => dnsname, + DNSPattern::Wildcard(dnsname) => dnsname, + } + } } /// A `DNSConstraint` represents a DNS name constraint as defined in [RFC 5280 4.2.1.10]. @@ -400,9 +413,8 @@ impl<'a> RFC822Constraint<'a> { #[cfg(test)] mod tests { - use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint, RFC822Name}; - use super::RFC822Constraint; + use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint, RFC822Name}; #[test] fn test_dnsname_debug_trait() { diff --git a/src/rust/cryptography-x509/Cargo.toml b/src/rust/cryptography-x509/Cargo.toml index 03f2c260890e..bdb30f131a8d 100644 --- a/src/rust/cryptography-x509/Cargo.toml +++ b/src/rust/cryptography-x509/Cargo.toml @@ -1,11 +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.65.0" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true +license.workspace = true [dependencies] asn1.workspace = true diff --git a/src/rust/cryptography-x509/src/certificate.rs b/src/rust/cryptography-x509/src/certificate.rs index 6db6eade0766..73565d3cc10c 100644 --- a/src/rust/cryptography-x509/src/certificate.rs +++ b/src/rust/cryptography-x509/src/certificate.rs @@ -2,12 +2,9 @@ // 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::DuplicateExtensionsError; -use crate::extensions::Extensions; -use crate::name; +use crate::extensions::{DuplicateExtensionsError, Extensions}; use crate::name::NameReadable; +use crate::{common, extensions, name}; #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct Certificate<'a> { @@ -34,12 +31,17 @@ impl<'a> Certificate<'a> { } } +// This should really be a wrapper around `BigUint` that rejects 0s, however +// for the time being we support invalid serial numbers (mostly because the MS +// trust store has a certificate with a negative serial number). +pub type SerialNumber<'a> = asn1::BigInt<'a>; + #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct TbsCertificate<'a> { #[explicit(0)] #[default(0)] pub version: u8, - pub serial: asn1::BigInt<'a>, + pub serial: SerialNumber<'a>, pub signature_alg: common::AlgorithmIdentifier<'a>, pub issuer: name::Name<'a>, diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs index 77ccd011a85e..183633b4a6ff 100644 --- a/src/rust/cryptography-x509/src/common.rs +++ b/src/rust/cryptography-x509/src/common.rs @@ -2,7 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use asn1::Asn1DefinedByWritable; +use asn1::{Asn1DefinedByWritable, SimpleAsn1Writable}; use crate::oid; @@ -130,11 +130,19 @@ pub enum AlgorithmParameters<'a> { #[defined_by(oid::PBKDF2_OID)] Pbkdf2(PBKDF2Params<'a>), + #[defined_by(oid::SCRYPT_OID)] + Scrypt(ScryptParams<'a>), #[defined_by(oid::HMAC_WITH_SHA1_OID)] HmacWithSha1(Option), + #[defined_by(oid::HMAC_WITH_SHA224_OID)] + HmacWithSha224(Option), #[defined_by(oid::HMAC_WITH_SHA256_OID)] HmacWithSha256(Option), + #[defined_by(oid::HMAC_WITH_SHA384_OID)] + HmacWithSha384(Option), + #[defined_by(oid::HMAC_WITH_SHA512_OID)] + HmacWithSha512(Option), // Used only in PKCS#7 AlgorithmIdentifiers // https://datatracker.ietf.org/doc/html/rfc3565#section-4.1 @@ -146,11 +154,23 @@ pub enum AlgorithmParameters<'a> { // AES-IV ::= OCTET STRING (SIZE(16)) #[defined_by(oid::AES_128_CBC_OID)] Aes128Cbc([u8; 16]), + #[defined_by(oid::AES_192_CBC_OID)] + Aes192Cbc([u8; 16]), #[defined_by(oid::AES_256_CBC_OID)] Aes256Cbc([u8; 16]), - #[defined_by(oid::PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC)] - Pbes1WithShaAnd3KeyTripleDesCbc(PBES1Params), + #[defined_by(oid::DES_EDE3_CBC_OID)] + DesEde3Cbc([u8; 8]), + + #[defined_by(oid::RC2_CBC)] + Rc2Cbc(Rc2CbcParams), + + #[defined_by(oid::PBE_WITH_MD5_AND_DES_CBC)] + PbeWithMd5AndDesCbc(PbeParams), + #[defined_by(oid::PBE_WITH_SHA_AND_3KEY_TRIPLEDES_CBC)] + PbeWithShaAnd3KeyTripleDesCbc(Pkcs12PbeParams<'a>), + #[defined_by(oid::PBE_WITH_SHA_AND_40_BIT_RC2_CBC)] + PbeWithShaAnd40BitRc2Cbc(Pkcs12PbeParams<'a>), #[default] Other(asn1::ObjectIdentifier, Option>), @@ -165,7 +185,28 @@ pub struct SubjectPublicKeyInfo<'a> { #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub struct AttributeTypeValue<'a> { pub type_id: asn1::ObjectIdentifier, - pub value: RawTlv<'a>, + pub value: AttributeValue<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] +pub enum AttributeValue<'a> { + UniversalString(asn1::UniversalString<'a>), + BmpString(asn1::BMPString<'a>), + PrintableString(asn1::PrintableString<'a>), + + // Must be last, because enums parse things in order. + AnyString(RawTlv<'a>), +} + +impl AttributeValue<'_> { + pub fn tag(&self) -> asn1::Tag { + match self { + AttributeValue::AnyString(tlv) => tlv.tag(), + AttributeValue::PrintableString(_) => asn1::PrintableString::TAG, + AttributeValue::UniversalString(_) => asn1::UniversalString::TAG, + AttributeValue::BmpString(_) => asn1::BMPString::TAG, + } + } } // Like `asn1::Tlv` but doesn't store `full_data` so it can be constructed from @@ -480,12 +521,36 @@ pub struct PBKDF2Params<'a> { pub prf: Box>, } +// RFC 7914 Section 7 #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] -pub struct PBES1Params { +pub struct ScryptParams<'a> { + pub salt: &'a [u8], + pub cost_parameter: u64, + pub block_size: u64, + pub parallelization_parameter: u64, + pub key_length: Option, +} + +// RFC 8018 Appendix A.3 +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] +pub struct PbeParams { pub salt: [u8; 8], pub iterations: u64, } +// From RFC 7202 Appendix C +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] +pub struct Pkcs12PbeParams<'a> { + pub salt: &'a [u8], + pub iterations: u64, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] +pub struct Rc2CbcParams { + pub version: Option, + pub iv: [u8; 8], +} + /// 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. @@ -498,7 +563,7 @@ impl<'a> UnvalidatedVisibleString<'a> { } impl<'a> asn1::SimpleAsn1Readable<'a> for UnvalidatedVisibleString<'a> { - const TAG: asn1::Tag = asn1::VisibleString::TAG; + const TAG: asn1::Tag = as asn1::SimpleAsn1Readable>::TAG; fn parse_data(data: &'a [u8]) -> asn1::ParseResult { Ok(UnvalidatedVisibleString( std::str::from_utf8(data) diff --git a/src/rust/cryptography-x509/src/crl.rs b/src/rust/cryptography-x509/src/crl.rs index ced8fb8e26b2..77b303a0c989 100644 --- a/src/rust/cryptography-x509/src/crl.rs +++ b/src/rust/cryptography-x509/src/crl.rs @@ -2,6 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use crate::certificate::SerialNumber; use crate::common::Asn1Operation; use crate::{common, extensions, name}; @@ -35,7 +36,7 @@ pub struct TBSCertList<'a> { #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub struct RevokedCertificate<'a> { - pub user_certificate: asn1::BigUint<'a>, + pub user_certificate: SerialNumber<'a>, pub revocation_date: common::Time, pub raw_crl_entry_extensions: Option>, } diff --git a/src/rust/cryptography-x509/src/csr.rs b/src/rust/cryptography-x509/src/csr.rs index 95745db9380e..c7385af953b3 100644 --- a/src/rust/cryptography-x509/src/csr.rs +++ b/src/rust/cryptography-x509/src/csr.rs @@ -2,10 +2,7 @@ // 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; +use crate::{common, extensions, name, oid}; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct Csr<'a> { diff --git a/src/rust/cryptography-x509/src/extensions.rs b/src/rust/cryptography-x509/src/extensions.rs index 2ffa8781d1a0..8a9df2a16482 100644 --- a/src/rust/cryptography-x509/src/extensions.rs +++ b/src/rust/cryptography-x509/src/extensions.rs @@ -4,10 +4,9 @@ use std::collections::HashSet; -use crate::common; +use crate::certificate::SerialNumber; use crate::common::Asn1Operation; -use crate::crl; -use crate::name; +use crate::{common, crl, name}; pub struct DuplicateExtensionsError(pub asn1::ObjectIdentifier); @@ -200,7 +199,7 @@ pub struct AuthorityKeyIdentifier<'a, Op: Asn1Operation> { #[implicit(1)] pub authority_cert_issuer: Option>, #[implicit(2)] - pub authority_cert_serial_number: Option>, + pub authority_cert_serial_number: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] @@ -303,6 +302,14 @@ pub struct Admissions<'a, Op: Asn1Operation> { pub contents_of_admissions: Op::SequenceOfVec<'a, Admission<'a, Op>>, } +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct PrivateKeyUsagePeriod { + #[implicit(0)] + pub not_before: Option, + #[implicit(1)] + pub not_after: Option, +} + #[cfg(test)] mod tests { use super::{BasicConstraints, Extension, Extensions, KeyUsage}; diff --git a/src/rust/cryptography-x509/src/lib.rs b/src/rust/cryptography-x509/src/lib.rs index 54c3b12aa942..b06b0a62afb3 100644 --- a/src/rust/cryptography-x509/src/lib.rs +++ b/src/rust/cryptography-x509/src/lib.rs @@ -17,3 +17,4 @@ pub mod ocsp_resp; pub mod oid; pub mod pkcs12; pub mod pkcs7; +pub mod pkcs8; diff --git a/src/rust/cryptography-x509/src/oid.rs b/src/rust/cryptography-x509/src/oid.rs index ee148a7896ee..55e2e4573636 100644 --- a/src/rust/cryptography-x509/src/oid.rs +++ b/src/rust/cryptography-x509/src/oid.rs @@ -45,6 +45,7 @@ pub const INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, pub const ACCEPTABLE_RESPONSES_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 4); pub const ADMISSIONS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 8, 3, 3); +pub const PRIVATE_KEY_USAGE_PERIOD_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 16); // Public key identifiers pub const EC_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 2, 1); @@ -151,13 +152,24 @@ pub const EKU_CERTIFICATE_TRANSPARENCY_OID: asn1::ObjectIdentifier = pub const PBES2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 13); pub const PBKDF2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 12); +pub const PBE_WITH_MD5_AND_DES_CBC: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 3); +pub const SCRYPT_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 11591, 4, 11); -pub const PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC: asn1::ObjectIdentifier = +pub const PBE_WITH_SHA_AND_3KEY_TRIPLEDES_CBC: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 1, 3); +pub const PBE_WITH_SHA_AND_40_BIT_RC2_CBC: asn1::ObjectIdentifier = + asn1::oid!(1, 2, 840, 113549, 1, 12, 1, 6); -pub const AES_256_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 42); -pub const AES_192_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 22); pub const AES_128_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 2); +pub const AES_192_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 22); +pub const AES_256_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 42); + +pub const DES_EDE3_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 3, 7); + +pub const RC2_CBC: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 3, 2); pub const HMAC_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 7); +pub const HMAC_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 8); pub const HMAC_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 9); +pub const HMAC_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 10); +pub const HMAC_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 11); diff --git a/src/rust/cryptography-x509/src/pkcs12.rs b/src/rust/cryptography-x509/src/pkcs12.rs index f8f518a4b615..9935d3bd9830 100644 --- a/src/rust/cryptography-x509/src/pkcs12.rs +++ b/src/rust/cryptography-x509/src/pkcs12.rs @@ -2,8 +2,10 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::common::{AlgorithmIdentifier, Utf8StoredBMPString}; -use crate::pkcs7; +use asn1::ObjectIdentifier; + +use crate::common::Utf8StoredBMPString; +use crate::{pkcs7, pkcs8}; pub const CERT_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 3); pub const KEY_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 1); @@ -12,15 +14,17 @@ pub const SHROUDED_KEY_BAG_OID: asn1::ObjectIdentifier = pub const X509_CERTIFICATE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 22, 1); pub const FRIENDLY_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 20); pub const LOCAL_KEY_ID_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 21); +pub const JDK_TRUSTSTORE_USAGE: asn1::ObjectIdentifier = + asn1::oid!(2, 16, 840, 1, 113894, 746875, 1, 1); -#[derive(asn1::Asn1Write)] +#[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct Pfx<'a> { pub version: u8, pub auth_safe: pkcs7::ContentInfo<'a>, pub mac_data: Option>, } -#[derive(asn1::Asn1Write)] +#[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct MacData<'a> { pub mac: pkcs7::DigestInfo<'a>, pub salt: &'a [u8], @@ -50,18 +54,21 @@ pub enum AttributeSet<'a> { #[defined_by(LOCAL_KEY_ID_OID)] LocalKeyId(asn1::SetOfWriter<'a, &'a [u8], [&'a [u8]; 1]>), + + #[defined_by(JDK_TRUSTSTORE_USAGE)] + JDKTruststoreUsage(asn1::SetOfWriter<'a, ObjectIdentifier, [ObjectIdentifier; 1]>), } #[derive(asn1::Asn1DefinedByWrite)] pub enum BagValue<'a> { #[defined_by(CERT_BAG_OID)] - CertBag(CertBag<'a>), + CertBag(Box>), #[defined_by(KEY_BAG_OID)] KeyBag(asn1::Tlv<'a>), #[defined_by(SHROUDED_KEY_BAG_OID)] - ShroudedKeyBag(EncryptedPrivateKeyInfo<'a>), + ShroudedKeyBag(pkcs8::EncryptedPrivateKeyInfo<'a>), } #[derive(asn1::Asn1Write)] @@ -76,9 +83,3 @@ pub enum CertType<'a> { #[defined_by(X509_CERTIFICATE_OID)] X509(asn1::OctetStringEncoded>), } - -#[derive(asn1::Asn1Write)] -pub struct EncryptedPrivateKeyInfo<'a> { - pub encryption_algorithm: AlgorithmIdentifier<'a>, - pub encrypted_data: &'a [u8], -} diff --git a/src/rust/cryptography-x509/src/pkcs8.rs b/src/rust/cryptography-x509/src/pkcs8.rs new file mode 100644 index 000000000000..c1f2b88c5b37 --- /dev/null +++ b/src/rust/cryptography-x509/src/pkcs8.rs @@ -0,0 +1,12 @@ +// 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::AlgorithmIdentifier; + +// RFC 5208, Section 6 +#[derive(asn1::Asn1Write, asn1::Asn1Read)] +pub struct EncryptedPrivateKeyInfo<'a> { + pub encryption_algorithm: AlgorithmIdentifier<'a>, + pub encrypted_data: &'a [u8], +} diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs index 26ee176bb935..d2906ec9e862 100644 --- a/src/rust/src/asn1.rs +++ b/src/rust/src/asn1.rs @@ -4,8 +4,7 @@ use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo}; use pyo3::pybacked::PyBackedBytes; -use pyo3::types::IntoPyDict; -use pyo3::types::PyAnyMethods; +use pyo3::types::{IntoPyDict, PyAnyMethods}; use pyo3::IntoPyObject; use crate::error::{CryptographyError, CryptographyResult}; diff --git a/src/rust/src/backend/aead.rs b/src/rust/src/backend/aead.rs index fc56b64d6553..ef6cbc01a318 100644 --- a/src/rust/src/backend/aead.rs +++ b/src/rust/src/backend/aead.rs @@ -2,10 +2,11 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::{PyAnyMethods, PyListMethods}; + use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; -use pyo3::types::{PyAnyMethods, PyListMethods}; fn check_length(data: &[u8]) -> CryptographyResult<()> { if data.len() > (i32::MAX as usize) { @@ -364,13 +365,13 @@ impl LazyEvpCipherAead { } } -#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] +#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] struct EvpAead { ctx: cryptography_openssl::aead::AeadCtx, tag_len: usize, } -#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] +#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] impl EvpAead { fn new( algorithm: cryptography_openssl::aead::AeadType, @@ -446,20 +447,22 @@ impl EvpAead { #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead")] struct ChaCha20Poly1305 { - #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] + #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] ctx: EvpAead, #[cfg(any( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_LIBRESSL, - all( - not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), - not(CRYPTOGRAPHY_IS_BORINGSSL) - ) + not(any( + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )) ))] ctx: EvpCipherAead, #[cfg(not(any( CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), CRYPTOGRAPHY_OPENSSL_320_OR_GREATER )))] @@ -476,9 +479,17 @@ impl ChaCha20Poly1305 { pyo3::exceptions::PyValueError::new_err("ChaCha20Poly1305 key must be 32 bytes."), )); } + if cryptography_openssl::fips::is_enabled() { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "ChaCha20Poly1305 is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )); + } cfg_if::cfg_if! { - if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] { + if #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] { Ok(ChaCha20Poly1305 { ctx: EvpAead::new( cryptography_openssl::aead::AeadType::ChaCha20Poly1305, @@ -491,15 +502,6 @@ impl ChaCha20Poly1305 { CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), ))] { - if cryptography_openssl::fips::is_enabled() { - return Err(CryptographyError::from( - exceptions::UnsupportedAlgorithm::new_err(( - "ChaCha20Poly1305 is not supported by this version of OpenSSL", - exceptions::Reasons::UNSUPPORTED_CIPHER, - )), - )); - } - Ok(ChaCha20Poly1305 { ctx: EvpCipherAead::new( openssl::cipher::Cipher::chacha20_poly1305(), @@ -509,15 +511,6 @@ impl ChaCha20Poly1305 { )?, }) } else { - if cryptography_openssl::fips::is_enabled() { - return Err(CryptographyError::from( - exceptions::UnsupportedAlgorithm::new_err(( - "ChaCha20Poly1305 is not supported by this version of OpenSSL", - exceptions::Reasons::UNSUPPORTED_CIPHER, - )), - )); - } - Ok(ChaCha20Poly1305{ ctx: LazyEvpCipherAead::new( openssl::cipher::Cipher::chacha20_poly1305(), @@ -589,6 +582,7 @@ struct AesGcm { CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), ))] ctx: EvpCipherAead, @@ -597,6 +591,7 @@ struct AesGcm { CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), )))] ctx: LazyEvpCipherAead, @@ -625,6 +620,7 @@ impl AesGcm { CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_AWSLC, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), ))] { Ok(AesGcm { @@ -915,6 +911,7 @@ impl AesSiv { let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::List); + #[cfg(not(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER))] if data_bytes.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("data must not be zero length"), @@ -949,7 +946,7 @@ impl AesOcb3 { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { cfg_if::cfg_if! { - if #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] { + if #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] { _ = key; Err(CryptographyError::from( @@ -1051,6 +1048,9 @@ impl AesOcb3 { name = "AESGCMSIV" )] struct AesGcmSiv { + #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] + ctx: EvpAead, + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] ctx: EvpCipherAead, } @@ -1072,7 +1072,22 @@ impl AesGcmSiv { }; cfg_if::cfg_if! { - if #[cfg(not(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER))] { + if #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] { + let _ = cipher_name; + let aead_type = match key.as_bytes().len() { + 16 => cryptography_openssl::aead::AeadType::Aes128GcmSiv, + 32 => cryptography_openssl::aead::AeadType::Aes256GcmSiv, + _ => return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Only 128-bit and 256-bit keys are supported for AES-GCM-SIV with AWS-LC or BoringSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )) + }; + Ok(AesGcmSiv { + ctx: EvpAead::new(aead_type, key.as_bytes(), 16)?, + }) + } else if #[cfg(not(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER))] { let _ = cipher_name; Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( @@ -1123,6 +1138,11 @@ impl AesGcmSiv { let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::Single); + #[cfg(not(any( + CRYPTOGRAPHY_OPENSSL_350_OR_GREATER, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] if data_bytes.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("data must not be zero length"), diff --git a/src/rust/src/backend/cipher_registry.rs b/src/rust/src/backend/cipher_registry.rs index 6157010c0652..92b9c600aea1 100644 --- a/src/rust/src/backend/cipher_registry.rs +++ b/src/rust/src/backend/cipher_registry.rs @@ -123,6 +123,7 @@ fn get_cipher_registry( let aes128 = types::AES128.get(py)?; let aes256 = types::AES256.get(py)?; let triple_des = types::TRIPLE_DES.get(py)?; + let des = types::DES.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] let camellia = types::CAMELLIA.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] @@ -135,8 +136,9 @@ fn get_cipher_registry( let sm4 = types::SM4.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] let seed = types::SEED.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_RC4"))] let arc4 = types::ARC4.get(py)?; - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] let chacha20 = types::CHACHA20.get(py)?; let rc2 = types::RC2.get(py)?; @@ -186,9 +188,13 @@ fn get_cipher_registry( m.add(&aes, &ecb, Some(192), Cipher::aes_192_ecb())?; m.add(&aes, &ecb, Some(256), Cipher::aes_256_ecb())?; - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] { m.add(&aes, &xts, Some(256), Cipher::aes_128_xts())?; + } + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + { m.add(&aes, &xts, Some(512), Cipher::aes_256_xts())?; } @@ -218,7 +224,7 @@ fn get_cipher_registry( m.add(&triple_des, &cbc, Some(192), Cipher::des_ede3_cbc())?; m.add(&triple_des, &ecb, Some(192), Cipher::des_ede3_ecb())?; - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] { m.add(&triple_des, &cfb8, Some(192), Cipher::des_ede3_cfb8())?; m.add(&triple_des, &cfb, Some(192), Cipher::des_ede3_cfb64())?; @@ -258,7 +264,7 @@ fn get_cipher_registry( } } - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] m.add(&chacha20, none_type.as_any(), None, Cipher::chacha20())?; // Don't register legacy ciphers if they're unavailable. In theory @@ -298,8 +304,11 @@ fn get_cipher_registry( m.add(&idea, &cfb, Some(128), Cipher::idea_cfb64())?; } + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_RC4"))] m.add(&arc4, none_type.as_any(), None, Cipher::rc4())?; + m.add(&des, &cbc, Some(64), Cipher::des_cbc())?; + if let Some(rc2_cbc) = Cipher::from_nid(openssl::nid::Nid::RC2_CBC) { m.add(&rc2, &cbc, Some(128), rc2_cbc)?; } diff --git a/src/rust/src/backend/ciphers.rs b/src/rust/src/backend/ciphers.rs index a469d7824eda..8f34f061ed51 100644 --- a/src/rust/src/backend/ciphers.rs +++ b/src/rust/src/backend/ciphers.rs @@ -2,13 +2,13 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::PyAnyMethods; +use pyo3::IntoPyObject; + use crate::backend::cipher_registry; use crate::buf::{CffiBuf, CffiMutBuf}; use crate::error::{CryptographyError, CryptographyResult}; -use crate::exceptions; -use crate::types; -use pyo3::types::PyAnyMethods; -use pyo3::IntoPyObject; +use crate::{exceptions, types}; pub(crate) struct CipherContext { ctx: openssl::cipher_ctx::CipherCtx, @@ -495,16 +495,14 @@ impl PyAEADDecryptionContext { if tag.len() < min_tag_length { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( - "Authentication tag must be {} bytes or longer.", - min_tag_length + "Authentication tag must be {min_tag_length} bytes or longer.", )), )); } else if tag.len() > 16 { return Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err(format!( - "Authentication tag cannot be more than {} bytes.", - 16 - )), + pyo3::exceptions::PyValueError::new_err( + "Authentication tag cannot be more than 16 bytes.", + ), )); } diff --git a/src/rust/src/backend/cmac.rs b/src/rust/src/backend/cmac.rs index 7519c1b88603..05a18deb9412 100644 --- a/src/rust/src/backend/cmac.rs +++ b/src/rust/src/backend/cmac.rs @@ -2,11 +2,12 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::{PyAnyMethods, PyBytesMethods}; + use crate::backend::cipher_registry; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; -use pyo3::types::{PyAnyMethods, PyBytesMethods}; #[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.cmac", diff --git a/src/rust/src/backend/dh.rs b/src/rust/src/backend/dh.rs index a19ab6342e90..a0296a075982 100644 --- a/src/rust/src/backend/dh.rs +++ b/src/rust/src/backend/dh.rs @@ -3,12 +3,12 @@ // for complete details. use cryptography_x509::common; +use pyo3::types::PyAnyMethods; use crate::asn1::encode_der_data; use crate::backend::utils; use crate::error::{CryptographyError, CryptographyResult}; use crate::{types, x509}; -use pyo3::types::PyAnyMethods; const MIN_MODULUS_SIZE: u32 = 512; @@ -119,7 +119,14 @@ fn dh_parameters_from_numbers( .transpose()?; let g = utils::py_int_to_bn(py, numbers.g.bind(py))?; - Ok(openssl::dh::Dh::from_pqg(p, q, g)?) + let dh = openssl::dh::Dh::from_pqg(p, q, g)?; + + if !dh.check_key()? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid DH parameters"), + )); + } + Ok(dh) } fn clone_dh( @@ -192,7 +199,7 @@ impl DHPrivateKey { }) } - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] fn public_key(&self) -> CryptographyResult { let orig_dh = self.pkey.dh().unwrap(); let dh = clone_dh(&orig_dh)?; @@ -235,6 +242,10 @@ impl DHPrivateKey { false, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] @@ -302,7 +313,7 @@ impl DHPublicKey { #[pyo3::pymethods] impl DHParameters { - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] fn generate_private_key(&self) -> CryptographyResult { let dh = clone_dh(&self.dh)?.generate_key()?; Ok(DHPrivateKey { @@ -396,7 +407,7 @@ impl DHPrivateNumbers { DHPrivateNumbers { x, public_numbers } } - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] #[pyo3(signature = (backend=None))] fn private_key( &self, @@ -411,14 +422,6 @@ impl DHPrivateNumbers { let priv_key = utils::py_int_to_bn(py, self.x.bind(py))?; 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 }) } @@ -449,7 +452,7 @@ impl DHPublicNumbers { } } - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] #[pyo3(signature = (backend=None))] fn public_key( &self, diff --git a/src/rust/src/backend/dsa.rs b/src/rust/src/backend/dsa.rs index 86ddac9c88d0..10a9553792df 100644 --- a/src/rust/src/backend/dsa.rs +++ b/src/rust/src/backend/dsa.rs @@ -2,11 +2,12 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::PyAnyMethods; + use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{error, exceptions}; -use pyo3::types::PyAnyMethods; #[pyo3::pyclass( frozen, @@ -151,6 +152,10 @@ impl DsaPrivateKey { false, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs index 37bfc9123dbd..71604490829b 100644 --- a/src/rust/src/backend/ec.rs +++ b/src/rust/src/backend/ec.rs @@ -5,12 +5,12 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -use pyo3::types::{PyAnyMethods, PyDictMethods}; +use pyo3::types::PyAnyMethods; use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509::common::cstr_from_literal; +use crate::utils::cstr_from_literal; use crate::{exceptions, types}; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] @@ -45,7 +45,8 @@ fn curve_from_py_curve( } let py_curve_name = py_curve.getattr(pyo3::intern!(py, "name"))?; - let nid = match &*py_curve_name.extract::()? { + let curve_name = &*py_curve_name.extract::()?; + let nid = match curve_name { "secp192r1" => openssl::nid::Nid::X9_62_PRIME192V1, "secp224r1" => openssl::nid::Nid::SECP224R1, "secp256r1" => openssl::nid::Nid::X9_62_PRIME256V1, @@ -67,11 +68,11 @@ fn curve_from_py_curve( "sect409k1" => openssl::nid::Nid::SECT409K1, "sect571k1" => openssl::nid::Nid::SECT571K1, - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] "brainpoolP256r1" => openssl::nid::Nid::BRAINPOOL_P256R1, - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] "brainpoolP384r1" => openssl::nid::Nid::BRAINPOOL_P384R1, - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] "brainpoolP512r1" => openssl::nid::Nid::BRAINPOOL_P512R1, curve_name => { @@ -84,33 +85,23 @@ fn curve_from_py_curve( } }; - Ok(openssl::ec::EcGroup::from_curve_name(nid)?) + Ok(openssl::ec::EcGroup::from_curve_name(nid).map_err(|_| { + exceptions::UnsupportedAlgorithm::new_err(( + format!("Curve {curve_name} is not supported"), + exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, + )) + })?) } fn py_curve_from_curve<'p>( py: pyo3::Python<'p>, curve: &openssl::ec::EcGroupRef, ) -> CryptographyResult> { - if curve.asn1_flag() == openssl::ec::Asn1Flag::EXPLICIT_CURVE { - return Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err( - "ECDSA keys with explicit parameters are unsupported at this time", - ), - )); - } + assert!(curve.asn1_flag() != openssl::ec::Asn1Flag::EXPLICIT_CURVE); let name = curve.curve_name().unwrap().short_name()?; - types::CURVE_TYPES - .get(py)? - .extract::>()? - .get_item(name)? - .ok_or_else(|| { - CryptographyError::from(exceptions::UnsupportedAlgorithm::new_err(( - format!("{name} is not a supported elliptic curve"), - exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, - ))) - }) + Ok(types::CURVE_TYPES.get(py)?.get_item(name)?) } fn check_key_infinity( @@ -135,8 +126,11 @@ pub(crate) fn private_key_from_pkey( py: pyo3::Python<'_>, pkey: &openssl::pkey::PKeyRef, ) -> CryptographyResult { - let curve = py_curve_from_curve(py, pkey.ec_key().unwrap().group())?; - check_key_infinity(&pkey.ec_key().unwrap())?; + let ec_key = pkey + .ec_key() + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid EC key"))?; + let curve = py_curve_from_curve(py, ec_key.group())?; + check_key_infinity(&ec_key)?; Ok(ECPrivateKey { pkey: pkey.to_owned(), curve: curve.into(), @@ -375,6 +369,10 @@ impl ECPrivateKey { false, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] diff --git a/src/rust/src/backend/ed25519.rs b/src/rust/src/backend/ed25519.rs index 721bac816882..66d2b74351cd 100644 --- a/src/rust/src/backend/ed25519.rs +++ b/src/rust/src/backend/ed25519.rs @@ -115,6 +115,10 @@ impl Ed25519PrivateKey { true, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] diff --git a/src/rust/src/backend/ed448.rs b/src/rust/src/backend/ed448.rs index ba743d02c1ef..611e1e144b9f 100644 --- a/src/rust/src/backend/ed448.rs +++ b/src/rust/src/backend/ed448.rs @@ -113,6 +113,10 @@ impl Ed448PrivateKey { true, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] diff --git a/src/rust/src/backend/hashes.rs b/src/rust/src/backend/hashes.rs index 09c75f336ec2..4eec658a6655 100644 --- a/src/rust/src/backend/hashes.rs +++ b/src/rust/src/backend/hashes.rs @@ -2,9 +2,10 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use pyo3::types::PyAnyMethods; use std::borrow::Cow; +use pyo3::types::PyAnyMethods; + use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; @@ -136,8 +137,115 @@ impl Hash { } } +#[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")] +pub(crate) struct XOFHash { + #[pyo3(get)] + algorithm: pyo3::Py, + ctx: openssl::hash::Hasher, + bytes_remaining: u64, + squeezed: bool, +} + +impl XOFHash { + pub(crate) fn update_bytes(&mut self, data: &[u8]) -> CryptographyResult<()> { + self.ctx.update(data)?; + Ok(()) + } +} + +#[pyo3::pymethods] +impl XOFHash { + #[new] + #[pyo3(signature = (algorithm))] + fn new( + py: pyo3::Python<'_>, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + ) -> CryptographyResult { + cfg_if::cfg_if! { + if #[cfg(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + not(CRYPTOGRAPHY_OPENSSL_330_OR_GREATER) + ))] { + let _ = py; + let _ = algorithm; + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Extendable output functions are not supported on LibreSSL or BoringSSL.", + )), + )) + } else { + if !algorithm.is_instance(&types::EXTENDABLE_OUTPUT_FUNCTION.get(py)?)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Expected instance of an extendable output function.", + ), + )); + } + let md = message_digest_from_algorithm(py, algorithm)?; + let ctx = openssl::hash::Hasher::new(md)?; + // We treat digest_size as the maximum total output for this API + let bytes_remaining = algorithm + .getattr(pyo3::intern!(py, "digest_size"))? + .extract::()?; + + Ok(XOFHash { + algorithm: algorithm.clone().unbind(), + ctx, + bytes_remaining, + squeezed: false, + }) + } + } + } + + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + if self.squeezed { + return Err(CryptographyError::from( + exceptions::AlreadyFinalized::new_err("Context was already squeezed."), + )); + } + self.update_bytes(data.as_bytes()) + } + #[cfg(all( + CRYPTOGRAPHY_OPENSSL_330_OR_GREATER, + not(CRYPTOGRAPHY_IS_LIBRESSL), + not(CRYPTOGRAPHY_IS_BORINGSSL), + ))] + fn squeeze<'p>( + &mut self, + py: pyo3::Python<'p>, + length: usize, + ) -> CryptographyResult> { + self.squeezed = true; + // We treat digest_size as the maximum total output for this API + self.bytes_remaining = self + .bytes_remaining + .checked_sub(length.try_into().unwrap()) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err( + "Exceeded maximum squeeze limit specified by digest_size.", + ) + })?; + let result = pyo3::types::PyBytes::new_with(py, length, |b| { + self.ctx.squeeze_xof(b).unwrap(); + Ok(()) + })?; + Ok(result) + } + + fn copy(&self, py: pyo3::Python<'_>) -> CryptographyResult { + Ok(XOFHash { + algorithm: self.algorithm.clone_ref(py), + ctx: self.ctx.clone(), + bytes_remaining: self.bytes_remaining, + squeezed: self.squeezed, + }) + } +} + #[pyo3::pymodule] pub(crate) mod hashes { #[pymodule_export] - use super::{hash_supported, Hash}; + use super::{hash_supported, Hash, XOFHash}; } diff --git a/src/rust/src/backend/hmac.rs b/src/rust/src/backend/hmac.rs index 4e2d06943377..7006e6bc8753 100644 --- a/src/rust/src/backend/hmac.rs +++ b/src/rust/src/backend/hmac.rs @@ -2,11 +2,12 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::PyBytesMethods; + use crate::backend::hashes::message_digest_from_algorithm; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -use pyo3::types::PyBytesMethods; #[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.hmac", diff --git a/src/rust/src/backend/kdf.rs b/src/rust/src/backend/kdf.rs index 2144caf1ea9a..41347295434d 100644 --- a/src/rust/src/backend/kdf.rs +++ b/src/rust/src/backend/kdf.rs @@ -2,6 +2,10 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +#[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] +use base64::engine::general_purpose::STANDARD_NO_PAD; +#[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] +use base64::engine::Engine; #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] use pyo3::types::PyBytesMethods; @@ -320,6 +324,127 @@ impl Argon2id { Ok(()) } + + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + fn derive_phc_encoded<'p>( + &mut self, + py: pyo3::Python<'p>, + key_material: CffiBuf<'_>, + ) -> CryptographyResult> { + let derived_key = self.derive(py, key_material)?; + let salt_bytes = self.salt.as_bytes(py); + + let salt_b64 = STANDARD_NO_PAD.encode(salt_bytes); + let hash_b64 = STANDARD_NO_PAD.encode(derived_key.as_bytes()); + + // Format the PHC string + let phc_string = format!( + "$argon2id$v=19$m={},t={},p={}${}${}", + self.memory_cost, self.iterations, self.lanes, salt_b64, hash_b64 + ); + + Ok(pyo3::types::PyString::new(py, &phc_string)) + } + + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + #[staticmethod] + #[pyo3(signature = (key_material, phc_encoded, secret=None))] + fn verify_phc_encoded( + py: pyo3::Python<'_>, + key_material: CffiBuf<'_>, + phc_encoded: &str, + secret: Option>, + ) -> CryptographyResult<()> { + let parts: Vec<_> = phc_encoded.split('$').collect(); + + if parts.len() != 6 || !parts[0].is_empty() || parts[1] != "argon2id" { + return Err(CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid PHC string format.", + ))); + } + + if parts[2] != "v=19" { + return Err(CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid version in PHC string.", + ))); + } + + // Parse parameters + let param_parts: Vec<&str> = parts[3].split(',').collect(); + if param_parts.len() != 3 { + return Err(CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid parameters in PHC string.", + ))); + } + + // Check parameters are in correct order: m, t, p + if !param_parts[0].starts_with("m=") + || !param_parts[1].starts_with("t=") + || !param_parts[2].starts_with("p=") + { + return Err(CryptographyError::from(exceptions::InvalidKey::new_err( + "Parameters must be in order: m, t, p.", + ))); + } + + // Parse memory cost (m) + let memory_cost = param_parts[0][2..].parse::().map_err(|_| { + CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid memory cost in PHC string.", + )) + })?; + + // Parse iterations (t) + let iterations = param_parts[1][2..].parse::().map_err(|_| { + CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid iterations in PHC string.", + )) + })?; + + // Parse lanes/parallelism (p) + let lanes = param_parts[2][2..].parse::().map_err(|_| { + CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid parallelism in PHC string.", + )) + })?; + + let salt_bytes = STANDARD_NO_PAD.decode(parts[4]).map_err(|_| { + CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid base64 salt in PHC string.", + )) + })?; + + let hash_bytes = STANDARD_NO_PAD.decode(parts[5]).map_err(|_| { + CryptographyError::from(exceptions::InvalidKey::new_err( + "Invalid base64 hash in PHC string.", + )) + })?; + + let salt = pyo3::types::PyBytes::new(py, &salt_bytes); + let mut argon2 = Argon2id::new( + py, + salt.into(), + hash_bytes.len(), + iterations, + lanes, + memory_cost, + None, + secret, + )?; + + let derived_key = argon2.derive(py, key_material)?; + let derived_bytes = derived_key.as_bytes(); + + if derived_bytes.len() != hash_bytes.len() + || !openssl::memcmp::eq(derived_bytes, &hash_bytes) + { + return Err(CryptographyError::from(exceptions::InvalidKey::new_err( + "Keys do not match.", + ))); + } + + Ok(()) + } } #[pyo3::pymodule] diff --git a/src/rust/src/backend/keys.rs b/src/rust/src/backend/keys.rs index 4a323adedc4c..1432fea0c992 100644 --- a/src/rust/src/backend/keys.rs +++ b/src/rust/src/backend/keys.rs @@ -4,10 +4,9 @@ use pyo3::IntoPyObject; -use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; -use crate::exceptions; +use crate::{exceptions, x509}; #[pyo3::pyfunction] #[pyo3(signature = (data, password, backend=None, *, unsafe_skip_rsa_key_validation=false))] @@ -19,7 +18,27 @@ fn load_der_private_key<'p>( unsafe_skip_rsa_key_validation: bool, ) -> CryptographyResult> { let _ = backend; - if let Ok(pkey) = openssl::pkey::PKey::private_key_from_der(data.as_bytes()) { + + load_der_private_key_bytes( + py, + data.as_bytes(), + password.as_ref().map(|v| v.as_bytes()), + unsafe_skip_rsa_key_validation, + ) +} + +pub(crate) fn load_der_private_key_bytes<'p>( + py: pyo3::Python<'p>, + data: &[u8], + password: Option<&[u8]>, + unsafe_skip_rsa_key_validation: bool, +) -> CryptographyResult> { + let pkey = cryptography_key_parsing::pkcs8::parse_private_key(data) + .or_else(|_| cryptography_key_parsing::ec::parse_pkcs1_private_key(data, None)) + .or_else(|_| cryptography_key_parsing::rsa::parse_pkcs1_private_key(data)) + .or_else(|_| cryptography_key_parsing::dsa::parse_pkcs1_private_key(data)); + + if let Ok(pkey) = pkey { if password.is_some() { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( @@ -30,13 +49,8 @@ fn load_der_private_key<'p>( return private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation); } - let password = password.as_ref().map(CffiBuf::as_bytes); - let mut status = utils::PasswordCallbackStatus::Unused; - let pkey = openssl::pkey::PKey::private_key_from_pkcs8_callback( - data.as_bytes(), - utils::password_callback(&mut status, password), - ); - let pkey = utils::handle_key_load_result(py, pkey, status, password)?; + let pkey = cryptography_key_parsing::pkcs8::parse_encrypted_private_key(data, password)?; + private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation) } @@ -50,17 +64,116 @@ fn load_pem_private_key<'p>( unsafe_skip_rsa_key_validation: bool, ) -> CryptographyResult> { let _ = backend; - let password = password.as_ref().map(CffiBuf::as_bytes); - let mut status = utils::PasswordCallbackStatus::Unused; - let pkey = openssl::pkey::PKey::private_key_from_pem_callback( + + let p = x509::find_in_pem( data.as_bytes(), - utils::password_callback(&mut status, password), - ); - let pkey = utils::handle_key_load_result(py, pkey, status, password)?; + |p| ["PRIVATE KEY", "ENCRYPTED PRIVATE KEY", "RSA PRIVATE KEY", "EC PRIVATE KEY", "DSA PRIVATE KEY"].contains(&p.tag()), + "Valid PEM but no BEGIN/END delimiters for a private key found. Are you sure this is a private key?" + )?; + let password = password.as_ref().map(|v| v.as_bytes()); + let mut password_used = false; + // TODO: Surely we can avoid this clone? + let tag = p.tag().to_string(); + let data = match p.headers().get("Proc-Type") { + Some("4,ENCRYPTED") => { + password_used = true; + let Some(dek_info) = p.headers().get("DEK-Info") else { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Encrypted PEM doesn't have a DEK-Info header.", + ), + )); + }; + let Some((cipher_algorithm, iv)) = dek_info.split_once(',') else { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Encrypted PEM's DEK-Info header is not valid.", + ), + )); + }; + + let password = match password { + None | Some(b"") => { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Password was not given but private key is encrypted", + ), + )) + } + Some(p) => p, + }; + + // There's no RFC that defines these, but these are the ones in + // very wide use that we support. + let cipher = match cipher_algorithm { + "AES-128-CBC" => openssl::symm::Cipher::aes_128_cbc(), + "AES-256-CBC" => openssl::symm::Cipher::aes_256_cbc(), + "DES-EDE3-CBC" => openssl::symm::Cipher::des_ede3_cbc(), + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Key encrypted with unknown cipher.", + ), + )) + } + }; + let iv = cryptography_crypto::encoding::hex_decode(iv).ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("DEK-Info IV is not valid hex") + })?; + let key = cryptography_crypto::pbkdf1::openssl_kdf( + openssl::hash::MessageDigest::md5(), + password, + iv.get(..8) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err( + "DEK-Info IV must be at least 8 bytes", + ) + })? + .try_into() + .unwrap(), + cipher.key_len(), + ) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Unable to derive key from password (are you in FIPS mode?)", + ) + })?; + openssl::symm::decrypt(cipher, &key, Some(&iv), p.contents()).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Incorrect password, could not decrypt key") + })? + } + Some(_) => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Proc-Type PEM header is not valid, key could not be decrypted.", + ), + )) + } + None => p.into_contents(), + }; + + let pkey = match tag.as_str() { + "PRIVATE KEY" => cryptography_key_parsing::pkcs8::parse_private_key(&data)?, + "RSA PRIVATE KEY" => cryptography_key_parsing::rsa::parse_pkcs1_private_key(&data)?, + "EC PRIVATE KEY" => cryptography_key_parsing::ec::parse_pkcs1_private_key(&data, None)?, + "DSA PRIVATE KEY" => cryptography_key_parsing::dsa::parse_pkcs1_private_key(&data)?, + _ => { + assert_eq!(tag, "ENCRYPTED PRIVATE KEY"); + password_used = true; + cryptography_key_parsing::pkcs8::parse_encrypted_private_key(&data, password)? + } + }; + if password.is_some() && !password_used { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Password was given but private key is not encrypted.", + ), + )); + } private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation) } -pub(crate) fn private_key_from_pkey<'p>( +fn private_key_from_pkey<'p>( py: pyo3::Python<'p>, pkey: &openssl::pkey::PKeyRef, unsafe_skip_rsa_key_validation: bool, @@ -72,20 +185,6 @@ pub(crate) fn private_key_from_pkey<'p>( )? .into_pyobject(py)? .into_any()), - openssl::pkey::Id::RSA_PSS => { - // 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. - let der_bytes = pkey.rsa()?.private_key_to_der()?; - let rsa = openssl::rsa::Rsa::private_key_from_der(&der_bytes)?; - let pkey = openssl::pkey::PKey::from_rsa(rsa)?; - Ok( - crate::backend::rsa::private_key_from_pkey(&pkey, unsafe_skip_rsa_key_validation)? - .into_pyobject(py)? - .into_any(), - ) - } openssl::pkey::Id::EC => Ok(crate::backend::ec::private_key_from_pkey(py, pkey)? .into_pyobject(py)? .into_any()), @@ -93,7 +192,11 @@ pub(crate) fn private_key_from_pkey<'p>( .into_pyobject(py)? .into_any()), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] openssl::pkey::Id::X448 => Ok(crate::backend::x448::private_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), @@ -102,7 +205,11 @@ pub(crate) fn private_key_from_pkey<'p>( .into_pyobject(py)? .into_any()), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] openssl::pkey::Id::ED448 => Ok(crate::backend::ed448::private_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), @@ -113,7 +220,11 @@ pub(crate) fn private_key_from_pkey<'p>( .into_pyobject(py)? .into_any()), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] openssl::pkey::Id::DHX => Ok(crate::backend::dh::private_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), @@ -207,7 +318,11 @@ fn public_key_from_pkey<'p>( openssl::pkey::Id::X25519 => Ok(crate::backend::x25519::public_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] openssl::pkey::Id::X448 => Ok(crate::backend::x448::public_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), @@ -215,7 +330,11 @@ fn public_key_from_pkey<'p>( openssl::pkey::Id::ED25519 => Ok(crate::backend::ed25519::public_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] openssl::pkey::Id::ED448 => Ok(crate::backend::ed448::public_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), @@ -227,7 +346,11 @@ fn public_key_from_pkey<'p>( .into_pyobject(py)? .into_any()), - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] openssl::pkey::Id::DHX => Ok(crate::backend::dh::public_key_from_pkey(pkey) .into_pyobject(py)? .into_any()), @@ -248,11 +371,11 @@ pub(crate) mod keys { #[cfg(test)] mod tests { - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] use super::{private_key_from_pkey, public_key_from_pkey}; #[test] - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] fn test_public_key_from_pkey_unknown_key() { pyo3::prepare_freethreaded_python(); @@ -267,7 +390,7 @@ mod tests { } #[test] - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] fn test_private_key_from_pkey_unknown_key() { pyo3::prepare_freethreaded_python(); diff --git a/src/rust/src/backend/mod.rs b/src/rust/src/backend/mod.rs index a447565d7229..143d4a402f7c 100644 --- a/src/rust/src/backend/mod.rs +++ b/src/rust/src/backend/mod.rs @@ -10,7 +10,11 @@ pub(crate) mod dh; pub(crate) mod dsa; pub(crate) mod ec; pub(crate) mod ed25519; -#[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] +#[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC +)))] pub(crate) mod ed448; pub(crate) mod hashes; pub(crate) mod hmac; @@ -20,5 +24,9 @@ pub(crate) mod poly1305; pub(crate) mod rsa; pub(crate) mod utils; pub(crate) mod x25519; -#[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] +#[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC +)))] pub(crate) mod x448; diff --git a/src/rust/src/backend/poly1305.rs b/src/rust/src/backend/poly1305.rs index 9b1d8165f8dc..7df961d54dae 100644 --- a/src/rust/src/backend/poly1305.rs +++ b/src/rust/src/backend/poly1305.rs @@ -2,17 +2,26 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::PyBytesMethods; + use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -use pyo3::types::PyBytesMethods; -#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] +#[cfg(any( + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_AWSLC +))] struct Poly1305Boring { context: cryptography_openssl::poly1305::Poly1305State, } -#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] +#[cfg(any( + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_AWSLC +))] impl Poly1305Boring { fn new(key: CffiBuf<'_>) -> CryptographyResult { if key.as_bytes().len() != 32 { @@ -40,12 +49,20 @@ impl Poly1305Boring { } } -#[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] +#[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC +)))] struct Poly1305Open { signer: openssl::sign::Signer<'static>, } -#[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] +#[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC +)))] impl Poly1305Open { fn new(key: CffiBuf<'_>) -> CryptographyResult { if cryptography_openssl::fips::is_enabled() { @@ -89,9 +106,17 @@ impl Poly1305Open { #[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")] struct Poly1305 { - #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] + #[cfg(any( + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_AWSLC + ))] inner: Option, - #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] inner: Option, } @@ -99,11 +124,19 @@ struct Poly1305 { impl Poly1305 { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { - #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] + #[cfg(any( + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_AWSLC + ))] return Ok(Poly1305 { inner: Some(Poly1305Boring::new(key)?), }); - #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] return Ok(Poly1305 { inner: Some(Poly1305Open::new(key)?), }); diff --git a/src/rust/src/backend/rsa.rs b/src/rust/src/backend/rsa.rs index 79b385ffb73f..b490e1c21722 100644 --- a/src/rust/src/backend/rsa.rs +++ b/src/rust/src/backend/rsa.rs @@ -5,11 +5,12 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use pyo3::types::PyAnyMethods; + use crate::backend::{hashes, utils}; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; -use pyo3::types::PyAnyMethods; #[pyo3::pyclass( frozen, @@ -415,6 +416,10 @@ impl RsaPrivateKey { false, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] diff --git a/src/rust/src/backend/utils.rs b/src/rust/src/backend/utils.rs index 832fdf3542f5..d61466da4870 100644 --- a/src/rust/src/backend/utils.rs +++ b/src/rust/src/backend/utils.rs @@ -2,10 +2,11 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::{PyAnyMethods, PyBytesMethods}; + use crate::backend::hashes::Hash; use crate::error::{CryptographyError, CryptographyResult}; -use crate::{error, types}; -use pyo3::types::{PyAnyMethods, PyBytesMethods}; +use crate::types; pub(crate) fn py_int_to_bn( py: pyo3::Python<'_>, @@ -402,67 +403,3 @@ pub(crate) fn calculate_digest_and_algorithm<'p>( Ok((data, algorithm)) } - -pub(crate) enum PasswordCallbackStatus { - Unused, - Used, - BufferTooSmall(usize), -} - -pub(crate) fn password_callback<'a>( - status: &'a mut PasswordCallbackStatus, - password: Option<&'a [u8]>, -) -> impl FnOnce(&mut [u8]) -> Result + 'a { - move |buf| { - *status = PasswordCallbackStatus::Used; - match password.as_ref() { - Some(p) if p.len() <= buf.len() => { - buf[..p.len()].copy_from_slice(p); - Ok(p.len()) - } - Some(_) => { - *status = PasswordCallbackStatus::BufferTooSmall(buf.len()); - Ok(0) - } - None => Ok(0), - } - } -} - -pub(crate) fn handle_key_load_result( - py: pyo3::Python<'_>, - pkey: Result, openssl::error::ErrorStack>, - status: PasswordCallbackStatus, - password: Option<&[u8]>, -) -> CryptographyResult> { - match (pkey, status, password) { - (Ok(k), PasswordCallbackStatus::Unused, None) - | (Ok(k), PasswordCallbackStatus::Used, Some(_)) => Ok(k), - - (Ok(_), PasswordCallbackStatus::Unused, Some(_)) => Err(CryptographyError::from( - pyo3::exceptions::PyTypeError::new_err( - "Password was given but private key is not encrypted.", - ), - )), - - (_, PasswordCallbackStatus::Used, None | Some(b"")) => Err(CryptographyError::from( - pyo3::exceptions::PyTypeError::new_err( - "Password was not given but private key is encrypted", - ), - )), - (_, PasswordCallbackStatus::BufferTooSmall(size), _) => Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err(format!( - "Passwords longer than {size} bytes are not supported" - )), - )), - (Err(e), _, _) => { - let errors = error::list_from_openssl_error(py, &e); - Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err(( - "Could not deserialize key data. The data may be in an incorrect format, the provided password may be incorrect, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).", - errors.unbind(), - )) - )) - } - } -} diff --git a/src/rust/src/backend/x25519.rs b/src/rust/src/backend/x25519.rs index 4cc6124aefc5..0c934d859c77 100644 --- a/src/rust/src/backend/x25519.rs +++ b/src/rust/src/backend/x25519.rs @@ -115,6 +115,10 @@ impl X25519PrivateKey { true, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] diff --git a/src/rust/src/backend/x448.rs b/src/rust/src/backend/x448.rs index 953302dd63d1..5c7e39d6b805 100644 --- a/src/rust/src/backend/x448.rs +++ b/src/rust/src/backend/x448.rs @@ -114,6 +114,10 @@ impl X448PrivateKey { true, ) } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } } #[pyo3::pymethods] diff --git a/src/rust/src/buf.rs b/src/rust/src/buf.rs index e55bf12a45be..9c7f85dc6da0 100644 --- a/src/rust/src/buf.rs +++ b/src/rust/src/buf.rs @@ -2,42 +2,91 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +#[cfg(not(Py_3_11))] use crate::types; -use pyo3::types::IntoPyDict; use pyo3::types::PyAnyMethods; use std::slice; -pub(crate) struct CffiBuf<'p> { - pyobj: pyo3::Bound<'p, pyo3::PyAny>, - _bufobj: pyo3::Bound<'p, pyo3::PyAny>, - buf: &'p [u8], +// Common error message generation +fn generate_non_convertible_buffer_error_msg(pyobj: &pyo3::Bound<'_, pyo3::PyAny>) -> String { + if pyobj.is_instance_of::() { + format!( + "Cannot convert \"{}\" instance to a buffer.\nDid you mean to pass a bytestring instead?", + pyobj.get_type() + ) + } else { + format!( + "Cannot convert \"{}\" instance to a buffer.", + pyobj.get_type() + ) + } } +#[cfg(Py_3_11)] +fn _extract_buffer_length( + pyobj: &pyo3::Bound<'_, pyo3::PyAny>, + mutable: bool, +) -> pyo3::PyResult<(Option>, usize, usize)> { + let buf = pyo3::buffer::PyBuffer::::get(pyobj).map_err(|_| { + let errmsg = generate_non_convertible_buffer_error_msg(pyobj); + pyo3::exceptions::PyTypeError::new_err(errmsg) + })?; + if mutable && buf.readonly() { + return Err(pyo3::exceptions::PyTypeError::new_err( + "Buffer is not writable.", + )); + }; + let ptr = buf.buf_ptr() as usize; + let len = buf.len_bytes(); + Ok((Some(buf), ptr, len)) +} + +#[cfg(not(Py_3_11))] fn _extract_buffer_length<'p>( pyobj: &pyo3::Bound<'p, pyo3::PyAny>, mutable: bool, -) -> pyo3::PyResult<(pyo3::Bound<'p, pyo3::PyAny>, usize)> { +) -> pyo3::PyResult<(pyo3::Bound<'p, pyo3::PyAny>, usize, usize)> { let py = pyobj.py(); let bufobj = if mutable { - let kwargs = [(pyo3::intern!(py, "require_writable"), true)].into_py_dict(py)?; + let kwargs = pyo3::types::IntoPyDict::into_py_dict( + [(pyo3::intern!(py, "require_writable"), true)], + py, + )?; types::FFI_FROM_BUFFER .get(py)? - .call((pyobj,), Some(&kwargs))? + .call((pyobj,), Some(&kwargs)) } else { - types::FFI_FROM_BUFFER.get(py)?.call1((pyobj,))? - }; + types::FFI_FROM_BUFFER.get(py)?.call1((pyobj,)) + } + .map_err(|_| { + let errmsg = generate_non_convertible_buffer_error_msg(pyobj); + pyo3::exceptions::PyTypeError::new_err(errmsg) + })?; let ptrval = types::FFI_CAST .get(py)? .call1((pyo3::intern!(py, "uintptr_t"), bufobj.clone()))? .call_method0(pyo3::intern!(py, "__int__"))? .extract::()?; - Ok((bufobj, ptrval)) + let len = bufobj.len()?; + Ok((bufobj, ptrval, len)) +} + +pub(crate) struct CffiBuf<'p> { + pyobj: pyo3::Bound<'p, pyo3::PyAny>, + #[cfg(not(Py_3_11))] + _bufobj: pyo3::Bound<'p, pyo3::PyAny>, + #[cfg(Py_3_11)] + _bufobj: Option>, + buf: &'p [u8], } impl<'a> CffiBuf<'a> { pub(crate) fn from_bytes(py: pyo3::Python<'a>, buf: &'a [u8]) -> Self { CffiBuf { pyobj: py.None().into_bound(py), + #[cfg(Py_3_11)] + _bufobj: None, + #[cfg(not(Py_3_11))] _bufobj: py.None().into_bound(py), buf, } @@ -54,8 +103,7 @@ impl<'a> CffiBuf<'a> { impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { fn extract_bound(pyobj: &pyo3::Bound<'a, pyo3::PyAny>) -> pyo3::PyResult { - let (bufobj, ptrval) = _extract_buffer_length(pyobj, false)?; - let len = bufobj.len()?; + let (bufobj, ptrval, len) = _extract_buffer_length(pyobj, false)?; let buf = if len == 0 { &[] } else { @@ -69,7 +117,6 @@ impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { // we're doing an unsound thing and living with it. unsafe { slice::from_raw_parts(ptrval as *const u8, len) } }; - Ok(CffiBuf { pyobj: pyobj.clone(), _bufobj: bufobj, @@ -80,7 +127,10 @@ impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { pub(crate) struct CffiMutBuf<'p> { _pyobj: pyo3::Bound<'p, pyo3::PyAny>, + #[cfg(not(Py_3_11))] _bufobj: pyo3::Bound<'p, pyo3::PyAny>, + #[cfg(Py_3_11)] + _bufobj: Option>, buf: &'p mut [u8], } @@ -92,9 +142,7 @@ impl CffiMutBuf<'_> { impl<'a> pyo3::conversion::FromPyObject<'a> for CffiMutBuf<'a> { fn extract_bound(pyobj: &pyo3::Bound<'a, pyo3::PyAny>) -> pyo3::PyResult { - let (bufobj, ptrval) = _extract_buffer_length(pyobj, true)?; - - let len = bufobj.len()?; + let (bufobj, ptrval, len) = _extract_buffer_length(pyobj, true)?; let buf = if len == 0 { &mut [] } else { @@ -108,7 +156,6 @@ impl<'a> pyo3::conversion::FromPyObject<'a> for CffiMutBuf<'a> { // we're doing an unsound thing and living with it. unsafe { slice::from_raw_parts_mut(ptrval as *mut u8, len) } }; - Ok(CffiMutBuf { _pyobj: pyobj.clone(), _bufobj: bufobj, diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs index 165b2b782483..9495cbbe2352 100644 --- a/src/rust/src/error.rs +++ b/src/rust/src/error.rs @@ -78,6 +78,21 @@ impl From for CryptographyError { exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, ))) } + cryptography_key_parsing::KeyParsingError::UnsupportedEncryptionAlgorithm(oid) => { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!( + "Unknown key encryption algorithm: {oid}" + ))) + } + cryptography_key_parsing::KeyParsingError::EncryptedKeyWithoutPassword => { + CryptographyError::Py(pyo3::exceptions::PyTypeError::new_err( + "Password was not given but private key is encrypted", + )) + } + cryptography_key_parsing::KeyParsingError::IncorrectPassword => { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err( + "Incorrect password, could not decrypt key", + )) + } } } } @@ -116,7 +131,7 @@ impl fmt::Display for CryptographyError { "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). Details: {asn1_error}", ) } - CryptographyError::Py(py_error) => write!(f, "{}", py_error), + CryptographyError::Py(py_error) => write!(f, "{py_error}"), CryptographyError::OpenSSL(error_stack) => { write!( f, diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index b2642c5ce999..dc4373915dc6 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -6,12 +6,15 @@ #![allow(unknown_lints, non_local_definitions, clippy::result_large_err)] #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] -use crate::error::CryptographyResult; +use std::env; + #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] use openssl::provider; #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] -use std::env; +use pyo3::PyTypeInfo; +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +use crate::error::CryptographyResult; mod asn1; mod backend; mod buf; @@ -23,6 +26,7 @@ mod pkcs12; mod pkcs7; mod test_support; pub(crate) mod types; +pub(crate) mod utils; mod x509; #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] @@ -50,19 +54,27 @@ fn is_fips_enabled() -> bool { } #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] -fn _initialize_providers() -> CryptographyResult { +fn _initialize_providers(py: pyo3::Python<'_>) -> CryptographyResult { // 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. - let load_legacy = env::var("CRYPTOGRAPHY_OPENSSL_NO_LEGACY") - .map(|v| v.is_empty() || v == "0") - .unwrap_or(true); + + let load_legacy = !cfg!(CRYPTOGRAPHY_BUILD_OPENSSL_NO_LEGACY) + && !env::var("CRYPTOGRAPHY_OPENSSL_NO_LEGACY").is_ok_and(|v| !v.is_empty() && v != "0"); + let legacy = if load_legacy { let legacy_result = provider::Provider::load(None, "legacy"); - _legacy_provider_error(legacy_result.is_ok())?; - Some(legacy_result?) + if legacy_result.is_err() { + let message = crate::utils::cstr_from_literal!("OpenSSL 3's legacy provider failed to load. Legacy algorithms will not be available. If you need those algorithms, check your OpenSSL configuration."); + let warning_cls = pyo3::exceptions::PyWarning::type_object(py).into_any(); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; + + None + } else { + Some(legacy_result?) + } } else { None }; @@ -74,15 +86,6 @@ fn _initialize_providers() -> CryptographyResult { }) } -fn _legacy_provider_error(success: bool) -> pyo3::PyResult<()> { - if !success { - return Err(pyo3::exceptions::PyRuntimeError::new_err( - "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." - )); - } - Ok(()) -} - #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] #[pyo3::pyfunction] fn enable_fips(providers: &mut LoadedProviders) -> CryptographyResult<()> { @@ -102,7 +105,10 @@ mod _rust { #[pymodule_export] use crate::oid::ObjectIdentifier; #[pymodule_export] - use crate::padding::{check_ansix923_padding, PKCS7PaddingContext, PKCS7UnpaddingContext}; + use crate::padding::{ + ANSIX923PaddingContext, ANSIX923UnpaddingContext, PKCS7PaddingContext, + PKCS7UnpaddingContext, + }; #[pymodule_export] use crate::pkcs12::pkcs12; #[pymodule_export] @@ -132,8 +138,8 @@ mod _rust { use crate::x509::sct::Sct; #[pymodule_export] use crate::x509::verify::{ - PolicyBuilder, PyClientVerifier, PyServerVerifier, PyStore, PyVerifiedClient, - VerificationError, + PolicyBuilder, PyClientVerifier, PyCriticality, PyExtensionPolicy, PyPolicy, + PyServerVerifier, PyStore, PyVerifiedClient, VerificationError, }; } @@ -170,7 +176,11 @@ mod _rust { use crate::backend::ec::ec; #[pymodule_export] use crate::backend::ed25519::ed25519; - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] #[pymodule_export] use crate::backend::ed448::ed448; #[pymodule_export] @@ -187,7 +197,11 @@ mod _rust { use crate::backend::rsa::rsa; #[pymodule_export] use crate::backend::x25519::x25519; - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_AWSLC + )))] #[pymodule_export] use crate::backend::x448::x448; #[pymodule_export] @@ -207,13 +221,22 @@ mod _rust { "CRYPTOGRAPHY_OPENSSL_320_OR_GREATER", cfg!(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER), )?; + openssl_mod.add( + "CRYPTOGRAPHY_OPENSSL_330_OR_GREATER", + cfg!(CRYPTOGRAPHY_OPENSSL_330_OR_GREATER), + )?; + openssl_mod.add( + "CRYPTOGRAPHY_OPENSSL_350_OR_GREATER", + cfg!(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER), + )?; openssl_mod.add("CRYPTOGRAPHY_IS_LIBRESSL", cfg!(CRYPTOGRAPHY_IS_LIBRESSL))?; openssl_mod.add("CRYPTOGRAPHY_IS_BORINGSSL", cfg!(CRYPTOGRAPHY_IS_BORINGSSL))?; + openssl_mod.add("CRYPTOGRAPHY_IS_AWSLC", cfg!(CRYPTOGRAPHY_IS_AWSLC))?; cfg_if::cfg_if! { if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] { - let providers = super::super::_initialize_providers()?; + let providers = super::super::_initialize_providers(openssl_mod.py())?; if providers.legacy.is_some() { openssl_mod.add("_legacy_provider_loaded", true)?; } else { @@ -251,14 +274,3 @@ mod _rust { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::_legacy_provider_error; - - #[test] - fn test_legacy_provider_error() { - assert!(_legacy_provider_error(true).is_ok()); - assert!(_legacy_provider_error(false).is_err()); - } -} diff --git a/src/rust/src/oid.rs b/src/rust/src/oid.rs index c034c3dcb601..ff9d4168ca2c 100644 --- a/src/rust/src/oid.rs +++ b/src/rust/src/oid.rs @@ -2,12 +2,14 @@ // 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::types; -use pyo3::types::PyAnyMethods; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use pyo3::types::PyAnyMethods; + +use crate::error::CryptographyResult; +use crate::types; + #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust")] pub(crate) struct ObjectIdentifier { pub(crate) oid: asn1::ObjectIdentifier, diff --git a/src/rust/src/padding.rs b/src/rust/src/padding.rs index eb16cfaaad41..27689640de28 100644 --- a/src/rust/src/padding.rs +++ b/src/rust/src/padding.rs @@ -20,7 +20,7 @@ fn constant_time_lt(a: u8, b: u8) -> u8 { duplicate_msb_to_all(a ^ ((a ^ b) | (a.wrapping_sub(b) ^ b))) } -pub(crate) fn check_pkcs7_padding(data: &[u8]) -> bool { +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"); @@ -42,8 +42,7 @@ pub(crate) fn check_pkcs7_padding(data: &[u8]) -> bool { (mismatch & 1) == 0 } -#[pyo3::pyfunction] -pub(crate) fn check_ansix923_padding(data: &[u8]) -> bool { +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"); @@ -110,6 +109,52 @@ impl PKCS7PaddingContext { } } +#[pyo3::pyclass] +pub(crate) struct ANSIX923PaddingContext { + block_size: usize, + length_seen: Option, +} + +#[pyo3::pymethods] +impl ANSIX923PaddingContext { + #[new] + pub(crate) fn new(block_size: usize) -> ANSIX923PaddingContext { + ANSIX923PaddingContext { + block_size: block_size / 8, + length_seen: Some(0), + } + } + + pub(crate) fn update<'a>( + &mut self, + buf: CffiBuf<'a>, + ) -> CryptographyResult> { + match self.length_seen.as_mut() { + Some(v) => { + *v += buf.as_bytes().len(); + Ok(buf.into_pyobj()) + } + None => Err(exceptions::already_finalized_error()), + } + } + + pub(crate) fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + match self.length_seen.take() { + Some(v) => { + let pad_size = self.block_size - (v % self.block_size); + // pad_size is between 1 and block_size by construction + let mut pad = vec![0_u8; pad_size - 1]; + pad.push(pad_size as u8); + Ok(pyo3::types::PyBytes::new(py, pad.as_slice())) + } + None => Err(exceptions::already_finalized_error()), + } + } +} + #[pyo3::pyclass] pub(crate) struct PKCS7UnpaddingContext { block_size: usize, @@ -169,6 +214,66 @@ impl PKCS7UnpaddingContext { } } +#[pyo3::pyclass] +pub(crate) struct ANSIX923UnpaddingContext { + block_size: usize, + buffer: Option>, +} + +#[pyo3::pymethods] +impl ANSIX923UnpaddingContext { + #[new] + pub(crate) fn new(block_size: usize) -> ANSIX923UnpaddingContext { + ANSIX923UnpaddingContext { + block_size: block_size / 8, + buffer: Some(Vec::new()), + } + } + + pub(crate) fn update<'p>( + &mut self, + buf: CffiBuf<'p>, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + match self.buffer.as_mut() { + Some(v) => { + v.extend_from_slice(buf.as_bytes()); + let finished_blocks = (v.len() / self.block_size).saturating_sub(1); + let result_size = finished_blocks * self.block_size; + let result = v.drain(..result_size); + Ok(pyo3::types::PyBytes::new(py, result.as_slice())) + } + None => Err(exceptions::already_finalized_error()), + } + } + + pub(crate) fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + match self.buffer.take() { + Some(v) => { + if v.len() != self.block_size { + return Err( + pyo3::exceptions::PyValueError::new_err("Invalid padding bytes.").into(), + ); + } + + if !check_ansix923_padding(&v) { + return Err( + pyo3::exceptions::PyValueError::new_err("Invalid padding bytes.").into(), + ); + } + + let pad_size = *v.last().unwrap(); + let result = &v[..v.len() - pad_size as usize]; + Ok(pyo3::types::PyBytes::new(py, result)) + } + None => Err(exceptions::already_finalized_error()), + } + } +} + #[cfg(test)] mod tests { use super::constant_time_lt; diff --git a/src/rust/src/pkcs12.rs b/src/rust/src/pkcs12.rs index 3de031a22b38..b31c6aa412e7 100644 --- a/src/rust/src/pkcs12.rs +++ b/src/rust/src/pkcs12.rs @@ -2,17 +2,21 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +use cryptography_x509::common::Utf8StoredBMPString; +use cryptography_x509::oid::EKU_ANY_KEY_USAGE_OID; +use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; +use pyo3::{IntoPyObject, PyTypeInfo}; + use crate::backend::{ciphers, hashes, hmac, kdf, keys}; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::padding::PKCS7PaddingContext; +use crate::utils::cstr_from_literal; use crate::x509::certificate::Certificate; use crate::{types, x509}; -use cryptography_x509::common::Utf8StoredBMPString; -use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; -use pyo3::IntoPyObject; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; #[pyo3::pyclass(frozen)] struct PKCS12Certificate { @@ -108,14 +112,14 @@ pub(crate) fn symmetric_encrypt( } enum EncryptionAlgorithm { - PBESv1SHA1And3KeyTripleDESCBC, + PBESHA1And3KeyTripleDESCBC, PBESv2SHA256AndAES256CBC, } impl EncryptionAlgorithm { fn salt_length(&self) -> usize { match self { - EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => 8, + EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => 8, EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => 16, } } @@ -127,11 +131,11 @@ impl EncryptionAlgorithm { iv: &'a [u8], ) -> cryptography_x509::common::AlgorithmIdentifier<'a> { match self { - EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => { + EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => { cryptography_x509::common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::Pbes1WithShaAnd3KeyTripleDesCbc(cryptography_x509::common::PBES1Params{ - salt: salt[..8].try_into().unwrap(), + params: cryptography_x509::common::AlgorithmParameters::PbeWithShaAnd3KeyTripleDesCbc(cryptography_x509::common::Pkcs12PbeParams{ + salt, iterations: cipher_kdf_iter, }), } @@ -178,26 +182,26 @@ impl EncryptionAlgorithm { fn encrypt( &self, py: pyo3::Python<'_>, - password: &[u8], + password: &str, cipher_kdf_iter: u64, salt: &[u8], iv: &[u8], data: &[u8], ) -> CryptographyResult> { match self { - EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => { - let key = pkcs12_kdf( + EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => { + let key = cryptography_crypto::pkcs12::kdf( password, salt, - KDF_ENCRYPTION_KEY_ID, + cryptography_crypto::pkcs12::KDF_ENCRYPTION_KEY_ID, cipher_kdf_iter, 24, openssl::hash::MessageDigest::sha1(), )?; - let iv = pkcs12_kdf( + let iv = cryptography_crypto::pkcs12::kdf( password, salt, - KDF_IV_ID, + cryptography_crypto::pkcs12::KDF_IV_ID, cipher_kdf_iter, 8, openssl::hash::MessageDigest::sha1(), @@ -213,7 +217,7 @@ impl EncryptionAlgorithm { symmetric_encrypt(py, triple_des, cbc, data) } EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { - let pass_buf = CffiBuf::from_bytes(py, password); + let pass_buf = CffiBuf::from_bytes(py, password.as_bytes()); let sha256 = types::SHA256.get(py)?.call0()?; let key = kdf::derive_pbkdf2_hmac( @@ -234,113 +238,10 @@ impl EncryptionAlgorithm { } } -const KDF_ENCRYPTION_KEY_ID: u8 = 1; -const KDF_IV_ID: u8 = 2; -const KDF_MAC_KEY_ID: u8 = 3; - -fn pkcs12_kdf( - pass: &[u8], - salt: &[u8], - id: u8, - rounds: u64, - key_len: usize, - hash_alg: openssl::hash::MessageDigest, -) -> CryptographyResult> { - // Encode the password as big-endian UTF-16 with NUL trailer - let pass = std::str::from_utf8(pass) - .map_err(|_| pyo3::exceptions::PyValueError::new_err("key must be valid UTF-8"))? - .encode_utf16() - .chain([0]) - .flat_map(|v| v.to_be_bytes()) - .collect::>(); - - // Comments are borrowed from BoringSSL. - // In the spec, |block_size| is called "v", but measured in bits. - let block_size = hash_alg.block_size(); - - // 1. Construct a string, D (the "diversifier"), by concatenating v/8 copies - // of ID. - let d = vec![id; block_size]; - - // 2. Concatenate copies of the salt together to create a string S of length - // v(ceiling(s/v)) bits (the final copy of the salt may be truncated to - // create S). Note that if the salt is the empty string, then so is S. - // - // 3. Concatenate copies of the password together to create a string P of - // length v(ceiling(p/v)) bits (the final copy of the password may be - // truncated to create P). Note that if the password is the empty string, - // then so is P. - // - // 4. Set I=S||P to be the concatenation of S and P. - let s_len = block_size * ((salt.len() + block_size - 1) / block_size); - let p_len = block_size * ((pass.len() + block_size - 1) / block_size); - - let mut init_key = vec![0; s_len + p_len]; - for i in 0..s_len { - init_key[i] = salt[i % salt.len()]; - } - for i in 0..p_len { - init_key[i + s_len] = pass[i % pass.len()]; - } - - let mut result = vec![0; key_len]; - let mut pos = 0; - loop { - // A. Set A_i=H^r(D||I). (i.e., the r-th hash of D||I, - // H(H(H(... H(D||I)))) - - let mut h = openssl::hash::Hasher::new(hash_alg)?; - h.update(&d)?; - h.update(&init_key)?; - let mut a = h.finish()?; - - for _ in 1..rounds { - let mut h = openssl::hash::Hasher::new(hash_alg)?; - h.update(&a)?; - a = h.finish()?; - } - - let to_add = a.len().min(result.len() - pos); - result[pos..pos + to_add].copy_from_slice(&a[..to_add]); - pos += to_add; - if pos == result.len() { - break; - } - - // B. Concatenate copies of A_i to create a string B of length v bits (the - // final copy of A_i may be truncated to create B). - let mut b = vec![0; block_size]; - for i in 0..block_size { - b[i] = a[i % a.len()]; - } - - // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit blocks, - // where k=ceiling(s/v)+ceiling(p/v), modify I by setting I_j=(I_j+B+1) mod - // 2^v for each j. - assert!(init_key.len() % block_size == 0); - let mut j = 0; - while j < init_key.len() { - let mut carry = 1u16; - let mut k = block_size - 1; - loop { - carry += init_key[k + j] as u16 + b[k] as u16; - init_key[j + k] = carry as u8; - carry >>= 8; - if k == 0 { - break; - } - k -= 1; - } - j += block_size; - } - } - - Ok(result) -} - fn pkcs12_attributes<'a>( friendly_name: Option<&'a [u8]>, local_key_id: Option<&'a [u8]>, + is_java_trusted_cert: bool, ) -> CryptographyResult< Option< asn1::SetOfWriter< @@ -371,6 +272,14 @@ fn pkcs12_attributes<'a>( ), }); } + if is_java_trusted_cert { + attrs.push(cryptography_x509::pkcs12::Attribute { + _attr_id: asn1::DefinedByMarker::marker(), + attr_values: cryptography_x509::pkcs12::AttributeSet::JDKTruststoreUsage( + asn1::SetOfWriter::new([EKU_ANY_KEY_USAGE_OID]), + ), + }); + } if attrs.is_empty() { Ok(None) @@ -383,44 +292,47 @@ fn cert_to_bag<'a>( cert: &'a Certificate, friendly_name: Option<&'a [u8]>, local_key_id: Option<&'a [u8]>, + is_java_trusted_cert: bool, ) -> CryptographyResult> { Ok(cryptography_x509::pkcs12::SafeBag { _bag_id: asn1::DefinedByMarker::marker(), - bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::CertBag( + bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::CertBag(Box::new( cryptography_x509::pkcs12::CertBag { _cert_id: asn1::DefinedByMarker::marker(), cert_value: asn1::Explicit::new(cryptography_x509::pkcs12::CertType::X509( asn1::OctetStringEncoded::new(cert.raw.borrow_dependent().clone()), )), }, - )), - attributes: pkcs12_attributes(friendly_name, local_key_id)?, + ))), + attributes: pkcs12_attributes(friendly_name, local_key_id, is_java_trusted_cert)?, }) } +struct KeySerializationEncryption<'a> { + password: pyo3::pybacked::PyBackedBytes, + mac_algorithm: pyo3::Bound<'a, pyo3::PyAny>, + mac_kdf_iter: u64, + cipher_kdf_iter: u64, + encryption_algorithm: Option, +} + #[allow(clippy::type_complexity)] fn decode_encryption_algorithm<'a>( py: pyo3::Python<'a>, encryption_algorithm: pyo3::Bound<'a, pyo3::PyAny>, -) -> CryptographyResult<( - pyo3::pybacked::PyBackedBytes, - pyo3::Bound<'a, pyo3::PyAny>, - u64, - u64, - Option, -)> { +) -> CryptographyResult> { let default_hmac_alg = types::SHA256.get(py)?.call0()?; let default_hmac_kdf_iter = 2048; let default_cipher_kdf_iter = 20000; if encryption_algorithm.is_instance(&types::NO_ENCRYPTION.get(py)?)? { - Ok(( - pyo3::types::PyBytes::new(py, b"").extract()?, - default_hmac_alg, - default_hmac_kdf_iter, - default_cipher_kdf_iter, - None, - )) + Ok(KeySerializationEncryption { + password: pyo3::types::PyBytes::new(py, b"").extract()?, + mac_algorithm: default_hmac_alg, + mac_kdf_iter: default_hmac_kdf_iter, + cipher_kdf_iter: default_cipher_kdf_iter, + encryption_algorithm: None, + }) } else if encryption_algorithm.is_instance(&types::ENCRYPTION_BUILDER.get(py)?)? && encryption_algorithm .getattr(pyo3::intern!(py, "_format"))? @@ -429,7 +341,7 @@ fn decode_encryption_algorithm<'a>( let key_cert_alg = encryption_algorithm.getattr(pyo3::intern!(py, "_key_cert_algorithm"))?; let cipher = if key_cert_alg.is(&types::PBES_PBESV1SHA1AND3KEYTRIPLEDESCBC.get(py)?) { - EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC + EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC } else if key_cert_alg.is(&types::PBES_PBESV2SHA256ANDAES256CBC.get(py)?) { EncryptionAlgorithm::PBESv2SHA256AndAES256CBC } else { @@ -455,25 +367,25 @@ fn decode_encryption_algorithm<'a>( default_cipher_kdf_iter }; - Ok(( - encryption_algorithm + Ok(KeySerializationEncryption { + password: encryption_algorithm .getattr(pyo3::intern!(py, "password"))? .extract()?, - hmac_alg, - default_hmac_kdf_iter, + mac_algorithm: hmac_alg, + mac_kdf_iter: default_hmac_kdf_iter, cipher_kdf_iter, - Some(cipher), - )) + encryption_algorithm: Some(cipher), + }) } else if encryption_algorithm.is_instance(&types::BEST_AVAILABLE_ENCRYPTION.get(py)?)? { - Ok(( - encryption_algorithm + Ok(KeySerializationEncryption { + password: encryption_algorithm .getattr(pyo3::intern!(py, "password"))? .extract()?, - default_hmac_alg, - default_hmac_kdf_iter, - default_cipher_kdf_iter, - Some(EncryptionAlgorithm::PBESv2SHA256AndAES256CBC), - )) + mac_algorithm: default_hmac_alg, + mac_kdf_iter: default_hmac_kdf_iter, + cipher_kdf_iter: default_cipher_kdf_iter, + encryption_algorithm: Some(EncryptionAlgorithm::PBESv2SHA256AndAES256CBC), + }) } else { Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Unsupported key encryption type"), @@ -487,6 +399,164 @@ enum CertificateOrPKCS12Certificate { PKCS12Certificate(pyo3::Py), } +fn serialize_safebags<'p>( + py: pyo3::Python<'p>, + safebags: &[cryptography_x509::pkcs12::SafeBag<'_>], + encryption_details: &KeySerializationEncryption<'_>, +) -> CryptographyResult> { + let password = std::str::from_utf8(&encryption_details.password) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("password must be valid UTF-8"))?; + let mut auth_safe_contents = vec![]; + let ( + plain_safebag_contents, + shrouded_safebag_contents, + auth_safe_salt, + auth_safe_iv, + auth_safe_ciphertext, + ); + + if let Some(e) = &encryption_details.encryption_algorithm { + // When encryption is applied, safebags that have already been encrypted (ShroudedKeyBag) + // should not be encrypted again, so they are placed in their own ContentInfo. + // See RFC 7292 4.1 + let mut shrouded_safebags = vec![]; + let mut plain_safebags = vec![]; + for safebag in safebags { + match safebag.bag_value.as_inner() { + cryptography_x509::pkcs12::BagValue::ShroudedKeyBag(_) => { + shrouded_safebags.push(safebag) + } + _ => plain_safebags.push(safebag), + } + } + if !plain_safebags.is_empty() { + plain_safebag_contents = + asn1::write_single(&asn1::SequenceOfWriter::new(plain_safebags))?; + auth_safe_salt = types::OS_URANDOM + .get(py)? + .call1((e.salt_length(),))? + .extract::()?; + auth_safe_iv = types::OS_URANDOM + .get(py)? + .call1((16,))? + .extract::()?; + auth_safe_ciphertext = e.encrypt( + py, + password, + encryption_details.cipher_kdf_iter, + &auth_safe_salt, + &auth_safe_iv, + &plain_safebag_contents, + )?; + + auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::EncryptedData(asn1::Explicit::new( + cryptography_x509::pkcs7::EncryptedData { + version: 0, + encrypted_content_info: cryptography_x509::pkcs7::EncryptedContentInfo { + content_type: cryptography_x509::pkcs7::PKCS7_DATA_OID, + content_encryption_algorithm: e.algorithm_identifier( + encryption_details.cipher_kdf_iter, + &auth_safe_salt, + &auth_safe_iv, + ), + encrypted_content: Some(&auth_safe_ciphertext), + }, + }, + )), + }); + } + if !shrouded_safebags.is_empty() { + shrouded_safebag_contents = + asn1::write_single(&asn1::SequenceOfWriter::new(shrouded_safebags))?; + auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( + &shrouded_safebag_contents, + ))), + }); + } + } else { + plain_safebag_contents = asn1::write_single(&asn1::SequenceOfWriter::new(safebags))?; + auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( + &plain_safebag_contents, + ))), + }); + } + + let auth_safe_content = asn1::write_single(&asn1::SequenceOfWriter::new(auth_safe_contents))?; + + let salt = types::OS_URANDOM + .get(py)? + .call1((8,))? + .extract::()?; + let mac_algorithm_md = + hashes::message_digest_from_algorithm(py, &encryption_details.mac_algorithm)?; + let mac_key = cryptography_crypto::pkcs12::kdf( + password, + &salt, + cryptography_crypto::pkcs12::KDF_MAC_KEY_ID, + encryption_details.mac_kdf_iter, + mac_algorithm_md.size(), + mac_algorithm_md, + )?; + let mac_digest = { + let mut h = hmac::Hmac::new_bytes(py, &mac_key, &encryption_details.mac_algorithm)?; + h.update_bytes(&auth_safe_content)?; + h.finalize(py)? + }; + let mac_algorithm_identifier = crate::x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS + [&*encryption_details + .mac_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::()?] + .clone(); + + let p12 = cryptography_x509::pkcs12::Pfx { + version: 3, + auth_safe: cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( + &auth_safe_content, + ))), + }, + mac_data: Some(cryptography_x509::pkcs12::MacData { + mac: cryptography_x509::pkcs7::DigestInfo { + algorithm: mac_algorithm_identifier, + digest: mac_digest.as_bytes(), + }, + salt: &salt, + iterations: encryption_details.mac_kdf_iter, + }), + }; + Ok(pyo3::types::PyBytes::new(py, &asn1::write_single(&p12)?)) +} + +#[pyo3::pyfunction] +#[pyo3(signature = (pkcs12_certs, encryption_algorithm))] +fn serialize_java_truststore<'p>( + py: pyo3::Python<'p>, + pkcs12_certs: Vec>, + encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { + let encryption_details = decode_encryption_algorithm(py, encryption_algorithm)?; + let mut safebags = vec![]; + + for cert in &pkcs12_certs { + safebags.push(cert_to_bag( + cert.get().certificate.get(), + cert.get().friendly_name.as_ref().map(|v| v.as_bytes(py)), + None, + true, + )?); + } + + serialize_safebags(py, &safebags, &encryption_details) +} + #[pyo3::pyfunction] #[pyo3(signature = (name, key, cert, cas, encryption_algorithm))] fn serialize_key_and_certificates<'p>( @@ -497,25 +567,15 @@ fn serialize_key_and_certificates<'p>( cas: Option>, encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { - let (password, mac_algorithm, mac_kdf_iter, cipher_kdf_iter, encryption_algorithm) = - decode_encryption_algorithm(py, encryption_algorithm)?; + let encryption_details = decode_encryption_algorithm(py, encryption_algorithm)?; + let password = std::str::from_utf8(&encryption_details.password) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("password must be valid UTF-8"))?; - let mut auth_safe_contents = vec![]; - let ( - cert_bag_contents, - cert_salt, - cert_iv, - cert_ciphertext, - key_bag_contents, - key_salt, - key_iv, - key_ciphertext, - ); + let mut safebags = vec![]; + let (key_salt, key_iv, key_ciphertext, pkcs8_bytes); let mut ca_certs = vec![]; let mut key_id = None; if cert.is_some() || cas.is_some() { - let mut cert_bags = vec![]; - if let Some(cert) = cert { if let Some(ref key) = key { if !cert @@ -531,10 +591,11 @@ fn serialize_key_and_certificates<'p>( key_id = Some(cert.fingerprint(py, &types::SHA1.get(py)?.call0()?)?); } - cert_bags.push(cert_to_bag( + safebags.push(cert_to_bag( cert, name, key_id.as_ref().map(|v| v.as_bytes()), + false, )?); } @@ -546,62 +607,18 @@ fn serialize_key_and_certificates<'p>( for cert in &ca_certs { let bag = match cert { CertificateOrPKCS12Certificate::Certificate(c) => { - cert_to_bag(c.get(), None, None)? + cert_to_bag(c.get(), None, None, false)? } CertificateOrPKCS12Certificate::PKCS12Certificate(c) => cert_to_bag( c.get().certificate.get(), c.get().friendly_name.as_ref().map(|v| v.as_bytes(py)), None, + false, )?, }; - cert_bags.push(bag); + safebags.push(bag); } } - - cert_bag_contents = asn1::write_single(&asn1::SequenceOfWriter::new(cert_bags))?; - if let Some(e) = &encryption_algorithm { - cert_salt = types::OS_URANDOM - .get(py)? - .call1((e.salt_length(),))? - .extract::()?; - cert_iv = types::OS_URANDOM - .get(py)? - .call1((16,))? - .extract::()?; - cert_ciphertext = e.encrypt( - py, - &password, - cipher_kdf_iter, - &cert_salt, - &cert_iv, - &cert_bag_contents, - )?; - - auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { - _content_type: asn1::DefinedByMarker::marker(), - content: cryptography_x509::pkcs7::Content::EncryptedData(asn1::Explicit::new( - cryptography_x509::pkcs7::EncryptedData { - version: 0, - encrypted_content_info: cryptography_x509::pkcs7::EncryptedContentInfo { - content_type: cryptography_x509::pkcs7::PKCS7_DATA_OID, - content_encryption_algorithm: e.algorithm_identifier( - cipher_kdf_iter, - &cert_salt, - &cert_iv, - ), - encrypted_content: Some(&cert_ciphertext), - }, - }, - )), - }) - } else { - auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { - _content_type: asn1::DefinedByMarker::marker(), - content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( - &cert_bag_contents, - ))), - }); - } } if let Some(key) = key { @@ -609,14 +626,14 @@ fn serialize_key_and_certificates<'p>( let pkcs8 = types::PRIVATE_FORMAT_PKCS8.get(py)?; let no_encryption = types::NO_ENCRYPTION.get(py)?.call0()?; - let pkcs8_bytes = key + pkcs8_bytes = key .call_method1( pyo3::intern!(py, "private_bytes"), (der, pkcs8, no_encryption), )? .extract::()?; - let key_bag = if let Some(e) = encryption_algorithm { + let key_bag = if let Some(ref e) = encryption_details.encryption_algorithm { key_salt = types::OS_URANDOM .get(py)? .call1((e.salt_length(),))? @@ -627,8 +644,8 @@ fn serialize_key_and_certificates<'p>( .extract::()?; key_ciphertext = e.encrypt( py, - &password, - cipher_kdf_iter, + password, + encryption_details.cipher_kdf_iter, &key_salt, &key_iv, &pkcs8_bytes, @@ -638,9 +655,9 @@ fn serialize_key_and_certificates<'p>( _bag_id: asn1::DefinedByMarker::marker(), bag_value: asn1::Explicit::new( cryptography_x509::pkcs12::BagValue::ShroudedKeyBag( - cryptography_x509::pkcs12::EncryptedPrivateKeyInfo { + cryptography_x509::pkcs8::EncryptedPrivateKeyInfo { encryption_algorithm: e.algorithm_identifier( - cipher_kdf_iter, + encryption_details.cipher_kdf_iter, &key_salt, &key_iv, ), @@ -648,7 +665,7 @@ fn serialize_key_and_certificates<'p>( }, ), ), - attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()))?, + attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()), false)?, } } else { let pkcs8_tlv = asn1::parse_single(&pkcs8_bytes)?; @@ -658,66 +675,18 @@ fn serialize_key_and_certificates<'p>( bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::KeyBag( pkcs8_tlv, )), - attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()))?, + attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()), false)?, } }; - key_bag_contents = asn1::write_single(&asn1::SequenceOfWriter::new([key_bag]))?; - auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { - _content_type: asn1::DefinedByMarker::marker(), - content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( - &key_bag_contents, - ))), - }); + safebags.push(key_bag); } - let auth_safe_content = asn1::write_single(&asn1::SequenceOfWriter::new(auth_safe_contents))?; - - let salt = types::OS_URANDOM - .get(py)? - .call1((8,))? - .extract::()?; - let mac_algorithm_md = hashes::message_digest_from_algorithm(py, &mac_algorithm)?; - let mac_key = pkcs12_kdf( - &password, - &salt, - KDF_MAC_KEY_ID, - mac_kdf_iter, - mac_algorithm_md.size(), - mac_algorithm_md, - )?; - let mac_digest = { - let mut h = hmac::Hmac::new_bytes(py, &mac_key, &mac_algorithm)?; - h.update_bytes(&auth_safe_content)?; - h.finalize(py)? - }; - let mac_algorithm_identifier = crate::x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS - [&*mac_algorithm - .getattr(pyo3::intern!(py, "name"))? - .extract::()?] - .clone(); - - let p12 = cryptography_x509::pkcs12::Pfx { - version: 3, - auth_safe: cryptography_x509::pkcs7::ContentInfo { - _content_type: asn1::DefinedByMarker::marker(), - content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( - &auth_safe_content, - ))), - }, - mac_data: Some(cryptography_x509::pkcs12::MacData { - mac: cryptography_x509::pkcs7::DigestInfo { - algorithm: mac_algorithm_identifier, - digest: mac_digest.as_bytes(), - }, - salt: &salt, - iterations: mac_kdf_iter, - }), - }; - Ok(pyo3::types::PyBytes::new(py, &asn1::write_single(&p12)?)) + serialize_safebags(py, &safebags, &encryption_details) } fn decode_p12( + py: pyo3::Python<'_>, data: CffiBuf<'_>, password: Option>, ) -> CryptographyResult { @@ -738,6 +707,12 @@ fn decode_p12( .parse2(password) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid password or PKCS12 data"))?; + if asn1::parse_single::>(data.as_bytes()).is_err() { + let warning_cls = pyo3::exceptions::PyUserWarning::type_object(py); + let message = cstr_from_literal!("PKCS#12 bundle could not be parsed as DER, falling back to parsing as BER. Please file an issue at https://github.com/pyca/cryptography/issues explaining how your PKCS#12 bundle was created. In the future, this may become an exception."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; + } + Ok(parsed) } @@ -755,10 +730,11 @@ fn load_key_and_certificates<'p>( )> { let _ = backend; - let p12 = decode_p12(data, password)?; + let p12 = decode_p12(py, data, password)?; let private_key = if let Some(pkey) = p12.pkey { - keys::private_key_from_pkey(py, &pkey, false)? + let pkey_bytes = pkey.private_key_to_pkcs8()?; + keys::load_der_private_key_bytes(py, &pkey_bytes, None, false)? } else { py.None().into_bound(py) }; @@ -774,7 +750,7 @@ fn load_key_and_certificates<'p>( if let Some(ossl_certs) = p12.ca { cfg_if::cfg_if! { if #[cfg(any( - CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC ))] { let it = ossl_certs.iter(); } else { @@ -802,10 +778,11 @@ fn load_pkcs12<'p>( ) -> CryptographyResult> { let _ = backend; - let p12 = decode_p12(data, password)?; + let p12 = decode_p12(py, data, password)?; let private_key = if let Some(pkey) = p12.pkey { - keys::private_key_from_pkey(py, &pkey, false)? + let pkey_bytes = pkey.private_key_to_pkcs8()?; + keys::load_der_private_key_bytes(py, &pkey_bytes, None, false)? } else { py.None().into_bound(py) }; @@ -827,7 +804,7 @@ fn load_pkcs12<'p>( if let Some(ossl_certs) = p12.ca { cfg_if::cfg_if! { if #[cfg(any( - CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC ))] { let it = ossl_certs.iter(); } else { @@ -856,54 +833,7 @@ fn load_pkcs12<'p>( pub(crate) mod pkcs12 { #[pymodule_export] use super::{ - load_key_and_certificates, load_pkcs12, serialize_key_and_certificates, PKCS12Certificate, + load_key_and_certificates, load_pkcs12, serialize_java_truststore, + serialize_key_and_certificates, PKCS12Certificate, }; } - -#[cfg(test)] -mod tests { - use super::{pkcs12_kdf, KDF_ENCRYPTION_KEY_ID, KDF_IV_ID, KDF_MAC_KEY_ID}; - - #[test] - fn test_pkcs12_kdf() { - for (password, salt, id, rounds, key_len, hash, expected_key) in [ - // From https://github.com/RustCrypto/formats/blob/master/pkcs12/tests/kdf.rs - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7F\xa3\x17~[\x07h\xa3\x11\x8b\xf8c" as &[u8]), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97sI\xdbn&\xcc\xc9\x98\xd9\xe8\xf8=l"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84G\x02\xc2\xc1\xf3\xb4c!\xe2RJM"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97s"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9d"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05'"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"dr\xc0\xeb\xad?\xabA#\xe8\xb5\xedx4\xde!\xee\xb2\x01\x87\xb3\xef\xf7\x8a}\x1c\xdf\xfa@4\x85\x1d"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"?\x91\x13\xf0\\0\xa9\x96\xc4\xa5\x16@\x9b\xda\xc9\xd0e\xf4B\x96\xcc\xd5+\xb7]\xe3\xfc\xfd\xbe+\xf10"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 100, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r"), - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 200, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r\x9c\xea`\x14\xd7\xfeb\xa2\xed\x92m\xc3ka0\x7f\x11\x9dd\xed\xbc\xebZ\x9cX\x13;\xbfu\xba\x0b\xef\x00\n\x1aQ\x80\xe4\xb1\xde}\x89\xc8\x95(\xbc\xb7\x89\x9a\x1eF\xfdM\xa0\xd9\xde\x8f\x8ee\xe8\xd0\xd7u\xe3=\x12G\xe7mYj401a\xb2\x19\xf3\x9a\xfd\xa4H\xbfQ\x8a(5\xfc^(\xf0\xb5Z\x1ba7\xa2\xc7\x0c\xf7"), - - ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha512(), b"\xb1J\x9f\x01\xbf\xd9\xdc\xe4\xc9\xd6m/\xe9\x93~_\xd9\xf1\xaf\xa5\x9e7\no\xa4\xfc\x81\xc1\xcc\x8e\xc8\xee"), - - // From https://cs.opensource.google/go/x/crypto/+/master:pkcs12/pbkdf_test.go - (b"sesame", b"\xff\xff\xff\xff\xff\xff\xff\xff", KDF_ENCRYPTION_KEY_ID, 2048, 24, openssl::hash::MessageDigest::sha1(), b"\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1"), - ] { - let result = pkcs12_kdf(password, salt, id, rounds, key_len, hash).map_err(|_| ()).unwrap(); - assert_eq!(result, expected_key); - } - } - - #[test] - fn test_pkcs12_kdf_error() { - // Key is not valid UTF-8 - let result = pkcs12_kdf( - b"\x91\x82%\xa1", - b"\x01\x02\x03\x04", - KDF_ENCRYPTION_KEY_ID, - 100, - 8, - openssl::hash::MessageDigest::sha256(), - ); - assert!(matches!(result, Err(_))); - } -} diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs index 90cd063f8b6a..db76d0bbedaa 100644 --- a/src/rust/src/pkcs7.rs +++ b/src/rust/src/pkcs7.rs @@ -11,9 +11,11 @@ use cryptography_x509::csr::Attribute; use cryptography_x509::pkcs7::PKCS7_DATA_OID; use cryptography_x509::{common, oid, pkcs7}; use once_cell::sync::Lazy; -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] use openssl::pkcs7::Pkcs7; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] +use pyo3::PyTypeInfo; use crate::asn1::encode_der_data; use crate::backend::ciphers; @@ -21,7 +23,9 @@ use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::padding::PKCS7UnpaddingContext; use crate::pkcs12::symmetric_encrypt; -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] +use crate::utils::cstr_from_literal; +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] use crate::x509::certificate::load_der_x509_certificate; use crate::{exceptions, types, x509}; @@ -84,6 +88,7 @@ fn serialize_certificates<'p>( fn encrypt_and_serialize<'p>( py: pyo3::Python<'p>, builder: &pyo3::Bound<'p, pyo3::PyAny>, + content_encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, options: &pyo3::Bound<'p, pyo3::types::PyList>, ) -> CryptographyResult> { @@ -95,14 +100,24 @@ fn encrypt_and_serialize<'p>( smime_canonicalize(raw_data.as_bytes(), text_mode).0 }; - // The message is encrypted with AES-128-CBC, which the S/MIME v3.2 RFC - // specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7) - let key = types::OS_URANDOM.get(py)?.call1((16,))?; - let aes128_algorithm = types::AES128.get(py)?.call1((&key,))?; + // Get the content encryption algorithm + let content_encryption_algorithm_type = content_encryption_algorithm; + let key_size = content_encryption_algorithm_type.getattr(pyo3::intern!(py, "key_size"))?; + let key = types::OS_URANDOM + .get(py)? + .call1((key_size.floor_div(8)?,))?; + let content_encryption_algorithm = content_encryption_algorithm_type.call1((&key,))?; + + // Get the mode let iv = types::OS_URANDOM.get(py)?.call1((16,))?; let cbc_mode = types::CBC.get(py)?.call1((&iv,))?; - let encrypted_content = symmetric_encrypt(py, aes128_algorithm, cbc_mode, &data_with_header)?; + let encrypted_content = symmetric_encrypt( + py, + content_encryption_algorithm, + cbc_mode, + &data_with_header, + )?; let py_recipients: Vec> = builder .getattr(pyo3::intern!(py, "_recipients"))? @@ -133,6 +148,13 @@ fn encrypt_and_serialize<'p>( }); } + // Prepare the algorithm parameters + let algorithm_parameters = if content_encryption_algorithm_type.eq(types::AES128.get(py)?)? { + AlgorithmParameters::Aes128Cbc(iv.extract()?) + } else { + AlgorithmParameters::Aes256Cbc(iv.extract()?) + }; + let enveloped_data = pkcs7::EnvelopedData { version: 0, recipient_infos: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( @@ -143,7 +165,7 @@ fn encrypt_and_serialize<'p>( content_type: PKCS7_DATA_OID, content_encryption_algorithm: AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: AlgorithmParameters::Aes128Cbc(iv.extract()?), + params: algorithm_parameters, }, encrypted_content: Some(&encrypted_content), }, @@ -267,8 +289,10 @@ fn decrypt_der<'p>( } }; - // Get algorithm - // TODO: implement all the possible algorithms + // The function can decrypt content encrypted with AES-128-CBC, which the S/MIME v3.2 + // RFC specifies as MUST support, and AES-256-CBC, which is specified as SHOULD+ + // support. More info: https://datatracker.ietf.org/doc/html/rfc5751#section-2.7 + // TODO: implement the possible algorithms from S/MIME 3.2 (and 4.0?) let algorithm_identifier = enveloped_data .encrypted_content_info .content_encryption_algorithm; @@ -279,10 +303,16 @@ fn decrypt_der<'p>( .get(py)? .call1((pyo3::types::PyBytes::new(py, &iv),))?, ), + AlgorithmParameters::Aes256Cbc(iv) => ( + types::AES256.get(py)?.call1((key,))?, + types::CBC + .get(py)? + .call1((pyo3::types::PyBytes::new(py, &iv),))?, + ), _ => { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( - "Only AES-128-CBC is currently supported for content decryption.", + "Only AES (with key sizes 128 or 256) with CBC mode is currently supported for content decryption.", exceptions::Reasons::UNSUPPORTED_SERIALIZATION, )), )); @@ -487,6 +517,7 @@ fn sign_and_serialize<'p>( py_private_key.clone(), py_hash_alg.clone(), rsa_padding.clone(), + None, &data_with_header, )?, ) @@ -536,6 +567,7 @@ fn sign_and_serialize<'p>( py_private_key.clone(), py_hash_alg.clone(), rsa_padding.clone(), + None, &signed_data, )?, ) @@ -676,7 +708,7 @@ fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [ } } -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] fn load_pkcs7_certificates( py: pyo3::Python<'_>, pkcs7: Pkcs7, @@ -686,7 +718,7 @@ fn load_pkcs7_certificates( let nid_string = nid.map_or("empty".to_string(), |n| n.as_raw().to_string()); return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( - format!("Only basic signed structures are currently supported. NID for this data was {}", nid_string), + format!("Only basic signed structures are currently supported. NID for this data was {nid_string}"), exceptions::Reasons::UNSUPPORTED_SERIALIZATION, )), )); @@ -717,13 +749,17 @@ fn load_pem_pkcs7_certificates<'p>( data: &[u8], ) -> CryptographyResult> { cfg_if::cfg_if! { - if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { - let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_pem(data).map_err(|_| { - CryptographyError::from(pyo3::exceptions::PyValueError::new_err( - "Unable to parse PKCS7 data", - )) - })?; - load_pkcs7_certificates(py, pkcs7_decoded) + if #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] { + let pem_block = pem::parse(data)?; + if pem_block.tag() != "PKCS7" { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The provided PEM data does not have the PKCS7 tag.", + ), + )); + } + + load_der_pkcs7_certificates(py, pem_block.contents()) } else { let _ = py; let _ = data; @@ -743,13 +779,20 @@ fn load_der_pkcs7_certificates<'p>( data: &[u8], ) -> CryptographyResult> { cfg_if::cfg_if! { - if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { + if #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] { let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_der(data).map_err(|_| { CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Unable to parse PKCS7 data", )) })?; - load_pkcs7_certificates(py, pkcs7_decoded) + let result = load_pkcs7_certificates(py, pkcs7_decoded)?; + if asn1::parse_single::>(data).is_err() { + let warning_cls = pyo3::exceptions::PyUserWarning::type_object(py); + let message = cstr_from_literal!("PKCS#7 certificates could not be parsed as DER, falling back to parsing as BER. Please file an issue at https://github.com/pyca/cryptography/issues explaining how your PKCS#7 certificates were created. In the future, this may become an exception."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; + } + + Ok(result) } else { let _ = py; let _ = data; diff --git a/src/rust/src/test_support.rs b/src/rust/src/test_support.rs index 8f4599723680..dfa2a0f74c2e 100644 --- a/src/rust/src/test_support.rs +++ b/src/rust/src/test_support.rs @@ -2,20 +2,21 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] -use crate::buf::CffiBuf; -use crate::error::CryptographyResult; -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] -use crate::types; -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] -use crate::x509::certificate::Certificate as PyCertificate; use asn1::SimpleAsn1Readable; use cryptography_x509::certificate::Certificate; use cryptography_x509::common::Time; use cryptography_x509::name::Name; -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] use pyo3::prelude::PyAnyMethods; +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] +use crate::buf::CffiBuf; +use crate::error::CryptographyResult; +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] +use crate::types; +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] +use crate::x509::certificate::Certificate as PyCertificate; + #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.test_support")] struct TestCertificate { #[pyo3(get)] @@ -58,7 +59,7 @@ fn test_parse_certificate(data: &[u8]) -> CryptographyResult { }) } -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] #[pyo3::pyfunction] #[pyo3(signature = (encoding, sig, msg, certs, options))] fn pkcs7_verify( @@ -105,7 +106,7 @@ fn pkcs7_verify( #[pyo3::pymodule] pub(crate) mod test_support { - #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] #[pymodule_export] use super::pkcs7_verify; #[pymodule_export] diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs index 37ca3f424249..7fb640d0daf5 100644 --- a/src/rust/src/types.rs +++ b/src/rust/src/types.rs @@ -47,6 +47,8 @@ pub static DEPRECATED_IN_42: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn42"]); pub static DEPRECATED_IN_43: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn43"]); +pub static DEPRECATED_IN_45: LazyPyImport = + LazyPyImport::new("cryptography.utils", &["DeprecatedIn45"]); pub static ENCODING: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", @@ -179,6 +181,9 @@ pub static REASON_FLAGS: LazyPyImport = LazyPyImport::new("cryptography.x509", & pub static ATTRIBUTE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attribute"]); pub static ATTRIBUTES: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attributes"]); +pub static EXTENSION_TYPE: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["ExtensionType"]); + pub static CRL_NUMBER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLNumber"]); pub static DELTA_CRL_INDICATOR: LazyPyImport = LazyPyImport::new("cryptography.x509", &["DeltaCRLIndicator"]); @@ -245,6 +250,8 @@ pub static SUBJECT_KEY_IDENTIFIER: LazyPyImport = pub static TLS_FEATURE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["TLSFeature"]); pub static SUBJECT_ALTERNATIVE_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["SubjectAlternativeName"]); +pub static PRIVATE_KEY_USAGE_PERIOD: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["PrivateKeyUsagePeriod"]); pub static POLICY_INFORMATION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["PolicyInformation"]); pub static USER_NOTICE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["UserNotice"]); @@ -487,11 +494,13 @@ pub static DSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( &["DSAPublicKey"], ); +#[cfg(not(Py_3_11))] pub static FFI_FROM_BUFFER: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.bindings._rust", &["_openssl", "ffi", "from_buffer"], ); +#[cfg(not(Py_3_11))] pub static FFI_CAST: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.bindings._rust", &["_openssl", "ffi", "cast"], @@ -506,6 +515,8 @@ pub static TRIPLE_DES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.decrepit.ciphers.algorithms", &["TripleDES"], ); +pub static DES: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["_DES"]); pub static AES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["AES"], @@ -548,6 +559,7 @@ pub static CAST5: LazyPyImport = LazyPyImport::new( #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] pub static IDEA: LazyPyImport = LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["IDEA"]); +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_RC4"))] pub static ARC4: LazyPyImport = LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["ARC4"]); pub static RC2: LazyPyImport = diff --git a/src/rust/src/utils.rs b/src/rust/src/utils.rs new file mode 100644 index 000000000000..741463b0b29a --- /dev/null +++ b/src/rust/src/utils.rs @@ -0,0 +1,11 @@ +// 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. + +macro_rules! cstr_from_literal { + ($str:expr) => { + std::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap() + }; +} + +pub(crate) use cstr_from_literal; diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs index adef55f6abf3..573cb9002f50 100644 --- a/src/rust/src/x509/certificate.rs +++ b/src/rust/src/x509/certificate.rs @@ -6,16 +6,15 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use cryptography_x509::certificate::Certificate as RawCertificate; -use cryptography_x509::common::Asn1Read; -use cryptography_x509::common::{AlgorithmParameters, Asn1ReadableOrWritable}; +use cryptography_x509::common::{AlgorithmParameters, Asn1Read, Asn1ReadableOrWritable}; use cryptography_x509::extensions::{ Admission, Admissions, AuthorityKeyIdentifier, BasicConstraints, DisplayText, DistributionPoint, DistributionPointName, DuplicateExtensionsError, ExtendedKeyUsage, - IssuerAlternativeName, KeyUsage, MSCertificateTemplate, NameConstraints, NamingAuthority, - PolicyConstraints, PolicyInformation, PolicyQualifierInfo, ProfessionInfo, Qualifier, - RawExtensions, SequenceOfAccessDescriptions, SequenceOfSubtrees, UserNotice, + Extension, IssuerAlternativeName, KeyUsage, MSCertificateTemplate, NameConstraints, + NamingAuthority, PolicyConstraints, PolicyInformation, PolicyQualifierInfo, + PrivateKeyUsagePeriod, ProfessionInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions, + SequenceOfSubtrees, SubjectAlternativeName, UserNotice, }; -use cryptography_x509::extensions::{Extension, SubjectAlternativeName}; use cryptography_x509::{common, oid}; use cryptography_x509_verification::ops::CryptoOps; use pyo3::types::{PyAnyMethods, PyListMethods}; @@ -25,7 +24,7 @@ use crate::asn1::{ }; use crate::backend::{hashes, keys}; use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509::common::cstr_from_literal; +use crate::utils::cstr_from_literal; use crate::x509::verify::PyCryptoOps; use crate::x509::{extensions, sct, sign}; use crate::{exceptions, types, x509}; @@ -116,7 +115,7 @@ impl Certificate { py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let bytes = self.raw.borrow_dependent().tbs_cert.serial.as_bytes(); - warn_if_negative_serial(py, bytes)?; + warn_if_not_positive(py, bytes)?; Ok(big_byte_slice_to_py_int(py, bytes)?) } @@ -419,9 +418,9 @@ pub(crate) fn load_der_x509_certificate( 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_dependent().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_dependent().tbs_cert.serial.as_bytes())?; + // determine if the serial is not positive and raise a warning if it is. We + // want to drop support for this sort of invalid encoding eventually. + warn_if_not_positive(py, raw.borrow_dependent().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 the JDK and we want to drop support for it eventually. // ECDSA was fixed in Java 16, DSA in Java 21. @@ -437,10 +436,10 @@ pub(crate) fn load_der_x509_certificate( }) } -fn warn_if_negative_serial(py: pyo3::Python<'_>, bytes: &'_ [u8]) -> pyo3::PyResult<()> { - if bytes[0] & 0x80 != 0 { +fn warn_if_not_positive(py: pyo3::Python<'_>, bytes: &[u8]) -> pyo3::PyResult<()> { + if bytes[0] & 0x80 != 0 || bytes == [0] { let warning_cls = types::DEPRECATED_IN_36.get(py)?; - let message = cstr_from_literal!("Parsed a negative serial number, which is disallowed by RFC 5280. Loading this certificate will cause an exception in the next release of cryptography."); + let message = cstr_from_literal!("Parsed a serial number which wasn't positive (i.e., it was negative or zero), which is disallowed by RFC 5280. Loading this certificate will cause an exception in a future release of cryptography."); pyo3::PyErr::warn(py, &warning_cls, message, 1)?; } Ok(()) @@ -675,7 +674,10 @@ pub(crate) fn parse_authority_key_identifier<'p>( ) -> Result, CryptographyError> { let aki = ext.value::>()?; let serial = match aki.authority_cert_serial_number { - Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.unbind(), + Some(biguint) => { + warn_if_not_positive(py, biguint.as_bytes())?; + big_byte_slice_to_py_int(py, biguint.as_bytes())?.unbind() + } None => py.None(), }; let issuer = match aki.authority_cert_issuer { @@ -947,6 +949,31 @@ pub fn parse_cert_ext<'p>( .call1((admission_authority, py_admissions))?, )) } + oid::PRIVATE_KEY_USAGE_PERIOD_OID => { + let pkup = ext.value::()?; + + let not_before = match &pkup.not_before { + Some(t) => { + let dt = t.as_datetime(); + Some(x509::datetime_to_py(py, dt)?) + } + None => None, + }; + + let not_after = match &pkup.not_after { + Some(t) => { + let dt = t.as_datetime(); + Some(x509::datetime_to_py(py, dt)?) + } + None => None, + }; + + Ok(Some( + types::PRIVATE_KEY_USAGE_PERIOD + .get(py)? + .call1((not_before, not_after))?, + )) + } _ => Ok(None), } } @@ -976,6 +1003,7 @@ pub(crate) fn create_x509_certificate( private_key: &pyo3::Bound<'_, pyo3::PyAny>, hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, + ecdsa_deterministic: Option, ) -> CryptographyResult { let sigalg = x509::sign::compute_signature_algorithm( py, @@ -1038,6 +1066,7 @@ pub(crate) fn create_x509_certificate( private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), + ecdsa_deterministic, &tbs_bytes, )?; let data = asn1::write_single(&cryptography_x509::certificate::Certificate { diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index 3ebdd44003da..f4ee46c9d16b 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -2,13 +2,15 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use cryptography_x509::common::{Asn1ReadableOrWritable, AttributeTypeValue, RawTlv}; +use asn1::SimpleAsn1Readable; +use cryptography_x509::common::{ + Asn1ReadableOrWritable, AttributeTypeValue, AttributeValue, RawTlv, +}; use cryptography_x509::extensions::{ AccessDescription, DuplicateExtensionsError, Extension, Extensions, RawExtensions, }; use cryptography_x509::name::{GeneralName, Name, NameReadable, OtherName, UnvalidatedIA5String}; -use pyo3::types::IntoPyDict; -use pyo3::types::{PyAnyMethods, PyListMethods}; +use pyo3::types::{IntoPyDict, PyAnyMethods, PyListMethods}; use crate::asn1::{oid_to_py_oid, py_oid_to_oid}; use crate::error::{CryptographyError, CryptographyResult}; @@ -60,30 +62,50 @@ pub(crate) fn encode_name_entry<'p>( let tag = attr_type .getattr(pyo3::intern!(py, "value"))? .extract::()?; - let value: pyo3::pybacked::PyBackedBytes = - if !attr_type.is(&types::ASN1_TYPE_BIT_STRING.get(py)?) { - let encoding = if attr_type.is(&types::ASN1_TYPE_BMP_STRING.get(py)?) { - "utf_16_be" - } else if attr_type.is(&types::ASN1_TYPE_UNIVERSAL_STRING.get(py)?) { - "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 raw_value = py_name_entry.getattr(pyo3::intern!(py, "value"))?; + let value = if attr_type.is(&types::ASN1_TYPE_BIT_STRING.get(py)?) { + AttributeValue::AnyString(RawTlv::new( + asn1::BitString::TAG, + ka.add(raw_value.extract()?), + )) + } else if attr_type.is(&types::ASN1_TYPE_BMP_STRING.get(py)?) { + AttributeValue::BmpString( + asn1::BMPString::new( + ka.add( + raw_value + .call_method1(pyo3::intern!(py, "encode"), ("utf_16_be",))? + .extract()?, + ), + ) + .unwrap(), + ) + } else if attr_type.is(&types::ASN1_TYPE_UNIVERSAL_STRING.get(py)?) { + AttributeValue::UniversalString( + asn1::UniversalString::new( + ka.add( + raw_value + .call_method1(pyo3::intern!(py, "encode"), ("utf_32_be",))? + .extract()?, + ), + ) + .unwrap(), + ) + } else { + AttributeValue::AnyString(RawTlv::new( + asn1::Tag::from_bytes(&[tag])?.0, + ka.add( + raw_value + .call_method1(pyo3::intern!(py, "encode"), ("utf8",))? + .extract()?, + ), + )) + }; let py_oid = py_name_entry.getattr(pyo3::intern!(py, "oid"))?; let oid = py_oid_to_oid(py_oid)?; Ok(AttributeTypeValue { type_id: oid, - value: RawTlv::new(asn1::Tag::from_bytes(&[tag])?.0, ka.add(value)), + value, }) } @@ -204,23 +226,26 @@ fn parse_name_attribute<'p>( )) })?; let py_tag = types::ASN1_TYPE_TO_ENUM.get(py)?.get_item(tag_val)?; - let py_data = match attribute.value.tag().as_u8() { - // BitString tag value - Some(3) => pyo3::types::PyBytes::new(py, attribute.value.data()).into_any(), - // 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",))? + let py_data = match attribute.value { + AttributeValue::AnyString(s) => { + if s.tag() == asn1::BitString::TAG { + pyo3::types::PyBytes::new(py, s.data()).into_any() + } else { + let parsed = std::str::from_utf8(s.data()) + .map_err(|_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?; + pyo3::types::PyString::new(py, parsed).into_any() + } + } + AttributeValue::PrintableString(printable_string) => { + pyo3::types::PyString::new(py, printable_string.as_str()).into_any() } - // UniversalString - Some(28) => { - let py_bytes = pyo3::types::PyBytes::new(py, attribute.value.data()); + AttributeValue::UniversalString(universal_string) => { + let py_bytes = pyo3::types::PyBytes::new(py, universal_string.as_utf32_be_bytes()); 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).into_any() + AttributeValue::BmpString(bmp_string) => { + let py_bytes = pyo3::types::PyBytes::new(py, bmp_string.as_utf16_be_bytes()); + py_bytes.call_method1(pyo3::intern!(py, "decode"), ("utf_16_be",))? } }; let kwargs = [(pyo3::intern!(py, "_validate"), false)].into_py_dict(py)?; @@ -530,11 +555,3 @@ pub(crate) fn datetime_now(py: pyo3::Python<'_>) -> pyo3::PyResult { - std::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap() - }; -} - -pub(crate) use cstr_from_literal; diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs index 027c178efe42..74660e6e7640 100644 --- a/src/rust/src/x509/crl.rs +++ b/src/rust/src/x509/crl.rs @@ -4,15 +4,14 @@ use std::sync::Arc; -use cryptography_x509::extensions::{Extension, IssuerAlternativeName}; -use cryptography_x509::{ - common::{self, Asn1Read}, - crl::{ - self, CertificateRevocationList as RawCertificateRevocationList, - RevokedCertificate as RawRevokedCertificate, - }, - name, oid, +use cryptography_x509::certificate::SerialNumber; +use cryptography_x509::common::{self, Asn1Read}; +use cryptography_x509::crl::{ + self, CertificateRevocationList as RawCertificateRevocationList, + RevokedCertificate as RawRevokedCertificate, }; +use cryptography_x509::extensions::{Extension, IssuerAlternativeName}; +use cryptography_x509::{name, oid}; use pyo3::types::{PyAnyMethods, PyListMethods, PySliceMethods}; use crate::asn1::{ @@ -20,7 +19,7 @@ use crate::asn1::{ }; use crate::backend::hashes::Hash; use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509::common::cstr_from_literal; +use crate::utils::cstr_from_literal; use crate::x509::{certificate, extensions, sign}; use crate::{exceptions, types, x509}; @@ -198,15 +197,11 @@ impl CertificateRevocationList { fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> pyo3::PyResult> { - let oid = self.signature_algorithm_oid(py)?; - match types::SIG_OIDS_TO_HASH.get(py)?.get_item(oid) { - Ok(v) => Ok(v), - Err(_) => Err(exceptions::UnsupportedAlgorithm::new_err(format!( - "Signature algorithm OID: {} not recognized", - self.owned.borrow_dependent().signature_algorithm.oid() - ))), - } + ) -> Result, CryptographyError> { + sign::identify_signature_hash_algorithm( + py, + &self.owned.borrow_dependent().signature_algorithm, + ) } #[getter] @@ -634,6 +629,7 @@ pub(crate) fn create_x509_crl( private_key: &pyo3::Bound<'_, pyo3::PyAny>, hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, + ecdsa_deterministic: Option, ) -> CryptographyResult { let sigalg = x509::sign::compute_signature_algorithm( py, @@ -656,7 +652,7 @@ pub(crate) fn create_x509_crl( py_revoked_cert.getattr(pyo3::intern!(py, "revocation_date_utc"))?; let serial_bytes = ka_bytes.add(py_uint_to_big_endian_bytes(py, serial_number)?); revoked_certs.push(crl::RevokedCertificate { - user_certificate: asn1::BigUint::new(serial_bytes).unwrap(), + user_certificate: SerialNumber::new(serial_bytes).unwrap(), revocation_date: x509::certificate::time_from_py(py, &py_revocation_date)?, raw_crl_entry_extensions: x509::common::encode_extensions( py, @@ -701,6 +697,7 @@ pub(crate) fn create_x509_crl( private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), + ecdsa_deterministic, &tbs_bytes, )?; let data = asn1::write_single(&crl::CertificateRevocationList { diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs index ae669d941bf5..8ab37c71ad07 100644 --- a/src/rust/src/x509/csr.rs +++ b/src/rust/src/x509/csr.rs @@ -13,7 +13,8 @@ use pyo3::types::{PyAnyMethods, PyListMethods}; use crate::asn1::{encode_der_data, oid_to_py_oid, py_oid_to_oid}; use crate::backend::keys; use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509::{certificate, common::cstr_from_literal, sign}; +use crate::utils::cstr_from_literal; +use crate::x509::{certificate, sign}; use crate::{exceptions, types, x509}; self_cell::self_cell!( @@ -221,17 +222,14 @@ impl CertificateSigningRequest { } #[getter] - fn is_signature_valid( - slf: pyo3::PyRef<'_, Self>, - py: pyo3::Python<'_>, - ) -> CryptographyResult { - let public_key = slf.public_key(py)?; + fn is_signature_valid(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let public_key = self.public_key(py)?; Ok(sign::verify_signature_with_signature_algorithm( py, public_key, - &slf.raw.borrow_dependent().signature_alg, - slf.raw.borrow_dependent().signature.as_bytes(), - &asn1::write_single(&slf.raw.borrow_dependent().csr_info)?, + &self.raw.borrow_dependent().signature_alg, + self.raw.borrow_dependent().signature.as_bytes(), + &asn1::write_single(&self.raw.borrow_dependent().csr_info)?, ) .is_ok()) } @@ -294,6 +292,7 @@ pub(crate) fn create_x509_csr( private_key: &pyo3::Bound<'_, pyo3::PyAny>, hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, + ecdsa_deterministic: Option, ) -> CryptographyResult { let sigalg = x509::sign::compute_signature_algorithm( py, @@ -383,6 +382,7 @@ pub(crate) fn create_x509_csr( private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), + ecdsa_deterministic, &tbs_bytes, )?; let data = asn1::write_single(&Csr { diff --git a/src/rust/src/x509/extensions.rs b/src/rust/src/x509/extensions.rs index 3b67dfa2ecd2..739455fa499b 100644 --- a/src/rust/src/x509/extensions.rs +++ b/src/rust/src/x509/extensions.rs @@ -2,14 +2,16 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use cryptography_x509::{common::Asn1Write, crl, extensions, oid}; +use cryptography_x509::certificate::SerialNumber; +use cryptography_x509::common::Asn1Write; +use cryptography_x509::{crl, extensions, oid}; +use pyo3::pybacked::PyBackedStr; +use pyo3::types::PyAnyMethods; use crate::asn1::{py_oid_to_oid, py_uint_to_big_endian_bytes}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, sct}; use crate::{types, x509}; -use pyo3::pybacked::PyBackedStr; -use pyo3::types::PyAnyMethods; fn encode_general_subtrees<'a>( py: pyo3::Python<'_>, @@ -58,7 +60,7 @@ pub(crate) fn encode_authority_key_identifier<'a>( let authority_cert_serial_number = if let Some(authority_cert_serial_number) = aki.authority_cert_serial_number { serial_bytes = py_uint_to_big_endian_bytes(py, authority_cert_serial_number)?; - Some(asn1::BigUint::new(&serial_bytes).unwrap()) + Some(SerialNumber::new(&serial_bytes).unwrap()) } else { None }; @@ -727,6 +729,39 @@ pub(crate) fn encode_extension( }; Ok(Some(asn1::write_single(&admission)?)) } + &oid::PRIVATE_KEY_USAGE_PERIOD_OID => { + let der = encode_private_key_usage_period(py, ext)?; + Ok(Some(der)) + } _ => Ok(None), } } + +pub(crate) fn encode_private_key_usage_period( + py: pyo3::Python<'_>, + ext: &pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { + let not_before = ext.getattr(pyo3::intern!(py, "not_before"))?; + let not_after = ext.getattr(pyo3::intern!(py, "not_after"))?; + + let not_before_value = if !not_before.is_none() { + let dt = x509::py_to_datetime(py, not_before)?; + Some(asn1::X509GeneralizedTime::new(dt)?) + } else { + None + }; + + let not_after_value = if !not_after.is_none() { + let dt = x509::py_to_datetime(py, not_after)?; + Some(asn1::X509GeneralizedTime::new(dt)?) + } else { + None + }; + + let pkup = extensions::PrivateKeyUsagePeriod { + not_before: not_before_value, + not_after: not_after_value, + }; + + Ok(asn1::write_single(&pkup)?) +} diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs index 2b3ae3df3656..c49d21aa931d 100644 --- a/src/rust/src/x509/ocsp_req.rs +++ b/src/rust/src/x509/ocsp_req.rs @@ -2,11 +2,8 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use cryptography_x509::{ - common, - ocsp_req::{self, OCSPRequest as RawOCSPRequest}, - oid, -}; +use cryptography_x509::ocsp_req::{self, OCSPRequest as RawOCSPRequest}; +use cryptography_x509::{common, oid}; use pyo3::types::{PyAnyMethods, PyListMethods}; use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes}; diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs index 25b1dc20d6d0..75ade7228c9a 100644 --- a/src/rust/src/x509/ocsp_resp.rs +++ b/src/rust/src/x509/ocsp_resp.rs @@ -4,17 +4,15 @@ use std::sync::Arc; -use cryptography_x509::ocsp_resp::SingleResponse; -use cryptography_x509::{ - common, - ocsp_resp::{self, OCSPResponse as RawOCSPResponse, SingleResponse as RawSingleResponse}, - oid, +use cryptography_x509::ocsp_resp::{ + self, OCSPResponse as RawOCSPResponse, SingleResponse, SingleResponse as RawSingleResponse, }; +use cryptography_x509::{common, oid}; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; -use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid}; +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::common::cstr_from_literal; +use crate::utils::cstr_from_literal; use crate::x509::{certificate, crl, extensions, ocsp, py_to_datetime, sct}; use crate::{exceptions, types, x509}; @@ -680,8 +678,6 @@ pub(crate) fn create_ocsp_response( .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>>; if response_status != SUCCESSFUL_RESPONSE { @@ -694,12 +690,6 @@ pub(crate) fn create_ocsp_response( } 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::Bound<'_, x509::certificate::Certificate>, @@ -753,8 +743,36 @@ pub(crate) fn create_ocsp_response( let ka_vec = cryptography_keepalive::KeepAlive::new(); let ka_bytes = cryptography_keepalive::KeepAlive::new(); + // Declare outside the if-block so the lifetimes are right. + let (py_cert, py_issuer, issuer_name_hash, issuer_key_hash, serial_number_bytes): ( + pyo3::PyRef<'_, x509::certificate::Certificate>, + pyo3::PyRef<'_, x509::certificate::Certificate>, + pyo3::pybacked::PyBackedBytes, + pyo3::pybacked::PyBackedBytes, + pyo3::pybacked::PyBackedBytes, + ); + let single_resp_resp = py_single_resp.getattr(pyo3::intern!(py, "_resp"))?; + let cert_id = if !single_resp_resp.is_none() { + (py_cert, py_issuer) = single_resp_resp.extract()?; + ocsp::certid_new(py, &ka_bytes, &py_cert, &py_issuer, &py_cert_hash_algorithm)? + } else { + let py_serial: pyo3::Bound<'_, pyo3::types::PyInt>; + (issuer_name_hash, issuer_key_hash, py_serial) = py_single_resp + .getattr(pyo3::intern!(py, "_resp_hash"))? + .extract()?; + serial_number_bytes = py_uint_to_big_endian_bytes(py, py_serial)?; + let serial_number = asn1::BigInt::new(&serial_number_bytes).unwrap(); + ocsp::certid_new_from_hash( + py, + &issuer_name_hash, + &issuer_key_hash, + serial_number, + py_cert_hash_algorithm, + )? + }; + let responses = vec![SingleResponse { - cert_id: ocsp::certid_new(py, &ka_bytes, &py_cert, &py_issuer, &py_cert_hash_algorithm)?, + cert_id, cert_status, next_update, this_update, @@ -816,6 +834,7 @@ pub(crate) fn create_ocsp_response( private_key.clone(), hash_algorithm.clone(), py.None().into_bound(py), + None, &tbs_bytes, )?; diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs index d826dda8fbae..05be30c463f6 100644 --- a/src/rust/src/x509/sign.rs +++ b/src/rust/src/x509/sign.rs @@ -285,6 +285,7 @@ pub(crate) fn sign_data<'p>( private_key: pyo3::Bound<'p, pyo3::PyAny>, hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, + ecdsa_deterministic: Option, data: &[u8], ) -> pyo3::PyResult { let key_type = identify_key_type(py, private_key.clone())?; @@ -294,7 +295,9 @@ pub(crate) fn sign_data<'p>( private_key.call_method1(pyo3::intern!(py, "sign"), (data,))? } KeyType::Ec => { - let ecdsa = types::ECDSA.get(py)?.call1((hash_algorithm,))?; + let ecdsa = types::ECDSA + .get(py)? + .call1((hash_algorithm, ecdsa_deterministic.unwrap_or(false)))?; private_key.call_method1(pyo3::intern!(py, "sign"), (data, ecdsa))? } KeyType::Rsa => { diff --git a/src/rust/src/x509/verify/extension_policy.rs b/src/rust/src/x509/verify/extension_policy.rs new file mode 100644 index 000000000000..a3e037963150 --- /dev/null +++ b/src/rust/src/x509/verify/extension_policy.rs @@ -0,0 +1,283 @@ +use std::collections::HashSet; +use std::sync::Arc; + +use cryptography_x509::extensions::Extension; +use cryptography_x509::oid::{ + AUTHORITY_INFORMATION_ACCESS_OID, AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID, + EXTENDED_KEY_USAGE_OID, KEY_USAGE_OID, NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID, + SUBJECT_KEY_IDENTIFIER_OID, +}; +use cryptography_x509_verification::ops::VerificationCertificate; +use cryptography_x509_verification::policy::{ + Criticality, ExtensionPolicy, ExtensionValidator, MaybeExtensionValidatorCallback, Policy, + PresentExtensionValidatorCallback, +}; +use cryptography_x509_verification::{ValidationError, ValidationErrorKind, ValidationResult}; +use pyo3::types::{PyAnyMethods, PyTypeMethods}; +use pyo3::{intern, PyResult}; + +use super::PyCryptoOps; +use crate::asn1::py_oid_to_oid; +use crate::types; +use crate::x509::certificate::parse_cert_ext; + +#[pyo3::pyclass( + frozen, + eq, + module = "cryptography.x509.verification", + name = "Criticality" +)] +#[derive(PartialEq, Eq, Clone)] +pub(crate) enum PyCriticality { + #[pyo3(name = "CRITICAL")] + Critical, + #[pyo3(name = "AGNOSTIC")] + Agnostic, + #[pyo3(name = "NON_CRITICAL")] + NonCritical, +} + +impl From for Criticality { + fn from(criticality: PyCriticality) -> Criticality { + match criticality { + PyCriticality::Critical => Criticality::Critical, + PyCriticality::Agnostic => Criticality::Agnostic, + PyCriticality::NonCritical => Criticality::NonCritical, + } + } +} + +#[pyo3::pyclass( + frozen, + module = "cryptography.x509.verification", + name = "ExtensionPolicy" +)] +pub(crate) struct PyExtensionPolicy { + inner_policy: ExtensionPolicy<'static, PyCryptoOps>, + already_set_oids: HashSet, +} + +impl PyExtensionPolicy { + pub(super) fn clone_inner_policy(&self) -> ExtensionPolicy<'static, PyCryptoOps> { + self.inner_policy.clone() + } + + fn new(inner_policy: ExtensionPolicy<'static, PyCryptoOps>) -> Self { + PyExtensionPolicy { + inner_policy, + already_set_oids: HashSet::new(), + } + } + + fn with_assigned_validator( + &self, + validator: ExtensionValidator<'static, PyCryptoOps>, + ) -> PyResult { + let oid = match &validator { + ExtensionValidator::NotPresent { oid } => oid, + ExtensionValidator::MaybePresent { oid, .. } => oid, + ExtensionValidator::Present { oid, .. } => oid, + } + .clone(); + if self.already_set_oids.contains(&oid) { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "ExtensionPolicy already configured for extension with OID {oid}" + ))); + } + + let mut policy = self.inner_policy.clone(); + match oid { + AUTHORITY_INFORMATION_ACCESS_OID => policy.authority_information_access = validator, + AUTHORITY_KEY_IDENTIFIER_OID => policy.authority_key_identifier = validator, + SUBJECT_KEY_IDENTIFIER_OID => policy.subject_key_identifier = validator, + KEY_USAGE_OID => policy.key_usage = validator, + SUBJECT_ALTERNATIVE_NAME_OID => policy.subject_alternative_name = validator, + BASIC_CONSTRAINTS_OID => policy.basic_constraints = validator, + NAME_CONSTRAINTS_OID => policy.name_constraints = validator, + EXTENDED_KEY_USAGE_OID => policy.extended_key_usage = validator, + _ => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "Unsupported extension OID: {oid}", + ))) + } + } + + let mut already_set_oids = self.already_set_oids.clone(); + already_set_oids.insert(oid); + Ok(PyExtensionPolicy { + inner_policy: policy, + already_set_oids, + }) + } +} + +fn oid_from_py_extension_type( + py: pyo3::Python<'_>, + extension_type: pyo3::Bound<'_, pyo3::types::PyType>, +) -> pyo3::PyResult { + if !extension_type.is_subclass(&types::EXTENSION_TYPE.get(py)?)? { + return Err(pyo3::exceptions::PyTypeError::new_err( + "extension_type must be a subclass of ExtensionType", + )); + } + + py_oid_to_oid(extension_type.getattr(intern!(py, "oid"))?) +} + +#[pyo3::pymethods] +impl PyExtensionPolicy { + #[staticmethod] + pub(crate) fn permit_all() -> Self { + PyExtensionPolicy::new(ExtensionPolicy::new_permit_all()) + } + + #[staticmethod] + pub(crate) fn webpki_defaults_ca() -> Self { + PyExtensionPolicy::new(ExtensionPolicy::new_default_webpki_ca()) + } + + #[staticmethod] + pub(crate) fn webpki_defaults_ee() -> Self { + PyExtensionPolicy::new(ExtensionPolicy::new_default_webpki_ee()) + } + + pub(crate) fn require_not_present( + &self, + py: pyo3::Python<'_>, + extension_type: pyo3::Bound<'_, pyo3::types::PyType>, + ) -> pyo3::PyResult { + let oid = oid_from_py_extension_type(py, extension_type)?; + self.with_assigned_validator(ExtensionValidator::NotPresent { oid }) + } + + #[pyo3(signature = (extension_type, criticality, validator_cb))] + pub(crate) fn may_be_present( + &self, + py: pyo3::Python<'_>, + extension_type: pyo3::Bound<'_, pyo3::types::PyType>, + criticality: PyCriticality, + validator_cb: Option, + ) -> pyo3::PyResult { + let oid = oid_from_py_extension_type(py, extension_type)?; + self.with_assigned_validator(ExtensionValidator::MaybePresent { + oid, + criticality: criticality.into(), + validator: validator_cb.map(wrap_maybe_validator_callback), + }) + } + + #[pyo3(signature = (extension_type, criticality, validator_cb))] + pub(crate) fn require_present( + &self, + py: pyo3::Python<'_>, + extension_type: pyo3::Bound<'_, pyo3::types::PyType>, + criticality: PyCriticality, + validator_cb: Option, + ) -> pyo3::PyResult { + let oid = oid_from_py_extension_type(py, extension_type)?; + self.with_assigned_validator(ExtensionValidator::Present { + oid, + criticality: criticality.into(), + validator: validator_cb.map(wrap_present_validator_callback), + }) + } +} + +fn wrap_maybe_validator_callback( + py_cb: pyo3::PyObject, +) -> MaybeExtensionValidatorCallback<'static, PyCryptoOps> { + Arc::new( + move |policy: &Policy<'_, PyCryptoOps>, + cert: &VerificationCertificate<'_, PyCryptoOps>, + ext: Option<&Extension<'_>>| { + pyo3::Python::with_gil(|py| { + invoke_py_validator_callback( + py, + &py_cb, + ( + policy.extra.clone_ref(py), + cert.extra().clone_ref(py), + make_py_extension(py, ext)?, + ), + ) + }) + }, + ) +} + +fn wrap_present_validator_callback( + py_cb: pyo3::PyObject, +) -> PresentExtensionValidatorCallback<'static, PyCryptoOps> { + Arc::new( + move |policy: &Policy<'_, PyCryptoOps>, + cert: &VerificationCertificate<'_, PyCryptoOps>, + ext: &Extension<'_>| { + pyo3::Python::with_gil(|py| { + invoke_py_validator_callback( + py, + &py_cb, + ( + policy.extra.clone_ref(py), + cert.extra().clone_ref(py), + make_py_extension(py, Some(ext))?.unwrap(), + ), + ) + }) + }, + ) +} + +fn make_py_extension<'chain, 'p>( + py: pyo3::Python<'p>, + ext: Option<&Extension<'p>>, +) -> ValidationResult<'chain, Option>, PyCryptoOps> { + Ok(match ext { + None => None, + Some(ext) => parse_cert_ext(py, ext).map_err(|e| { + ValidationError::new(ValidationErrorKind::Other(format!( + "{e} (while converting Extension to Python object)" + ))) + })?, + }) +} + +fn invoke_py_validator_callback<'py>( + py: pyo3::Python<'py>, + py_cb: &pyo3::PyObject, + args: impl pyo3::call::PyCallArgs<'py>, +) -> ValidationResult<'static, (), PyCryptoOps> { + let result = py_cb.bind(py).call1(args).map_err(|e| { + ValidationError::new(ValidationErrorKind::Other(format!( + "Python extension validator failed: {e}", + ))) + })?; + + if !result.is_none() { + let error_kind = + ValidationErrorKind::Other("Python validator must return None.".to_string()); + Err(ValidationError::new(error_kind)) + } else { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use cryptography_x509::extensions::Extension; + + #[test] + fn test_make_py_extension_fail() { + pyo3::Python::with_gil(|py| { + let invalid_extension = Extension { + // SubjectAlternativeName + extn_id: asn1::ObjectIdentifier::from_string("2.5.29.17").unwrap(), + critical: false, + extn_value: &[], + }; + let result = super::make_py_extension(py, Some(&invalid_extension)); + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!(format!("{error}").contains("(while converting Extension to Python object)")); + }) + } +} diff --git a/src/rust/src/x509/verify.rs b/src/rust/src/x509/verify/mod.rs similarity index 63% rename from src/rust/src/x509/verify.rs rename to src/rust/src/x509/verify/mod.rs index 39bfb7952a86..e08d4e4cd313 100644 --- a/src/rust/src/x509/verify.rs +++ b/src/rust/src/x509/verify/mod.rs @@ -2,32 +2,37 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use cryptography_x509::{ - certificate::Certificate, extensions::SubjectAlternativeName, oid::SUBJECT_ALTERNATIVE_NAME_OID, -}; -use cryptography_x509_verification::{ - ops::{CryptoOps, VerificationCertificate}, - policy::{Policy, Subject}, - trust_store::Store, - types::{DNSName, IPAddress}, -}; +use cryptography_x509::certificate::Certificate; +use cryptography_x509::extensions::SubjectAlternativeName; +use cryptography_x509::oid::SUBJECT_ALTERNATIVE_NAME_OID; +use cryptography_x509_verification::ops::{CryptoOps, VerificationCertificate}; +use cryptography_x509_verification::policy::{Policy, PolicyDefinition, Subject}; +use cryptography_x509_verification::trust_store::Store; +use cryptography_x509_verification::types::{DNSName, IPAddress}; use pyo3::types::{PyAnyMethods, PyListMethods}; +mod extension_policy; +mod policy; +pub(crate) use extension_policy::{PyCriticality, PyExtensionPolicy}; +pub(crate) use policy::PyPolicy; + +use super::parse_general_names; use crate::backend::keys; use crate::error::{CryptographyError, CryptographyResult}; use crate::types; +use crate::utils::cstr_from_literal; use crate::x509::certificate::Certificate as PyCertificate; -use crate::x509::common::{datetime_now, datetime_to_py, py_to_datetime}; +use crate::x509::common::{datetime_now, py_to_datetime}; use crate::x509::sign; -use super::parse_general_names; - +#[derive(Clone)] pub(crate) struct PyCryptoOps {} impl CryptoOps for PyCryptoOps { type Key = pyo3::Py; type Err = CryptographyError; type CertificateExtra = pyo3::Py; + type PolicyExtra = pyo3::Py; fn public_key(&self, cert: &Certificate<'_>) -> Result { pyo3::Python::with_gil(|py| -> Result { @@ -81,6 +86,8 @@ pub(crate) struct PolicyBuilder { time: Option, store: Option>, max_chain_depth: Option, + ca_ext_policy: Option>, + ee_ext_policy: Option>, } impl PolicyBuilder { @@ -89,6 +96,8 @@ impl PolicyBuilder { time: self.time.clone(), store: self.store.as_ref().map(|s| s.clone_ref(py)), max_chain_depth: self.max_chain_depth, + ca_ext_policy: self.ca_ext_policy.as_ref().map(|p| p.clone_ref(py)), + ee_ext_policy: self.ee_ext_policy.as_ref().map(|p| p.clone_ref(py)), } } } @@ -101,18 +110,20 @@ impl PolicyBuilder { time: None, store: None, max_chain_depth: None, + ca_ext_policy: None, + ee_ext_policy: None, } } fn time( &self, py: pyo3::Python<'_>, - new_time: pyo3::Bound<'_, pyo3::PyAny>, + time: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { policy_builder_set_once_check!(self, time, "validation time"); Ok(PolicyBuilder { - time: Some(py_to_datetime(py, new_time)?), + time: Some(py_to_datetime(py, time)?), ..self.py_clone(py) }) } @@ -120,12 +131,12 @@ impl PolicyBuilder { fn store( &self, py: pyo3::Python<'_>, - new_store: pyo3::Py, + store: pyo3::Py, ) -> CryptographyResult { policy_builder_set_once_check!(self, store, "trust store"); Ok(PolicyBuilder { - store: Some(new_store), + store: Some(store), ..self.py_clone(py) }) } @@ -133,12 +144,29 @@ impl PolicyBuilder { fn max_chain_depth( &self, py: pyo3::Python<'_>, - new_max_chain_depth: u8, + max_chain_depth: u8, ) -> CryptographyResult { policy_builder_set_once_check!(self, max_chain_depth, "maximum chain depth"); Ok(PolicyBuilder { - max_chain_depth: Some(new_max_chain_depth), + max_chain_depth: Some(max_chain_depth), + ..self.py_clone(py) + }) + } + + #[pyo3(signature = (*, ca_policy, ee_policy))] + fn extension_policies( + &self, + py: pyo3::Python<'_>, + ca_policy: pyo3::Py, + ee_policy: pyo3::Py, + ) -> CryptographyResult { + // Enough to check one of the two, since they can only be set together. + policy_builder_set_once_check!(self, ca_ext_policy, "extension policies"); + + Ok(PolicyBuilder { + ca_ext_policy: Some(ca_policy), + ee_ext_policy: Some(ee_policy), ..self.py_clone(py) }) } @@ -160,10 +188,30 @@ impl PolicyBuilder { None => datetime_now(py)?, }; - // TODO: Pass extension policies here once implemented in cryptography-x509-verification. - let policy = Policy::client(PyCryptoOps {}, time, self.max_chain_depth); + let policy_definition = OwnedPolicyDefinition::try_new(None, |_subject| { + PolicyDefinition::client( + PyCryptoOps {}, + time, + self.max_chain_depth, + self.ca_ext_policy + .as_ref() + .map(|p| p.get().clone_inner_policy()), + self.ee_ext_policy + .as_ref() + .map(|p| p.get().clone_inner_policy()), + ) + .map_err(pyo3::exceptions::PyValueError::new_err) + })?; + + let py_policy = PyPolicy { + policy_definition, + subject: py.None(), + }; - Ok(PyClientVerifier { policy, store }) + Ok(PyClientVerifier { + py_policy: pyo3::Py::new(py, py_policy)?, + store, + }) } fn build_server_verifier( @@ -188,29 +236,45 @@ impl PolicyBuilder { }; let subject_owner = build_subject_owner(py, &subject)?; - let policy = OwnedPolicy::try_new(subject_owner, |subject_owner| { - let subject = build_subject(py, subject_owner)?; - - // TODO: Pass extension policies here once implemented in cryptography-x509-verification. - Ok::, pyo3::PyErr>(Policy::server( - PyCryptoOps {}, - subject, - time, - self.max_chain_depth, - )) - })?; + let policy_definition = + OwnedPolicyDefinition::try_new(Some(subject_owner), |subject_owner| { + let subject = build_subject( + py, + subject_owner + .as_ref() + .expect("subject_owner for ServerVerifier can not be None"), + )?; + + PolicyDefinition::server( + PyCryptoOps {}, + subject, + time, + self.max_chain_depth, + self.ca_ext_policy + .as_ref() + .map(|p| p.get().clone_inner_policy()), + self.ee_ext_policy + .as_ref() + .map(|p| p.get().clone_inner_policy()), + ) + .map_err(pyo3::exceptions::PyValueError::new_err) + })?; + + let py_policy = PyPolicy { + policy_definition, + subject, + }; Ok(PyServerVerifier { - py_subject: subject, - policy, + py_policy: pyo3::Py::new(py, py_policy)?, store, }) } } -type PyCryptoPolicy<'a> = Policy<'a, PyCryptoOps>; +type PyCryptoPolicyDefinition<'a> = PolicyDefinition<'a, PyCryptoOps>; -/// This enum exists solely to provide heterogeneously typed ownership for `OwnedPolicy`. +/// This enum exists solely to provide heterogeneously typed ownership for `OwnedPolicyDefinition`. enum SubjectOwner { // TODO: Switch this to `Py` once Pyo3's `to_str()` preserves a // lifetime relationship between an a `PyString` and its borrowed `&str` @@ -222,11 +286,11 @@ enum SubjectOwner { } self_cell::self_cell!( - struct OwnedPolicy { - owner: SubjectOwner, + struct OwnedPolicyDefinition { + owner: Option, #[covariant] - dependent: PyCryptoPolicy, + dependent: PyCryptoPolicyDefinition, } ); @@ -242,36 +306,55 @@ pub(crate) struct PyVerifiedClient { chain: pyo3::Py, } +macro_rules! warn_verifier_deprecated_getter { + ($py: expr, $class_name: literal, $property_name: literal) => {{ + let warning_cls = types::DEPRECATED_IN_45.get($py)?; + let message = cstr_from_literal!(concat!( + "The `", + $property_name, + "` property on `", + $class_name, + "` is deprecated and will be removed in cryptography 46.0.", + " Access via `", + $class_name, + ".policy.", + $property_name, + "` instead." + )); + pyo3::PyErr::warn($py, &warning_cls, message, 1) + }}; +} + #[pyo3::pyclass( frozen, name = "ClientVerifier", module = "cryptography.hazmat.bindings._rust.x509" )] pub(crate) struct PyClientVerifier { - policy: PyCryptoPolicy<'static>, + #[pyo3(get, name = "policy")] + py_policy: pyo3::Py, #[pyo3(get)] store: pyo3::Py, } impl PyClientVerifier { - fn as_policy(&self) -> &Policy<'_, PyCryptoOps> { - &self.policy + fn as_policy_def(&self) -> &PyCryptoPolicyDefinition<'_> { + self.py_policy.get().policy_definition.borrow_dependent() } } #[pyo3::pymethods] impl PyClientVerifier { #[getter] - fn validation_time<'p>( - &self, - py: pyo3::Python<'p>, - ) -> pyo3::PyResult> { - datetime_to_py(py, &self.as_policy().validation_time) + fn validation_time(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + warn_verifier_deprecated_getter!(py, "ClientVerifier", "validation_time")?; + self.py_policy.get().validation_time(py) } #[getter] - fn max_chain_depth(&self) -> u8 { - self.as_policy().max_chain_depth + fn max_chain_depth(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + warn_verifier_deprecated_getter!(py, "ClientVerifier", "max_chain_depth")?; + Ok(self.py_policy.get().max_chain_depth()) } fn verify( @@ -280,7 +363,7 @@ impl PyClientVerifier { leaf: pyo3::Py, intermediates: Vec>, ) -> CryptographyResult { - let policy = self.as_policy(); + let policy = Policy::new(self.as_policy_def(), self.py_policy.clone_ref(py)); let store = self.store.get(); let intermediates = intermediates @@ -293,7 +376,7 @@ impl PyClientVerifier { let chain = cryptography_x509_verification::verify( &v, &intermediates, - policy, + &policy, store.raw.borrow_dependent(), ) .or_else(|e| handle_validation_error(py, e))?; @@ -303,22 +386,24 @@ impl PyClientVerifier { py_chain.append(c.extra())?; } - // NOTE: These `unwrap()`s cannot fail, since the underlying policy - // enforces the presence of a SAN and the well-formedness of the - // extension set. - let leaf_san = &chain[0] + // NOTE: The `unwrap()` cannot fail, since the underlying policy + // enforces the well-formedness of the extension set. + let subjects = match &chain[0] .certificate() .extensions() .ok() .unwrap() .get_extension(&SUBJECT_ALTERNATIVE_NAME_OID) - .unwrap(); - - let leaf_gns = leaf_san.value::>()?; - let py_gns = parse_general_names(py, &leaf_gns)?; + { + Some(leaf_san) => { + let leaf_gns = leaf_san.value::>()?; + Some(parse_general_names(py, &leaf_gns)?.unbind()) + } + None => None, + }; Ok(PyVerifiedClient { - subjects: Some(py_gns.into()), + subjects, chain: py_chain.unbind(), }) } @@ -330,32 +415,36 @@ impl PyClientVerifier { module = "cryptography.hazmat.bindings._rust.x509" )] pub(crate) struct PyServerVerifier { - #[pyo3(get, name = "subject")] - py_subject: pyo3::Py, - policy: OwnedPolicy, + #[pyo3(get, name = "policy")] + py_policy: pyo3::Py, #[pyo3(get)] store: pyo3::Py, } impl PyServerVerifier { - fn as_policy(&self) -> &Policy<'_, PyCryptoOps> { - self.policy.borrow_dependent() + fn as_policy_def(&self) -> &PyCryptoPolicyDefinition<'_> { + self.py_policy.get().policy_definition.borrow_dependent() } } #[pyo3::pymethods] impl PyServerVerifier { #[getter] - fn validation_time<'p>( - &self, - py: pyo3::Python<'p>, - ) -> pyo3::PyResult> { - datetime_to_py(py, &self.as_policy().validation_time) + fn subject(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + warn_verifier_deprecated_getter!(py, "ServerVerifier", "subject")?; + Ok(self.py_policy.get().subject(py)) } #[getter] - fn max_chain_depth(&self) -> u8 { - self.as_policy().max_chain_depth + fn validation_time(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + warn_verifier_deprecated_getter!(py, "ServerVerifier", "validation_time")?; + self.py_policy.get().validation_time(py) + } + + #[getter] + fn max_chain_depth(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + warn_verifier_deprecated_getter!(py, "ServerVerifier", "max_chain_depth")?; + Ok(self.py_policy.get().max_chain_depth()) } fn verify<'p>( @@ -364,7 +453,7 @@ impl PyServerVerifier { leaf: pyo3::Py, intermediates: Vec>, ) -> CryptographyResult> { - let policy = self.as_policy(); + let policy = Policy::new(self.as_policy_def(), self.py_policy.clone_ref(py)); let store = self.store.get(); let intermediates = intermediates @@ -377,7 +466,7 @@ impl PyServerVerifier { let chain = cryptography_x509_verification::verify( &v, &intermediates, - policy, + &policy, store.raw.borrow_dependent(), ) .or_else(|e| handle_validation_error(py, e))?; @@ -488,3 +577,15 @@ impl PyStore { }) } } + +#[cfg(test)] +mod tests { + use super::PyCryptoOps; + + #[test] + fn test_crypto_ops_clone() { + // Just for coverage. + // The trait is needed to be able to clone ExtensionPolicy<'_, PyCryptoOps>. + let _ = PyCryptoOps {}.clone(); + } +} diff --git a/src/rust/src/x509/verify/policy.rs b/src/rust/src/x509/verify/policy.rs new file mode 100644 index 000000000000..825046a018b1 --- /dev/null +++ b/src/rust/src/x509/verify/policy.rs @@ -0,0 +1,42 @@ +use super::OwnedPolicyDefinition; +use crate::asn1::oid_to_py_oid; +use crate::x509::datetime_to_py; + +/// Python-accessible wrapper for a cryptography_x509_verification::policy::Policy. +#[pyo3::pyclass(module = "cryptography.x509.verification", name = "Policy", frozen)] +pub(crate) struct PyPolicy { + pub(super) policy_definition: OwnedPolicyDefinition, + pub(super) subject: pyo3::PyObject, +} + +#[pyo3::pymethods] +impl PyPolicy { + #[getter] + pub(super) fn max_chain_depth(&self) -> u8 { + self.policy_definition.borrow_dependent().max_chain_depth + } + + #[getter] + pub(super) fn subject(&self, py: pyo3::Python<'_>) -> pyo3::PyObject { + self.subject.clone_ref(py) + } + + #[getter] + pub(super) fn validation_time(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let time = &self.policy_definition.borrow_dependent().validation_time; + Ok(datetime_to_py(py, time)?.as_unbound().clone_ref(py)) + } + + #[getter] + fn extended_key_usage(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let eku_oid = &self.policy_definition.borrow_dependent().extended_key_usage; + Ok(oid_to_py_oid(py, eku_oid)?.as_unbound().clone_ref(py)) + } + + #[getter] + fn minimum_rsa_modulus(&self) -> usize { + self.policy_definition + .borrow_dependent() + .minimum_rsa_modulus + } +} diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 901eec59776f..a48dc653f033 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -4,7 +4,6 @@ import itertools -import os import pytest @@ -22,10 +21,7 @@ DummyMode, ) from ...hazmat.primitives.test_rsa import rsa_key_2048 -from ...utils import ( - load_vectors_from_file, - raises_unsupported_algorithm, -) +from ...utils import raises_unsupported_algorithm # Make ruff happy since we're importing fixtures that pytest patches in as # func args @@ -54,7 +50,9 @@ def test_openssl_version_text(self): to be true for every OpenSSL-alike. """ version = backend.openssl_version_text() - assert version.startswith(("OpenSSL", "LibreSSL", "BoringSSL")) + assert version.startswith( + ("OpenSSL", "LibreSSL", "BoringSSL", "AWS-LC") + ) # Verify the correspondence between these two. And do it in a way that # ensures coverage. @@ -68,6 +66,11 @@ def test_openssl_version_text(self): if rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert version.startswith("BoringSSL") + if version.startswith("AWS-LC"): + assert rust_openssl.CRYPTOGRAPHY_IS_AWSLC + if rust_openssl.CRYPTOGRAPHY_IS_AWSLC: + assert version.startswith("AWS-LC") + def test_openssl_version_number(self): assert backend.openssl_version_number() > 0 @@ -126,7 +129,10 @@ def test_rsa_padding_supported_pkcs1v15(self): def test_rsa_padding_supported_pss(self): assert ( backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ) ) is True ) @@ -200,27 +206,6 @@ def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_2048): ) -class TestOpenSSLSerializationWithOpenSSL: - def test_very_long_pem_serialization_password(self): - password = b"x" * 1025 - - with pytest.raises(ValueError, match="Passwords longer than"): - load_vectors_from_file( - os.path.join( - "asymmetric", - "Traditional_OpenSSL_Serialization", - "key1.pem", - ), - lambda pemfile: ( - serialization.load_pem_private_key( - pemfile.read().encode(), - password, - unsafe_skip_rsa_key_validation=False, - ) - ), - ) - - class TestRSAPEMSerialization: def test_password_length_limit(self, rsa_key_2048): password = b"x" * 1024 @@ -230,35 +215,3 @@ def test_password_length_limit(self, rsa_key_2048): serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(password), ) - - -@pytest.mark.skipif( - backend._lib.Cryptography_HAS_EVP_PKEY_DHX == 1, - reason="Requires OpenSSL without EVP_PKEY_DHX", -) -@pytest.mark.supported( - only_if=lambda backend: backend.dh_supported(), - skip_message="Requires DH support", -) -class TestOpenSSLDHSerialization: - @pytest.mark.parametrize( - ("key_path", "loader_func"), - [ - ( - os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"), - serialization.load_pem_private_key, - ), - ( - os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"), - serialization.load_der_private_key, - ), - ], - ) - def test_private_load_dhx_unsupported( - self, key_path, loader_func, backend - ): - key_bytes = load_vectors_from_file( - key_path, lambda pemfile: pemfile.read(), mode="rb" - ) - with pytest.raises(ValueError): - loader_func(key_bytes, None, backend) diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index db6410d5d1e5..26afde9005a9 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -24,7 +24,10 @@ def test_ssl_ctx_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() # SSL_OP_ALL is 0 on BoringSSL - if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: + if not ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): assert b.lib.SSL_OP_ALL > 0 ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL @@ -39,7 +42,10 @@ def test_ssl_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() # SSL_OP_ALL is 0 on BoringSSL - if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: + if not ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): assert b.lib.SSL_OP_ALL > 0 ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL @@ -76,7 +82,10 @@ def test_openssl_assert_error_on_stack(self): 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 rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: + if not ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): assert b"data not multiple of block length" in error.reason_text def test_version_mismatch(self): @@ -103,5 +112,8 @@ def test_rust_internal_error(self): 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 rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: + if not ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): assert b"data not multiple of block length" in error.reason_text diff --git a/tests/hazmat/primitives/fixtures_ec.py b/tests/hazmat/primitives/fixtures_ec.py index fa671ac558c1..55f730971986 100644 --- a/tests/hazmat/primitives/fixtures_ec.py +++ b/tests/hazmat/primitives/fixtures_ec.py @@ -250,8 +250,7 @@ EC_KEY_SECP224R1 = ec.EllipticCurvePrivateNumbers( private_value=int( - "234854340492774342642505519082413233282383066880756900834047566251" - "50" + "23485434049277434264250551908241323328238306688075690083404756625150" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECP224R1(), diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py index b94ee52ad2d7..20d23270cda8 100644 --- a/tests/hazmat/primitives/test_aead.py +++ b/tests/hazmat/primitives/test_aead.py @@ -11,6 +11,7 @@ import pytest from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, @@ -734,12 +735,20 @@ def test_data_too_large(self): with pytest.raises(OverflowError): aessiv.decrypt(b"very very irrelevant", [large_data]) - def test_no_empty_encryption(self): + def test_empty(self): key = AESSIV.generate_key(256) aessiv = AESSIV(key) - with pytest.raises(ValueError): - aessiv.encrypt(b"", None) + if rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER: + assert ( + AESSIV( + b"+'\xe4)\xfbl\x02g\x8eX\x9c\xccD7\xc5\xad\xfbD\xb31\xabm!\xea2\x17'\xe6\xec\x03\xd3T" + ).encrypt(b"", [b""]) + == b"\xb2\xb25N7$\xdc\xda\xa8^\xcf\x02\x9bI\xa9\x0c" + ) + else: + with pytest.raises(ValueError): + aessiv.encrypt(b"", None) with pytest.raises(InvalidTag): aessiv.decrypt(b"", None) @@ -891,13 +900,30 @@ def test_invalid_nonce_length(self, backend): with pytest.raises(ValueError): aesgcmsiv.decrypt(nonce, pt, None) - def test_no_empty_encryption(self): + def test_empty(self): key = AESGCMSIV.generate_key(256) aesgcmsiv = AESGCMSIV(key) nonce = os.urandom(12) - with pytest.raises(ValueError): - aesgcmsiv.encrypt(nonce, b"", None) + if ( + not rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): + with pytest.raises(ValueError): + aesgcmsiv.encrypt(nonce, b"", None) + else: + # From RFC 8452 + assert ( + AESGCMSIV( + b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ).encrypt( + b"\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"", + b"", + ) + == b"\xdc \xe2\xd8?%p[\xb4\x9eC\x9e\xcaV\xde%" + ) with pytest.raises(InvalidTag): aesgcmsiv.decrypt(nonce, b"", None) @@ -919,6 +945,15 @@ def test_vectors(self, backend, subtests): ct = binascii.unhexlify(vector["ciphertext"]) tag = binascii.unhexlify(vector["tag"]) pt = binascii.unhexlify(vector.get("plaintext", b"")) + + # AWS-LC and BoringSSL only support AES-GCM-SIV with + # 128- and 256-bit keys + if len(key) == 24 and ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): + continue + aesgcmsiv = AESGCMSIV(key) computed_ct = aesgcmsiv.encrypt(nonce, pt, aad) assert computed_ct[:-16] == ct @@ -941,6 +976,15 @@ def test_vectors_invalid(self, backend, subtests): nonce = binascii.unhexlify(vector["iv"]) aad = binascii.unhexlify(vector.get("aad", b"")) ct = binascii.unhexlify(vector["ciphertext"]) + + # AWS-LC and BoringSSL only support AES-GCM-SIV with + # 128- and 256-bit keys + if len(key) == 24 and ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): + continue + aesgcmsiv = AESGCMSIV(key) with pytest.raises(InvalidTag): badkey = AESGCMSIV(AESGCMSIV.generate_key(256)) @@ -974,6 +1018,13 @@ def test_bad_key(self, backend): with pytest.raises(ValueError): AESGCMSIV(b"0" * 31) + if ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + AESGCMSIV(b"0" * 24) + def test_bad_generate_key(self, backend): with pytest.raises(TypeError): AESGCMSIV.generate_key(object()) # type:ignore[arg-type] diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 64ec26687952..1564b2a83793 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -17,12 +17,6 @@ from .utils import _load_all_params, generate_encrypt_test -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms.AES(b"\x00" * 32), modes.XTS(b"\x00" * 16) - ), - skip_message="Does not support AES XTS", -) class TestAESModeXTS: def test_xts_vectors(self, backend, subtests): # This list comprehension excludes any vector that does not have a @@ -44,9 +38,11 @@ def test_xts_vectors(self, backend, subtests): 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 - ) + alg = algorithms.AES(key) + mode = modes.XTS(tweak) + if not backend.cipher_supported(alg, mode): + pytest.skip(f"AES-{alg.key_size}-XTS not supported") + cipher = base.Cipher(alg, mode, backend) enc = cipher.encryptor() computed_ct = enc.update(pt) + enc.finalize() assert computed_ct == ct @@ -54,24 +50,38 @@ def test_xts_vectors(self, backend, subtests): 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() - with pytest.raises(ValueError): - enc.update(b"0" * 15) + def test_xts_too_short(self, backend, subtests): + for key in [ + b"thirty_two_byte_keys_are_great!!", + b"\x00" * 32 + b"\x01" * 32, + ]: + with subtests.test(): + key = b"\x00" * 32 + b"\x01" * 32 + mode = modes.XTS(b"\x00" * 16) + alg = algorithms.AES(key) + if not backend.cipher_supported(alg, mode): + pytest.skip(f"AES-{alg.key_size}-XTS not supported") + cipher = base.Cipher(alg, mode) + enc = cipher.encryptor() + with pytest.raises(ValueError): + enc.update(b"0" * 15) @pytest.mark.supported( only_if=lambda backend: not rust_openssl.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_no_duplicate_keys_encryption(self, backend, subtests): + key1 = bytes(range(16)) * 2 + key2 = key1 + key1 + mode = modes.XTS(b"\x00" * 16) + for key in [key1, key2]: + with subtests.test(): + alg = algorithms.AES(key) + cipher = base.Cipher(alg, mode) + if not backend.cipher_supported(alg, mode): + pytest.skip(f"AES-{alg.key_size}-XTS not supported") + with pytest.raises(ValueError, match="duplicated keys"): + cipher.encryptor() def test_xts_unsupported_with_aes128_aes256_classes(self): with pytest.raises(TypeError): @@ -274,7 +284,7 @@ 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(f"AES in {mode.name} mode not supported") + pytest.skip(f"AES-{key.key_size} in {mode.name} mode not supported") cipher = base.Cipher(key, mode, backend) enc = cipher.encryptor() ct = enc.update(data) + enc.finalize() diff --git a/tests/hazmat/primitives/test_argon2.py b/tests/hazmat/primitives/test_argon2.py index 7ea79d8b9359..8db9c8cf0c92 100644 --- a/tests/hazmat/primitives/test_argon2.py +++ b/tests/hazmat/primitives/test_argon2.py @@ -3,6 +3,7 @@ # for complete details. +import base64 import binascii import os @@ -158,3 +159,112 @@ def test_verify(self, backend): Argon2id( salt=b"salt" * 2, length=32, iterations=1, lanes=1, memory_cost=32 ).verify(b"password", digest) + + def test_derive_phc_encoded(self, backend): + # Test that we can generate a PHC formatted string + argon2id = Argon2id( + salt=b"0" * 8, + length=32, + iterations=2, + lanes=2, + memory_cost=64, + ) + encoded = argon2id.derive_phc_encoded(b"password") + + # Verify the general format is correct + assert encoded == ( + "$argon2id$v=19$m=64,t=2,p=2$" + "MDAwMDAwMDA$" + "jFn1qYAgmfVKFWVeUGQcVK4d8RSiQJFTS7R7VII+fRk" + ) + + def test_verify_phc_encoded(self): + # First generate a PHC string + argon2id = Argon2id( + salt=b"0" * 8, + length=32, + iterations=1, + lanes=1, + memory_cost=32, + ) + encoded = argon2id.derive_phc_encoded(b"password") + + Argon2id.verify_phc_encoded(b"password", encoded) + Argon2id( + salt=b"0" * 8, + length=32, + iterations=1, + lanes=1, + memory_cost=32, + ).verify(b"password", base64.b64decode(encoded.split("$")[-1] + "=")) + + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded(b"wrong_password", encoded) + + def test_verify_phc_vector(self): + # From https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#example + Argon2id.verify_phc_encoded( + b"hunter2", + "$argon2id$v=19$m=65536,t=2,p=1$gZiV/M1gPc22ElAH/Jh1Hw$CWOrkoo7oJBQ/iyh7uJ0LO2aLEfrHwTWllSAxT0zRno", + secret=b"pepper", + ) + + def test_verify_phc_encoded_invalid_format(self): + # Totally invalid string + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded(b"password", "not-a-valid-format") + + # Invalid algorithm + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2i$v=19$m=32,t=1,p=1$c2FsdHNhbHQ$hash" + ) + + # Invalid version + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2id$v=18$m=32,t=1,p=1$c2FsdHNhbHQ$hash" + ) + + # Missing parameters + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2id$v=19$m=32,t=1$c2FsdHNhbHQ$hash" + ) + + # Parameters in wrong order + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2id$v=19$t=1,m=32,p=1$c2FsdHNhbHQ$hash" + ) + + # Invalid memory cost + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2id$v=19$m=abc,t=1,p=1$!invalid!$hash" + ) + + # Invalid iterations + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2id$v=19$m=32,t=abc,p=1$!invalid!$hash" + ) + + # Invalid lanes + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2id$v=19$m=32,t=1,p=abc$!invalid!$hash" + ) + + # Invalid base64 in salt + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", "$argon2id$v=19$m=32,t=1,p=1$!invalid!$hash" + ) + + # Invalid base64 in hash + with pytest.raises(InvalidKey): + Argon2id.verify_phc_encoded( + b"password", + "$argon2id$v=19$m=32,t=1,p=1$c2FsdHNhbHQ$!invalid!", + ) diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index 20dcb54d1b1d..a79e422ebdaf 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -250,6 +250,15 @@ def test_update_into_buffer_too_small_gcm(self, backend): with pytest.raises(ValueError): encryptor.update_into(b"testing", buf) + def test_update_with_invalid_type(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.GCM(b"\x00" * 12), backend) + encryptor = c.encryptor() + with pytest.raises(TypeError, match="bytestring instead?"): + encryptor.update("hello") # type: ignore[arg-type] + with pytest.raises(TypeError, match="instance to a buffer"): + encryptor.update(object) # type: ignore[arg-type] + @pytest.mark.skipif( sys.platform not in {"linux", "darwin"}, reason="mmap required" diff --git a/tests/hazmat/primitives/test_concatkdf.py b/tests/hazmat/primitives/test_concatkdf.py index f0dd18828125..3a6e994be304 100644 --- a/tests/hazmat/primitives/test_concatkdf.py +++ b/tests/hazmat/primitives/test_concatkdf.py @@ -147,8 +147,7 @@ def test_derive(self, backend): ) okm = binascii.unhexlify( - b"64ce901db10d558661f10b6836a122a7" - b"605323ce2f39bf27eaaac8b34cf89f2f" + b"64ce901db10d558661f10b6836a122a7605323ce2f39bf27eaaac8b34cf89f2f" ) oinfo = binascii.unhexlify( @@ -168,8 +167,7 @@ def test_buffer_protocol(self, backend): ) okm = binascii.unhexlify( - b"64ce901db10d558661f10b6836a122a7" - b"605323ce2f39bf27eaaac8b34cf89f2f" + b"64ce901db10d558661f10b6836a122a7605323ce2f39bf27eaaac8b34cf89f2f" ) oinfo = binascii.unhexlify( @@ -189,8 +187,7 @@ def test_derive_explicit_salt(self, backend): ) okm = binascii.unhexlify( - b"64ce901db10d558661f10b6836a122a7" - b"605323ce2f39bf27eaaac8b34cf89f2f" + b"64ce901db10d558661f10b6836a122a7605323ce2f39bf27eaaac8b34cf89f2f" ) oinfo = binascii.unhexlify( @@ -212,8 +209,7 @@ def test_verify(self, backend): ) okm = binascii.unhexlify( - b"64ce901db10d558661f10b6836a122a7" - b"605323ce2f39bf27eaaac8b34cf89f2f" + b"64ce901db10d558661f10b6836a122a7605323ce2f39bf27eaaac8b34cf89f2f" ) oinfo = binascii.unhexlify( diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index c1f847a212a1..a09f77863532 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -72,6 +72,14 @@ def test_dh_parameternumbers(): dh.DHParameterNumbers(P_1536, 2, "hello") # type: ignore[arg-type] +@pytest.mark.skip_fips(reason="RHEL8 FIPS doesn't like this") +def test_dh_invalid_parameter_numbers(): + # invalid q + params = dh.DHParameterNumbers(P_1536, 2, 12345) + with pytest.raises(ValueError): + params.parameters() + + def test_dh_numbers(): params = dh.DHParameterNumbers(P_1536, 2) @@ -483,6 +491,18 @@ def test_public_key_copy(self): assert key1 == key2 + @pytest.mark.skip_fips(reason="non-FIPS parameters") + def test_private_key_copy(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhkey.pem"), + lambda pemfile: pemfile.read(), + mode="rb", + ) + key1 = serialization.load_pem_private_key(key_bytes, None, backend) + key2 = copy.copy(key1) + + assert key1 == key2 + @pytest.mark.supported( only_if=lambda backend: backend.dh_supported(), diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index fa75b8d9a000..3979de79cd72 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -409,6 +409,16 @@ def test_public_key_copy(self): assert key1 == key2 + def test_private_key_copy(self): + 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) + key2 = copy.copy(key1) + + assert key1 == key2 + @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), @@ -555,21 +565,18 @@ def test_prehashed_digest_mismatch(self, backend): only_if=lambda _: ( rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC or rust_openssl.CRYPTOGRAPHY_OPENSSL_309_OR_GREATER ), - skip_message="Requires OpenSSL 3.0.9+, LibreSSL, or BoringSSL", + skip_message="Requires OpenSSL 3.0.9+, LibreSSL, BoringSSL, or AWS-LC", ) def test_nilpotent(self): - try: - key = load_vectors_from_file( - os.path.join("asymmetric", "DSA", "custom", "nilpotent.pem"), - lambda pemfile: serialization.load_pem_private_key( - pemfile.read().encode(), password=None - ), - ) - except ValueError: - # LibreSSL simply rejects this key on load. - return + key = load_vectors_from_file( + os.path.join("asymmetric", "DSA", "custom", "nilpotent.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), password=None + ), + ) assert isinstance(key, dsa.DSAPrivateKey) with pytest.raises(ValueError): diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 2a30c6661f55..d0117d4e59b0 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -64,6 +64,14 @@ def _skip_curve_unsupported(backend, curve: ec.EllipticCurve): ) +def _skip_deterministic_ecdsa_unsupported(backend): + if not backend.ecdsa_deterministic_supported(): + pytest.skip( + f"ECDSA deterministic signing is not supported by this" + f" backend {backend}" + ) + + def _skip_exchange_algorithm_unsupported(backend, algorithm, curve): if not backend.elliptic_curve_exchange_algorithm_supported( algorithm, curve @@ -82,6 +90,7 @@ def test_get_curve_for_oid(): class DummyCurve(ec.EllipticCurve): name = "dummy-curve" key_size = 1 + group_order = 1 class DummySignatureAlgorithm(ec.EllipticCurveSignatureAlgorithm): @@ -138,7 +147,12 @@ def test_derive_point_at_infinity(backend): # BoringSSL rejects infinity points before it ever gets to us, so it # uses a more generic error message. match = ( - "infinity" if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL else "Invalid" + "infinity" + if not ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) + else "Invalid" ) with pytest.raises(ValueError, match=match): ec.derive_private_key(q, ec.SECP256R1()) @@ -330,6 +344,21 @@ def test_generate_unknown_curve(self, backend): is False ) + @pytest.mark.skip_fips( + reason="Some FIPS curves aren't supported but work anyways" + ) + @pytest.mark.parametrize("curve", ec._CURVE_TYPES.values()) + def test_generate_unsupported_curve( + self, backend, curve: ec.EllipticCurve + ): + if backend.elliptic_curve_supported(curve): + return + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ): + ec.generate_private_key(curve) + def test_unknown_signature_algoritm(self, backend): _skip_curve_unsupported(backend, ec.SECP192R1()) @@ -434,13 +463,7 @@ def test_load_invalid_public_ec_key_from_numbers(self, backend): 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 rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL - else None - ) + match = r"infinity|invalid form|Invalid key" with pytest.raises(ValueError, match=match): serialization.load_pem_public_key( textwrap.dedent( @@ -466,6 +489,18 @@ def test_load_invalid_ec_key_from_pem(self, backend): backend=backend, ) + def test_load_private_scalar_greater_than_order_pem(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + data = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec-invalid-private-scalar.pem" + ), + lambda pemfile: pemfile.read().encode(), + ) + with pytest.raises(ValueError): + serialization.load_pem_private_key(data, password=None) + def test_signatures(self, backend, subtests): vectors = itertools.chain( load_vectors_from_file( @@ -744,6 +779,17 @@ def test_public_key_copy(self, backend): assert key1 == key2 + def test_private_key_copy(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) + key2 = copy.copy(key1) + + assert key1 == key2 + class TestECSerialization: @pytest.mark.parametrize( @@ -1081,6 +1127,55 @@ def test_load_public_keys(self, key_file, curve, backend): assert isinstance(key, ec.EllipticCurvePublicKey) assert isinstance(key.curve, curve) + def test_pkcs8_inconsistent_curve(self): + # The curve can appear twice in a PKCS8 EC key, error if they're not + # consistent + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "ec-inconsistent-curve.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + serialization.load_pem_private_key(data, password=None) + + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "ec-inconsistent-curve2.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + serialization.load_pem_private_key(data, password=None) + + def test_pkcs8_consistent_curve(self): + # Like the above, but both the inner and outer curves match + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "ec-consistent-curve.pem"), + lambda f: serialization.load_pem_private_key( + f.read(), password=None + ), + mode="rb", + ) + assert isinstance(key, EllipticCurvePrivateKey) + assert isinstance(key.curve, ec.SECP256R1) + + def test_load_private_key_missing_curve(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "EC", "ec-missing-curve.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + serialization.load_pem_private_key(data, password=None) + + def test_load_private_key_invalid_version(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "ec-invalid-version.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + serialization.load_pem_private_key(data, password=None) + class TestEllipticCurvePEMPublicKeySerialization: @pytest.mark.parametrize( diff --git a/tests/hazmat/primitives/test_ed25519.py b/tests/hazmat/primitives/test_ed25519.py index 26f7d0c71b07..fddb39d0d6d5 100644 --- a/tests/hazmat/primitives/test_ed25519.py +++ b/tests/hazmat/primitives/test_ed25519.py @@ -6,6 +6,7 @@ import binascii import copy import os +import textwrap import pytest @@ -269,6 +270,15 @@ def test_round_trip_private_serialization( loaded_key = load_func(serialized, passwd, backend) assert isinstance(loaded_key, Ed25519PrivateKey) + def test_invalid_public_key_pem(self): + with pytest.raises(ValueError): + serialization.load_pem_public_key( + textwrap.dedent(""" + -----BEGIN PUBLIC KEY----- + MCswBQYDK2VwAyIA//////////////////////////////////////////// + -----END PUBLIC KEY-----""").encode() + ) + def test_buffer_protocol(self, backend): private_bytes = os.urandom(32) key = Ed25519PrivateKey.from_private_bytes(bytearray(private_bytes)) @@ -317,3 +327,19 @@ def test_public_key_copy(backend): key2 = copy.copy(key1) assert key1 == key2 + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support", +) +def test_private_key_copy(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) + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py index 6c7bdedea39d..de1e84fb7105 100644 --- a/tests/hazmat/primitives/test_ed448.py +++ b/tests/hazmat/primitives/test_ed448.py @@ -6,6 +6,7 @@ import binascii import copy import os +import textwrap import pytest @@ -245,6 +246,15 @@ def test_invalid_public_bytes(self, backend): serialization.Encoding.PEM, serialization.PublicFormat.Raw ) + def test_invalid_public_key_pem(self): + with pytest.raises(ValueError): + serialization.load_pem_public_key( + textwrap.dedent(""" + -----BEGIN PUBLIC KEY----- + MCswBQYDK2VxAyIA//////////////////////////////////////////// + -----END PUBLIC KEY-----""").encode() + ) + def test_buffer_protocol(self, backend): private_bytes = os.urandom(57) key = Ed448PrivateKey.from_private_bytes(bytearray(private_bytes)) @@ -311,3 +321,19 @@ def test_public_key_copy(backend): key2 = copy.copy(key1) assert key1 == key2 + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support", +) +def test_private_key_copy(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) + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py index df1ee4ec1131..6bacf2d4155d 100644 --- a/tests/hazmat/primitives/test_padding.py +++ b/tests/hazmat/primitives/test_padding.py @@ -132,6 +132,11 @@ def test_bytearray(self): final = unpadder.update(padded) + unpadder.finalize() assert final == unpadded + unpadded + def test_block_size_padding(self): + padder = padding.PKCS7(64).padder() + data = padder.update(b"a" * 8) + padder.finalize() + assert data == b"a" * 8 + b"\x08" * 8 + class TestANSIX923: @pytest.mark.parametrize("size", [127, 4096, -2]) @@ -172,9 +177,9 @@ def __str__(self): str(mybytes()) padder = padding.ANSIX923(128).padder() - padder.update(mybytes(b"abc")) + data = padder.update(mybytes(b"abc")) + padder.finalize() unpadder = padding.ANSIX923(128).unpadder() - unpadder.update(mybytes(padder.finalize())) + unpadder.update(mybytes(data)) assert unpadder.finalize() == b"abc" @pytest.mark.parametrize( @@ -240,3 +245,8 @@ def test_bytearray(self): unpadder = padding.ANSIX923(128).unpadder() final = unpadder.update(padded) + unpadder.finalize() assert final == unpadded + unpadded + + def test_block_size_padding(self): + padder = padding.ANSIX923(64).padder() + data = padder.update(b"a" * 8) + padder.finalize() + assert data == b"a" * 8 + b"\x00" * 7 + b"\x08" diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index 71b16b538229..96b4d59ebc55 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -3,7 +3,9 @@ # for complete details. +import contextlib import os +import typing from datetime import datetime, timezone import pytest @@ -30,6 +32,7 @@ PKCS12KeyAndCertificates, load_key_and_certificates, load_pkcs12, + serialize_java_truststore, serialize_key_and_certificates, ) @@ -87,7 +90,13 @@ def test_load_pkcs12_ec_keys(self, filename, password, backend): skip_message="Does not support RC2", ) def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): - self._test_load_pkcs12_ec_keys(filename, password, backend) + if filename == "no-password.p12": + ctx: typing.Any = pytest.warns(UserWarning) + else: + ctx = contextlib.nullcontext() + + with ctx: + self._test_load_pkcs12_ec_keys(filename, password, backend) def test_load_key_and_cert_cert_only(self, backend): cert, _ = _load_ca(backend) @@ -487,13 +496,26 @@ def test_generate_wrong_types(self, backend): exc.value ) - def test_generate_no_cert(self, backend): + @pytest.mark.parametrize( + ("encryption_algorithm", "password"), + [ + (serialization.BestAvailableEncryption(b"password"), b"password"), + ( + serialization.PrivateFormat.PKCS12.encryption_builder().build( + b"not a password" + ), + b"not a password", + ), + (serialization.NoEncryption(), None), + ], + ) + def test_generate_no_cert(self, backend, encryption_algorithm, password): _, key = _load_ca(backend) p12 = serialize_key_and_certificates( - None, key, None, None, serialization.NoEncryption() + None, key, None, None, encryption_algorithm ) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( - p12, None, backend + p12, password, backend ) assert parsed_cert is None assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) @@ -716,6 +738,193 @@ def test_generate_localkeyid(self, backend, encryption_algorithm): assert p12.count(cert.fingerprint(hashes.SHA1())) == count +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) +class TestPKCS12TrustStoreCreation: + def test_generate_valid_truststore(self, backend): + # serialize_java_truststore adds a special attribute to each + # certificate's safebag. As we cannot read this back currently, + # comparison against a pre-verified file is necessary. + cert1 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert2 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + encryption = serialization.NoEncryption() + p12 = serialize_java_truststore( + [ + PKCS12Certificate(cert1, b"cert1"), + PKCS12Certificate(cert2, None), + ], + encryption, + ) + + # The golden file was verified with: + # keytool -list -keystore java-truststore.p12 + # Ensuring both entries are listed with "trustedCertEntry" + golden_bytes = load_vectors_from_file( + os.path.join("pkcs12", "java-truststore.p12"), + lambda data: data.read(), + mode="rb", + ) + + # The last 49 bytes are the MAC digest, and will vary each call, so we + # can ignore them. + mac_digest_size = 49 + assert p12[:-mac_digest_size] == golden_bytes[:-mac_digest_size] + + def test_generate_certs_friendly_names(self, backend): + cert1 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert2 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + encryption = serialization.NoEncryption() + p12 = serialize_java_truststore( + [ + PKCS12Certificate(cert1, b"cert1"), + PKCS12Certificate(cert2, None), + ], + encryption, + ) + + p12_cert = load_pkcs12(p12, None, backend) + cas = p12_cert.additional_certs + assert cas[0].certificate == cert1 + assert cas[0].friendly_name == b"cert1" + assert cas[1].certificate == cert2 + assert cas[1].friendly_name is None + + @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, + ): + 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") + cert = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + assert isinstance(cert, x509.Certificate) + p12 = serialize_java_truststore( + [PKCS12Certificate(cert, b"name")], 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_more_certs = load_key_and_certificates( + p12, b"password", backend + ) + assert parsed_more_certs == [cert] + + def test_invalid_utf8_friendly_name(self, backend): + cert, _ = _load_ca(backend) + with pytest.raises(ValueError): + serialize_java_truststore( + [PKCS12Certificate(cert, b"\xc9")], + serialization.NoEncryption(), + ) + + def test_generate_empty_certs(self): + with pytest.raises(ValueError) as exc: + serialize_java_truststore([], serialization.NoEncryption()) + assert str(exc.value) == ("You must supply at least one cert") + + def test_generate_unsupported_encryption_type(self, backend): + cert, _ = _load_ca(backend) + with pytest.raises(ValueError) as exc: + serialize_java_truststore( + [PKCS12Certificate(cert, None)], + DummyKeySerializationEncryption(), + ) + assert str(exc.value) == "Unsupported key encryption type" + + def test_generate_wrong_types(self, backend): + cert, key = _load_ca(backend) + encryption = serialization.NoEncryption() + with pytest.raises(TypeError) as exc: + serialize_java_truststore(cert, encryption) + + with pytest.raises(TypeError) as exc: + serialize_java_truststore([cert], encryption) + assert "object cannot be converted to 'PKCS12Certificate'" in str( + exc.value + ) + + with pytest.raises(TypeError) as exc: + serialize_java_truststore( + [PKCS12Certificate(cert, None), key], + encryption, + ) + assert "object cannot be converted to 'PKCS12Certificate'" in str( + exc.value + ) + + with pytest.raises(TypeError) as exc: + serialize_java_truststore([PKCS12Certificate(cert, None)], cert) + assert str(exc.value) == ( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + with pytest.raises(TypeError) as exc: + serialize_java_truststore([key], encryption) + assert "object cannot be converted to 'PKCS12Certificate'" in str( + exc.value + ) + + @pytest.mark.skip_fips( reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." ) diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 64f14b9dc8a0..1496a23e1b2e 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -3,6 +3,7 @@ # for complete details. +import contextlib import email.parser import os import typing @@ -15,6 +16,7 @@ from cryptography.hazmat.bindings._rust import test_support from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa +from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.serialization import pkcs7 from tests.x509.test_x509 import _generate_ca_and_leaf @@ -42,6 +44,12 @@ def test_load_invalid_pem_pkcs7(self, backend): with pytest.raises(ValueError): pkcs7.load_pem_pkcs7_certificates(b"nonsense") + with pytest.raises(ValueError): + pkcs7.load_pem_pkcs7_certificates(b""" +-----BEGIN CERTIFICATE----- +-----END CERTIFICATE----- + """) + def test_not_bytes_der(self, backend): with pytest.raises(TypeError): pkcs7.load_der_pkcs7_certificates(38) # type: ignore[arg-type] @@ -69,11 +77,19 @@ def test_load_pkcs7_pem(self, backend): ], ) 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", - ) + if filepath.endswith("p7b"): + ctx: typing.Any = pytest.warns(UserWarning) + else: + ctx = contextlib.nullcontext() + + with ctx: + certs = load_vectors_from_file( + filepath, + lambda derfile: pkcs7.load_der_pkcs7_certificates( + derfile.read() + ), + mode="rb", + ) assert len(certs) == 2 assert certs[0].subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME @@ -899,6 +915,21 @@ def test_not_a_cert(self, backend): b"notacert", # type: ignore[arg-type] ) + def test_set_content_encryption_algorithm_twice(self, backend): + builder = pkcs7.PKCS7EnvelopeBuilder() + builder = builder.set_content_encryption_algorithm(algorithms.AES128) + with pytest.raises(ValueError): + builder.set_content_encryption_algorithm(algorithms.AES128) + + def test_invalid_content_encryption_algorithm(self, backend): + class InvalidAlgorithm: + pass + + with pytest.raises(TypeError): + pkcs7.PKCS7EnvelopeBuilder().set_content_encryption_algorithm( + InvalidAlgorithm, # type: ignore[arg-type] + ) + def test_encrypt_invalid_options(self, backend): cert, _ = _load_rsa_cert_key() builder = ( @@ -1148,6 +1179,25 @@ def test_pkcs7_decrypt_der( ) assert decrypted == data.replace(b"\n", b"\r\n") + def test_pkcs7_decrypt_aes_256_cbc_encrypted_content( + self, backend, data, certificate, private_key + ): + # Encryption + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .set_content_encryption_algorithm(algorithms.AES256) + .add_recipient(certificate) + ) + enveloped = builder.encrypt(serialization.Encoding.PEM, []) + + # Test decryption: new lines are canonicalized to '\r\n' when + # encryption has no Binary option + decrypted = pkcs7.pkcs7_decrypt_pem( + enveloped, certificate, private_key, [] + ) + assert decrypted == data.replace(b"\n", b"\r\n") + @pytest.mark.parametrize( "header", [ @@ -1318,7 +1368,7 @@ def test_smime_decrypt_unsupported_content_encryption_algorithm( self, backend, data, certificate, private_key ): enveloped = load_vectors_from_file( - os.path.join("pkcs7", "enveloped-aes-256-cbc.pem"), + os.path.join("pkcs7", "enveloped-triple-des.pem"), loader=lambda pemfile: pemfile.read(), mode="rb", ) diff --git a/tests/hazmat/primitives/test_poly1305.py b/tests/hazmat/primitives/test_poly1305.py index 1a579bbd34bc..a1c62a15e544 100644 --- a/tests/hazmat/primitives/test_poly1305.py +++ b/tests/hazmat/primitives/test_poly1305.py @@ -133,8 +133,7 @@ def test_invalid_key_length(self, backend): def test_buffer_protocol(self, backend): key = binascii.unhexlify( - b"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cb" - b"c207075c0" + b"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0" ) msg = binascii.unhexlify( b"2754776173206272696c6c69672c20616e642074686520736c69746" @@ -143,13 +142,13 @@ def test_buffer_protocol(self, backend): b"52074686520626f726f676f7665732c0a416e6420746865206d6f6d" b"65207261746873206f757467726162652e" ) - key = bytearray(key) - poly = Poly1305(key) + buffer_key = bytearray(key) + poly = Poly1305(buffer_key) poly.update(bytearray(msg)) assert poly.finalize() == binascii.unhexlify( b"4541669a7eaaee61e708dc7cbcc5eb62" ) - assert Poly1305.generate_tag(key, msg) == binascii.unhexlify( + assert Poly1305.generate_tag(buffer_key, msg) == binascii.unhexlify( b"4541669a7eaaee61e708dc7cbcc5eb62" ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 92cf9da1ba92..17c8c7c1f543 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -10,12 +10,7 @@ import pytest -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.exceptions import InvalidSignature, _Reasons from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.hazmat.primitives.asymmetric import utils as asym_utils @@ -251,10 +246,6 @@ 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 rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL, - skip_message="Does not support RSA PSS loading", - ) @pytest.mark.parametrize( "path", [ @@ -302,24 +293,6 @@ def test_load_pss_pub_keys_strips_constraints(self, backend): b"badsig", b"whatever", padding.PKCS1v15(), hashes.SHA256() ) - @pytest.mark.supported( - only_if=lambda backend: rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL, - 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( @@ -530,11 +503,20 @@ def test_pss_signing(self, subtests, backend): hashes.SHA1(), ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH, + ) + ), + skip_message="Does not support PSS with these parameters.", + ) @pytest.mark.parametrize( "hash_alg", [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()], ) - def test_pss_signing_sha2(self, rsa_key_2048, hash_alg, backend): + def test_pss_sha2_max_length(self, rsa_key_2048, hash_alg, backend): _skip_pss_hash_algorithm_unsupported(backend, hash_alg) private_key = rsa_key_2048 public_key = private_key.public_key() @@ -1067,7 +1049,7 @@ def test_pss_verification(self, subtests, backend): salt_length=padding.PSS.AUTO, ) ), - skip_message="Does not support PSS.", + skip_message="Does not support PSS with these parameters.", ) def test_pss_verify_auto_salt_length( self, rsa_key_2048: rsa.RSAPrivateKey, backend @@ -1207,7 +1189,7 @@ def test_invalid_pss_signature_recover( public_key = private_key.public_key() pss_padding = padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH, + salt_length=padding.PSS.DIGEST_LENGTH, ) signature = private_key.sign(b"sign me", pss_padding, hashes.SHA256()) @@ -2400,6 +2382,10 @@ def test_invalid_recover_prime_factors(self): rsa.rsa_recover_prime_factors(34, 3, 7) with pytest.raises(ValueError): rsa.rsa_recover_prime_factors(629, 17, 20) + with pytest.raises(ValueError): + rsa.rsa_recover_prime_factors(21, 1, 1) + with pytest.raises(ValueError): + rsa.rsa_recover_prime_factors(21, -1, -1) class TestRSAPrivateKeySerialization: @@ -2781,3 +2767,9 @@ def test_public_key_copy(self, rsa_key_2048: rsa.RSAPrivateKey): key2 = copy.copy(key1) assert key1 == key2 + + def test_private_key_copy(self, rsa_key_2048: rsa.RSAPrivateKey): + key1 = rsa_key_2048 + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 32e0ded0ead5..119fb4d49c6e 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -10,6 +10,8 @@ import pytest +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.decrepit.ciphers.algorithms import _DES, RC2 from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, @@ -19,7 +21,8 @@ x448, x25519, ) -from cryptography.hazmat.primitives.hashes import SHA1 +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.hazmat.primitives.hashes import MD5, SHA1 from cryptography.hazmat.primitives.serialization import ( BestAvailableEncryption, Encoding, @@ -405,6 +408,185 @@ def test_wrong_parameters_format(self, backend): with pytest.raises(ValueError): load_der_parameters(param_data, backend) + def test_load_pkcs8_private_key_invalid_version(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "invalid-version.der"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_der_private_key(data, password=None) + + def test_load_pkcs8_private_key_unknown_oid(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unknown-oid.der"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_der_private_key(data, password=None) + + @pytest.mark.skip_fips(reason="3DES is not FIPS") + def test_load_pkcs8_private_key_3des_encryption(self): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "enc-rsa-3des.pem"), + lambda f: load_pem_private_key(f.read(), password=b"password"), + mode="rb", + ) + assert isinstance(key, rsa.RSAPrivateKey) + assert key.key_size == 2048 + + def test_load_pkcs8_private_key_unknown_encryption_algorithm(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "enc-unknown-algorithm.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + + def test_load_pkcs8_private_key_unknown_pbkdf2_prf(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "enc-unknown-pbkdf2-prf.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + + def test_load_pkcs8_private_key_unknown_kdf(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "enc-unknown-kdf.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + + @pytest.mark.skip_fips(reason="3DES unsupported in FIPS") + def test_load_pkcs8_pbes_long_salt(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "rsa-pbe-3des-long-salt.pem"), + lambda f: f.read(), + mode="rb", + ) + key = load_pem_private_key( + data, password=b"password", unsafe_skip_rsa_key_validation=True + ) + assert isinstance(key, rsa.RSAPrivateKey) + assert key.key_size == 4096 + assert key.private_numbers().public_numbers.e == 65537 + + @pytest.mark.parametrize( + "filename", + [ + "rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha224.pem", + "rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha384.pem", + "rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha512.pem", + ], + ) + @pytest.mark.skip_fips(reason="3DES unsupported in FIPS") + def test_load_pkscs8_pbkdf_prf(self, filename: str): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", filename), + lambda f: load_pem_private_key(f.read(), password=b"PolarSSLTest"), + mode="rb", + ) + assert isinstance(key, rsa.RSAPrivateKey) + assert key.key_size == 2048 + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + RC2(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ) + and not ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ), + skip_message="Does not support RC2 CBC", + ) + def test_load_pkcs8_40_bit_rc2(self): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "rsa-40bitrc2.pem"), + lambda f: load_pem_private_key(f.read(), password=b"baz"), + mode="rb", + ) + assert isinstance(key, rsa.RSAPrivateKey) + assert key.key_size == 1024 + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + RC2(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ) + and not ( + rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ), + skip_message="Does not support RC2 CBC", + ) + def test_load_pkcs8_rc2_cbc(self): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "rsa-rc2-cbc.pem"), + lambda f: load_pem_private_key( + f.read(), password=b"Red Hat Enterprise Linux 7.4" + ), + mode="rb", + ) + assert isinstance(key, rsa.RSAPrivateKey) + assert key.key_size == 2048 + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + RC2(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support RC2 CBC", + ) + def test_load_pkcs8_rc2_cbc_effective_key_length(self): + data = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "rsa-rc2-cbc-effective-key-length.pem" + ), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + + def test_load_pkcs8_aes_192_cbc(self): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "rsa-aes-192-cbc.pem"), + lambda f: load_pem_private_key(f.read(), password=b"PolarSSLTest"), + mode="rb", + ) + assert isinstance(key, rsa.RSAPrivateKey) + assert key.key_size == 2048 + + @pytest.mark.supported( + only_if=lambda backend: backend.scrypt_supported(), + skip_message="Scrypt required", + ) + def test_load_pkcs8_scrypt(self): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "ed25519-scrypt.pem"), + lambda f: load_pem_private_key(f.read(), password=b"hunter42"), + mode="rb", + ) + assert isinstance(key, ed25519.Ed25519PrivateKey) + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(MD5()) + and backend.cipher_supported(_DES(), modes.CBC(b"\x00" * 8)), + skip_message="Does not support DES MD5", + ) + def test_load_pkcs8_pbe_with_md5_and_des_cbc(self): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "rsa-pbewithmd5anddescbc.pem"), + lambda f: load_pem_private_key(f.read(), password=b"hunter2"), + mode="rb", + ) + assert isinstance(key, rsa.RSAPrivateKey) + assert key.key_size == 2048 + class TestPEMSerialization: @pytest.mark.parametrize( @@ -1071,6 +1253,93 @@ def test_load_bad_encryption_oid_key(self, key_file, password, backend): ), ) + def test_encrypted_pkcs8_non_utf_password(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "enc-rsa-pkcs8.pem"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"\xff") + + def test_rsa_private_key_invalid_version(self): + data = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "rsa-wrong-version.pem", + ), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=None) + + def test_dsa_private_key_invalid_version(self): + data = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "dsa-wrong-version.pem", + ), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=None) + + def test_pem_encryption_missing_dek_info(self): + data = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "key1-no-dek-info.pem", + ), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + + def test_pem_encryption_malformed_dek_info(self): + data = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "key1-malformed-dek-info.pem", + ), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + + def test_pem_encryption_malformed_iv(self): + data = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "key1-malformed-iv.pem", + ), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + + def test_pem_encryption_short_iv(self): + data = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "key1-short-iv.pem", + ), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + class TestKeySerializationEncryptionTypes: def test_non_bytes_password(self): diff --git a/tests/hazmat/primitives/test_ssh.py b/tests/hazmat/primitives/test_ssh.py index 82f398305e21..41a6d3f61d06 100644 --- a/tests/hazmat/primitives/test_ssh.py +++ b/tests/hazmat/primitives/test_ssh.py @@ -10,7 +10,12 @@ import pytest from cryptography import utils -from cryptography.exceptions import InvalidSignature, InvalidTag +from cryptography.exceptions import ( + InvalidSignature, + InvalidTag, + UnsupportedAlgorithm, +) +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, @@ -32,6 +37,7 @@ load_ssh_public_identity, load_ssh_public_key, ssh, + ssh_key_fingerprint, ) from ...doubles import DummyKeySerializationEncryption @@ -255,6 +261,26 @@ def test_load_ssh_private_key(self, key_file, backend): maxline = max(map(len, priv_data2.split(b"\n"))) assert maxline < 80 + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires Ed25519 support", + ) + @pytest.mark.parametrize( + "key_file", + [ + "sk-ecdsa-nopsw.key", + "sk-ed25519-nopsw.key", + ], + ) + def test_load_unsupported_ssh_private_key(self, key_file): + data = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", key_file), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(UnsupportedAlgorithm): + load_ssh_private_key(data, None) + @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires Ed25519 support", @@ -351,7 +377,7 @@ def test_bcrypt_encryption(self, backend): ) assert pub1 == pub2 - with pytest.raises(ValueError): + with pytest.raises(TypeError): decoded_key = load_ssh_private_key(encdata, None, backend) with pytest.raises(ValueError): decoded_key = load_ssh_private_key(encdata, b"wrong", backend) @@ -587,6 +613,15 @@ def test_ssh_errors_bad_secrets(self, backend): with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) + def test_ssh_errors_unencrypted_with_password(self): + data = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key"), + lambda f: f.read(), + mode="rb", + ) + with pytest.raises(TypeError): + load_ssh_private_key(data, password=b"password") + @pytest.mark.supported( only_if=lambda backend: backend.elliptic_curve_supported( ec.SECP192R1() @@ -835,6 +870,16 @@ def test_load_ssh_public_key_rsa(self, backend): assert numbers == expected + def test_unsafe_skip_rsa_key_validation(self): + key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key"), + lambda f: load_ssh_private_key( + f.read(), password=None, unsafe_skip_rsa_key_validation=True + ), + mode="rb", + ) + assert isinstance(key, rsa.RSAPrivateKey) + class TestDSSSSHSerialization: def test_load_ssh_public_key_dss_too_short(self, backend): @@ -1240,7 +1285,7 @@ def test_not_bytes(self): "these aren't bytes" # type:ignore[arg-type] ) - def test_load_ssh_public_key(self, backend): + def test_load_ssh_public_key(self): # This test will be removed when we implement load_ssh_public_key # in terms of load_ssh_public_identity. Needed for coverage now. pub_data = load_vectors_from_file( @@ -1309,11 +1354,11 @@ def test_invalid_encodings(self, filename): with pytest.raises(ValueError): load_ssh_public_identity(data) - def test_invalid_line_format(self, backend): + def test_invalid_line_format(self): with pytest.raises(ValueError): load_ssh_public_identity(b"whaaaaaaaaaaat") - def test_invalid_b64(self, backend): + def test_invalid_b64(self): with pytest.raises(ValueError): load_ssh_public_identity(b"ssh-rsa-cert-v01@openssh.com invalid") @@ -1336,7 +1381,7 @@ def test_inner_outer_key_type_mismatch(self): b"+FxCje1GpAAAAIGf9opl4YoC5XcO92WMFEwUdE3jUQtBg3GRQlXBqFcoL" ) - def test_loads_a_cert_empty_principals(self, backend): + def test_loads_a_cert_empty_principals(self): data = load_vectors_from_file( os.path.join( "asymmetric", @@ -1353,7 +1398,7 @@ def test_loads_a_cert_empty_principals(self, backend): assert cert.extensions == {} assert cert.critical_options == {} - def test_public_bytes(self, backend): + def test_public_bytes(self): data = load_vectors_from_file( os.path.join( "asymmetric", @@ -1825,3 +1870,105 @@ def test_load_application(self): def test_load_application_valueerror(self): with pytest.raises(ValueError): ssh.load_application(self.ssh_str("hss:test")) + + +class TestSSHKeyFingerprint: + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Does not support MD5", + ) + def test_ssh_key_fingerprint_rsa_md5(self): + ssh_key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"), + lambda f: f.read(), + mode="rb", + ) + public_key = load_ssh_public_key(ssh_key) + fingerprint = ssh_key_fingerprint(public_key, hashes.MD5()) + assert fingerprint == b"\x10G\xc2es\xd6QIH\x0b\x81\x1f6\x04{R" + + def test_ssh_key_fingerprint_rsa_sha256(self): + ssh_key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"), + lambda f: f.read(), + mode="rb", + ) + public_key = load_ssh_public_key(ssh_key) + fingerprint = ssh_key_fingerprint(public_key, hashes.SHA256()) + assert fingerprint == ( + b"\x80\xc0u\xcaV$\xfc\xeb\x04\xb1\x83]\x9a\x1e\xa1\x8d\x17" + b"\xc4d\xa23\xbek\xa4\xe9 \x92j\x89\xe6\xe8%" + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()) + and backend.ed25519_supported(), + skip_message="Does not support MD5 or Ed25519", + ) + def test_ssh_key_fingerprint_ed25519_md5(self): + ssh_key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "ed25519-nopsw.key.pub"), + lambda f: f.read(), + mode="rb", + ) + public_key = load_ssh_public_key(ssh_key) + fingerprint = ssh_key_fingerprint(public_key, hashes.MD5()) + assert fingerprint == b"\xe5R=\x01\x9e\xa0\xc1\xe9\x8c?L|\xc5\x94W\x85" + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Ed25519 not supported", + ) + def test_ssh_key_fingerprint_ed25519_sha256(self): + ssh_key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "ed25519-nopsw.key.pub"), + lambda f: f.read(), + mode="rb", + ) + public_key = load_ssh_public_key(ssh_key) + fingerprint = ssh_key_fingerprint(public_key, hashes.SHA256()) + assert fingerprint == ( + b'\x92z-\xb4\xaf\xf4,\x15\xa5\xc6\xf36p83\xcc"]CJi\x16V?\x879' + b"GZVS8\xb9" + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Does not support MD5", + ) + def test_ssh_key_fingerprint_ecdsa_md5(self): + ssh_key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "ecdsa-nopsw.key.pub"), + lambda f: f.read(), + mode="rb", + ) + public_key = load_ssh_public_key(ssh_key) + fingerprint = ssh_key_fingerprint(public_key, hashes.MD5()) + assert fingerprint == b"\re\xf2-\xfaGq\x8c^\x16\xb05+\x06\x1b7" + + def test_ssh_key_fingerprint_ecdsa_sha256(self): + ssh_key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "ecdsa-nopsw.key.pub"), + lambda f: f.read(), + mode="rb", + ) + public_key = load_ssh_public_key(ssh_key) + fingerprint = ssh_key_fingerprint(public_key, hashes.SHA256()) + assert fingerprint == ( + b"[\xa5\xab\xe9\xdf\r\xe5\x1er\xd6\xbc\xd9\x97\xc2\xf4\xdc" + b"\xd4\xe0\xaf\x17\xc6\x91wIJ\xf5" + ) + + def test_ssh_key_fingerprint_unsupported_hash(self): + ssh_key = load_vectors_from_file( + os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"), + lambda f: f.read(), + mode="rb", + ) + public_key = load_ssh_public_key(ssh_key) + with pytest.raises(TypeError): + ssh_key_fingerprint(public_key, hashes.SHA1()) # type: ignore[arg-type] + + def test_ssh_key_fingerprint_unsupported_key(self): + with pytest.raises(ValueError): + ssh_key_fingerprint(object(), hashes.SHA256()) # type: ignore[arg-type] diff --git a/tests/hazmat/primitives/test_x25519.py b/tests/hazmat/primitives/test_x25519.py index b68286e1e5f0..6597c2ae9c32 100644 --- a/tests/hazmat/primitives/test_x25519.py +++ b/tests/hazmat/primitives/test_x25519.py @@ -6,6 +6,7 @@ import binascii import copy import os +import textwrap import pytest @@ -62,12 +63,10 @@ def test_rfc7748(self, vector, backend): def test_rfc7748_1000_iteration(self, backend): old_private = private = public = binascii.unhexlify( - b"090000000000000000000000000000000000000000000000000000000000" - b"0000" + b"0900000000000000000000000000000000000000000000000000000000000000" ) shared_key = binascii.unhexlify( - b"684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d9953" - b"2c51" + b"684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51" ) private_key = X25519PrivateKey.from_private_bytes(private) public_key = X25519PublicKey.from_public_bytes(public) @@ -322,6 +321,15 @@ def test_round_trip_private_serialization( loaded_key = load_func(serialized, passwd, backend) assert isinstance(loaded_key, X25519PrivateKey) + def test_invalid_public_key_pem(self): + with pytest.raises(ValueError): + serialization.load_pem_public_key( + textwrap.dedent(""" + -----BEGIN PUBLIC KEY----- + MCswBQYDK2VuAyIA//////////////////////////////////////////// + -----END PUBLIC KEY-----""").encode() + ) + def test_buffer_protocol(self, backend): private_bytes = bytearray(os.urandom(32)) key = X25519PrivateKey.from_private_bytes(private_bytes) @@ -369,3 +377,19 @@ def test_public_key_copy(backend): key2 = copy.copy(key1) assert key1 == key2 + + +@pytest.mark.supported( + only_if=lambda backend: backend.x25519_supported(), + skip_message="Requires OpenSSL with X25519 support", +) +def test_private_key_copy(backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb", + ) + key1 = serialization.load_der_private_key(key_bytes, None) + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_x448.py b/tests/hazmat/primitives/test_x448.py index 46f4856c180d..75b9dd0b48c8 100644 --- a/tests/hazmat/primitives/test_x448.py +++ b/tests/hazmat/primitives/test_x448.py @@ -6,6 +6,7 @@ import binascii import copy import os +import textwrap import pytest @@ -247,6 +248,15 @@ def test_invalid_public_bytes(self, backend): serialization.Encoding.PEM, serialization.PublicFormat.Raw ) + def test_invalid_public_key_pem(self): + with pytest.raises(ValueError): + serialization.load_pem_public_key( + textwrap.dedent(""" + -----BEGIN PUBLIC KEY----- + MCswBQYDK2VvAyIA//////////////////////////////////////////// + -----END PUBLIC KEY-----""").encode() + ) + def test_buffer_protocol(self, backend): private_bytes = binascii.unhexlify( b"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d" @@ -297,3 +307,19 @@ def test_public_key_copy(backend): key2 = copy.copy(key1) assert key1 == key2 + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support", +) +def test_private_key_copy(backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb", + ) + key1 = serialization.load_der_private_key(key_bytes, None) + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_xofhash.py b/tests/hazmat/primitives/test_xofhash.py new file mode 100644 index 000000000000..2c7a68022803 --- /dev/null +++ b/tests/hazmat/primitives/test_xofhash.py @@ -0,0 +1,132 @@ +# 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 binascii +import os +import random +import sys + +import pytest + +from cryptography.exceptions import AlreadyFinalized, UnsupportedAlgorithm +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import hashes + +from ...utils import load_nist_vectors +from .utils import _load_all_params + + +@pytest.mark.supported( + only_if=lambda backend: ( + not rust_openssl.CRYPTOGRAPHY_OPENSSL_330_OR_GREATER + or rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ), + skip_message="Requires backend without XOF support", +) +def test_unsupported_boring_libre(backend): + with pytest.raises(UnsupportedAlgorithm): + hashes.XOFHash(hashes.SHAKE128(digest_size=32)) + + +@pytest.mark.supported( + only_if=lambda backend: rust_openssl.CRYPTOGRAPHY_OPENSSL_330_OR_GREATER, + skip_message="Requires backend with XOF support", +) +class TestXOFHash: + def test_hash_reject_unicode(self, backend): + m = hashes.XOFHash(hashes.SHAKE128(sys.maxsize)) + with pytest.raises(TypeError): + m.update("\u00fc") # type: ignore[arg-type] + + def test_incorrect_hash_algorithm_type(self, backend): + with pytest.raises(TypeError): + # Instance required + hashes.XOFHash(hashes.SHAKE128) # type: ignore[arg-type] + + with pytest.raises(TypeError): + hashes.XOFHash(hashes.SHA256()) # type: ignore[arg-type] + + def test_raises_update_after_squeeze(self, backend): + h = hashes.XOFHash(hashes.SHAKE128(digest_size=256)) + h.update(b"foo") + h.squeeze(5) + + with pytest.raises(AlreadyFinalized): + h.update(b"bar") + + def test_copy(self, backend): + h = hashes.XOFHash(hashes.SHAKE128(digest_size=256)) + h.update(b"foo") + h.update(b"bar") + h2 = h.copy() + assert h2.squeeze(10) == h.squeeze(10) + + def test_exhaust_bytes(self, backend): + h = hashes.XOFHash(hashes.SHAKE128(digest_size=256)) + h.update(b"foo") + with pytest.raises(ValueError): + h.squeeze(257) + h.squeeze(200) + h.squeeze(56) + with pytest.raises(ValueError): + h.squeeze(1) + + +@pytest.mark.supported( + only_if=lambda backend: rust_openssl.CRYPTOGRAPHY_OPENSSL_330_OR_GREATER, + skip_message="Requires backend with XOF support", +) +class TestXOFSHAKE128: + def test_shake128_variable(self, backend, subtests): + vectors = _load_all_params( + os.path.join("hashes", "SHAKE"), + ["SHAKE128VariableOut.rsp"], + load_nist_vectors, + ) + 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.XOFHash(shake) + m.update(msg) + remaining = output_length + data = b"" + stride = random.randint(1, 128) + while remaining > 0: + stride = remaining if remaining < stride else stride + data += m.squeeze(stride) + remaining -= stride + assert data == binascii.unhexlify(vector["output"]) + + +@pytest.mark.supported( + only_if=lambda backend: rust_openssl.CRYPTOGRAPHY_OPENSSL_330_OR_GREATER, + skip_message="Requires backend with XOF support", +) +class TestXOFSHAKE256: + 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.XOFHash(shake) + m.update(msg) + remaining = output_length + data = b"" + stride = random.randint(1, 128) + while remaining > 0: + stride = remaining if remaining < stride else stride + data += m.squeeze(stride) + remaining -= stride + assert data == binascii.unhexlify(vector["output"]) diff --git a/tests/test_utils.py b/tests/test_utils.py index 5e5f506f82b1..6221a00a3f84 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2732,13 +2732,11 @@ def test_load_fips_ecdsa_key_pair_vectors(): 16, ), "x": int( - "1c7475da9a161e4b3f7d6b086494063543a979e34b8d7ac4" - "4204d47bf9f", + "1c7475da9a161e4b3f7d6b086494063543a979e34b8d7ac44204d47bf9f", 16, ), "y": int( - "131cbd433f112871cc175943991b6a1350bf0cdd57ed8c83" - "1a2a7710c92", + "131cbd433f112871cc175943991b6a1350bf0cdd57ed8c831a2a7710c92", 16, ), }, @@ -2749,13 +2747,11 @@ def test_load_fips_ecdsa_key_pair_vectors(): 16, ), "x": int( - "d37500a0391d98d3070d493e2b392a2c79dc736c097ed24b" - "7dd5ddec44", + "d37500a0391d98d3070d493e2b392a2c79dc736c097ed24b7dd5ddec44", 16, ), "y": int( - "1d996cc79f37d8dba143d4a8ad9a8a60ed7ea760aae1ddba" - "34d883f65d9", + "1d996cc79f37d8dba143d4a8ad9a8a60ed7ea760aae1ddba34d883f65d9", 16, ), }, @@ -3750,35 +3746,29 @@ def test_load_kasvs_ecdh_vectors(): "COUNT": 0, "CAVS": { "d": int( - "e53a88af7cf8ce6bf13c8b9ad191494e37a6acc1368c71f4" - "306e39e5", + "e53a88af7cf8ce6bf13c8b9ad191494e37a6acc1368c71f4306e39e5", 16, ), "x": int( - "3a24217c4b957fea922eec9d9ac52d5cb4b3fcd95efde1e4" - "fa0dd6e2", + "3a24217c4b957fea922eec9d9ac52d5cb4b3fcd95efde1e4fa0dd6e2", 16, ), "y": int( - "775b94025a808eb6f4af14ea4b57dca576c35373c6dc198b" - "15b981df", + "775b94025a808eb6f4af14ea4b57dca576c35373c6dc198b15b981df", 16, ), }, "IUT": { "d": int( - "09f51e302c6a0fe6ff48f34c208c6af91e70f65f88102e6f" - "cab9af4a", + "09f51e302c6a0fe6ff48f34c208c6af91e70f65f88102e6fcab9af4a", 16, ), "x": int( - "c5d5706ccd7424c74fd616e699865af96e56f39adea6aa05" - "9e5092b5", + "c5d5706ccd7424c74fd616e699865af96e56f39adea6aa059e5092b5", 16, ), "y": int( - "f0729077bb602404d56d2f7e2ba5bb2f383df4a542556788" - "1ff0165d", + "f0729077bb602404d56d2f7e2ba5bb2f383df4a5425567881ff0165d", 16, ), }, @@ -3984,35 +3974,29 @@ def test_load_kasvs_ecdh_kdf_vectors(): "COUNT": 50, "CAVS": { "d": int( - "540904b67b3716823dd621ed72ad3dbc615887b4f56f910b" - "78a57199", + "540904b67b3716823dd621ed72ad3dbc615887b4f56f910b78a57199", 16, ), "x": int( - "28e5f3a72d8f6b8499dd1bcdfceafcecec68a0d715789bcf" - "4b55fe15", + "28e5f3a72d8f6b8499dd1bcdfceafcecec68a0d715789bcf4b55fe15", 16, ), "y": int( - "8c8006a7da7c1a19f5328d7e865522b0c0dfb9a29b2c46dc" - "96590d2a", + "8c8006a7da7c1a19f5328d7e865522b0c0dfb9a29b2c46dc96590d2a", 16, ), }, "IUT": { "d": int( - "5e717ae889fc8d67be11c2ebe1a7d3550051448d68a040b2" - "dee8e327", + "5e717ae889fc8d67be11c2ebe1a7d3550051448d68a040b2dee8e327", 16, ), "x": int( - "ae7f3db340b647d61713f5374c019f1be2b28573cb6219bb" - "7b747223", + "ae7f3db340b647d61713f5374c019f1be2b28573cb6219bb7b747223", 16, ), "y": int( - "800e6bffcf97c15864ec6e5673fb83359b45f89b8a26a27f" - "6f3dfbff", + "800e6bffcf97c15864ec6e5673fb83359b45f89b8a26a27f6f3dfbff", 16, ), }, diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index d3b26a2ab3ba..5bee2f9a9ee0 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -138,18 +138,7 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): ) def test_rsa_pss_signature(backend, wycheproof): 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: pytest.skip( "PSS with digest={} and MGF digest={} not supported".format( @@ -157,6 +146,23 @@ def test_rsa_pss_signature(backend, wycheproof): wycheproof.testgroup["mgfSha"], ) ) + if backend._fips_enabled and ( + isinstance(digest, hashes.SHA1) + or isinstance(mgf_digest, hashes.SHA1) + # FIPS 186-4 only allows salt length == digest length for PSS + or wycheproof.testgroup["sLen"] != mgf_digest.digest_size + # inner MGF1 hash must match outer hash + or wycheproof.testgroup["sha"] != wycheproof.testgroup["mgfSha"] + ): + pytest.skip("Invalid params for FIPS") + + key = wycheproof.cache_value_to_group( + "cached_key", + lambda: serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), + ), + ) + assert isinstance(key, rsa.RSAPublicKey) if wycheproof.valid or wycheproof.acceptable: key.verify( @@ -202,6 +208,11 @@ def test_rsa_pss_signature(backend, wycheproof): "rsa_oaep_misc_test.json", ) def test_rsa_oaep_encryption(backend, wycheproof): + if backend._fips_enabled and wycheproof.has_flag("SmallIntegerCiphertext"): + pytest.skip( + "Small integer ciphertexts are rejected in OpenSSL 3.5 FIPS" + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] assert digest is not None diff --git a/tests/wycheproof/test_x25519.py b/tests/wycheproof/test_x25519.py index 571c1d137573..42b4bb7eff58 100644 --- a/tests/wycheproof/test_x25519.py +++ b/tests/wycheproof/test_x25519.py @@ -20,10 +20,8 @@ ) @wycheproof_tests("x25519_test.json") def test_x25519(backend, wycheproof): - assert set(wycheproof.testgroup.items()) == { - ("curve", "curve25519"), - ("type", "XdhComp"), - } + assert wycheproof.testgroup["curve"] == "curve25519" + assert wycheproof.testgroup["type"] == "XdhComp" private_key = X25519PrivateKey.from_private_bytes( binascii.unhexlify(wycheproof.testcase["private"]) diff --git a/tests/wycheproof/test_x448.py b/tests/wycheproof/test_x448.py index bdad0cb8510f..69b9b723bf45 100644 --- a/tests/wycheproof/test_x448.py +++ b/tests/wycheproof/test_x448.py @@ -20,10 +20,8 @@ ) @wycheproof_tests("x448_test.json") def test_x448(backend, wycheproof): - assert set(wycheproof.testgroup.items()) == { - ("curve", "curve448"), - ("type", "XdhComp"), - } + assert wycheproof.testgroup["curve"] == "curve448" + assert wycheproof.testgroup["type"] == "XdhComp" private_key = X448PrivateKey.from_private_bytes( binascii.unhexlify(wycheproof.testcase["private"]) diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py index d7723b288cf5..dab29943f449 100644 --- a/tests/x509/test_ocsp.py +++ b/tests/x509/test_ocsp.py @@ -505,17 +505,6 @@ def test_invalid_add_response(self): time, 0, # type:ignore[arg-type] ) - with pytest.raises(ValueError): - builder.add_response( - cert, - issuer, - hashes.SHA256(), - ocsp.OCSPCertStatus.REVOKED, - time, - time, - time - datetime.timedelta(days=36500), - None, - ) def test_invalid_certificates(self): builder = ocsp.OCSPResponseBuilder() @@ -1008,6 +997,130 @@ def test_sign_unknown_private_key(self, backend): with pytest.raises(TypeError): builder.sign(object(), hashes.SHA256()) # type:ignore[arg-type] + def test_add_response_by_hash(self): + builder = ocsp.OCSPResponseBuilder() + current_time = ( + datetime.datetime.now(datetime.timezone.utc) + .replace(tzinfo=None) + .replace(microsecond=0) + ) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + + # These values would typically be derived from real certificates + issuer_name_hash = b"a" * 32 + issuer_key_hash = b"b" * 32 + serial_number = 12345 + + builder = builder.add_response_by_hash( + issuer_name_hash, + issuer_key_hash, + serial_number, + hashes.SHA256(), + ocsp.OCSPCertStatus.GOOD, + this_update, + next_update, + None, + None, + ) + + root_cert, private_key = _generate_root() + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ) + resp = builder.sign(private_key, hashes.SHA256()) + + # These assertions validate the expected values are in the response + assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD + assert resp.issuer_key_hash == issuer_key_hash + assert resp.issuer_name_hash == issuer_name_hash + assert resp.serial_number == serial_number + assert isinstance(resp.hash_algorithm, hashes.SHA256) + + def test_add_response_then_add_response_by_hash(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPResponseBuilder() + time = datetime.datetime.now(datetime.timezone.utc).replace( + tzinfo=None + ) + builder = builder.add_response( + cert, + issuer, + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + time, + time, + None, + None, + ) + # Fails calling a second time with add_response_by_hash + with pytest.raises(ValueError): + builder.add_response_by_hash( + b"0" * 20, + b"0" * 20, + 1, + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + time, + time, + None, + None, + ) + + def test_add_response_by_hash_bad_hash(self): + builder = ocsp.OCSPResponseBuilder() + time = datetime.datetime.now(datetime.timezone.utc).replace( + tzinfo=None + ) + with pytest.raises(ValueError): + builder.add_response_by_hash( + b"0" * 20, + b"0" * 20, + 1, + "notahash", # type: ignore[arg-type] + ocsp.OCSPCertStatus.GOOD, + time, + time, + None, + None, + ) + with pytest.raises(ValueError): + builder.add_response_by_hash( + b"0" * 19, + b"0" * 20, + 1, + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + time, + time, + None, + None, + ) + with pytest.raises(ValueError): + builder.add_response_by_hash( + b"0" * 20, + b"0" * 21, + 1, + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + time, + time, + None, + None, + ) + with pytest.raises(TypeError): + builder.add_response_by_hash( + b"0" * 20, + b"0" * 20, + "notanint", # type: ignore[arg-type] + hashes.SHA1(), + ocsp.OCSPCertStatus.GOOD, + time, + time, + None, + None, + ) + @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.BLAKE2b(digest_size=64) diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 39f4997ad61c..ac4b58350c35 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -48,7 +48,10 @@ from ..hazmat.primitives.fixtures_rsa import ( RSA_KEY_2048_ALT, ) -from ..hazmat.primitives.test_ec import _skip_curve_unsupported +from ..hazmat.primitives.test_ec import ( + _skip_curve_unsupported, + _skip_deterministic_ecdsa_unsupported, +) from ..hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 from ..utils import ( load_nist_vectors, @@ -604,6 +607,16 @@ def test_verify_argument_must_be_a_public_key(self, backend): with pytest.raises(TypeError): crl.is_signature_valid(object) # type: ignore[arg-type] + def test_crl_issuer_invalid_printable_string(self): + data = _load_cert( + os.path.join( + "x509", "custom", "crl_issuer_invalid_printable_string.der" + ), + lambda v: v, + ) + with pytest.raises(ValueError): + x509.load_der_x509_crl(data) + class TestRevokedCertificate: def test_revoked_basics(self, backend): @@ -852,7 +865,7 @@ def test_load_cert_pub_key(self, backend): assert isinstance(pss, padding.PSS) assert isinstance(pss._mgf, padding.MGF1) assert isinstance(pss._mgf._algorithm, hashes.SHA256) - assert pss._salt_length == 222 + assert pss._salt_length == 32 assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) pub_key.verify( cert.signature, @@ -1148,7 +1161,7 @@ def test_tbs_precertificate_bytes_duplicate_extensions_raises( with pytest.raises( x509.DuplicateExtension, - match="Duplicate 2.5.29.19 extension found", + match=r"Duplicate 2\.5\.29\.19 extension found", ): cert.tbs_precertificate_bytes @@ -2855,6 +2868,11 @@ def test_sign_pss_length_options( computed_len, backend, ): + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len + ) + if not backend.rsa_padding_supported(pss): + pytest.skip("PSS padding with these parameters not supported") builder = ( x509.CertificateBuilder() .subject_name( @@ -2868,9 +2886,6 @@ def test_sign_pss_length_options( .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 @@ -3605,6 +3620,71 @@ def test_build_cert_with_ec_private_key( x509.DNSName("cryptography.io"), ] + def test_build_cert_with_deterministic_ecdsa_signature(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + _skip_deterministic_ecdsa_unsupported(backend) + + private_key = ec.generate_private_key(ec.SECP256R1()) + + 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([])) + .subject_name(x509.Name([])) + .public_key(private_key.public_key()) + .not_valid_before(not_valid_before) + .not_valid_after(not_valid_after) + ) + cert1 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + cert2 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + cert_nondet = builder.sign(private_key, hashes.SHA256(), backend) + cert_nondet2 = builder.sign( + private_key, hashes.SHA256(), backend, ecdsa_deterministic=False + ) + + assert cert1.signature == cert2.signature + assert cert1.signature != cert_nondet.signature + assert cert_nondet.signature != cert_nondet2.signature + + private_key.public_key().verify( + cert1.signature, + cert1.tbs_certificate_bytes, + ec.ECDSA(hashes.SHA256()), + ) + + def test_sign_deterministic_wrong_key_type(self, rsa_key_2048, 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([])) + .subject_name(x509.Name([])) + .public_key(rsa_key_2048.public_key()) + .not_valid_before(not_valid_before) + .not_valid_after(not_valid_after) + ) + with pytest.raises(TypeError): + builder.sign( + rsa_key_2048, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + def test_build_cert_with_bmpstring_universalstring_name( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): @@ -4383,6 +4463,10 @@ def test_build_cert_with_rsa_key_too_small( encipher_only=False, decipher_only=False, ), + x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2002, 1, 1, 12, 1), + not_after=datetime.datetime(2030, 12, 31, 8, 30), + ), x509.OCSPNoCheck(), x509.SubjectKeyIdentifier, ], @@ -4771,6 +4855,61 @@ def test_build_ca_request_with_ec(self, backend): assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 + def test_build_ca_request_with_deterministic_ec(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + _skip_deterministic_ecdsa_unsupported(backend) + + private_key = ec.generate_private_key(ec.SECP256R1()) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([]) + ) + csr1 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + csr2 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + + csr_nondet = builder.sign( + private_key, + hashes.SHA256(), + backend, + ) + + csr_nondet2 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ) + + assert csr1.signature == csr2.signature + assert csr1.signature != csr_nondet.signature + assert csr_nondet.signature != csr_nondet2.signature + private_key.public_key().verify( + csr1.signature, + csr1.tbs_certrequest_bytes, + ec.ECDSA(hashes.SHA256()), + ) + + def test_csr_deterministic_wrong_key_type(self, rsa_key_2048, backend): + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([]) + ) + with pytest.raises(TypeError): + builder.sign( + rsa_key_2048, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", @@ -5286,6 +5425,12 @@ def test_sign_pss_length_options( computed_len, backend, ): + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len + ) + if not backend.rsa_padding_supported(pss): + pytest.skip("PSS padding with these parameters not supported") + builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) @@ -5946,9 +6091,9 @@ def test_init_bitstring_not_allowed_random_oid(self): def test_init_none_value(self): with pytest.raises(TypeError): - x509.NameAttribute( + x509.NameAttribute( # type:ignore[type-var] NameOID.ORGANIZATION_NAME, - None, # type:ignore[arg-type] + None, ) def test_init_bad_length(self): @@ -6010,12 +6155,12 @@ def test_distinguished_name(self): assert na.rfc4514_string() == "2.5.4.15=banking" # non-utf8 attribute (bitstring with raw bytes) - na = x509.NameAttribute( + na_bytes = x509.NameAttribute( x509.ObjectIdentifier("2.5.4.45"), b"\x01\x02\x03\x04", _ASN1Type.BitString, ) - assert na.rfc4514_string() == "2.5.4.45=#01020304" + assert na_bytes.rfc4514_string() == "2.5.4.45=#01020304" def test_distinguished_name_custom_attrs(self): name = x509.Name( diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py index afa8380216c5..ff3360659d42 100644 --- a/tests/x509/test_x509_crlbuilder.py +++ b/tests/x509/test_x509_crlbuilder.py @@ -25,7 +25,10 @@ from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 -from ..hazmat.primitives.test_ec import _skip_curve_unsupported +from ..hazmat.primitives.test_ec import ( + _skip_curve_unsupported, + _skip_deterministic_ecdsa_unsupported, +) from ..hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 from .test_x509 import DummyExtension @@ -299,6 +302,7 @@ def test_sign_pss(self, rsa_key_2048: rsa.RSAPrivateKey, backend): crl = builder.sign(private_key, hashes.SHA256(), rsa_padding=pss) assert len(crl) == 0 assert isinstance(crl.signature_algorithm_parameters, padding.PSS) + assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) assert crl.signature_algorithm_parameters._salt_length == 32 private_key.public_key().verify( crl.signature, @@ -968,3 +972,69 @@ def test_sign_with_revoked_certificates( .value == ci ) + + def test_build_crl_with_deterministic_ecdsa_signature(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + _skip_deterministic_ecdsa_unsupported(backend) + + private_key = ec.generate_private_key(ec.SECP256R1()) + + 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([])) + .last_update(last_update) + .next_update(next_update) + ) + crl1 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + crl2 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) + + crl_nondet = builder.sign( + private_key, + hashes.SHA256(), + backend, + ) + + crl_nondet2 = builder.sign( + private_key, + hashes.SHA256(), + backend, + ecdsa_deterministic=False, + ) + + assert crl1.signature == crl2.signature + assert crl1.signature != crl_nondet.signature + assert crl_nondet.signature != crl_nondet2.signature + private_key.public_key().verify( + crl1.signature, crl1.tbs_certlist_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_deterministic_signature_wrong_key_type( + self, rsa_key_2048, backend + ): + 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([])) + .last_update(last_update) + .next_update(next_update) + ) + with pytest.raises(TypeError): + builder.sign( + rsa_key_2048, + hashes.SHA256(), + backend, + ecdsa_deterministic=True, + ) diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py index f1a32b83c09a..6dbdd3027b55 100644 --- a/tests/x509/test_x509_ext.py +++ b/tests/x509/test_x509_ext.py @@ -486,8 +486,7 @@ def test_repr(self): nr = x509.NoticeReference("org", [1, 3, 4]) assert repr(nr) == ( - "" + "" ) def test_eq(self): @@ -1876,6 +1875,223 @@ def test_key_cert_sign_crl_sign(self, backend): assert ku.crl_sign is True +class TestPrivateKeyUsagePeriodExtension: + def test_not_validity(self): + with pytest.raises(TypeError): + x509.PrivateKeyUsagePeriod("notValidBefore", "notValidAfter") # type:ignore[arg-type] + + def test_repr(self): + period = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + assert repr(period) == ( + "" + ) + + def test_eq(self): + period = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + period2 = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + assert period == period2 + + def test_ne(self): + period = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + period2 = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2014, 1, 1), + ) + assert period != period2 + assert period != object() + + def test_hash(self): + period = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + period2 = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + period3 = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2014, 1, 1), + ) + assert hash(period) == hash(period2) + assert hash(period) != hash(period3) + + def test_both_none(self): + with pytest.raises(ValueError): + x509.PrivateKeyUsagePeriod( + not_before=None, + not_after=None, + ) + + def test_invalid_not_after_type(self): + with pytest.raises(TypeError): + x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after="invalid type", # type:ignore[arg-type] + ) + + def test_not_before_after_not_after(self): + with pytest.raises( + ValueError, match="not_before must be before not_after" + ): + x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2014, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + + def test_public_bytes(self): + period = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1, 0, 0, 0), + not_after=datetime.datetime(2013, 1, 1, 0, 0, 0), + ) + serialized = period.public_bytes() + + assert serialized == ( + b"\x30\x22\x80\x0f\x32\x30\x31\x32\x30\x31\x30\x31\x30" + b"\x30\x30\x30\x30\x30\x5a\x81\x0f\x32\x30\x31\x33\x30" + b"\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a" + ) + + def test_only_not_before(self): + period = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=None, + ) + assert period.not_before == datetime.datetime(2012, 1, 1) + assert period.not_after is None + + serialized = period.public_bytes() + + assert serialized == ( + b"\x30\x11\x80\x0f\x32\x30\x31\x32\x30\x31\x30\x31\x30" + b"\x30\x30\x30\x30\x30\x5a" + ) + + def test_only_not_after(self): + period = x509.PrivateKeyUsagePeriod( + not_before=None, + not_after=datetime.datetime(2013, 1, 1), + ) + assert period.not_before is None + assert period.not_after == datetime.datetime(2013, 1, 1) + + serialized = period.public_bytes() + + assert serialized == ( + b"\x30\x11\x81\x0f\x32\x30\x31\x33\x30\x31\x30\x31\x30" + b"\x30\x30\x30\x30\x30\x5a" + ) + + def test_load_pem_certificate_with_extension(self, backend): + cert_path = os.path.join( + "x509", "custom", "private_key_usage_period_both_dates.pem" + ) + cert = load_vectors_from_file( + cert_path, + lambda pemdata: x509.load_pem_x509_certificate(pemdata.read()), + mode="rb", + ) + ext = cert.extensions.get_extension_for_class( + x509.PrivateKeyUsagePeriod + ) + assert ext.critical is False + + assert ext.value.not_before == datetime.datetime(2024, 1, 1, 0, 0) + assert ext.value.not_after == datetime.datetime( + 2024, 12, 31, 23, 59, 59 + ) + + def test_load_pem_only_not_before(self, backend): + cert_path = os.path.join( + "x509", "custom", "private_key_usage_period_only_not_before.pem" + ) + cert = load_vectors_from_file( + cert_path, + lambda pemdata: x509.load_pem_x509_certificate(pemdata.read()), + mode="rb", + ) + ext = cert.extensions.get_extension_for_class( + x509.PrivateKeyUsagePeriod + ) + + assert ext.value.not_before == datetime.datetime(2024, 1, 1, 0, 0) + assert ext.value.not_after is None + + def test_load_pem_only_not_after(self, backend): + cert_path = os.path.join( + "x509", "custom", "private_key_usage_period_only_not_after.pem" + ) + cert = load_vectors_from_file( + cert_path, + lambda pemdata: x509.load_pem_x509_certificate(pemdata.read()), + mode="rb", + ) + ext = cert.extensions.get_extension_for_class( + x509.PrivateKeyUsagePeriod + ) + + assert ext.value.not_before is None + assert ext.value.not_after == datetime.datetime( + 2024, 12, 31, 23, 59, 59 + ) + + def test_certificate_builder_with_extension(self, backend): + private_key = ec.generate_private_key(ec.SECP256R1()) + + period = x509.PrivateKeyUsagePeriod( + not_before=datetime.datetime(2012, 1, 1), + not_after=datetime.datetime(2013, 1, 1), + ) + + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name( + [ + x509.NameAttribute( + x509.NameOID.COMMON_NAME, "cryptography.io" + ), + ] + ) + ) + builder = builder.issuer_name( + x509.Name( + [ + x509.NameAttribute( + x509.NameOID.COMMON_NAME, "cryptography.io" + ), + ] + ) + ) + builder = builder.not_valid_before(datetime.datetime(2010, 1, 1)) + builder = builder.not_valid_after(datetime.datetime(2020, 1, 1)) + builder = builder.serial_number(123) + builder = builder.public_key(private_key.public_key()) + builder = builder.add_extension(period, critical=True) + + certificate = builder.sign(private_key, hashes.SHA256()) + + ext = certificate.extensions.get_extension_for_class( + x509.PrivateKeyUsagePeriod + ) + + assert ext.critical is True + assert ext.value.not_before == datetime.datetime(2012, 1, 1) + assert ext.value.not_after == datetime.datetime(2013, 1, 1) + + class TestDNSName: def test_non_a_label(self): with pytest.raises(ValueError): diff --git a/tests/x509/verification/test_limbo.py b/tests/x509/verification/test_limbo.py index d0402c4ce30a..2e38b8f6a909 100644 --- a/tests/x509/verification/test_limbo.py +++ b/tests/x509/verification/test_limbo.py @@ -2,6 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations + import datetime import ipaddress import json diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py index 879f41c3eb77..c0c0c16f5b97 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -6,18 +6,26 @@ import os from functools import lru_cache from ipaddress import IPv4Address +from typing import Optional, Type import pytest -from cryptography import x509 +from cryptography import utils, x509 +from cryptography.hazmat._oid import ExtendedKeyUsageOID +from cryptography.x509 import ExtensionType from cryptography.x509.general_name import DNSName, IPAddress from cryptography.x509.verification import ( + Criticality, + ExtensionPolicy, + Policy, PolicyBuilder, Store, VerificationError, ) from tests.x509.test_x509 import _load_cert +WEBPKI_MINIMUM_RSA_MODULUS = 2048 + @lru_cache(maxsize=1) def dummy_store() -> Store: @@ -44,20 +52,20 @@ def test_max_chain_depth_already_set(self): PolicyBuilder().max_chain_depth(8).max_chain_depth(9) def test_ipaddress_subject(self): - policy = ( + verifier = ( PolicyBuilder() .store(dummy_store()) .build_server_verifier(IPAddress(IPv4Address("0.0.0.0"))) ) - assert policy.subject == IPAddress(IPv4Address("0.0.0.0")) + assert verifier.policy.subject == IPAddress(IPv4Address("0.0.0.0")) def test_dnsname_subject(self): - policy = ( + verifier = ( PolicyBuilder() .store(dummy_store()) .build_server_verifier(DNSName("cryptography.io")) ) - assert policy.subject == DNSName("cryptography.io") + assert verifier.policy.subject == DNSName("cryptography.io") def test_subject_bad_types(self): # Subject must be a supported GeneralName type @@ -86,11 +94,26 @@ def test_builder_pattern(self): builder = builder.store(store) builder = builder.max_chain_depth(max_chain_depth) - verifier = builder.build_server_verifier(DNSName("cryptography.io")) - assert verifier.subject == DNSName("cryptography.io") - assert verifier.validation_time == now + subject = DNSName("cryptography.io") + verifier = builder.build_server_verifier(subject) + assert verifier.policy.subject == subject + assert verifier.policy.validation_time == now + assert verifier.policy.max_chain_depth == max_chain_depth + with pytest.warns(utils.DeprecatedIn45): + assert verifier.subject == subject + with pytest.warns(utils.DeprecatedIn45): + assert verifier.validation_time == now + with pytest.warns(utils.DeprecatedIn45): + assert verifier.max_chain_depth == max_chain_depth + + assert ( + verifier.policy.extended_key_usage + == ExtendedKeyUsageOID.SERVER_AUTH + ) + assert ( + verifier.policy.minimum_rsa_modulus == WEBPKI_MINIMUM_RSA_MODULUS + ) assert verifier.store == store - assert verifier.max_chain_depth == max_chain_depth def test_build_server_verifier_missing_store(self): with pytest.raises( @@ -128,12 +151,33 @@ def test_verify(self): validation_time = datetime.datetime.fromisoformat( "2018-11-16T00:00:00+00:00" ) + max_chain_depth = 16 + builder = PolicyBuilder().store(store) - builder = builder.time(validation_time).max_chain_depth(16) + builder = builder.time(validation_time).max_chain_depth( + max_chain_depth + ) verifier = builder.build_client_verifier() - assert verifier.validation_time == validation_time.replace(tzinfo=None) - assert verifier.max_chain_depth == 16 + assert verifier.policy.subject is None + assert verifier.policy.validation_time == validation_time.replace( + tzinfo=None + ) + assert verifier.policy.max_chain_depth == max_chain_depth + with pytest.warns(utils.DeprecatedIn45): + assert verifier.validation_time == validation_time.replace( + tzinfo=None + ) + with pytest.warns(utils.DeprecatedIn45): + assert verifier.max_chain_depth == max_chain_depth + + assert ( + verifier.policy.extended_key_usage + == ExtendedKeyUsageOID.CLIENT_AUTH + ) + assert ( + verifier.policy.minimum_rsa_modulus == WEBPKI_MINIMUM_RSA_MODULUS + ) assert verifier.store is store verified_client = verifier.verify(leaf, []) @@ -222,3 +266,390 @@ def test_error_message(self): match=r"", ): verifier.verify(leaf, []) + + +SUPPORTED_EXTENSION_TYPES = ( + x509.AuthorityInformationAccess, + x509.AuthorityKeyIdentifier, + x509.SubjectKeyIdentifier, + x509.KeyUsage, + x509.SubjectAlternativeName, + x509.BasicConstraints, + x509.NameConstraints, + x509.ExtendedKeyUsage, +) + + +class TestCustomExtensionPolicies: + leaf = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + ) + ca = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + ) + store = Store([ca]) + validation_time = datetime.datetime.fromisoformat( + "2018-11-16T00:00:00+00:00" + ) + + def test_builder_methods(self): + ext_policy = ExtensionPolicy.permit_all() + ext_policy = ext_policy.require_not_present(x509.BasicConstraints) + + def ensure_duplicate_ext_throws(method, *args): + oid_str = x509.BasicConstraints.oid.dotted_string + with pytest.raises( + ValueError, + match="ExtensionPolicy already configured for" + f" extension with OID {oid_str}", + ): + method(ext_policy, x509.BasicConstraints, *args) + + ensure_duplicate_ext_throws(ExtensionPolicy.require_not_present) + ensure_duplicate_ext_throws( + ExtensionPolicy.may_be_present, Criticality.AGNOSTIC, None + ) + ensure_duplicate_ext_throws( + ExtensionPolicy.require_present, Criticality.AGNOSTIC, None + ) + + with pytest.raises(TypeError): + + class _Extension: + pass + + ext_policy.require_present( + _Extension, # type: ignore + Criticality.AGNOSTIC, + None, + ) + + def test_unsupported_extension(self): + ext_policy = ExtensionPolicy.permit_all() + pattern = ( + f"Unsupported extension OID: {x509.Admissions.oid.dotted_string}" + ) + with pytest.raises( + ValueError, + match=pattern, + ): + ext_policy.may_be_present( + x509.Admissions, + Criticality.AGNOSTIC, + None, + ) + + @staticmethod + def _make_validator_cb(extension_type: Type[ExtensionType]): + def validator_cb(policy, cert, ext: Optional[ExtensionType]): + assert isinstance(policy, Policy) + assert ( + policy.validation_time + == TestCustomExtensionPolicies.validation_time.replace( + tzinfo=None + ) + ) + assert isinstance(cert, x509.Certificate) + assert ext is None or isinstance(ext, extension_type) + + return validator_cb + + def test_require_not_present(self): + default_ee = ExtensionPolicy.webpki_defaults_ee() + no_basic_constraints_ee = default_ee.require_not_present( + x509.BasicConstraints + ) + + default_builder = ( + PolicyBuilder().store(self.store).time(self.validation_time) + ) + builder_no_basic_constraints = default_builder.extension_policies( + ca_policy=ExtensionPolicy.webpki_defaults_ca(), + ee_policy=no_basic_constraints_ee, + ) + + default_builder.build_client_verifier().verify(self.leaf, []) + + with pytest.raises( + VerificationError, + match="Certificate contains prohibited extension", + ): + builder_no_basic_constraints.build_client_verifier().verify( + self.leaf, [] + ) + + def test_require_present(self): + default_builder = ( + PolicyBuilder().store(self.store).time(self.validation_time) + ) + builder_require_subject_keyid = default_builder.extension_policies( + ca_policy=ExtensionPolicy.webpki_defaults_ca(), + ee_policy=ExtensionPolicy.webpki_defaults_ee().require_present( + x509.SubjectKeyIdentifier, + Criticality.AGNOSTIC, + self._make_validator_cb(x509.SubjectKeyIdentifier), + ), + ) + builder_require_san = default_builder.extension_policies( + ca_policy=ExtensionPolicy.webpki_defaults_ca(), + ee_policy=ExtensionPolicy.webpki_defaults_ee().require_present( + x509.SubjectAlternativeName, + Criticality.AGNOSTIC, + self._make_validator_cb(x509.SubjectAlternativeName), + ), + ) + + default_builder.build_client_verifier().verify(self.leaf, []) + builder_require_san.build_client_verifier().verify(self.leaf, []) + + with pytest.raises( + VerificationError, + match="missing required extension", + ): + builder_require_subject_keyid.build_client_verifier().verify( + self.leaf, [] + ) + + def test_criticality_constraints(self): + builder = PolicyBuilder().store(self.store).time(self.validation_time) + noncrit_key_usage_builder = builder.extension_policies( + ca_policy=ExtensionPolicy.webpki_defaults_ca(), + ee_policy=ExtensionPolicy.webpki_defaults_ee().require_present( + x509.KeyUsage, Criticality.NON_CRITICAL, None + ), + ) + critical_eku_builder = builder.extension_policies( + ca_policy=ExtensionPolicy.webpki_defaults_ca(), + ee_policy=ExtensionPolicy.webpki_defaults_ee().require_present( + x509.ExtendedKeyUsage, Criticality.CRITICAL, None + ), + ) + + def make_pattern(extension_type: Type[ExtensionType]): + return ( + f"invalid extension: {extension_type.oid.dotted_string}:" + " Certificate extension has incorrect criticality" + ) + + builder.build_client_verifier().verify(self.leaf, []) + with pytest.raises( + VerificationError, + match=make_pattern(x509.KeyUsage), + ): + noncrit_key_usage_builder.build_client_verifier().verify( + self.leaf, [] + ) + with pytest.raises( + VerificationError, + match=make_pattern(x509.ExtendedKeyUsage), + ): + critical_eku_builder.build_client_verifier().verify(self.leaf, []) + + @pytest.mark.parametrize( + "extension_type", + SUPPORTED_EXTENSION_TYPES, + ) + def test_custom_cb_pass(self, extension_type: Type[x509.ExtensionType]): + ca_ext_policy = ExtensionPolicy.webpki_defaults_ca() + ee_ext_policy = ExtensionPolicy.webpki_defaults_ee() + + if extension_type is x509.SubjectAlternativeName: + # subjectAltName must be required for server verification + ee_ext_policy = ee_ext_policy.require_present( + extension_type, + Criticality.AGNOSTIC, + self._make_validator_cb(extension_type), + ) + else: + ee_ext_policy = ee_ext_policy.may_be_present( + extension_type, + Criticality.AGNOSTIC, + self._make_validator_cb(extension_type), + ) + + builder = PolicyBuilder().store(self.store) + builder = builder.time(self.validation_time).max_chain_depth(16) + builder = builder.extension_policies( + ca_policy=ca_ext_policy, ee_policy=ee_ext_policy + ) + + builder.build_client_verifier().verify(self.leaf, []) + + path = builder.build_server_verifier( + DNSName("cryptography.io") + ).verify(self.leaf, []) + assert path == [self.leaf, self.ca] + + @pytest.mark.parametrize( + "extension_type", + SUPPORTED_EXTENSION_TYPES, + ) + def test_custom_cb_exception_fails_verification(self, extension_type): + ca_ext_policy = ExtensionPolicy.webpki_defaults_ca() + ee_ext_policy = ExtensionPolicy.webpki_defaults_ee() + + def validator(*_): + raise ValueError("test") + + if extension_type is x509.BasicConstraints: + # basicConstraints must be required in a ca extension policy + ca_ext_policy = ca_ext_policy.require_present( + extension_type, + Criticality.AGNOSTIC, + validator, + ) + else: + ca_ext_policy = ca_ext_policy.may_be_present( + extension_type, + Criticality.AGNOSTIC, + validator, + ) + + builder = PolicyBuilder().store(self.store).time(self.validation_time) + builder = builder.extension_policies( + ca_policy=ca_ext_policy, ee_policy=ee_ext_policy + ) + + for verifier in ( + builder.build_client_verifier(), + builder.build_server_verifier(DNSName("cryptography.io")), + ): + with pytest.raises( + VerificationError, + match="Python extension validator failed: ValueError: test", + ): + verifier.verify(self.leaf, []) + + def test_custom_cb_no_retval_enforced(self): + ca_ext_policy = ExtensionPolicy.webpki_defaults_ca() + ee_ext_policy = ExtensionPolicy.webpki_defaults_ee() + + def validator(*_): + return False + + ee_ext_policy = ee_ext_policy.may_be_present( + x509.ExtendedKeyUsage, + Criticality.AGNOSTIC, + validator, + ) + + builder = PolicyBuilder().store(self.store).time(self.validation_time) + builder = builder.extension_policies( + ca_policy=ca_ext_policy, ee_policy=ee_ext_policy + ) + + for verifier in ( + builder.build_client_verifier(), + builder.build_server_verifier(DNSName("cryptography.io")), + ): + with pytest.raises( + VerificationError, + match="Python validator must return None.", + ): + verifier.verify(self.leaf, []) + + def test_no_subject_alt_name(self): + leaf = _load_cert( + os.path.join("x509", "custom", "no_sans.pem"), + x509.load_pem_x509_certificate, + ) + + store = Store([leaf]) + validation_time = datetime.datetime.fromisoformat( + "2025-04-14T00:00:00+00:00" + ) + + builder = PolicyBuilder().store(store) + builder = builder.time(validation_time) + + with pytest.raises( + VerificationError, + match="missing required extension", + ): + builder.build_client_verifier().verify(leaf, []) + with pytest.raises( + VerificationError, + match="missing required extension", + ): + builder.build_server_verifier(DNSName("example.com")).verify( + leaf, [] + ) + + builder = builder.extension_policies( + ca_policy=ExtensionPolicy.webpki_defaults_ca(), + ee_policy=ExtensionPolicy.permit_all(), + ) + + verified_client = builder.build_client_verifier().verify(leaf, []) + assert verified_client.subjects is None + + # Trying to build a ServerVerifier with an EE ExtensionPolicy + # that doesn't require SAN extension must fail. + with pytest.raises( + ValueError, + match=( + "An EE extension policy used for server verification" + " must require the subjectAltName extension to be present." + ), + ): + builder.build_server_verifier(DNSName("example.com")) + + def test_ca_ext_policy_must_require_basic_constraints(self): + ca_policies = [ + ExtensionPolicy.webpki_defaults_ca().require_not_present( + x509.BasicConstraints + ), + ExtensionPolicy.webpki_defaults_ca().may_be_present( + x509.BasicConstraints, Criticality.AGNOSTIC, None + ), + ] + + for ca_policy in ca_policies: + builder = ( + PolicyBuilder().store(self.store).time(self.validation_time) + ) + builder = builder.extension_policies( + ca_policy=ca_policy, + ee_policy=ExtensionPolicy.webpki_defaults_ee(), + ) + pattern = ( + "A CA extension policy must require the" + " basicConstraints extension to be present." + ) + with pytest.raises( + ValueError, + match=pattern, + ): + builder.build_server_verifier(DNSName("example.com")) + with pytest.raises( + ValueError, + match=pattern, + ): + builder.build_client_verifier() + + def test_wrong_subject_alt_name(self): + ee_extension_policy = ( + ExtensionPolicy.webpki_defaults_ee().require_present( + x509.SubjectAlternativeName, Criticality.AGNOSTIC, None + ) + ) + builder = PolicyBuilder().store(self.store) + builder = builder.time(self.validation_time) + builder = builder.extension_policies( + ca_policy=ExtensionPolicy.webpki_defaults_ca(), + ee_policy=ee_extension_policy, + ) + + builder.build_client_verifier().verify(self.leaf, []) + + # For ServerVerifier, SAN must be matched against the subject + # even if the extension policy permits any SANs. + with pytest.raises( + VerificationError, + match="leaf certificate has no matching subjectAltName", + ): + builder.build_server_verifier(DNSName("wrong.io")).verify( + self.leaf, [] + ) diff --git a/vectors/cryptography_vectors/__about__.py b/vectors/cryptography_vectors/__about__.py index 98114348efa6..1de1efd7c20b 100644 --- a/vectors/cryptography_vectors/__about__.py +++ b/vectors/cryptography_vectors/__about__.py @@ -6,4 +6,4 @@ "__version__", ] -__version__ = "44.0.0" +__version__ = "45.0.3" diff --git a/vectors/cryptography_vectors/asymmetric/EC/ec-missing-curve.pem b/vectors/cryptography_vectors/asymmetric/EC/ec-missing-curve.pem new file mode 100644 index 000000000000..287e78c0d23b --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/ec-missing-curve.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MGsCAQEEIGIq02UsfuTvGOrZRnJGulum7SYqHHa3aJX3LpEqExJPoUQDQgAEJLzz +buz2tRnLFlOL+6bTX6giVavAsc6NDFFT0IMCd2ibTTNUDDkFGsgq0cH5JYPg/6xU +lMBFKrWYe3yQ4has9w== +-----END EC PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp256k1-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp256k1-explicit-no-seed.pem new file mode 100644 index 000000000000..503595523c89 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp256k1-explicit-no-seed.pem @@ -0,0 +1,10 @@ +-----BEGIN PRIVATE KEY----- +MIIBYQIBADCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA//////////// +/////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEE +eb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio +/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQ +NkFBAgEBBG0wawIBAQQg0fB/gU29JVUd5ElgaN2mwKFJGvzItUe0T2StN0Ezet6h +RANCAATF+z+muwej787mrhx40dzqUKtEqk3DgqAWw0sbY3nO/VjBpSJzsSLWIFyN +VGWxRUM46VmGL3sHMIMAXJ0vEH0d +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp256k1-pub-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp256k1-pub-explicit-no-seed.pem new file mode 100644 index 000000000000..aaac3fcf6cd0 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp256k1-pub-explicit-no-seed.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA//////////////// +/////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5m +fvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0 +SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFB +AgEBA0IABMX7P6a7B6PvzuauHHjR3OpQq0SqTcOCoBbDSxtjec79WMGlInOxItYg +XI1UZbFFQzjpWYYvewcwgwBcnS8QfR0= +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp256r1-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-explicit-no-seed.pem new file mode 100644 index 000000000000..03cbded5142a --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-explicit-no-seed.pem @@ -0,0 +1,10 @@ +-----BEGIN PRIVATE KEY----- +MIIBYQIBADCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////wAAAAEA +AAAAAAAAAAAAAAD///////////////8wRAQg/////wAAAAEAAAAAAAAAAAAAAAD/ +//////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLBEEE +axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W +K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 +YyVRAgEBBG0wawIBAQQgsR15A3k6KPVE303H1m4DEH6XJhjMvvhPWN4VrRCSdLeh +RANCAARkJWmeqojxqcbDHsnkDszH1K1VvfgJhDNsBQHkMgHUjZrbY3268a5/EvBO +U2CftfRxuAe02xjg0wU3zks7NaCb +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp256r1-explicit-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-explicit-seed.pem new file mode 100644 index 000000000000..738dc3470f3b --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-explicit-seed.pem @@ -0,0 +1,10 @@ +-----BEGIN PRIVATE KEY----- +MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB +AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA +///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV +AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg +9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A +AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQg2iYcnTtVn5DB +X9NKoAWnvMVXU2MorY2hCT4rN0sQG7ahRANCAARbHCXJP9mtfMEf46dFCDcCVW1q +sZgc0jTt9GKB/o1Rz8UoxyyWWxzX+lW402CpnNqZbKGVs0MuhZxv9BsDdMsY +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp256r1-pub-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-pub-explicit-no-seed.pem new file mode 100644 index 000000000000..f3194a09794a --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-pub-explicit-no-seed.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAA +AAAAAAAAAAD///////////////8wRAQg/////wAAAAEAAAAAAAAAAAAAAAD///// +//////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLBEEEaxfR +8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84z +V2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVR +AgEBA0IABGQlaZ6qiPGpxsMeyeQOzMfUrVW9+AmEM2wFAeQyAdSNmttjfbrxrn8S +8E5TYJ+19HG4B7TbGODTBTfOSzs1oJs= +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp256r1-pub-explicit-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-pub-explicit-seed.pem new file mode 100644 index 000000000000..db90edb57358 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp256r1-pub-explicit-seed.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA +AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// +///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd +NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 +RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA +//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABFscJck/2a18wR/jp0UINwJV +bWqxmBzSNO30YoH+jVHPxSjHLJZbHNf6VbjTYKmc2plsoZWzQy6FnG/0GwN0yxg= +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp384r1-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-explicit-no-seed.pem new file mode 100644 index 000000000000..2bf336404488 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-explicit-no-seed.pem @@ -0,0 +1,13 @@ +-----BEGIN PRIVATE KEY----- +MIIB9QIBADCCAU0GByqGSM49AgEwggFAAgEBMDwGByqGSM49AQECMQD///////// +/////////////////////////////////v////8AAAAAAAAAAP////8wZAQw//// +//////////////////////////////////////7/////AAAAAAAAAAD////8BDCz +MS+n4j7n5JiOBWvj+C0ZGB2cbv6BQRIDFAiPUBOHWsZWOY2KLtGdKoXI7dPsKu8E +YQSqh8oivosFN46xxx7zIK10bh07Younm5hZ90HgglQqOFUC8l2/VSlsOlReOHJ2 +Crc2F95KliYsb12emL+Sktwp+PQdvSiaFHzp2jETtfC4wApgsc4dfoGdekMdfJDq +Dl8CMQD////////////////////////////////HY02B9Dct31gaDbJIsKd67OwZ +aszFKXMCAQEEgZ4wgZsCAQEEMP3yCnX3tWWsI5ScYiVMB4FN69h2mfxzECqxrePl +BUH68ozPkgB0y4UyIcUMn8sGHqFkA2IABP9QJ2J6GAT/BbPqr2M5mS+6kEJ7M1DS +gTVhuR3LX4eK5g2n0YWD1yeGjjg/fZRgqWnJ9DVMR2z6c9cMpXyUGHuuQlEolIzv +Nlj0Ox1nVti9N3bIXgSc/d7PBlWTThshVA== +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp384r1-explicit-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-explicit-seed.pem new file mode 100644 index 000000000000..584fcb338b76 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-explicit-seed.pem @@ -0,0 +1,13 @@ +-----BEGIN PRIVATE KEY----- +MIICDAIBADCCAWQGByqGSM49AgEwggFXAgEBMDwGByqGSM49AQECMQD///////// +/////////////////////////////////v////8AAAAAAAAAAP////8wewQw//// +//////////////////////////////////////7/////AAAAAAAAAAD////8BDCz +MS+n4j7n5JiOBWvj+C0ZGB2cbv6BQRIDFAiPUBOHWsZWOY2KLtGdKoXI7dPsKu8D +FQCjNZJqoxmieh0AiWpnc6SCes2scwRhBKqHyiK+iwU3jrHHHvMgrXRuHTtii6eb +mFn3QeCCVCo4VQLyXb9VKWw6VF44cnYKtzYX3kqWJixvXZ6Yv5KS3Cn49B29KJoU +fOnaMRO18LjACmCxzh1+gZ16Qx18kOoOXwIxAP////////////////////////// +/////8djTYH0Ny3fWBoNskiwp3rs7BlqzMUpcwIBAQSBnjCBmwIBAQQwPKjQ9aIk +HbtFJwY4V91r/G4wU3MSdTJMIn4SVTch5Ata0Ar++W74TcJqRo6KsiTqoWQDYgAE +8SpH9fXRoy5xLBbPwngCf3Obyyy3AsilHH32mWfxVbP4fmoZ69jxbXSvOxFUWgMM +M8e8RoqYuNMQPW6z5oNGDuVuQfFwDmQS5CuYC6Me2u5c6JvgDegOwHRm0imnn194 +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp384r1-pub-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-pub-explicit-no-seed.pem new file mode 100644 index 000000000000..df5e2b1ba59d --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-pub-explicit-no-seed.pem @@ -0,0 +1,12 @@ +-----BEGIN PUBLIC KEY----- +MIIBtTCCAU0GByqGSM49AgEwggFAAgEBMDwGByqGSM49AQECMQD///////////// +/////////////////////////////v////8AAAAAAAAAAP////8wZAQw//////// +//////////////////////////////////7/////AAAAAAAAAAD////8BDCzMS+n +4j7n5JiOBWvj+C0ZGB2cbv6BQRIDFAiPUBOHWsZWOY2KLtGdKoXI7dPsKu8EYQSq +h8oivosFN46xxx7zIK10bh07Younm5hZ90HgglQqOFUC8l2/VSlsOlReOHJ2Crc2 +F95KliYsb12emL+Sktwp+PQdvSiaFHzp2jETtfC4wApgsc4dfoGdekMdfJDqDl8C +MQD////////////////////////////////HY02B9Dct31gaDbJIsKd67OwZaszF +KXMCAQEDYgAE/1AnYnoYBP8Fs+qvYzmZL7qQQnszUNKBNWG5Hctfh4rmDafRhYPX +J4aOOD99lGCpacn0NUxHbPpz1wylfJQYe65CUSiUjO82WPQ7HWdW2L03dsheBJz9 +3s8GVZNOGyFU +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp384r1-pub-explicit-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-pub-explicit-seed.pem new file mode 100644 index 000000000000..b5d6beb0e110 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp384r1-pub-explicit-seed.pem @@ -0,0 +1,12 @@ +-----BEGIN PUBLIC KEY----- +MIIBzDCCAWQGByqGSM49AgEwggFXAgEBMDwGByqGSM49AQECMQD///////////// +/////////////////////////////v////8AAAAAAAAAAP////8wewQw//////// +//////////////////////////////////7/////AAAAAAAAAAD////8BDCzMS+n +4j7n5JiOBWvj+C0ZGB2cbv6BQRIDFAiPUBOHWsZWOY2KLtGdKoXI7dPsKu8DFQCj +NZJqoxmieh0AiWpnc6SCes2scwRhBKqHyiK+iwU3jrHHHvMgrXRuHTtii6ebmFn3 +QeCCVCo4VQLyXb9VKWw6VF44cnYKtzYX3kqWJixvXZ6Yv5KS3Cn49B29KJoUfOna +MRO18LjACmCxzh1+gZ16Qx18kOoOXwIxAP////////////////////////////// +/8djTYH0Ny3fWBoNskiwp3rs7BlqzMUpcwIBAQNiAATxKkf19dGjLnEsFs/CeAJ/ +c5vLLLcCyKUcffaZZ/FVs/h+ahnr2PFtdK87EVRaAwwzx7xGipi40xA9brPmg0YO +5W5B8XAOZBLkK5gLox7a7lzom+AN6A7AdGbSKaefX3g= +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp521r1-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-explicit-no-seed.pem new file mode 100644 index 000000000000..d0c8c07bc695 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-explicit-no-seed.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICmQIBADCCAbkGByqGSM49AgEwggGsAgEBME0GByqGSM49AQECQgH///////// +//////////////////////////////////////////////////////////////// +/////////////zCBiARCAf////////////////////////////////////////// +///////////////////////////////////////////8BEIAUZU+uWGOHJofkpoh +oLaFQO6i2nJbmbMV87i0iZGO8QnhVhk5Uex+k3sWUsC9O7G/BzVz34g9LDTx70Uf +1GtQPwAEgYUEAMaFjga3BATpzZ4+y2YjlbRCnGSBOQU/tSH4KK9ga009uqFLXnfv +51ko/h3BJ6L/qN4zSLPBhWpCm/l+fjHC5b1mARg5KWp4mjvABFyKX7QsfRvZmPVE +SVebRGgXr70XJz5mLJfucple9CZAxVC5AT+tB2E1PHCGonLCQIi+lHaf0WZQAkIB +///////////////////////////////////////////6UYaHg78vlmt/zAFI9wml +0Du1ybiJnEeuu2+3HpE4ZAkCAQEEgdYwgdMCAQEEQgB01EEtlzfayNIInreIvwX6 +FP/ZH/YBJNkoAfTtc16+cO1rZVWxkMHHriD8N2Qwqd9QnJME6jWHzb1Effu9xQB3 +8KGBiQOBhgAEASibXsZQxJ6voXpafbPdqVmssNfA5rJda8h67iRgIYmRKnkYdlIX +QPa48PjqsbdnKW6YmV/QODmeAIyhGVUtDyBwATc4pptFdB9ABipSL0VWGuuSC8ir +YE0maGnNclJqnbZEaoOkQgYw2QSYdRvnabUQrycxq5JRgbD5KzxIUOSmAZHs +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp521r1-explicit-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-explicit-seed.pem new file mode 100644 index 000000000000..0e7fd9d329f4 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-explicit-seed.pem @@ -0,0 +1,17 @@ +-----BEGIN PRIVATE KEY----- +MIICsAIBADCCAdAGByqGSM49AgEwggHDAgEBME0GByqGSM49AQECQgH///////// +//////////////////////////////////////////////////////////////// +/////////////zCBnwRCAf////////////////////////////////////////// +///////////////////////////////////////////8BEIAUZU+uWGOHJofkpoh +oLaFQO6i2nJbmbMV87i0iZGO8QnhVhk5Uex+k3sWUsC9O7G/BzVz34g9LDTx70Uf +1GtQPwADFQDQnogAKRy4U5bMZxc5MoSqoNpkugSBhQQAxoWOBrcEBOnNnj7LZiOV +tEKcZIE5BT+1Ifgor2BrTT26oUted+/nWSj+HcEnov+o3jNIs8GFakKb+X5+McLl +vWYBGDkpaniaO8AEXIpftCx9G9mY9URJV5tEaBevvRcnPmYsl+5ymV70JkDFULkB +P60HYTU8cIaicsJAiL6Udp/RZlACQgH///////////////////////////////// +//////////pRhoeDvy+Wa3/MAUj3CaXQO7XJuImcR667b7cekThkCQIBAQSB1jCB +0wIBAQRCAeIS+AXTSxkEd9Oll6ax8sC5uZIYJBwGzoXyoPMLllSo3ZWhmTPykqDb +Ouymh4B6d7v5zH04x+hA90g+DC0MgXfAoYGJA4GGAAQAU1BgMCzHl5BFxXJ9G7Kc +ZlcKv8qBWoZkadGVcdxSXGu7VH0OckMcRfmgi3gjJvu+yxTSjAlLWwmM5JA5Bbn1 +TSoAja1sdGJE97x4Eeh+teTZ4xELYAXfA+jWrSCnOpmmAJoj4QPPpT4FPVcoGXjU +LJSq5XunFjM2uo3WL491wq3Y8LY= +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp521r1-pub-explicit-no-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-pub-explicit-no-seed.pem new file mode 100644 index 000000000000..00ff94d6934d --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-pub-explicit-no-seed.pem @@ -0,0 +1,15 @@ +-----BEGIN PUBLIC KEY----- +MIICRjCCAbkGByqGSM49AgEwggGsAgEBME0GByqGSM49AQECQgH///////////// +//////////////////////////////////////////////////////////////// +/////////zCBiARCAf////////////////////////////////////////////// +///////////////////////////////////////8BEIAUZU+uWGOHJofkpohoLaF +QO6i2nJbmbMV87i0iZGO8QnhVhk5Uex+k3sWUsC9O7G/BzVz34g9LDTx70Uf1GtQ +PwAEgYUEAMaFjga3BATpzZ4+y2YjlbRCnGSBOQU/tSH4KK9ga009uqFLXnfv51ko +/h3BJ6L/qN4zSLPBhWpCm/l+fjHC5b1mARg5KWp4mjvABFyKX7QsfRvZmPVESVeb +RGgXr70XJz5mLJfucple9CZAxVC5AT+tB2E1PHCGonLCQIi+lHaf0WZQAkIB//// +///////////////////////////////////////6UYaHg78vlmt/zAFI9wml0Du1 +ybiJnEeuu2+3HpE4ZAkCAQEDgYYABAEom17GUMSer6F6Wn2z3alZrLDXwOayXWvI +eu4kYCGJkSp5GHZSF0D2uPD46rG3ZylumJlf0Dg5ngCMoRlVLQ8gcAE3OKabRXQf +QAYqUi9FVhrrkgvIq2BNJmhpzXJSap22RGqDpEIGMNkEmHUb52m1EK8nMauSUYGw ++Ss8SFDkpgGR7A== +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp521r1-pub-explicit-seed.pem b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-pub-explicit-seed.pem new file mode 100644 index 000000000000..00b9a7ac737a --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp521r1-pub-explicit-seed.pem @@ -0,0 +1,15 @@ +-----BEGIN PUBLIC KEY----- +MIICXTCCAdAGByqGSM49AgEwggHDAgEBME0GByqGSM49AQECQgH///////////// +//////////////////////////////////////////////////////////////// +/////////zCBnwRCAf////////////////////////////////////////////// +///////////////////////////////////////8BEIAUZU+uWGOHJofkpohoLaF +QO6i2nJbmbMV87i0iZGO8QnhVhk5Uex+k3sWUsC9O7G/BzVz34g9LDTx70Uf1GtQ +PwADFQDQnogAKRy4U5bMZxc5MoSqoNpkugSBhQQAxoWOBrcEBOnNnj7LZiOVtEKc +ZIE5BT+1Ifgor2BrTT26oUted+/nWSj+HcEnov+o3jNIs8GFakKb+X5+McLlvWYB +GDkpaniaO8AEXIpftCx9G9mY9URJV5tEaBevvRcnPmYsl+5ymV70JkDFULkBP60H +YTU8cIaicsJAiL6Udp/RZlACQgH///////////////////////////////////// +//////pRhoeDvy+Wa3/MAUj3CaXQO7XJuImcR667b7cekThkCQIBAQOBhgAEAFNQ +YDAsx5eQRcVyfRuynGZXCr/KgVqGZGnRlXHcUlxru1R9DnJDHEX5oIt4Iyb7vssU +0owJS1sJjOSQOQW59U0qAI2tbHRiRPe8eBHofrXk2eMRC2AF3wPo1q0gpzqZpgCa +I+EDz6U+BT1XKBl41CyUquV7pxYzNrqN1i+PdcKt2PC2 +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/ec-consistent-curve.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-consistent-curve.pem new file mode 100644 index 000000000000..36acb5224716 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-consistent-curve.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgYirTZSx+5O8Y6tlG +cka6W6btJiocdrdolfcukSoTEk+gCgYIKoZIzj0DAQehRANCAAQkvPNu7Pa1GcsW +U4v7ptNfqCJVq8Cxzo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7 +fJDiFqz3 +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/ec-inconsistent-curve.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-inconsistent-curve.pem new file mode 100644 index 000000000000..cc6616d16cb4 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-inconsistent-curve.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGQAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHYwdAIBAQQgYirTZSx+5O8Y6tlG +cka6W6btJiocdrdolfcukSoTEk+gBwYFK4EEACKhRANCAAQkvPNu7Pa1GcsWU4v7 +ptNfqCJVq8Cxzo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7fJDi +Fqz3 +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/ec-inconsistent-curve2.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-inconsistent-curve2.pem new file mode 100644 index 000000000000..e7f597d0fea0 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-inconsistent-curve2.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGQAgEAMBAGByqGSM49AgEGBSuBBAAiBHkwdwIBAQQgYirTZSx+5O8Y6tlGcka6 +W6btJiocdrdolfcukSoTEk+gCgYIKoZIzj0DAQehRANCAAQkvPNu7Pa1GcsWU4v7 +ptNfqCJVq8Cxzo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7fJDi +Fqz3 +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/ec-invalid-private-scalar.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-invalid-private-scalar.pem new file mode 100644 index 000000000000..dea4b73ccb39 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-invalid-private-scalar.pem @@ -0,0 +1,7 @@ +-----BEGIN PRIVATE KEY----- +MIGUAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHoweAIBAQRz//////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +////////AA== +-----END PRIVATE KEY----- + diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/ec-invalid-version.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-invalid-version.pem new file mode 100644 index 000000000000..408c814ed137 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/ec-invalid-version.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBEQQgYirTZSx+5O8Y6tlG +cka6W6btJiocdrdolfcukSoTEk+gCgYIKoZIzj0DAQehRANCAAQkvPNu7Pa1GcsW +U4v7ptNfqCJVq8Cxzo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7 +fJDiFqz3 +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/ed25519-scrypt.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/ed25519-scrypt.pem new file mode 100644 index 000000000000..1f0562d80e3d --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/ed25519-scrypt.pem @@ -0,0 +1,6 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjmIR4jSK1p4AICQAAC +AQgCAQEwHQYJYIZIAWUDBAEqBBCb0KYlHyJU+f1ZY4h8J88BBEDMYrp3PA9JX6s2 +aOT8782wjnig7hXgoVAT9iq+CNqnQgZe6zZtbmyYzDsOfmm9yGHIiv648D26Hixt +mdBtFzYM +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/enc-rsa-3des.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-rsa-3des.pem new file mode 100644 index 000000000000..90cdcf57defa --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-rsa-3des.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQI9fSurwMcOA4CAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECKALBF48zvQnBIIEyKDIy72IMJoZ +4q7LsG0dCSa8oGI/CtAnC9YqRlDj+paWoGKDUkzxnMloUbJkpQlTEYRHXp0xKtdP +IcCjWFqWeQjsaJUlwILNLiliVpbyW/0PLmNQmRSfvLhlZ77rRk08DyLU0mcW2zRX +DuKHuxGhdlmte7EKsNf8czch9hDXqrCLqxlzr86K0pwT40W1r32TgQdX68edluoj +acggWuEzeTTKy1BKkVtlCq63dflgRfSo0as+dYX38wxzC6O6hxKpax277ijoJZHc +QsXxi/zREa+gtVOq9D6Vz5E+MmmIIAzVrXsFe1Uj4wYb3XUSO6WnJ9GlrixqWeu3 +9lkOZOEKyyDgIY+twn06kyZBspKnXvQMMPjeiSSeaqI9LA0qpvRsxuWCxyTJ2YZI +s+xab8j5g5RKOmrt1bGtLl66tcrGNP9jYC5pjMNl6fz3c8+oxC0Bun4q+yOA9QzG +4GaiA834x+9wtsEBSjlMB5AMwYH+1ODo6Q+VUAWH1qBvCm/gQT2mvSgcrz6bcGJI +gimfzl/IbqVuVkWl7yFqNN/renE47pvy34Dbymb0FBK/5Gb1FImno3CcAkCuaEJ1 +sWdx2Ej0Ezit8v1iJN2q29xlD7MrxB0uPvklUPRlD9RVcDJ15GwBPA8ugN/Fjj50 +2BiMJ2/uqBoEnAjMyStINArS5PWL6gthIXenVJ4w0wegBciCsGo4G7UFQ0z/w2Je +7NJ8TjwKdTYJdAfgO5Rr8u6j0ybn72T/+QJfjugNLufRx4sakvPZR90/AFb2YX+L +kgCVS3ySOfom9p5JcxdnI8omelBIi1Qa9xwPKMPaV6oYkqBVjmcDDZocC6qN15PD +jCrgGryV3Fsn5OLYTB+EQDLNqmo+qd1O0pNY2THwD/DGGlx6VhmeQnWdt534g5lo +clQOmLXEeUWIb2u5PanakqNpY5mBQcOJ88/RS+oGAjTGU0e3I1zLb6EN/Ftndjv1 +sfEh+HMwHxIWxdnJb6z6m73XJr4z30VGN8e+f1lC8c9SJ9aTQ/9vH3bsaXLW6GFY +DBisBg2/+vMwRSG9PkYrp1p6rGAhwbaofnZE5zApT7PFEX2RVNPU7lgXn84ycRHw +gZ89Mpa9zShL4T1PS8BrKwS7AH/se7ofKW/s8Z9SgngTWj0Efd4hZmn/EenVHBWf +kjAkvKIgGE8FJF1QlmU5dHDFhRiUGXIaB1rYAcwwuwB06fxRqEL3pU6jkHSru3ry +sYaY/cfpd5D5PT+FlxkzAPH1iiC3knXpcotWpJ2iQshsw9ifwg/vVJB0n20+Rxeu +XTgwiT+X5mJNAQUCj6aExWUg+D5gPnJPwFmzAWBGKWrvwI+vI6zIv4MJywzU+Ei8 +1lU5rezPovAbGSTwUBPDydhORua0P8tVT8KPMmPJhza6IORTPpzdEOCXCOH17CWg +VWKjYvEul8CdNh4O3CJDU4lN8yn6RXCBPK4NKDea17GCIEBgnOnpFny+jdfNT+Ce +9aNh8ah61vbPag9EM2okmBlbnpkhUO+x8K8prZHZE7qRgUbmn1cJwIP6pNN/263q +S2uKZMnoaT65BaQh9wpgSvWmDup3/lGG/C2+m0k087QBVHMSfpTK9WcZ94BbzoeR +S9rWCU2k/woEUOv3hssY5w== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-algorithm.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-algorithm.pem new file mode 100644 index 000000000000..d79a38452330 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-algorithm.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFIDBSBgkqhkiG9w0BBQ0wRTApBgkqhkiG9w0BBQwwHAQI9fSurwMcOA4CAggA +MAwGCCqGSIb3DQIJBQAwGAYMKoZIhvcNAwcXgkEDBAigCwRePM70JwSCBMigyMu9 +iDCaGeKuy7BtHQkmvKBiPwrQJwvWKkZQ4/qWlqBig1JM8ZzJaFGyZKUJUxGER16d +MSrXTyHAo1halnkI7GiVJcCCzS4pYlaW8lv9Dy5jUJkUn7y4ZWe+60ZNPA8i1NJn +Fts0Vw7ih7sRoXZZrXuxCrDX/HM3IfYQ16qwi6sZc6/OitKcE+NFta99k4EHV+vH +nZbqI2nIIFrhM3k0ystQSpFbZQqut3X5YEX0qNGrPnWF9/MMcwujuocSqWsdu+4o +6CWR3ELF8Yv80RGvoLVTqvQ+lc+RPjJpiCAM1a17BXtVI+MGG911EjulpyfRpa4s +alnrt/ZZDmThCssg4CGPrcJ9OpMmQbKSp170DDD43okknmqiPSwNKqb0bMblgsck +ydmGSLPsWm/I+YOUSjpq7dWxrS5eurXKxjT/Y2AuaYzDZen893PPqMQtAbp+Kvsj +gPUMxuBmogPN+MfvcLbBAUo5TAeQDMGB/tTg6OkPlVAFh9agbwpv4EE9pr0oHK8+ +m3BiSIIpn85fyG6lblZFpe8hajTf63pxOO6b8t+A28pm9BQSv+Rm9RSJp6NwnAJA +rmhCdbFncdhI9BM4rfL9YiTdqtvcZQ+zK8QdLj75JVD0ZQ/UVXAydeRsATwPLoDf +xY4+dNgYjCdv7qgaBJwIzMkrSDQK0uT1i+oLYSF3p1SeMNMHoAXIgrBqOBu1BUNM +/8NiXuzSfE48CnU2CXQH4DuUa/Luo9Mm5+9k//kCX47oDS7n0ceLGpLz2UfdPwBW +9mF/i5IAlUt8kjn6JvaeSXMXZyPKJnpQSItUGvccDyjD2leqGJKgVY5nAw2aHAuq +jdeTw4wq4Bq8ldxbJ+Ti2EwfhEAyzapqPqndTtKTWNkx8A/wxhpcelYZnkJ1nbed ++IOZaHJUDpi1xHlFiG9ruT2p2pKjaWOZgUHDifPP0UvqBgI0xlNHtyNcy2+hDfxb +Z3Y79bHxIfhzMB8SFsXZyW+s+pu91ya+M99FRjfHvn9ZQvHPUifWk0P/bx927Gly +1uhhWAwYrAYNv/rzMEUhvT5GK6daeqxgIcG2qH52ROcwKU+zxRF9kVTT1O5YF5/O +MnER8IGfPTKWvc0oS+E9T0vAaysEuwB/7Hu6Hylv7PGfUoJ4E1o9BH3eIWZp/xHp +1RwVn5IwJLyiIBhPBSRdUJZlOXRwxYUYlBlyGgda2AHMMLsAdOn8UahC96VOo5B0 +q7t68rGGmP3H6XeQ+T0/hZcZMwDx9Yogt5J16XKLVqSdokLIbMPYn8IP71SQdJ9t +PkcXrl04MIk/l+ZiTQEFAo+mhMVlIPg+YD5yT8BZswFgRilq78CPryOsyL+DCcsM +1PhIvNZVOa3sz6LwGxkk8FATw8nYTkbmtD/LVU/CjzJjyYc2uiDkUz6c3RDglwjh +9ewloFVio2LxLpfAnTYeDtwiQ1OJTfMp+kVwgTyuDSg3mtexgiBAYJzp6RZ8vo3X +zU/gnvWjYfGoetb2z2oPRDNqJJgZW56ZIVDvsfCvKa2R2RO6kYFG5p9XCcCD+qTT +f9ut6ktrimTJ6Gk+uQWkIfcKYEr1pg7qd/5RhvwtvptJNPO0AVRzEn6UyvVnGfeA +W86HkUva1glNpP8KBFDr94bLGOc= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-kdf.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-kdf.pem new file mode 100644 index 000000000000..744fb32c1d98 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-kdf.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFIDBSBgkqhkiG9w0BBQ0wRTAtBg0qhkiG9w0BBQyCQYJBMBwECPX0rq8DHDgO +AgIIADAMBggqhkiG9w0CCQUAMBQGCCqGSIb3DQMHBAigCwRePM70JwSCBMigyMu9 +iDCaGeKuy7BtHQkmvKBiPwrQJwvWKkZQ4/qWlqBig1JM8ZzJaFGyZKUJUxGER16d +MSrXTyHAo1halnkI7GiVJcCCzS4pYlaW8lv9Dy5jUJkUn7y4ZWe+60ZNPA8i1NJn +Fts0Vw7ih7sRoXZZrXuxCrDX/HM3IfYQ16qwi6sZc6/OitKcE+NFta99k4EHV+vH +nZbqI2nIIFrhM3k0ystQSpFbZQqut3X5YEX0qNGrPnWF9/MMcwujuocSqWsdu+4o +6CWR3ELF8Yv80RGvoLVTqvQ+lc+RPjJpiCAM1a17BXtVI+MGG911EjulpyfRpa4s +alnrt/ZZDmThCssg4CGPrcJ9OpMmQbKSp170DDD43okknmqiPSwNKqb0bMblgsck +ydmGSLPsWm/I+YOUSjpq7dWxrS5eurXKxjT/Y2AuaYzDZen893PPqMQtAbp+Kvsj +gPUMxuBmogPN+MfvcLbBAUo5TAeQDMGB/tTg6OkPlVAFh9agbwpv4EE9pr0oHK8+ +m3BiSIIpn85fyG6lblZFpe8hajTf63pxOO6b8t+A28pm9BQSv+Rm9RSJp6NwnAJA +rmhCdbFncdhI9BM4rfL9YiTdqtvcZQ+zK8QdLj75JVD0ZQ/UVXAydeRsATwPLoDf +xY4+dNgYjCdv7qgaBJwIzMkrSDQK0uT1i+oLYSF3p1SeMNMHoAXIgrBqOBu1BUNM +/8NiXuzSfE48CnU2CXQH4DuUa/Luo9Mm5+9k//kCX47oDS7n0ceLGpLz2UfdPwBW +9mF/i5IAlUt8kjn6JvaeSXMXZyPKJnpQSItUGvccDyjD2leqGJKgVY5nAw2aHAuq +jdeTw4wq4Bq8ldxbJ+Ti2EwfhEAyzapqPqndTtKTWNkx8A/wxhpcelYZnkJ1nbed ++IOZaHJUDpi1xHlFiG9ruT2p2pKjaWOZgUHDifPP0UvqBgI0xlNHtyNcy2+hDfxb +Z3Y79bHxIfhzMB8SFsXZyW+s+pu91ya+M99FRjfHvn9ZQvHPUifWk0P/bx927Gly +1uhhWAwYrAYNv/rzMEUhvT5GK6daeqxgIcG2qH52ROcwKU+zxRF9kVTT1O5YF5/O +MnER8IGfPTKWvc0oS+E9T0vAaysEuwB/7Hu6Hylv7PGfUoJ4E1o9BH3eIWZp/xHp +1RwVn5IwJLyiIBhPBSRdUJZlOXRwxYUYlBlyGgda2AHMMLsAdOn8UahC96VOo5B0 +q7t68rGGmP3H6XeQ+T0/hZcZMwDx9Yogt5J16XKLVqSdokLIbMPYn8IP71SQdJ9t +PkcXrl04MIk/l+ZiTQEFAo+mhMVlIPg+YD5yT8BZswFgRilq78CPryOsyL+DCcsM +1PhIvNZVOa3sz6LwGxkk8FATw8nYTkbmtD/LVU/CjzJjyYc2uiDkUz6c3RDglwjh +9ewloFVio2LxLpfAnTYeDtwiQ1OJTfMp+kVwgTyuDSg3mtexgiBAYJzp6RZ8vo3X +zU/gnvWjYfGoetb2z2oPRDNqJJgZW56ZIVDvsfCvKa2R2RO6kYFG5p9XCcCD+qTT +f9ut6ktrimTJ6Gk+uQWkIfcKYEr1pg7qd/5RhvwtvptJNPO0AVRzEn6UyvVnGfeA +W86HkUva1glNpP8KBFDr94bLGOc= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-pbkdf2-prf.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-pbkdf2-prf.pem new file mode 100644 index 000000000000..7f60bbbe6c07 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/enc-unknown-pbkdf2-prf.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFIDBSBgkqhkiG9w0BBQ0wRTAtBgkqhkiG9w0BBQwwIAQI9fSurwMcOA4CAggA +MBAGDCqGSIb3DQIJgVWCQQUAMBQGCCqGSIb3DQMHBAigCwRePM70JwSCBMigyMu9 +iDCaGeKuy7BtHQkmvKBiPwrQJwvWKkZQ4/qWlqBig1JM8ZzJaFGyZKUJUxGER16d +MSrXTyHAo1halnkI7GiVJcCCzS4pYlaW8lv9Dy5jUJkUn7y4ZWe+60ZNPA8i1NJn +Fts0Vw7ih7sRoXZZrXuxCrDX/HM3IfYQ16qwi6sZc6/OitKcE+NFta99k4EHV+vH +nZbqI2nIIFrhM3k0ystQSpFbZQqut3X5YEX0qNGrPnWF9/MMcwujuocSqWsdu+4o +6CWR3ELF8Yv80RGvoLVTqvQ+lc+RPjJpiCAM1a17BXtVI+MGG911EjulpyfRpa4s +alnrt/ZZDmThCssg4CGPrcJ9OpMmQbKSp170DDD43okknmqiPSwNKqb0bMblgsck +ydmGSLPsWm/I+YOUSjpq7dWxrS5eurXKxjT/Y2AuaYzDZen893PPqMQtAbp+Kvsj +gPUMxuBmogPN+MfvcLbBAUo5TAeQDMGB/tTg6OkPlVAFh9agbwpv4EE9pr0oHK8+ +m3BiSIIpn85fyG6lblZFpe8hajTf63pxOO6b8t+A28pm9BQSv+Rm9RSJp6NwnAJA +rmhCdbFncdhI9BM4rfL9YiTdqtvcZQ+zK8QdLj75JVD0ZQ/UVXAydeRsATwPLoDf +xY4+dNgYjCdv7qgaBJwIzMkrSDQK0uT1i+oLYSF3p1SeMNMHoAXIgrBqOBu1BUNM +/8NiXuzSfE48CnU2CXQH4DuUa/Luo9Mm5+9k//kCX47oDS7n0ceLGpLz2UfdPwBW +9mF/i5IAlUt8kjn6JvaeSXMXZyPKJnpQSItUGvccDyjD2leqGJKgVY5nAw2aHAuq +jdeTw4wq4Bq8ldxbJ+Ti2EwfhEAyzapqPqndTtKTWNkx8A/wxhpcelYZnkJ1nbed ++IOZaHJUDpi1xHlFiG9ruT2p2pKjaWOZgUHDifPP0UvqBgI0xlNHtyNcy2+hDfxb +Z3Y79bHxIfhzMB8SFsXZyW+s+pu91ya+M99FRjfHvn9ZQvHPUifWk0P/bx927Gly +1uhhWAwYrAYNv/rzMEUhvT5GK6daeqxgIcG2qH52ROcwKU+zxRF9kVTT1O5YF5/O +MnER8IGfPTKWvc0oS+E9T0vAaysEuwB/7Hu6Hylv7PGfUoJ4E1o9BH3eIWZp/xHp +1RwVn5IwJLyiIBhPBSRdUJZlOXRwxYUYlBlyGgda2AHMMLsAdOn8UahC96VOo5B0 +q7t68rGGmP3H6XeQ+T0/hZcZMwDx9Yogt5J16XKLVqSdokLIbMPYn8IP71SQdJ9t +PkcXrl04MIk/l+ZiTQEFAo+mhMVlIPg+YD5yT8BZswFgRilq78CPryOsyL+DCcsM +1PhIvNZVOa3sz6LwGxkk8FATw8nYTkbmtD/LVU/CjzJjyYc2uiDkUz6c3RDglwjh +9ewloFVio2LxLpfAnTYeDtwiQ1OJTfMp+kVwgTyuDSg3mtexgiBAYJzp6RZ8vo3X +zU/gnvWjYfGoetb2z2oPRDNqJJgZW56ZIVDvsfCvKa2R2RO6kYFG5p9XCcCD+qTT +f9ut6ktrimTJ6Gk+uQWkIfcKYEr1pg7qd/5RhvwtvptJNPO0AVRzEn6UyvVnGfeA +W86HkUva1glNpP8KBFDr94bLGOc= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/invalid-version.der b/vectors/cryptography_vectors/asymmetric/PKCS8/invalid-version.der new file mode 100644 index 000000000000..f3612258e4c4 Binary files /dev/null and b/vectors/cryptography_vectors/asymmetric/PKCS8/invalid-version.der differ diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-40bitrc2.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-40bitrc2.pem new file mode 100644 index 000000000000..bf38bb25ae5e --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-40bitrc2.pem @@ -0,0 +1,17 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIICojAcBgoqhkiG9w0BDAEGMA4ECB9AcawAQml3AgIB5ASCAoD7irNdXxakUVL0 +5i77zxkcoRSXThYfMwWhp20viHg+jOn6tLQB+ZODSvGuR21iDAcK3lPbxYBrDz3h +vgAcLJPmbTQPqmcvkNXtcN2b86NMalOm24SJzjKRv1/8gRC4w2W9BY9OOagugTzs +lXfXNEf2eTx0OiTV0Nught4j6Vt4pEA4ZvLBer6a3k4/BTjm9uvwq4oRGsfeixkn +VJ27dz5ZyUmwVyzfCQww1gAAMQIX/LAPQKfkAiBuYfHHP3H/tiOIGj7Xmt3Ktknu +j1uAoNUX6/IYQwrS87HQ1txTl19p6HMqnIBncalVRk1VfkckNCILw3c9P8xzxSB0 +sRep7f0sh/JAai2CF+nSLlLsfRoPNwBO0kvJZDeXRxKCOwmjK3DdwWuKHpar3ccF +4cgS7dVK0tYur6XoqR/AqfqG8PuP6bbwZWB+i+irmPI24v+177AOYVkrUngeYWOP +VKkX8Yupl9f3jTBVP1/YSlOaXZ3zXn6BV52mPjJHGY1GkTuWJ7ZCLzSruhBVsauG +mhoVAp8AaYoIHfJHGvcZHCZvMMjINVjkkpQBq4sl/OQ+K1E30Q4Amfc8s12T+yWJ +ypn8BhmxeAy4NbAYp4gc/u61rh22nSz8nswPNyR/mMpK60Wp61oFWr7QL9ABAoQJ +09jPzumO/B9WQ6CQvZ0fNNvBfVSg3/OzhY0quznHGalJqahORtP1lcV1m5mrCd1Z +8NWf7hIA/paMntlrkgRXAB36K/AqvS563TMDPWn71Jj7bErPw+8WlIeuEs6I8265 +sQpvNvpamuxunxRTnjeXyC1x4ZU+LDZT2ZG1y1G/mGYm9nRVPkvdgn0OHzQEgD9Q +R1QRZL+9 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-aes-192-cbc.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-aes-192-cbc.pem new file mode 100644 index 000000000000..0a36a716dcb3 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-aes-192-cbc.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI8i+OtR0wbD0CAggA +MAwGCCqGSIb3DQIKBQAwHQYJYIZIAWUDBAEWBBBHvOq1294P18bekzyYVp4QBIIE +0AJnZHjPZcPYKdSNaNfPfc2s+UmTgYeLCun5sd+9KIYyozJ2ljZTijsdp/hItWTu +DmHrfLTLV8mtL/OFJ83u0rDoHVfSrDLwFMAy/nmbtlLYPFEfU9MQ8s2OtvKuobmI +b3x7b+MrTlG5ConptsQQw5tl3dza9DZGfHUnO2EzXorytSMLFCGeQskzbN7Y/Sbf +2+IL5yoifcfPddTbKDyTa77K2516tK2+WTU/VUfv2r5d5SiivZLuMjIYrbneHYoq +hW30BZozCqJKJ5G2jwNjLUjPirA6qtS0Y1tIb5rRjZ0pSy1X5oIQL2laZLrDo9gP +/Ud8m1k2nv9Uv9HPM+G4xCMSiJVaptYPyzFQACcSdA/BVUdBC0EwzIj2nbaoAlM0 ++sZ2Asbohnds/AsDz+/b6MaMKg9Onoort0zF/HtpSII6+WSmvGOaV2469JEIvZlU +JIn1YugpDPIe6/B35J9sYfvVNKVsvJntCKxmcz6Nw2VvPKXC3o/bseBqAhLKDMZZ +Hr3id3O7bN2ng3lKuGofmQeMYnW4zb4coXytdc/XCvf63xE0NsUEBFuRMpc9iocC +2RMBEzNyE4tnigI61T/zkpwgBic1p/isGoXMdPWl+Z+IAIYgyxOVwO9g78yVW9tp +1xF9WzJrGHKNT9RLmINyo3jt/wRj8Q+T0EG45cDQcHwpyXdNS614hUCIaeTvQcR9 +8F+f4D8IvL+GJt2EtbqL+D687X/hptNehpFf+uxGiHQfrtOvYS/ArNrewa1ts9nq +SMAE7Hb7MzFdnhDqRFBa+//H1jvNkDx3qXfb1/MNE8pR6vjcueKKQ0BzlrNX1O2C +oz0OCMeDfXZhWdYmNjLNcdbonrvq5Z9nOUEdw2lNWELT4lOAmqgA/xBFdQa4glCx +WS1r6DyjgTdGlPbcGugRuTcYXNx6iikWzoS1369maz+WV9qW7r8kA1Fs7WUiYnOb +I1E06yQKVANe+t2SQYN2jPK3EsFFOBxG9tlcXbZVxvx9m6XJR7f7YnLPN+b0f1qF +cT2c5IhK5pKRiZds82lWBnk+eli+qUXILIBbDvBmY4PyPk+kyewAHI1HWBfoSH/3 +aLIV6JPgwjAJKnr0++jUqETID/yGyLHNNy1u4ALyAfbFVU//RGmwAxhrBNPdVVGb +rBfKL+VL8Hu/m5XWXez0nHKyD8J1i/XO1OutBsXYxEn6Xnu9rJn8F6nJ+XB3zt6K +QdkUp85t3GM0wyizuPRWJrSVfYyjV41yEBXqe2lgqTT9dpvpgIRtvUeq83e8PD/3 +6qKoeTv+3cppCFZ3vLArGvsvRTcbfc3YEzXxz6gc/1HTzd8UpCnA/9+jepG3IzRL +1bLs8QVzIBAT/UpuC6QWUdAR/JZMEFLU5FnRh6oXuh2Zys66Ot7LyNhnGlSEPlXI +polURx0bew+QigBGiH7NpyMgRi9Wh+1HOA/wsAp4X7O+DhaX6vdiDbQoilN1LclU +TRFShpuaxwRA1ek2Jz3JLn7wCsGaVXrd2v/CgrxofCWzGjR2RWj9hAkV4eoJ3G6A +x3DhMRrqXc/O3ON9TyhKBZP1g35In5bZmBUv/o+7eYV7KDETxPwsD3A+dCqUJObU +kyZehu2DsfyZFI98SnecRpb0M0vi6ZZueCykOVec6xkX +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-pbe-3des-long-salt.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-pbe-3des-long-salt.pem new file mode 100644 index 000000000000..0ea24f106673 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-pbe-3des-long-salt.pem @@ -0,0 +1,53 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIJdjAoBgoqhkiG9w0BDAEDMBoEFGDV4FIOX7x+wuD8+HxUyvCh7beeAgIIAASC +CUilyA7jms8ptwpQ8qkRgFZUC6nM3wHnMNgTiSCPUCPJBVVQH93tKwskpjVopy/z +moLY7YAkCaOoK0hBuq7RPG7Q5Fu/c3cJygEETf5CGBBddEyu79KKBngg/RzQRBYM +0zW6GJzYhlt6nmbAg2bc6OS0pRnwdpcMlsbQ9N3t3MCS4TqnK8r7q22z/jVUc4qn +N/lXMKR1dEGq78rzSx2jWQtuL0ABePAWaRMCLtktNE4X5+INW+wfKFC6N/e7NRvE +Xyc/zRpz8vcpPo2yo7d3doCshk6GgZ+wmivpJvVX6rc738VXih9aYr1BC3OqkApM +mPL99kpIY8kGxuU+Udlz/rOW/+qwfTxitf4ztxRsaQkxtqoehEzF05TFJJoDRPve +nXmfKBPC9iweceulRXLUXrrdis19WFfYkY9bFxGc6VJCahLk3cSS4Ad2ptShEuK/ +BXfwQzo2SWjZplTi/6AtXk3RHUbhK+egBrlnHV/yb0K5XuFEPwI/HtRFwosnKpGY +WGgwgzQQp3tXjTGRKgihzW+29/mxlIaQ2o93cgYE/GyWgGT+yLuUbMZ4df+LSYLH +JWmIH4gsjYGRtbfVIIefwoWKnFVaNsLRQ2JNWYBqUMpaWUn+ynbyQ8UJcW1CT8mv +7LzhYWRHq+0NKYGXxt9auPOpdyZZ78mOvuJrCP5remHCA39idoSolR4iTxd7CRZm +4M7xxWSehOht7FAYOR3ujJpILXpTYVcec+jPcd2h18RvwsStOqkigTpJN23UEfCx +mIzxSlm0DqntDpgR3Hd35KOAHo/yMnA2FsuXf/TXL45gJJEs7LzNE8qcstHT6TvH +oznddjij8QYrRvtRFJpczS7Z3uKKWTPfx3QMskOCCXkZNa4DplEy5HkaL5GQHMGH +qsUrFpsRU+8uWt7M1ueBfnBfOsq8NG8YBqzVOtirm8qfq8KetyQdVWQLR/swBdcP +CIaaw9SiGcXX5fi8LD8wo6qsUdXZamKj9a5d0OT+FVnf/prkVicbdDZV8IuFRXSa +ADhC7ItcSELRl/IPDJcqOodIFEtaVsome1u3gxE02ckjTkOnrAOZEYb4OdZOoPcA +o8mc6bxkUCUou1P/DpYHsP93z1lhWGUPt8/aWhZ27qJ9v78ZRxdoWenlYqucRQ35 +JorySI33g2+aAKIoVeT/dwWT8mX9UYvNnJB4XqOn745jcB4TNDTQzMQ6aF076kUW +PiOYSza7E63MUp7K+Ye+wEAHzcb/QtZCyJqfav5e/h4qaqkmMD7CCyF5OXJUcJu+ +fLq2S0q+LDtX3gEE75+rdqmOZqskEBvPuArbJtQYIcP4UtbEyJd311lHxIOJTVRb +iEDkhAgFXSYrbk7cbiBl2tf331CuQNvVph0qREOu/mDDNeHSRCADv78j6Q2D8A/K +kvJohTxbk9GQA0Ek2S2Y1tOWoH6fgK8TZ9dSWncwSjZWB+lpN8+DQVWMzgVxJLK7 +fbijbGu5JJvOaw+LDiUhKesmLjjsjgSjKZa12JLMZAK5diZ2mE2mEDqS0yyfBkyR +g0rd2NO5XsLPBSA4Coknnkx4yOxV5AIg8hS8UsDAuI0s7MRnOirrVR4J/06JR7b/ +CMrVu7yF/2aYOFW64XxaaOX8jmmSdUYcGzckvQWNcEoNFj9LJa698F7sMj7nZYa/ +C2GDbxNwY5HsjV6wlcDW0fKyqz7xFCc7/EUjVeMBtuVKanxuzfgEP0e0mL9NZX0e +j1L4bJikXQw2kSJxxdfRuFloPJypKIcizaRkmZo4ew0PY1Bu3+aEcG/MZK0TBvrK +DrXKaD+H7RArQyFD0jmti/jEMhvq3nkuB4D5SAgyuExMQTHQAE3tm4y076N2qNFR +ODHdSvS34c/JhqZ83PHc16oJBkcrpaUzyyUV+kZTn8PjnIoCUow8ewKgHorULPzU +j7hYPJHvtoXemfjQvVSBKzKojaAtuV5vtTGz1Xpj2XmMrCegVBthixfKKCtXmLlb +D4/mfjTISVXDZZYMH4NHInt00WRoRus7EIJ+M3e1HScCAVejPBu0H+fwPvGUvD43 +Jz9pRmcwczM69F4XyhCUjfnqsKzcAK5fX476j6GtAq031U1jn187Oz1KiqCGonjw +Rm3s2Ok6zjlEpEPIVELDaPgErcqD7uryRbSmxqX99Avz0erzV57LFIm/zqPWAwQx +huXFl2yj94Pzp0iTxANEq3msL6syTVVCYwqnAp+4eMyPRQZH3hSj5/Y+6kt8zcYH +GMBluzeQ72bhf4hG76aYZQlcPt1UAROu31gnfmMC7xLHKXHDsNyxJzW+HuOj1IOX +YOXJOyAYXf+LF6Hs1whmrY7dN5upfBfcUWrJTn2oJ9uNtsvXo+VawFQR1K0gdpWK +b4hVJZOqmLhN987YVcaKYkm4jJ4QwA9LKLiQLEz24lMWKdaCBSZoJWa5C+sL0tl2 +bO2A7u5vcIS3++zqyt7339swffjfXihy1+0tRZVQBcOayGbHuYIBhfcAOKbn7nNw +fFgXk6G5ylXAKZitCBDJsbfOND4RrkqwJ27rR4wIDXHf3XNENBDAhskt/UH5rR+N +NaYVW7vhQh0f4wUlHRgJPxfB9Wm5uTvAr/vD7p2vuuxQDKGmD7WRY7pAQSWZpU9E +0HlsSyNYdAHTaggUvMdnlj8//ZKQ2vuXdKyXzHiNsPVFkVEdhn5WJkjM+nuuH2oI +RP5XLIrXaIc7aUbg7MH7FepBomeUHc1xFxpI/Q9trQ3g9HWi1Ce93Gm74qwiIArK +i7W5qSumrgzWRizj+4bFqt5UotuDV8xguMvrBgHw9cSsBQX0umiFpLWKMPikbC+h +FSUy7o7OBtlk5p+RIwf1cSKy+agImo2rVTuyexWXqffb3ErePy6jwe/o1oQ1wQfT +qCrmDvv8CsPOiT4wlR2uQ/eNLcJ2b+I6Doos4RNSyKwaZ1Q0NU1TUTNZ7LL4C5h4 +YP62e5pgBuzdshbp+1JBDI5gjzpB5XIA8OSh/BVc3Mpu/Au72vPosoMQpk8UyZma +PXk7WvTuf+xFp3GTNatCmkaheE6EjjHrhIcZKsDvCezLnOEKlBSojrL3AGiAFuL9 +GdSL1Qkrwwr+Ra/m/UhShsLFQ7N2zb1EDStBQ66uCMYVCFPAvbFysNq5VADK6Kj7 +2oCgLQpMQl9P+qBepCyRjb3ZiUS7KgVukhI= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-pbewithmd5anddescbc.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-pbewithmd5anddescbc.pem new file mode 100644 index 000000000000..31e35e30b2f6 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-pbewithmd5anddescbc.pem @@ -0,0 +1,29 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6TAbBgkqhkiG9w0BBQMwDgQIhYYzsDWw6gECAggABIIEyA65HvBbm8RHj7j3 +dMlCfZYpQ+xJ29e2XbrNj6aRLPMAndfz0LGaMS5CC0+DVNyrBAaqCxftFikwBItC +3fIPewrj6RjDBafxJcKpMx2dP5nj1tZk/e0RQqdCk6X2dQmlsB6P+wz0WOyFjrs2 +YMoWwaXkZcdtcmUvsrtT/Y+iiIlS6sCl1xHuaGEV06TjZWsUkL3NbZvYDc+2PVIQ +uPTDEidgOLWRz7b7k0Q1lEhhnU5VtFGHvPrfo7WDg88TXu37D2hp4GmRL3fvI9r5 +bLkMBn6R4trULqE2paggq8xW/7/RfrLSDgtlRGytOAMcvDRDQxotQgvyP6d+SYIh +xlUsl3HhsjLz8LqdaMLNIU2XqgmPyFNKiH8/Zva4Snw/ZL3LkqQNcPUjM7XqU6qk +jxH1sKIaiPE4bXb+WhhGkg91EsQz313S72tYyglcXMTkkgT5qg/ogujbimtjDvBo +EWgtAI4pNw1DYJsMh8fzbYINVn2r21OKhOaL/j/kZyn0oIrnfFEnCoQf3BrjUwnm +ErhpfDcPpq/WlmOipQHOh/AiydFj0KNVn5Z8DAxTSwnkR4jQE0ZUIMDm66g4x5hD +xdNSxiKkNVDfAARxSRkSKdyZoZgZPVLLW1PuEk+AaC/WrcU0K5eMM1HKOwUAxs3H +f5if/FPF6dI1Wn5/X0GAFWA03B1lotO6R2dthAYedjV7gSZoqKfDNhMFqdAfnZg6 +u/2yIYqcSAcmXISJlCeNjZa6Yk6GbzVdUDCPTacwonwoKeNR09guzYVGmjFBdJVq +CwUgWnQdrvc+lmLLie70d84zicLBwaAdRD+XiaRCzjVcg4XOn1pTd2aRzeTQP6Pm +rBCcgobD1VcTN6SpyDbh9xep0IRvLJqtVrA0XJoXwSImkHUNFPPUmHHaFpOx2CJL +bUj/QuYRKiTaNG78YEx6LfNdldfYKGcQs7ntqK6LJ3ZI6ll4mMEU7KoAIx9HLe77 +HQFpBE4zCOfGlmtkUv6iDyHI3T/KE5xIx6SjxQC3LgbK0UyJLXEGabv3mXNwOixm +ixj+pmlSeJgZodIJ0ySEOZealDUqiPZ5OLJA9S1YUzhkNwWqgBwBVVWrh+ZptiRy +kUXZRHd9RLM1I80jQavaWtclqbjpKcnsVtZgKTvL0nCQ2obeKkhkrGmW9gZ75mW7 +qZ6ZhAEEM7Woq8x2228e9SY2sLUS1F8HX0gSxgeZcj2Pu/yNVEiueUbA5sDHny8w +nCKcFO2YoJCuy9N5vSGI4oLrfkhYLGD4T49WWHbQ1FxiuXJyHyMvOic3+nvbSXg9 +CV8VDTJt0M+t2cfQI1HXtDmtzzZBad2BTcWkp8bK+uaknIkWcvMrOtCQv4s6Weev +9IfDAbVSFTziZhTQkOTMOQl7bRvJxdDWvIU6vJPbvU/1G/k5aT84EgSodsQOiRWP +GCPtd1Ky3r0/q5Wn9YVWLZ1J8lojMeZXRb30rb702Fr88E2EpEXm3LRDtWa04y7M +pzuzrt3wzjG4FtTG6M8i4iZuFhAiRnbpcQAYJzpsAnd7CDhAl1SB8HaUTJAkdP6L +dTPRsndlkMtkW+mgGjiQOh8DKLJyxIsPa42ZSDMxf/y+DVBGAWmdQnlI9R3ejfw0 +FQtb3ngwMaEa63Eiag== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-rc2-cbc-effective-key-length.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-rc2-cbc-effective-key-length.pem new file mode 100644 index 000000000000..d4a81d9414ef --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-rc2-cbc-effective-key-length.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFFzBJBgkqhkiG9w0BBQ0wPDAeBgkqhkiG9w0BBQwwEQQIdEQTzibS3T4CAggA +AgEQMBoGCCqGSIb3DQMCMA4CAgECBAiLFADJWNVGFgSCBMi91CAKKUhya1MYkBAq +nC4LyUuOGpXVBmanWPw2pWB+DteSIzLGCj5X4uzZNLAEe7L5aIx/yxquKGdI75sy +a0Y2zayjoXim+9eql704PCgOYZRACzAefj9TdONFWKPnAdpazxrqAIHxA8/Vx4Gf +XnFVKxUVvpsyqIC+gfyqyAUO3BV2DyQJb7nkbhjAchYIvzG+Aiu8o/MEwMKHxxyk +k7jCzSsX6KZ/FZHFpMtsT0v+7PdGX0/qmSE2nv4NPGu3zFhveJ26w9Xb52ZL0frY +yvZ4IAf6e27oKnuLM7bEOPqDcgPSzR+1ky42FhWfqTQYIQMQ6rQliuFTe5DMDGI/ +FN+syeOnmYjMPs0LlP/4wOWP4Xk/vx1dqBe7Xqitqm7Wn48sTIV1vMpdTZU1U06a +Dlam4FH5tHTB+jaGGwFWTmm95pv2XCKRlfYlJuOe7wEnYtx+Sq5BE4gaFlyxWH5+ +93KfFqXg7ghv+E818vqHsi46tNyh8kV+bgQGGxiIRogI7aWgfqdO10EUsUt/DTvP ++PKWB37sdwi1OaNw345CLEz/2E0466c7xfKE/lSnwsN1Ng2t0eM2aRhHA3jIWhoX +BVtDegBSVTYRUSYJo/Bh+4YecxX/NyK9eUYUaMF9N0JC+Sz0y4cO9244yDAOfREM +UiiNruEMJ245z3NF0KsLgGAJZyMClraTCJfKHgCI9JDNA9SKnoRt2XT6jnVwniqq +wnV9iiR87FQhyJ77Sr5xYJRRBXSgCJLW3IY3SRGUJH5Uxe9VtMX6kxv1G49WppjK +QHq3yn3SBYyukPCVgma4V8FPr0PsaHMlq+crk8S3pS4/nBgruMfgKKx+R+fr/T1k +Ro4jK38E8kfXOX3YKckzb2C6UXqzZ8/5fKFW9LJ0gOHdwQb18e+R/juTPepOhOlz +2oFrWpk48WnahmeHIcP0AdnRsc+HwYHwUNKLByi09zzv1x27OBAXTgwsGtZvdv/3 +8O3dElMfj2AJLGf+nu49tuikAHTJgIn5qNLiY/Mt266t66HKzJRwJHitxQskUg8L +h9eqMw5TmkD9S3u0p/T19z7r9fNrIpc7ms65ehHIbo1Zu4SomMMT07Aqkm/nrNUP +paCqQJhySXCyI0mVPUilz9iZEVWQbeyB1BYrvUIxJBOdq4/2PKJMpR5TooNdUvAQ +Bboj6XBDTLFWpcvviGwg/Bw2TijOI6mkXgMNLohRqpNdA11lwkKSgYLOydAf0AQs +xHAqkBhWlJT3vkj4PcTQaD+wjT4HzqSLN2NASdcTqYpS1c/IfGiesb5c5VBPdszN +cZ7nD0MxG+pN3aCFilM0PRvUxEryebwVOfDVXwdUiMv3qu051LKfejxkH+7v2lqD +DPAv82WrXENz8mIDHrjo7FjKcixBAs4v8zLI255J8WTZvnKlJtgvm9WTw9gLVrs+ +bptWsyTb+90XRAfnqHOcjQrZtqhZzuxor6+G38FOW0X4fmrcqDQ1KWP6RkLY/VCe +vlz76G2Hth6iGCsJAzqT/cF+wpQoj6Dpbe1SjLgSUMv9SRQug0QO6AWJnGW2ccNr +qI/3QtnCuY7Dwu+WufvB6sksEBpQRsZTDnsov6Ss0nVjnWfTRLIb043v5p5ntx8d +OSVmpjnO63mLBaQ= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-rc2-cbc.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-rc2-cbc.pem new file mode 100644 index 000000000000..6e2d289d1ba2 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa-rc2-cbc.pem @@ -0,0 +1,31 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFFjBIBgkqhkiG9w0BBQ0wOzAeBgkqhkiG9w0BBQwwEQQIdEQTzibS3T4CAggA +AgEQMBkGCCqGSIb3DQMCMA0CAToECIsUAMlY1UYWBIIEyL3UIAopSHJrUxiQECqc +LgvJS44aldUGZqdY/DalYH4O15IjMsYKPlfi7Nk0sAR7svlojH/LGq4oZ0jvmzJr +RjbNrKOheKb716qXvTg8KA5hlEALMB5+P1N040VYo+cB2lrPGuoAgfEDz9XHgZ9e +cVUrFRW+mzKogL6B/KrIBQ7cFXYPJAlvueRuGMByFgi/Mb4CK7yj8wTAwofHHKST +uMLNKxfopn8VkcWky2xPS/7s90ZfT+qZITae/g08a7fMWG94nbrD1dvnZkvR+tjK +9nggB/p7bugqe4sztsQ4+oNyA9LNH7WTLjYWFZ+pNBghAxDqtCWK4VN7kMwMYj8U +36zJ46eZiMw+zQuU//jA5Y/heT+/HV2oF7teqK2qbtafjyxMhXW8yl1NlTVTTpoO +VqbgUfm0dMH6NoYbAVZOab3mm/ZcIpGV9iUm457vASdi3H5KrkETiBoWXLFYfn73 +cp8WpeDuCG/4TzXy+oeyLjq03KHyRX5uBAYbGIhGiAjtpaB+p07XQRSxS38NO8/4 +8pYHfux3CLU5o3DfjkIsTP/YTTjrpzvF8oT+VKfCw3U2Da3R4zZpGEcDeMhaGhcF +W0N6AFJVNhFRJgmj8GH7hh5zFf83Ir15RhRowX03QkL5LPTLhw73bjjIMA59EQxS +KI2u4QwnbjnPc0XQqwuAYAlnIwKWtpMIl8oeAIj0kM0D1IqehG3ZdPqOdXCeKqrC +dX2KJHzsVCHInvtKvnFglFEFdKAIktbchjdJEZQkflTF71W0xfqTG/Ubj1ammMpA +erfKfdIFjK6Q8JWCZrhXwU+vQ+xocyWr5yuTxLelLj+cGCu4x+AorH5H5+v9PWRG +jiMrfwTyR9c5fdgpyTNvYLpRerNnz/l8oVb0snSA4d3BBvXx75H+O5M96k6E6XPa +gWtamTjxadqGZ4chw/QB2dGxz4fBgfBQ0osHKLT3PO/XHbs4EBdODCwa1m92//fw +7d0SUx+PYAksZ/6e7j226KQAdMmAifmo0uJj8y3brq3rocrMlHAkeK3FCyRSDwuH +16ozDlOaQP1Le7Sn9PX3Puv182silzuazrl6EchujVm7hKiYwxPTsCqSb+es1Q+l +oKpAmHJJcLIjSZU9SKXP2JkRVZBt7IHUFiu9QjEkE52rj/Y8okylHlOig11S8BAF +uiPpcENMsValy++IbCD8HDZOKM4jqaReAw0uiFGqk10DXWXCQpKBgs7J0B/QBCzE +cCqQGFaUlPe+SPg9xNBoP7CNPgfOpIs3Y0BJ1xOpilLVz8h8aJ6xvlzlUE92zM1x +nucPQzEb6k3doIWKUzQ9G9TESvJ5vBU58NVfB1SIy/eq7TnUsp96PGQf7u/aWoMM +8C/zZatcQ3PyYgMeuOjsWMpyLEECzi/zMsjbnknxZNm+cqUm2C+b1ZPD2AtWuz5u +m1azJNv73RdEB+eoc5yNCtm2qFnO7Givr4bfwU5bRfh+atyoNDUpY/pGQtj9UJ6+ +XPvobYe2HqIYKwkDOpP9wX7ClCiPoOlt7VKMuBJQy/1JFC6DRA7oBYmcZbZxw2uo +j/dC2cK5jsPC75a5+8HqySwQGlBGxlMOeyi/pKzSdWOdZ9NEshvTje/mnme3Hx05 +JWamOc7reYsFpA== +-----END ENCRYPTED PRIVATE KEY----- + diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha224.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha224.pem new file mode 100644 index 000000000000..8ed64603f042 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha224.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIur3B1wRZWJ0CAggA +MAwGCCqGSIb3DQIIBQAwFAYIKoZIhvcNAwcECEnKPmr6wiNuBIIEyKNZuEXIk0Eo +AC7KnJWaEhSDsr4zte/uGDTeOGRVT6MreaWUH3i/zwHXsavEBsw9ksLYqxXsIeJ9 +jfbn24gxlnKC4NR/GyDaIUBnwGlCZKGxoteoXBDXbQTFGLeHKs0ABUqjLZaPKvNB +qt9wQS+zQ8I6zSQyslUfcDr3CZNgHADdmDFiKisAmT1pbtBgPgzmxLNSmx9C1qwG +ejuZ/SJ0YYAdRPkDh1p2yEiAIfRVFTgWcjltcd69yDk7huA/2VCxWJyVDCGrEnlm +UJyybUcXXofneBp/g0J3njaIbIftmYIC+763EKD/dqVIRXVxrkHyYcvZ2nVNUT73 +Uflk+JuHIjTO4jHXiPcaPdAEPLeB2D3Geq5ISYOvTzOeurfD16Y9hrN3IHi9gedm +JTcEPkAx2hcb19h74XlV5tcQ5ImsPgLRl0euODN07+nj14AFxCQhuoGx+Yj04NkK +dV/l1rLsbmLiqr4n+y5ezGr0GJARVinLCBehptzxaipXPzRW71IQSddbtlSl1rz5 +Npv0HlwGgwTacv7T0ZdWncaw0VjxjXAwHBD82fCiuH3qZAXEa0M4drxROeIncart +MIky9qIRjfImr3oh6GLxNBB3FEFFf+23CO+Qt3vrh0j8sVYn3cpbgHcqv0q4fca7 +Sq2okw4RjxcDHyLgWiR20tUkqJT8FYQr0u0Ay+LT2YVVO7+EQVqvlraQcOS4Fkfa +Vnggn6sdyhWWCV1rab0v81qZYBvRoUK/ynICKCbXaJ8d1mirdNGgs3FxpVAiUPZ6 +LYZ21Uwtj9OoeEQ06GPKq60xHjUmTsNiEkh31AIlSAgdsN/0+pUiD6f1lCWfiLUi +8MuFUDXqkqXAvnJW2/mKrLvcx7Ebm02rkNw7AdAnUnEx9BGxD1B0TVZtRid6mPSO +kXv7adNyBH7qoI9vGGQ1ptNRcNxhxqgGgtfwI+0mV6P6G8BJMl8urZYN8aAC7dJX +/k9EICTUcOU6nIyFFe8tk4kkcjdo9BNkgB4JjANT4ptR2w950tYVqDMHBm1eKPBC +bL3SnDDm4Cplsy7zAdUPsCe7/Zk3K2SJwUj/lDUTDGCTtq4RplfDEBWb218XWgA6 +rHgi9/EFH3YCZM8EiE9Mnx9UafdnfKhk3tm3I5nKo56C54os/EKL8W+lhXYdK9dz +peehTsjEQjF0/1OE0097XlCShP8E0bdluoFkD8mKYC7mGv0muJLuHdGMEaCKzKoS +LBKpZNYdOu2wlFfCkf8zSWO4eZYKbSUL88AoEM7A/kquQsQnb80FkciPFazlF9lb +ihxh3YD+TNH58zpYvqgOZkBflW4kKIYbyWOm+ARMq+eVph1aNKMdzeW7Gmf1Fab3 +SQmfuEBAfS8u5ghW3J57q8gSJSGB8bpYWAmNGGeQE2g8C6HTxJ34kU2HoFLo8a1/ +cqrExWl0/lkhwqc7PpvJbKIMxVOOXtVMrzG2XBCkfQSmtwwOqH1g6AZv+6sXyLZJ +PmvQ+R/23+eDqp/lymz0G6F6B10pldgqt5FHYxGaVEp7GIx6L+GtI6G2qGxpHJA9 +x//r3gdd21Fd6y7qHYOLO4fEYAe2sN0mJVjxFLsg9AhCzfxKEHsit5LMdTkGFRG0 +XGP/QsVNcWJaYyaKTXaTCQ== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha384.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha384.pem new file mode 100644 index 000000000000..0d1b587f5b86 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha384.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIYFcs8Uhn2poCAggA +MAwGCCqGSIb3DQIKBQAwFAYIKoZIhvcNAwcECKCBLl+C+3nCBIIEyEnIPlXdh1e3 ++cnyhX7dCRzR/NsygcRBJUPdwRUMAaOo/t+oZxFmHnblchxQ+pFoHrI9GVwg8uID +meEHlzSSKt8kOTvJ3C148jRFJy61YH6k5GEN+z5ihS9uTszaXRUlEsGfP1/SzWY9 +ME+pX+0kwJ4az87mYKyNUwK4U5d65Ic30pvRJc4unvFtRz6wtwqU+EV283pXHfyc +VNgQFjb1IPHEz/PSuE9p94mQvdIbVmuK2dRiMag/HcABvVhxzLldKyEHHhrHR0pa +gc41+3HVjz0b6RPE24zNrxA9bU+1URGwlkIlh7Jpc/ZuYRj6LQ33xUdYZcMZw0b4 +pSFJcUgX+GUXLyWLqhIxxc+GIeL2Vt5G0ea5KEqxOvSj2bJV2/JA0KtmrcIjX5Kz +d/9bAvxatcqIikVNVkQpUc1glKiIBfVrmyJ4XUlX9i5F3cgl18zrYUI4zPSBn8o5 +yxSfCuIMx+3zS4BiyugGNOclIbpLMjQuMrXxrt7S+QlXfdbXvyNfxa3qfqf7/P2k +ykxl0z1bjvkck6XoFGXdb13isUEtY2NjujZKZe55BLGqr7FsIIQSTAHilwMpK+CV +fA1EL4ck1+7FV+l8fJ0nN1Li1xOnDeAFuO2m91uibNMYPvRSoX9c+HQKXCdGfiuk +5tfNaq8bbXeIJ/P8wTjMZqI2l6HZRuXvvmRHN2zZ4BSsT3+61xtvSTISEimDSm5T +hYY583LG5lpFoOC0Y4EUw/ltmQpKW7AGkLg7SyC9oKvoeWM4c2t8HrL3iKPXtkwd +A/iEfZTxzmR57u+ZMlbws0evPiZQml8voJnuT6qwbos7g7V/Pc3Rj+b84JZcI2Jz +D89/VudIHfFDTXC/gcSRG4bd0glILJHT9FOCAlX5TEuRyeWasoVOV+m3Pi8vQM1u +tCsjE9UdoIdhoI5j94VhzHApdD4fePcQW9DysYa2R10gWIZKUvhUHH3FWLR2X2gK +Wiz5YkhEGXBRtDHd4cx8EM1bJMKwFyYXjXTPGfGlGiPt8b9u4F++IlsKcgGgPIvh +2rIm4jHuN3LRRlFkJ5B0kuOOxZ6GBfxasS+Ix4DZoIfqZsGNI5Wu2ikGZOKxX7Ij +G9RvcdpVV8C2Y+M9qI2+x93WAtQ+NRJo4/+gJ0O9bVUhjjAmIHu2bMtbvr9aPJhd +OpB9VQxB3c5mEXkNOV52oOGnIGVjbJMb4e3/MRpWtTFVcX6r200Gn6Hn3MnWZXdd +H7pOpAowTcTlFcbJ0WWjfZygj5HKKUOFzPYNnXKizjzQhF6yK0mphKFY+8tpFQqB +mV/1HlWJTSsAmh/FN21B2qq+KRiwMdpzKIEKC47mK+dzzo1mrTqmExvbiaLG8upr +KMb/lEnSCasiZKTh71J3+5vUE+Nw73rYNZcdh7fj+GBK9KJ3hdKwYc/9yyQx1Lua +4aXnUM6vQAsV+OLYNQE8vXMRtuftbPbV9sqiBLPIc/0P2EJ9mbEye8FM+koHUCKo +xtJe5SK36DMwAas6tjimouVgWTcAdbq9r8jQlCJ1WxXPUcCJdv6pFQUGKQ+34TMK +uWOhErUNRdqel9DthU5ig5dZs2DqlzbRzWYosZc1B6Q4/nua2JiBi8IeqtPILr2a +JYJ9DNzxn07lcFHiVgrJuA== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha512.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha512.pem new file mode 100644 index 000000000000..dd9897b3adcc --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/rsa_pkcs8_pbes2_pbkdf2_2048_3des_sha512.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQI9z8gVJbtqxwCAggA +MAwGCCqGSIb3DQILBQAwFAYIKoZIhvcNAwcECCQqQHRFeFdeBIIEyMJpY0A21GrC +pKBL07F7zOyuFIdwQT2f0wnL6lPWvUg02M2jlHCLDYlciCeUhE9fHUDA67814lvM +dlZ8KgCsp+2mqkoZB/hRvvS+ZdUqkwSI1J3Wt5hz4dKq0cebJWpDAcY/+031+zTU +9iCshfsWAGdlcAIBZOEXDwejNfIayp5cFKvQqg7kmED+KN71QmSVmVyKafh5m0SC +2Y3CoZTQ1982VImx4ZOfh+r86XNkrKLj3KYC1K6DR64Uwq2yLNoypTjdUig81ste +Dhqm+0YXVN4dxXCLF4desKWxN9v78VmCuHvYkRyunj9Q43GVp51cMQfFRBLWIqnB +OrT8k020lne0MxO1xju2sr3GWA4Wn6MLqrxSdfTq+P7ZYcSh2BchkDPslxi5gNPS +Hv5o28rkVW/K34UQw72Kur5JGMRNwJpye2rSPUbtLKb0z81nPzJMP+BCl9DttTr2 +zDkkn/AFBRuKH0uWrKv+9f7FDu4hxsdFFnLcD6kWlX/V37b5tYAcy9Atd7lykw8F +K8wAoYZHyzYaIR5otYV5XgjMcw+z9U+5t4ouXSYght88Y10Tq1IYnIx0I55KaV44 +uCdrptsKnXXWvIux8h8p/SUwvJOrECc/nYxyfS42diH3V3VGV78fw6n74nDOYnLK +ruIASg92TXUp3Qd8xdoiqdTfx8ZCgNy0mmrYycrP3cUciAYURuKWjjdTN++fk2Vx +Rw1KTFgTf0Z3dxEMIKDHHDiGUbO9cE8oEMWCv0YJ9n97suoIN3vOcifxG/93RE5M +1xe91IEY494/DdgsMqb0D4T0G5rbFHnNY8bTDKIDpvZKzcbnm9vnxPi7Q1S1kkJG +230apDz1Rln0AFO51SAVS8QoF5wP69cL9vrC5miVh3mwqkDVoHnLNpJrT1o/XcVR +Jl1j1t9lgFNJhVTltTPza4FydXRe2ZBCNKpDci1jFtD8KYZGOCc+PQtJ0Wtcx4qJ +KVGO52gUT+DSxmaKd+3RyG7MsDw1CPT8inHkACa2G+GGQvqukbjLppQDkvmUPkTa +fEotMYqnlvqznwiWURl962lyRJJsxClC6Q9R7Pe7pxohsthIHgZFMMuECenUdhYj +3TdqtKKdbShoF2SBnwYUVScH2VR2ZE8ZLlldNIA+WswG4x242NoemE76JC6DyUQN +WaxFLL813TmiLYtRq1QZsiqCqr2jRBMJA4cdCt4jMZXpLd8heviNtcPmf6uEpHV6 +VBQmun8dCQAUeCHKsrkOLnAcnrIl9gPlyR6qVAI8tnfs4IezjnvAh7+cN8cQ1AZw +xRvoAHJfR7GMT7Rp/GTLrSYU+swlnjrDLQ7DwZ6seOVyzmKo1zRjysQ7qF5m6ELp +hlu6ED1/VZZw2kSbv6BVzYmWHCGnuyl/n9zXImMR9vcM/uTogjc/38F4zBlSyz78 +wHy4EWMn2jWyRYYFfwwLvrxmU1IHkNUKYfaM6qeq7F8R7cqbZhZ1cCrAGcIhPrPy +ig7iEmTblRw+ARmY+cjUuJtbU/a38kEfCMIbKKnUg4vUnO6s2XCGG9TpmcLR1Ti/ +80tOsEuvg5ZJB3FFGHhSH1gDMAKQwCkcP4wbP/YhzBhq9WU24AA82RtOsFV4xjFV +ptyV+PmEpJl0DpDeIv0I+w== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/unknown-oid.der b/vectors/cryptography_vectors/asymmetric/PKCS8/unknown-oid.der new file mode 100644 index 000000000000..3e276c1b0d9e Binary files /dev/null and b/vectors/cryptography_vectors/asymmetric/PKCS8/unknown-oid.der differ diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/dsa-wrong-version.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/dsa-wrong-version.pem new file mode 100644 index 000000000000..bfc7f50afe7a --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/dsa-wrong-version.pem @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBEQKBgQCHc0onPSpqqR3lE69+wLgJJ4LISkPPLwxbPnO1mSJNnjhvucXC +NjmetDFkPSO2R3MkruD4MCLkKlvIEnIhH8pG32R7GNHLubIp/qcjRJ7NXtS5cG6p +LU4I1NWlekKUBAjQP2plM3U81Ut3JM39qGYZTM8NPGH0uWTIFn8PpVEzUwIVAIW0 +sPS7m+gJzXCJ6brM/y4iSyzxAoGAZOMeOwOLp3iOcd5AjbXkdDIBSggMQeHbkD9f +ztMLhhxLaMvygncP6DOIxpmC1LU+APB+DSqyIwhm2ag0Fuo7QYpF4nzZGeX7VWem +WnGgcKSzkMStlGueW1lnFkrUcRk8H8IksuZtxiNSgDMvPsxRxLx9m1pulbNI9Izh +QDkxDAACgYAP0fYZ4Nytae+Xm870Q1PC6kkI3DHKLxnJEudKqRzuMvaa5DauXC30 +L2Ifb93GBciTKPd/LAK6EcVnXiIgp/U1eTqzgNjzKAjJRIRBg70a2tbYJ71dRHOW +FqdGw3uIr1Hu9IZQk0qzyS0WP7ADXmhCsAqHMiCgwrHy/CYIo950EwIUErkN0hjz +Mf7Jz8+drwf9tboRi44= +-----END DSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-dek-info.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-dek-info.pem new file mode 100644 index 000000000000..4ad88b207573 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-dek-info.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC + +18phyq8pG3Tgov4rWiT0moaDbzIOk7v4/4Jnw3sc6IuMFmAYnIKHRs75hQdlFAxG +uSXcAKzCzjhkzgSNyNaJ8ZgeDM+DskDTA109iQWCeSxKZkuHBm2Xux9p7ynEhrMf ++z0Dd5W36KRPs0PRwVoUAv/AYaLizBbAXaEx/e21uDB2cVnA2EhjEXEz7KZnqTWm +qbSEAv/IJos1Eh1IvLupxh5naaRxfrHZgKu638ybxuxzJx+zn2DeB7g9uqVf3lCp +B5bsoqumIhxBmIS7pKeWIq+GFVQuuHcDozRVolFuUvMkPdPfaGQjLI+ynaAfA9WH +MULcRcBL+S8cp4xv8jmyW0n4Elak0ixw1UJLjeSrIGYLB+ZkYXPiUjhYZPzbKzdE +rLstyGfFXH8Vjw6921P6iVH/JvskF9aj4NvYyZqxo9YznIN9nI8GWmqJgLyIYHET +Ur5mp1/O+KGLWMzfX09/fUVF/mXBibcnJ/sixGCH4yNZR5kpnas6H8SmaGgKE1zk +KYeuicGHm6nZ/uyjoL/AwvbUL1y9tHJ0vn816cCRdJ4ELZ5dotGPREPmkWzjv08A +ZeTmdsgsGuUY/5mKZdIqlWCgrSKaZvS81+5tYgf0qMLBsAbLPDJy9kzTwCsEYxmh +x9QxUeQ/UWVsMn6JqeBVp0B5z/sLcdx6GkFVGs9U2Al3aykVhrVq+0RUiYafluod +Mkz1AczAxFtqdgaQIJbrwEAXoMc8/l8dunbuYoRuuf1y259U61aTm6wcknnDUZKs +13sDVdcRZq1Lc1JI3B586Z7Jh0r/4HPiK1zearKLBPKZA6kEj4RzG3GUQVPxzpoD +NDP8FxVgMy022+gylWr2EwZ/QWigIKeop0qRCeuPgju44Fvf0Z300GmpIwOjsPWT +Ksmqw+erTT2UcN62z4+J0TvL44T9wpWbPcyxOe1r5HLpRkkBebMPNMlPZ4WGagsz +jn0ctw7GwsJbKgyqturB83ZfuJv9lGkrXHOjrjeQNCebYDmybHl/aag8BKKYOiFW +MkHmda+Jmq817aqcwVedMKs4CwdrE6frp2wgAIngzCILLVfyTa8v5HxpkezpKS3p +Lia3/xkSrJwzd9ncNe43OVDlFbTE6fm/ycES8vhvS2NotuL/gZ9WpLOFPKCFl2CZ +Cg6CUlTngEevd8kUrlt8BIEOPyhWqZOkxb1Q+Jr7PUQjgjQXmuxYoZ647xOYdIbd +RQZd3oEFjQYTXTT7hHOuB+FehaJPEfIqJDIxVSs0gVhETaCn7L7jcq3uko3W2IpV +qbVYBDv6+ae6Ia0dSTCtWGmqj0heIE1OOtMe7do0RijeeUz8snn6N7GYxVsQv+dg +0zeV/2RdPz/N898agdJZywjCUwxVPIKXl4MpFEy79rhGBq7q8aImDRlrdMZNy9BJ +nARaiDZ0ifmdh+smPWj/WuiAsYnuJBEFAQ88xECHbSXeJ6+Y/VS2jaJlMtL2tObW +mB/vq+Kfj6yfMxYaxtjOIpqBQfGZVlNwkq9BEeEwUcas5QBrRktUS5taU3/FlfyC +P3DsU4vseQILnqmEty7TWdHqw3up3Japzc3cTP9h4xxXuux+FmRuVdq0lfSPXB5E +-----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-iv.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-iv.pem new file mode 100644 index 000000000000..3e1dee476fd7 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-malformed-iv.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,zzzz + +18phyq8pG3Tgov4rWiT0moaDbzIOk7v4/4Jnw3sc6IuMFmAYnIKHRs75hQdlFAxG +uSXcAKzCzjhkzgSNyNaJ8ZgeDM+DskDTA109iQWCeSxKZkuHBm2Xux9p7ynEhrMf ++z0Dd5W36KRPs0PRwVoUAv/AYaLizBbAXaEx/e21uDB2cVnA2EhjEXEz7KZnqTWm +qbSEAv/IJos1Eh1IvLupxh5naaRxfrHZgKu638ybxuxzJx+zn2DeB7g9uqVf3lCp +B5bsoqumIhxBmIS7pKeWIq+GFVQuuHcDozRVolFuUvMkPdPfaGQjLI+ynaAfA9WH +MULcRcBL+S8cp4xv8jmyW0n4Elak0ixw1UJLjeSrIGYLB+ZkYXPiUjhYZPzbKzdE +rLstyGfFXH8Vjw6921P6iVH/JvskF9aj4NvYyZqxo9YznIN9nI8GWmqJgLyIYHET +Ur5mp1/O+KGLWMzfX09/fUVF/mXBibcnJ/sixGCH4yNZR5kpnas6H8SmaGgKE1zk +KYeuicGHm6nZ/uyjoL/AwvbUL1y9tHJ0vn816cCRdJ4ELZ5dotGPREPmkWzjv08A +ZeTmdsgsGuUY/5mKZdIqlWCgrSKaZvS81+5tYgf0qMLBsAbLPDJy9kzTwCsEYxmh +x9QxUeQ/UWVsMn6JqeBVp0B5z/sLcdx6GkFVGs9U2Al3aykVhrVq+0RUiYafluod +Mkz1AczAxFtqdgaQIJbrwEAXoMc8/l8dunbuYoRuuf1y259U61aTm6wcknnDUZKs +13sDVdcRZq1Lc1JI3B586Z7Jh0r/4HPiK1zearKLBPKZA6kEj4RzG3GUQVPxzpoD +NDP8FxVgMy022+gylWr2EwZ/QWigIKeop0qRCeuPgju44Fvf0Z300GmpIwOjsPWT +Ksmqw+erTT2UcN62z4+J0TvL44T9wpWbPcyxOe1r5HLpRkkBebMPNMlPZ4WGagsz +jn0ctw7GwsJbKgyqturB83ZfuJv9lGkrXHOjrjeQNCebYDmybHl/aag8BKKYOiFW +MkHmda+Jmq817aqcwVedMKs4CwdrE6frp2wgAIngzCILLVfyTa8v5HxpkezpKS3p +Lia3/xkSrJwzd9ncNe43OVDlFbTE6fm/ycES8vhvS2NotuL/gZ9WpLOFPKCFl2CZ +Cg6CUlTngEevd8kUrlt8BIEOPyhWqZOkxb1Q+Jr7PUQjgjQXmuxYoZ647xOYdIbd +RQZd3oEFjQYTXTT7hHOuB+FehaJPEfIqJDIxVSs0gVhETaCn7L7jcq3uko3W2IpV +qbVYBDv6+ae6Ia0dSTCtWGmqj0heIE1OOtMe7do0RijeeUz8snn6N7GYxVsQv+dg +0zeV/2RdPz/N898agdJZywjCUwxVPIKXl4MpFEy79rhGBq7q8aImDRlrdMZNy9BJ +nARaiDZ0ifmdh+smPWj/WuiAsYnuJBEFAQ88xECHbSXeJ6+Y/VS2jaJlMtL2tObW +mB/vq+Kfj6yfMxYaxtjOIpqBQfGZVlNwkq9BEeEwUcas5QBrRktUS5taU3/FlfyC +P3DsU4vseQILnqmEty7TWdHqw3up3Japzc3cTP9h4xxXuux+FmRuVdq0lfSPXB5E +-----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-no-dek-info.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-no-dek-info.pem new file mode 100644 index 000000000000..39c0589af00e --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-no-dek-info.pem @@ -0,0 +1,29 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED + +18phyq8pG3Tgov4rWiT0moaDbzIOk7v4/4Jnw3sc6IuMFmAYnIKHRs75hQdlFAxG +uSXcAKzCzjhkzgSNyNaJ8ZgeDM+DskDTA109iQWCeSxKZkuHBm2Xux9p7ynEhrMf ++z0Dd5W36KRPs0PRwVoUAv/AYaLizBbAXaEx/e21uDB2cVnA2EhjEXEz7KZnqTWm +qbSEAv/IJos1Eh1IvLupxh5naaRxfrHZgKu638ybxuxzJx+zn2DeB7g9uqVf3lCp +B5bsoqumIhxBmIS7pKeWIq+GFVQuuHcDozRVolFuUvMkPdPfaGQjLI+ynaAfA9WH +MULcRcBL+S8cp4xv8jmyW0n4Elak0ixw1UJLjeSrIGYLB+ZkYXPiUjhYZPzbKzdE +rLstyGfFXH8Vjw6921P6iVH/JvskF9aj4NvYyZqxo9YznIN9nI8GWmqJgLyIYHET +Ur5mp1/O+KGLWMzfX09/fUVF/mXBibcnJ/sixGCH4yNZR5kpnas6H8SmaGgKE1zk +KYeuicGHm6nZ/uyjoL/AwvbUL1y9tHJ0vn816cCRdJ4ELZ5dotGPREPmkWzjv08A +ZeTmdsgsGuUY/5mKZdIqlWCgrSKaZvS81+5tYgf0qMLBsAbLPDJy9kzTwCsEYxmh +x9QxUeQ/UWVsMn6JqeBVp0B5z/sLcdx6GkFVGs9U2Al3aykVhrVq+0RUiYafluod +Mkz1AczAxFtqdgaQIJbrwEAXoMc8/l8dunbuYoRuuf1y259U61aTm6wcknnDUZKs +13sDVdcRZq1Lc1JI3B586Z7Jh0r/4HPiK1zearKLBPKZA6kEj4RzG3GUQVPxzpoD +NDP8FxVgMy022+gylWr2EwZ/QWigIKeop0qRCeuPgju44Fvf0Z300GmpIwOjsPWT +Ksmqw+erTT2UcN62z4+J0TvL44T9wpWbPcyxOe1r5HLpRkkBebMPNMlPZ4WGagsz +jn0ctw7GwsJbKgyqturB83ZfuJv9lGkrXHOjrjeQNCebYDmybHl/aag8BKKYOiFW +MkHmda+Jmq817aqcwVedMKs4CwdrE6frp2wgAIngzCILLVfyTa8v5HxpkezpKS3p +Lia3/xkSrJwzd9ncNe43OVDlFbTE6fm/ycES8vhvS2NotuL/gZ9WpLOFPKCFl2CZ +Cg6CUlTngEevd8kUrlt8BIEOPyhWqZOkxb1Q+Jr7PUQjgjQXmuxYoZ647xOYdIbd +RQZd3oEFjQYTXTT7hHOuB+FehaJPEfIqJDIxVSs0gVhETaCn7L7jcq3uko3W2IpV +qbVYBDv6+ae6Ia0dSTCtWGmqj0heIE1OOtMe7do0RijeeUz8snn6N7GYxVsQv+dg +0zeV/2RdPz/N898agdJZywjCUwxVPIKXl4MpFEy79rhGBq7q8aImDRlrdMZNy9BJ +nARaiDZ0ifmdh+smPWj/WuiAsYnuJBEFAQ88xECHbSXeJ6+Y/VS2jaJlMtL2tObW +mB/vq+Kfj6yfMxYaxtjOIpqBQfGZVlNwkq9BEeEwUcas5QBrRktUS5taU3/FlfyC +P3DsU4vseQILnqmEty7TWdHqw3up3Japzc3cTP9h4xxXuux+FmRuVdq0lfSPXB5E +-----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-short-iv.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-short-iv.pem new file mode 100644 index 000000000000..695a797e6483 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1-short-iv.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,aaaa + +18phyq8pG3Tgov4rWiT0moaDbzIOk7v4/4Jnw3sc6IuMFmAYnIKHRs75hQdlFAxG +uSXcAKzCzjhkzgSNyNaJ8ZgeDM+DskDTA109iQWCeSxKZkuHBm2Xux9p7ynEhrMf ++z0Dd5W36KRPs0PRwVoUAv/AYaLizBbAXaEx/e21uDB2cVnA2EhjEXEz7KZnqTWm +qbSEAv/IJos1Eh1IvLupxh5naaRxfrHZgKu638ybxuxzJx+zn2DeB7g9uqVf3lCp +B5bsoqumIhxBmIS7pKeWIq+GFVQuuHcDozRVolFuUvMkPdPfaGQjLI+ynaAfA9WH +MULcRcBL+S8cp4xv8jmyW0n4Elak0ixw1UJLjeSrIGYLB+ZkYXPiUjhYZPzbKzdE +rLstyGfFXH8Vjw6921P6iVH/JvskF9aj4NvYyZqxo9YznIN9nI8GWmqJgLyIYHET +Ur5mp1/O+KGLWMzfX09/fUVF/mXBibcnJ/sixGCH4yNZR5kpnas6H8SmaGgKE1zk +KYeuicGHm6nZ/uyjoL/AwvbUL1y9tHJ0vn816cCRdJ4ELZ5dotGPREPmkWzjv08A +ZeTmdsgsGuUY/5mKZdIqlWCgrSKaZvS81+5tYgf0qMLBsAbLPDJy9kzTwCsEYxmh +x9QxUeQ/UWVsMn6JqeBVp0B5z/sLcdx6GkFVGs9U2Al3aykVhrVq+0RUiYafluod +Mkz1AczAxFtqdgaQIJbrwEAXoMc8/l8dunbuYoRuuf1y259U61aTm6wcknnDUZKs +13sDVdcRZq1Lc1JI3B586Z7Jh0r/4HPiK1zearKLBPKZA6kEj4RzG3GUQVPxzpoD +NDP8FxVgMy022+gylWr2EwZ/QWigIKeop0qRCeuPgju44Fvf0Z300GmpIwOjsPWT +Ksmqw+erTT2UcN62z4+J0TvL44T9wpWbPcyxOe1r5HLpRkkBebMPNMlPZ4WGagsz +jn0ctw7GwsJbKgyqturB83ZfuJv9lGkrXHOjrjeQNCebYDmybHl/aag8BKKYOiFW +MkHmda+Jmq817aqcwVedMKs4CwdrE6frp2wgAIngzCILLVfyTa8v5HxpkezpKS3p +Lia3/xkSrJwzd9ncNe43OVDlFbTE6fm/ycES8vhvS2NotuL/gZ9WpLOFPKCFl2CZ +Cg6CUlTngEevd8kUrlt8BIEOPyhWqZOkxb1Q+Jr7PUQjgjQXmuxYoZ647xOYdIbd +RQZd3oEFjQYTXTT7hHOuB+FehaJPEfIqJDIxVSs0gVhETaCn7L7jcq3uko3W2IpV +qbVYBDv6+ae6Ia0dSTCtWGmqj0heIE1OOtMe7do0RijeeUz8snn6N7GYxVsQv+dg +0zeV/2RdPz/N898agdJZywjCUwxVPIKXl4MpFEy79rhGBq7q8aImDRlrdMZNy9BJ +nARaiDZ0ifmdh+smPWj/WuiAsYnuJBEFAQ88xECHbSXeJ6+Y/VS2jaJlMtL2tObW +mB/vq+Kfj6yfMxYaxtjOIpqBQfGZVlNwkq9BEeEwUcas5QBrRktUS5taU3/FlfyC +P3DsU4vseQILnqmEty7TWdHqw3up3Japzc3cTP9h4xxXuux+FmRuVdq0lfSPXB5E +-----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/rsa-wrong-version.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/rsa-wrong-version.pem new file mode 100644 index 000000000000..a50760d64295 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/rsa-wrong-version.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBEQKCAQEAubZR/vxN1MmxwDEu5p8IA5kNWlOXhd0U8faIDZGY7h9xs7q7 +Hr6Xd4azC+oXDyS3oOexFvLGkIzzdJI5hJJBh4benU4PXz5W176euXHT+KT4EgV8 ++fkFO4KdHFTRo0D+XJCm4iilhx2pAHcBQbTG5vKYQJcYyxZGek9f9jiCsgQlUCj0 +l0Xe3Hyktcum14rPMrZQ8Gv4GGLtoIVqFOh2ftQIY0IoSm+XUulkNfcRmgXMMiCp +VHdKkx2+vh8asN+drq7bEydBw7XEjhoUJszZVPubUUDBTa7Jp5vpx8jlBhDftInH +U5mZz8FKx1dlurSuio312Ww940wSQ1saAs9uyQIDAQABAoIBAAmnaNIfWIZtaQrr +ePDZJzKqA/qEP5YLB5nfwx59c/HmUDlTxYK+zU3pLSk7OoakKyg3Ux/fxU23Xg0w +cBgBqFwSDpl7zisZKQI0cQ4v1MvnUNP9qrZYk8U5BXohuKIgG05Bi23/R0I5Bajg +sX/dFL07CDTMsKfCA9jmLmq0xlUtm3d4R8h050OsFZQqIYFrsXeRkhXuI1Bk+wp7 +O6qvrBSS4psvyA3Ba2M1Jdg+7XP6R6VamJQUilA1jrlMYrGehPPX2vhmzWpgaSDV +S6QdeqZI53fVJp/gCxKoz1zPgj9iwejcRC7Dp+M1aRP0RJGbqkpccpk0WBdUO0rd +X5waR38CgYEA+DN/vNS1ThTUImiJcl2dxxPkDIfmLOGIalF8cps9Ez3FGb+wJggX +iFCdK1A7wJZr3GfEV3HkH5hEzuG+losyY3NdbEfZgdrP3h/iEQxKy/5lZZmJC48T +HCDSRokZWfRdBtT63yBflPnqBQxmHv3HYNdHGhljvxYzODfvbcT4268CgYEAv4wq +1UrPZ/i2h4SfkezkdhkB6KvIsLyGBPVeZK1BOmIC27KOrARj+HgRwcqCaw7q+1PR +FbUN5ad190xenPgWG/wDD15AJmQ4jqHvfQrehVWeTmjO9RnLT1guxB+ZQknYuGCn +Qz8GEjIoJ6h7PMDXhQdYEbdrzLyQ/xU6EVkvowcCgYEA4M3MUd0bBkjJRw0GCOcQ +BANZF5xzd40jAKEjpa5DqEzXXBYJ1riXj+jsIhH+vNXBhhUaedV3OMKy9+rxs+sJ +zZftMyj0sa/dfKPGH4jRqmiVsGta/HQva9eyfR6qLpatN4XqX/QzfnzJYJ81U7aq +QmVaSiJa/PV/mNjY7MRuXpMCgYEAkErtpVlCnocMMVAlyI6Ul6ZE+toVR5Xsu2V/ +YwXkwi89CfUbZtez22PPtJVx42YMe6FrOxf1zQ92XQGJsGNufEw+neAZIRKUTFYO +i7qZYAXcSCLJ7Hcu4amDKTjIgdgRSut8dLrQPvrLpvxTQbPfZpXesRHkQgm2jIGY +CaOOsBcCgYA3ijrhl4w4Hc47SGsDhgHPBt+ndof9zS1WcyOAv/TzLuwgAnA0vNU7 +6AFi5AVKt/79vD5f6SOqgTDSyasB1qcP2jYV8GaIbqYQ4Gwpz1wuBkmkDKk28pC3 +ec2eK8O4cJUmZn91oQFuJorjuVAa5GluyMGvCdxWeAQVH96xSG7lEg== +-----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/pkcs12/java-truststore.p12 b/vectors/cryptography_vectors/pkcs12/java-truststore.p12 new file mode 100644 index 000000000000..02d8e7220f2a Binary files /dev/null and b/vectors/cryptography_vectors/pkcs12/java-truststore.p12 differ diff --git a/vectors/cryptography_vectors/pkcs7/enveloped-aes-256-cbc.pem b/vectors/cryptography_vectors/pkcs7/enveloped-aes-256-cbc.pem deleted file mode 100644 index bddac0b4ea30..000000000000 --- a/vectors/cryptography_vectors/pkcs7/enveloped-aes-256-cbc.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PKCS7----- -MIICmwYJKoZIhvcNAQcDoIICjDCCAogCAQAxggJDMIICPwIBADAnMBoxGDAWBgNV -BAMMD2NyeXB0b2dyYXBoeSBDQQIJAOcS06ClbtbJMA0GCSqGSIb3DQEBAQUABIIC -ACTeTHyg8zwnBdhLFogSBMInoAqc8HHZ+3vRN57MJ9UA4MIkqgrUEMg2sYwNkpuS -pT3B0tw3CbrJwL4SemPul1FuYMluTRdhJuI9wskR9BvE6d+BlmnFSjNGdt1y9RM+ -7ZqViXGA2t2HVRQ42Q43tkDUL7gMzveYZ1LxG1d+GNbfKLHVqJLokIe+IQYtyRay -3Tck7l/cC2VpI9lwmF+DugpZbagmb3pSij/ZSzzub3PwNp4YaL2YSa1Vkswdm3LD -jhOMSKyw7jIn2e9gQ3VI8vzh/38OFFFoKq7sAGvNGSLDbCHm6AKvOylksnTCUBF2 -6mbNWaaNpRjCQU+8N5/1UblJAs/voG+hGuWbGjS6z4v6mYvIr5731rQjxYbIpZRT -B6+lu9sCbwHuYQKe8MBlsn0+Y/o7l25m+xOfeRK1UGViUNV+2G2SQKY2CnfBoPis -lZSwKv1mfYifT1bsVyTsDWi0yr3BdbhVRI4pLziNrMFJ5tJhN2Y8HB2FGLlmzJtM -YRyljlMtj3YrYnhX82dKIwlrLfoWYP90tiiGh3DlqUTVCj4Y/IBmFGF6VpKWYZ0F -1VGwR8dDt0a0IonoBo3T4OtqUStlMkWgwGyNlauZnXt4jHoP5ECZ23TLpAtLCgUE -BuTiSXYFHaz+ToomhzTqrqznhLf9PRV+TM96/66xYdSYMDwGCSqGSIb3DQEHATAd -BglghkgBZQMEASoEEFSk9vw7RRWfjkB3sVedCgqAEPYXgbXvcA4rj2DCHA80Etg= ------END PKCS7----- diff --git a/vectors/cryptography_vectors/pkcs7/enveloped-triple-des.pem b/vectors/cryptography_vectors/pkcs7/enveloped-triple-des.pem new file mode 100644 index 000000000000..e43d1a9da653 --- /dev/null +++ b/vectors/cryptography_vectors/pkcs7/enveloped-triple-des.pem @@ -0,0 +1,16 @@ +-----BEGIN PKCS7----- +MIICkgYJKoZIhvcNAQcDoIICgzCCAn8CAQAxggJDMIICPwIBADAnMBoxGDAWBgNV +BAMMD2NyeXB0b2dyYXBoeSBDQQIJAOcS06ClbtbJMA0GCSqGSIb3DQEBAQUABIIC +AFCp88C3EJNc3WTTMaWqoKL/aBhrW/utkceKN89Vjmqk1gbdsbK/jZhuBlleSESj +HrZ2wcfubY8UthsVLUfxMUvjSJh2WdZ99IwmPGOtvvPcEWN8mYXO+Q7wN3zyl0cu +aVOZS0NpXm1y9bnbLt2RrohSrTlQ+zyDDPEYUOa1eNX7WOr7hUuVEVchraFHMX7O +kKjWdbVolXvFeqXn3TuHSxRoRIWhhmLNpFUH3lFTUtmpmHHL5W0Qfld/kL0Cagar +gMjSWWWPB0uyd7ufVbDAfGuQFzxWrUy3hQiLhWHxe3hV2vsXcpvBBieIwJKlb5G7 +GwbkdOV7wyqiRv8WUtWSwpn1finxypfGGeNfeYdU9M7WuWJ88govos9nIsP2bbyB +hITtKZlZIYBTCimihy691v6QlbdQ79pENq6QWaZlXtcZW6K9Iqq+GY4P2Se6phow +gnGSgR19NRr7rhe78qAJR1fMGDyPPSMSAAEWyrEMupMig23/iLSlZPt+fG58STOq +3eHw2zNSWSwqbrA0ZUB+YtAh03dy6bWzSx8//Pu64DiJYFtkwR5J+Wzu1S3xZPiy +DRLhlfYSzEGZVqd/8b459WU5t4VnyRL0kFSqMLiTl1Drowut7qypWcBoaRgq9j+D +lZxCM5YYPeetGBWZb7zLNWH16h29t5yuoHDoLl1IJunbMDMGCSqGSIb3DQEHATAU +BggqhkiG9w0DBwQI1ISrWpzpTc+AEBSqVJaeybYcyk7DnBT2pyE= +-----END PKCS7----- diff --git a/vectors/cryptography_vectors/x509/custom/crl_issuer_invalid_printable_string.der b/vectors/cryptography_vectors/x509/custom/crl_issuer_invalid_printable_string.der new file mode 100644 index 000000000000..1221dc5e0d6b Binary files /dev/null and b/vectors/cryptography_vectors/x509/custom/crl_issuer_invalid_printable_string.der differ diff --git a/vectors/cryptography_vectors/x509/custom/no_sans.pem b/vectors/cryptography_vectors/x509/custom/no_sans.pem new file mode 100644 index 000000000000..868f231d1ec8 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/no_sans.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEhDCCAmygAwIBAgIUNqGKNaeU8T72PN96oVdufjupN0gwDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5IENBMB4XDTI1MDIxMDEwMTAzM1oX +DTI2MDIxMDEwMTAzM1owgYQxCzAJBgNVBAYTAkNaMQ8wDQYDVQQIDAZQcmFndWUx +DzANBgNVBAcMBlByYWd1ZTEVMBMGA1UECgwMQ3J5cHRvZ3JhcGh5MRYwFAYDVQQD +DA1MZWFmIENlcnRpc29uMSQwIgYJKoZIhvcNAQkBFhVsZWFmQGNyeXB0b2dyYXBo +eS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0ZfBrwF4V4vdb +4GrmK8kx8OI/SfmR/T327YyxYHgqsMCyyQTpbci/dep/ystBC3MypN2hY4esLYHg +BX0/gqZEZ7i8hYgvoB8RCu3gqf5kNRPrCZG98E+oyJxbgt2nvEroiSeB55WE4ies +5L0m9JFpROG0aRi5erG0vtCs96DSYBqaurS8ODe2H64V6MbNztahpFJ309Jf+v34 +E9hVRSTMwRo1YGowUEqA1kHN2hI6vgjmU2JlpYgK7fRROVANxvhCm5LxPrCM/sxA +PSOyIxRun+Q7dq/1B9xzMF2v9rrfKkQZd8W++RwqVG/lkgJps0ZRwci61J+4iuSX +qrpzDGvvAgMBAAGjVzBVMB0GA1UdDgQWBBTC/a413NsoTouAhT9JtaQTICh13zA0 +BgNVHSMELTAroR6kHDAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkgQ0GCCQDnEtOg +pW7WyTANBgkqhkiG9w0BAQsFAAOCAgEAhDdG8x9uDLatY8INb2D3mv01N6TgsuzA +8Moc8RsuMUW6Ftshyq1bKeNLJKbfhMbIaypcB+i6v6b/KdFOLtI2BZuTsleR/827 +EXfXrOKE8aWLBVAGWGxolz+lBw85OUZ5B6wNXdCKiwojC8Z8X2emTr9ZxfYkTd/0 +DXsmSjahn0guQSXUqLagFaPfDWG4szzicUhhjXIV4n7BBJ9MwoqXFumcMTooE5hC +UezRBSmvnsJSFajSnmJrAHDfF4xB+NrdFCd9S4jfz6BPqFe5vtm45qe51ZrWxaIR +h4THnfPkuwX7uP9hLI7BZloF5raflP8xQ48EXAwLglYk0U7vPyQOoVoX4oQ0sQyc +dIcZXIkOj5wafhxpUXzsm2GRIUFM7OlaLCN3Cs/7Ycf+/F/oJrdSKyrY12jJPShr +Q0ZbOaKAULe3MqDSQ41z1SJ3/5QSvJQAnx7pwinMD6bhLNvj6+9C059hBQjYIq11 +QGuEU0QP0O8jmQjfdQAF59XWSJ71+AN8PAcQNGkUNqqrcJMj3r72yJzu2sU0zwFF +xRP52RDKvm4oKAEzVII9otNnUCUKCbx03PmX1odWQ78fM+ZimYeh+U6iq3J3N/++ +no9NDshr2FHran0VMQRCM2xLaEif5m5JK/020qlDYOMugDScFc2SxPaoJ7S8O53s +j+MkVT8aaWM= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/private_key_usage_period_both_dates.pem b/vectors/cryptography_vectors/x509/custom/private_key_usage_period_both_dates.pem new file mode 100644 index 000000000000..5de5f28fd056 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/private_key_usage_period_both_dates.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgIJAMU1dJdpbGOMMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDDAKBgNVBAoMA2F0czEL +MAkGA1UECwwCSVQxEDAOBgNVBAMMB2F0cy5jb20wHhcNMjUwNDA4MTg0NDU5WhcN +MjYwNDA4MTg0NDU5WjBUMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNV +BAcMAlNGMQwwCgYDVQQKDANhdHMxCzAJBgNVBAsMAklUMRAwDgYDVQQDDAdhdHMu +Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyfSIFjlCwzf2enYw +DENTR9T7CFIXUZT9HWv7y1eX98xcXn4hm3gd+VaevzjaOsJoKJQ95VVQW0btu/uX +7Q2Op1GYixM4XrjjJ3JS1tQn2ipEHh13dhvSbTCyz5OmltW2+fCRM5vB7qScq0zg +aIW33b6e8qg/V3uNO5ds1dwafxwSwCh12dQ+NgqHzj/q79F+ekWsXsjcNXr5Wk4e +YxRd0JcHz31oyjW7ICpk88j52Ma2AiwYMjPZaR6VPxu15HgMH1bG4YGaKTMgox3s +biIS47A5L1yTXjA+isSQ31Vdasg7Xg7eJOQy7qDebbqYgYDRFSl8hdTdWMSCOzw3 +iZkTHQIDAQABo0owSDAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIFoDArBgNVHRAE +JDAigA8yMDI0MDEwMTAwMDAwMFqBDzIwMjQxMjMxMjM1OTU5WjANBgkqhkiG9w0B +AQsFAAOCAQEAgiKvICeKsXTJm7tl9iZMyRLXss/b/dsT/IuWFzbbfMDVRHnOCeYz +lSNDF1gaexjo5kDQw0cURMVK96MjugwMNJG+vZewp1o8n8cbb8Yz/qyKoaqSZo0/ +n8+qkd5dhH7vm5sIjkOKw1fo6uNyTk6LMuLU53kHQp0NoopHNwz2BS2ym0Alt1ss +yg6mU+8oWk68esRSuZ6HQjzme05WVhDtMZDMQqlG7AYQ1Xz+3qsnxLz6ufxuuvxM +MgGuCA/Bd4bKx9bOivjhT/F/n69QCnjZjTEc0MFgi/mFHkG/giwH3iOaMm5Y88vZ +A+Ct40V/ISQ960sViO9nuSqH3gYDJ5+WPA== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/private_key_usage_period_only_not_after.pem b/vectors/cryptography_vectors/x509/custom/private_key_usage_period_only_not_after.pem new file mode 100644 index 000000000000..52e471f95c46 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/private_key_usage_period_only_not_after.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDajCCAlKgAwIBAgIJAIcDydQiQfdwMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV +BAYTAklOMQswCQYDVQQIDAJKSDELMAkGA1UEBwwCQVMxDTALBgNVBAoMBGFicmEx +EDAOBgNVBAsMB2NhZGFicmExDTALBgNVBAMMBC5jb20wHhcNMjUwNDA4MDQyNjI5 +WhcNMjYwNDA4MDQyNjI5WjBXMQswCQYDVQQGEwJJTjELMAkGA1UECAwCSkgxCzAJ +BgNVBAcMAkFTMQ0wCwYDVQQKDARhYnJhMRAwDgYDVQQLDAdjYWRhYnJhMQ0wCwYD +VQQDDAQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArn7G+zoG +lODjyRM7M3yOom8yxxYU17hRY95HcjK9nFXed3T/8pOgvXCsJ4htRFg8GzOwbtlU +IXjeeFncAFrsZ2/K0FeIBwPGfrPe45FHngchyjMPifdKp4ptWZDt5pKvYIJG04NV +PvrBcfZNkUsJZXmpEsjtZIpOB/LmMD8lCkeKAMb1rkI4wwTr7MC1N0Id8RlWmmNr +Wywo6pPuoDd8p7KcjzsbxDyzRRkDVQyEdX9s9L00dS+S5H21i8fV0M5Edn2Tj9/L +dCmL4wqk08FNVAVqp9/bgH8G15C5QpvMjBZbDniZhqsSkI6z0uApfmmQloIGLW0I +f0FTyyrQAgv5owIDAQABozkwNzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIFoDAa +BgNVHRAEEzARgQ8yMDI0MTIzMTIzNTk1OVowDQYJKoZIhvcNAQELBQADggEBAH7L +uxrcQ0Uwnx+YyQSZZN64DKRAEpe1xmkoKrmuG179vzCuN/6SmRUx83hUxyuPBzKy +O8dr3ei5U348cqr0uJNd9u6fLMcO80bTSA3vRKjZlf4SOkFvSUGAfoG676x7bH0m +UrU85oq+cys02u48zeHAwBkb1wJSnvgciGvVrtIGFeGVrrOWtQT99o4R2dh/wNqn +wHMi2dCO4YqipIqzzsDh3PrzhEoXBOJ+g84cC3Ji48bG8PR8Ms41PijVxBucqsdT +s7FGtadBSDqvI/eoQOx3FhLPuoc4FZJrYS40Br9mDrF6BhtlyWtzHs8vlA9Lm72d +JOdVRdibTVwyC0VXF20= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/private_key_usage_period_only_not_before.pem b/vectors/cryptography_vectors/x509/custom/private_key_usage_period_only_not_before.pem new file mode 100644 index 000000000000..d4aa94bae2bc --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/private_key_usage_period_only_not_before.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYjCCAkqgAwIBAgIJAL2s9y3RtqQTMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDDAKBgNVBAoMA0FUUzEL +MAkGA1UECwwCSVQxDzANBgNVBAMMBml0LmNvbTAeFw0yNTA0MDgxODQ2MjdaFw0y +NjA0MDgxODQ2MjdaMFMxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE +BwwCU0YxDDAKBgNVBAoMA0FUUzELMAkGA1UECwwCSVQxDzANBgNVBAMMBml0LmNv +bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMRNX9Al7UIZKLDlWoZT +30Xd9sL8m63XNtsZm+CSc97OPh5BPfbLAAtf5kzIbhZIXAI0tS3HPQxxFWD6qJ4T +dRi5YX+/fD1qZiUSI/QiLU6whH7kqwTcO4MGmOfUmGufRZk1QiWJsnqhlBBWnIKE +gSRhPPAy4DZztE/MJv6RuTfxggpCKw7qCfaBjBjdszJiL6OzQOloKcI3J6PEUyDv +cMcTiXqWdoPhPtF//3w6KZ71iZPdmUgIjsBTtOU8R5qcbSq7HkPegF7GAE2RZpc2 +eaOHwJM3NYdrkk5ynKb0YHDrGlLl7cIvSDxF6WbloyxOrWmtMgKs7QZ73vE3Fq+I +GTsCAwEAAaM5MDcwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBaAwGgYDVR0QBBMw +EYAPMjAyNDAxMDEwMDAwMDBaMA0GCSqGSIb3DQEBCwUAA4IBAQA5Hs1s2Zx0OFE0 +o/GpbQdDESSJtiiFhz+2e4OA+nWMe4wgZSzqchtg4gYF3EKlFt9MLhaMfsMX8JsD +COwePBpIW0Dwl+NqTxrHc/BCwCSayHY/F1tEIiSDdPyhMtFkcflRM2zJD8vHHP0q +9r0A4nOF4VYwW9IeIECNuhe5IM9AOn0nLdkzt8ESMTwI48kV1j2JXeBOH1cNy6j/ +1g8StoHyIEQlPY/mSndpiBOlT7g/rkm+ZmUMMQliDco8y13I0dd6mqW8EYlBhjf6 +yFUoUYubgyT8zAn1erJi3yuozMw4L+OSOf6p9lQYM8Ld/DMuPQ9Nmi70q6FfT9Z4 +fhLPJNOl +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem index e0509174c823..906bf43147fb 100644 --- a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem +++ b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem @@ -1,21 +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 +MIIDeTCCAi2gAwIBAgIUXlaVdgeEIMp9IqY8UwE76aL54owwQQYJKoZIhvcNAQEK +MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF +AKIDAgEgMBkxFzAVBgNVBAMMDmNyeXB0b2dhcGh5LmlvMB4XDTI1MDUwMTE5MTcx +NVoXDTI2MDUwMTE5MTcxNVowGTEXMBUGA1UEAwwOY3J5cHRvZ2FwaHkuaW8wggEg +MAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKCAQEAt1jpboUoNppBVamc+nA+zEjljn/g +PbRFCvyveRd8Yr0p8y1mlmjKXcQlXcHPVM4TopgFXqDykIHXxJxLV56ysb4KUGe0 +nxpmhEso5ZGUgkDIIoH0NAQAsS8rS2ZzNJcLrLGrMY6DRgFsa+G6h2DvMwglnsX+ ++a8FIm7Vu+OZnfWpDEuhJU4TRtHVviJSYkFMckyYBB48k1MU+0b4pezHconZmMEi +sBFFbwarNvowf2i/tRESe3myKXfiJsZZ2UzdE3FqycSgw1tx8qV/Z8myozUWuihI +dw8TGbbsJhEeVFxQEP/DVzC6HHDI3EVpr2jPYeIE60hhZwM7jUmQscLerQIDAQAB +o1MwUTAdBgNVHQ4EFgQUb1QD8QEIQn5DALIAujTDATssNcQwHwYDVR0jBBgwFoAU +b1QD8QEIQn5DALIAujTDATssNcQwDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0B +AQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQC +AQUAogMCASADggEBAFQIq9+51vAjBwHapeNe6LaTfPoVrWAKBFz9oJn5rHsk1DQP +glLyi7CQYzz5ByYvA4oXMzN84iSmi500uGeG2g5gPWQJfGFdycmyCEfEzXO6xnJR +YxsHVOcBUI0iME7BnREVmHrAMY4wKRDNzF3Cau/STT3m/RTEGWZM6gMx2SeWw5c0 +uUusHoStyIxM53UyydrwImauiKdFj8uDcELPP7CK+xhEqfxUg8P2q2kKfKN8ODne +7UdQ8aZBvey/n28qZimDY9Q96cjLgI6h/RkhQ/4tVNg6D3sPtUu1XEYyc5rZ97T6 +x63waW4waRdIPbIfVc9s21432MVBscXZNaHopOM= -----END CERTIFICATE----- diff --git a/vectors/pyproject.toml b/vectors/pyproject.toml index 7760ca6448da..02e190c8665c 100644 --- a/vectors/pyproject.toml +++ b/vectors/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "cryptography_vectors" -version = "44.0.0" +version = "45.0.3" authors = [ {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} ]