diff --git a/.github/ISSUE_TEMPLATE/openssl-release.md b/.github/ISSUE_TEMPLATE/openssl-release.md index 110d06d09c52..1b0cadc1018a 100644 --- a/.github/ISSUE_TEMPLATE/openssl-release.md +++ b/.github/ISSUE_TEMPLATE/openssl-release.md @@ -1,5 +1,5 @@ - [ ] Windows, macOS, `manylinux` - - [ ] Send a pull request to `pyca/infra` updating the [version and hash](https://github.com/pyca/infra/blob/main/cryptography-manylinux/openssl-version.sh) + - [ ] Send a pull request to `pyca/infra` updating the [version and hash](https://github.com/pyca/infra/blob/main/cryptography-linux/openssl-version.sh) - [ ] Wait for it to be merged - [ ] Wait for the Github Actions job to complete - [ ] Changelog entry diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml index 9b0c9271300d..31af7422da04 100644 --- a/.github/actions/cache/action.yml +++ b/.github/actions/cache/action.yml @@ -1,57 +1,21 @@ -name: Cache rust and pip -description: Caches rust and pip data to speed builds +name: Cache +description: Caches build data to speed builds inputs: - additional-paths: - description: 'Additional paths to add to the cache' - required: false - default: '' key: description: 'extra cache key components' required: false default: '' -outputs: - cache-hit: - description: 'Was the cache hit?' - value: ${{ steps.cache.outputs.cache-hit }} runs: using: "composite" steps: - - name: Get rust version - id: rust-version - run: echo "version=$(rustc --version | sha256sum | cut -d ' ' -f 1)" >> $GITHUB_OUTPUT - shell: bash - - name: Get pip cache dir - id: pip-cache - run: | - # Determine the path to our Python. It's in venv for our containers - # but just standard $PATH for setup-python pythons. - if [[ -f "/venv/bin/python" ]]; then - echo "dir=$(/venv/bin/python -m pip cache dir)" >> $GITHUB_OUTPUT - elif which python >/dev/null; then - echo "dir=$(python -m pip cache dir)" >> $GITHUB_OUTPUT - fi - shell: bash - name: Normalize key id: normalized-key run: echo "key=$(echo "${{ inputs.key }}" | tr -d ',')" >> $GITHUB_OUTPUT shell: bash - - uses: actions/cache@v3.3.1 - id: cache + - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 with: - path: | - ${{ steps.pip-cache.outputs.dir }} - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - src/rust/target/ - ${{ inputs.additional-paths }} - key: cargo-pip-${{ runner.os }}-${{ runner.arch }}-${{ steps.normalized-key.outputs.key }}-6-${{ hashFiles('**/Cargo.lock', '**/*.rs') }}-${{ steps.rust-version.version }} - - name: Size of cache items - run: | - du -sh ~/.cargo/registry/index/ - du -sh ~/.cargo/registry/cache/ - du -sh src/rust/target/ - shell: bash - if: ${{ steps.cache.outputs.cache-hit }} + key: ${{ steps.normalized-key.outputs.key }}-2 + workspaces: "./src/rust/ -> target" diff --git a/.github/actions/fetch-vectors/action.yml b/.github/actions/fetch-vectors/action.yml new file mode 100644 index 000000000000..c1df58824014 --- /dev/null +++ b/.github/actions/fetch-vectors/action.yml @@ -0,0 +1,20 @@ +name: Clone test vectors +description: Clones the wycheproof and x509-limbo repositories + +runs: + using: "composite" + + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + repository: "google/wycheproof" + path: "wycheproof" + # Latest commit on the wycheproof master branch, as of Oct 28, 2023. + ref: "d9f6ec7d8bd8c96da05368999094e4a75ba5cb3d" # wycheproof-ref + + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + repository: "C2SP/x509-limbo" + path: "x509-limbo" + # Latest commit on the x509-limbo main branch, as of Jan 23, 2024. + ref: "cf66142f5c27b64c987c6f0aa4c10b8c9677b41c" # x509-limbo-ref diff --git a/.github/actions/mtime-fix/action.yml b/.github/actions/mtime-fix/action.yml deleted file mode 100644 index 42779037ce87..000000000000 --- a/.github/actions/mtime-fix/action.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Fix mtime -description: Fixes mtime so cargo will reuse caches more effectively - -runs: - using: "composite" - - steps: - - run: | - GIT_WORKS=$(git rev-parse --is-inside-work-tree 2>/dev/null || true) - if [ "$GIT_WORKS" != "true" ]; then - echo "The git available is probably too old so checkout didn't create a real git clone, skipping mtime fix" - exit 0 - fi - ls -Rla src/rust src/_cffi_src - echo "Verifying commits are monotonic because if they're not caching gets wrecked" - COMMIT_ORDER=$(git log --pretty=format:%cd --date=format-local:%Y%m%d%H%M.%S -5) - SORTED_COMMIT_ORDER=$(git log --pretty=format:%cd --date=format-local:%Y%m%d%H%M.%S -5 | sort -rn) - if [ "$COMMIT_ORDER" != "$SORTED_COMMIT_ORDER" ]; then - echo "Commits are not monotonic, git may have changed how date formatting works" - exit 1 - fi - echo "Setting mtimes for dirs" - for f in $(git ls-tree -t -r --name-only HEAD src/rust src/_cffi_src); do touch -t $(git log --pretty=format:%cd --date=format-local:%Y%m%d%H%M.%S -1 HEAD -- "$f") "$f"; done - echo "Done" - ls -Rla src/rust src/_cffi_src - shell: bash diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml index 8fa9cca4e630..a005d6b7462d 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@v3.1.2 + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: coverage-data path: | diff --git a/.github/actions/wycheproof/action.yml b/.github/actions/wycheproof/action.yml deleted file mode 100644 index 6ededc54b15d..000000000000 --- a/.github/actions/wycheproof/action.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Clone wycheproof -description: Clones the wycheproof repository - -runs: - using: "composite" - - steps: - - uses: actions/checkout@v3.5.2 - with: - repository: "google/wycheproof" - path: "wycheproof" - ref: "master" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 273a64e735bc..225922bd21a6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,28 +4,38 @@ updates: directory: "/" schedule: interval: "daily" + time: "06:00" + timezone: "America/New_York" open-pull-requests-limit: 1024 - package-ecosystem: "github-actions" directory: "/.github/actions/cache/" schedule: interval: "daily" + time: "06:00" + timezone: "America/New_York" open-pull-requests-limit: 1024 - package-ecosystem: "github-actions" directory: "/.github/actions/upload-coverage/" schedule: interval: "daily" + time: "06:00" + timezone: "America/New_York" open-pull-requests-limit: 1024 - package-ecosystem: "github-actions" - directory: "/.github/actions/wycheproof/" + directory: "/.github/actions/fetch-vectors/" schedule: interval: "daily" + time: "06:00" + timezone: "America/New_York" open-pull-requests-limit: 1024 - package-ecosystem: cargo directory: "/src/rust/" schedule: interval: daily + time: "06:00" + timezone: "America/New_York" allow: # Also update indirect dependencies - dependency-type: all @@ -35,4 +45,20 @@ updates: directory: "/" schedule: interval: daily + time: "06:00" + timezone: "America/New_York" + allow: + # Also update indirect dependencies + - dependency-type: all + open-pull-requests-limit: 1024 + + - package-ecosystem: pip + directory: "/.github/requirements/" + schedule: + interval: daily + time: "06:00" + timezone: "America/New_York" + allow: + # Also update indirect dependencies + - dependency-type: all open-pull-requests-limit: 1024 diff --git a/.github/requirements/build-requirements.in b/.github/requirements/build-requirements.in new file mode 100644 index 000000000000..bdf6916690ca --- /dev/null +++ b/.github/requirements/build-requirements.in @@ -0,0 +1,8 @@ +# Must be kept sync with build-system.requires at pyproject.toml +setuptools>=61.0.0 +wheel +cffi>=1.12; platform_python_implementation != 'PyPy' +setuptools-rust>=0.11.4 + +# WARN: changing the requirements here DOES NOT update the dependencies used for building at the github workflow, as the build process used build-requirements.txt +# To update build-requirements.txt according to the dependencies here, run pip-compile --allow-unsafe --generate-hashes build-requirements.in diff --git a/.github/requirements/build-requirements.txt b/.github/requirements/build-requirements.txt new file mode 100644 index 000000000000..1b6bb11dcd3b --- /dev/null +++ b/.github/requirements/build-requirements.txt @@ -0,0 +1,88 @@ +# +# 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.16.0 ; platform_python_implementation != "PyPy" \ + --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ + --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ + --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ + --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ + --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ + --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ + --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ + --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ + --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ + --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ + --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ + --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ + --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ + --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ + --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ + --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ + --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ + --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ + --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ + --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ + --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ + --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ + --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ + --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ + --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ + --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ + --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ + --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ + --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ + --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ + --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ + --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ + --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ + --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ + --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ + --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ + --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ + --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ + --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ + --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ + --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ + --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ + --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ + --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ + --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ + --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ + --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ + --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ + --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ + --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ + --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ + --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 + # via -r build-requirements.in +pycparser==2.21 \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + # via cffi +semantic-version==2.10.0 \ + --hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \ + --hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177 + # via setuptools-rust +setuptools-rust==1.8.1 \ + --hash=sha256:94b1dd5d5308b3138d5b933c3a2b55e6d6927d1a22632e509fcea9ddd0f7e486 \ + --hash=sha256:b5324493949ccd6aa0c03890c5f6b5f02de4512e3ac1697d02e9a6c02b18aa8e + # via -r build-requirements.in +tomli==2.0.1 \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f + # via setuptools-rust +wheel==0.42.0 \ + --hash=sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d \ + --hash=sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8 + # via -r build-requirements.in + +# The following packages are considered to be unsafe in a requirements file: +setuptools==69.0.3 \ + --hash=sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05 \ + --hash=sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78 + # via + # -r build-requirements.in + # setuptools-rust diff --git a/.github/requirements/publish-requirements.in b/.github/requirements/publish-requirements.in new file mode 100644 index 000000000000..dd98b8990e7b --- /dev/null +++ b/.github/requirements/publish-requirements.in @@ -0,0 +1,6 @@ +twine +requests +sigstore + +# WARN: changing the requirements here DOES NOT update the dependencies used for publishing at the github workflow, as the process used publish-requirements.txt +# To update publish-requirements.txt according to the dependencies here, run pip-compile --allow-unsafe --generate-hashes publish-requirements.in \ No newline at end of file diff --git a/.github/requirements/publish-requirements.txt b/.github/requirements/publish-requirements.txt new file mode 100644 index 000000000000..2de251b3aa5b --- /dev/null +++ b/.github/requirements/publish-requirements.txt @@ -0,0 +1,577 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --generate-hashes publish-requirements.in +# +annotated-types==0.6.0 \ + --hash=sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43 \ + --hash=sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d + # via pydantic +appdirs==1.4.4 \ + --hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 \ + --hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 + # via sigstore +betterproto==2.0.0b6 \ + --hash=sha256:720ae92697000f6fcf049c69267d957f0871654c8b0d7458906607685daee784 \ + --hash=sha256:a0839ec165d110a69d0d116f4d0e2bec8d186af4db826257931f0831dab73fcf + # via sigstore-protobuf-specs +certifi==2023.11.17 \ + --hash=sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1 \ + --hash=sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474 + # via requests +cffi==1.16.0 \ + --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ + --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ + --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ + --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ + --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ + --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ + --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ + --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ + --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ + --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ + --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ + --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ + --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ + --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ + --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ + --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ + --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ + --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ + --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ + --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ + --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ + --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ + --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ + --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ + --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ + --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ + --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ + --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ + --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ + --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ + --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ + --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ + --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ + --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ + --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ + --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ + --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ + --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ + --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ + --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ + --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ + --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ + --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ + --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ + --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ + --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ + --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ + --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ + --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ + --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ + --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ + --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 + # via cryptography +charset-normalizer==3.3.2 \ + --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ + --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ + --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ + --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ + --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ + --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ + --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ + --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ + --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ + --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ + --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ + --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ + --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ + --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ + --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ + --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ + --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ + --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ + --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ + --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ + --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ + --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ + --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ + --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ + --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ + --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ + --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ + --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ + --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ + --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ + --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ + --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ + --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ + --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ + --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ + --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ + --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ + --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ + --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ + --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ + --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ + --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ + --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ + --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ + --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ + --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ + --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ + --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ + --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ + --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ + --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ + --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ + --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ + --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ + --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ + --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ + --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ + --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ + --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ + --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ + --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ + --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ + --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ + --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ + --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ + --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ + --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ + --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ + --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ + --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ + --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ + --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ + --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ + --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ + --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ + --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ + --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ + --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ + --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ + --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ + --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ + --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ + --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ + --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ + --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ + --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ + --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ + --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ + --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ + --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 + # via requests +cryptography==41.0.7 \ + --hash=sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960 \ + --hash=sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a \ + --hash=sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc \ + --hash=sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a \ + --hash=sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf \ + --hash=sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1 \ + --hash=sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39 \ + --hash=sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406 \ + --hash=sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a \ + --hash=sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a \ + --hash=sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c \ + --hash=sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be \ + --hash=sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15 \ + --hash=sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2 \ + --hash=sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d \ + --hash=sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157 \ + --hash=sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003 \ + --hash=sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248 \ + --hash=sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a \ + --hash=sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec \ + --hash=sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309 \ + --hash=sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7 \ + --hash=sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d + # via + # pyopenssl + # secretstorage + # sigstore +dnspython==2.5.0 \ + --hash=sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6 \ + --hash=sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15 + # via email-validator +docutils==0.20.1 \ + --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ + --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b + # via readme-renderer +email-validator==2.1.0.post1 \ + --hash=sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44 \ + --hash=sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637 + # via pydantic +grpclib==0.4.7 \ + --hash=sha256:2988ef57c02b22b7a2e8e961792c41ccf97efc2ace91ae7a5b0de03c363823c3 + # via betterproto +h2==4.1.0 \ + --hash=sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d \ + --hash=sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb + # via grpclib +hpack==4.0.0 \ + --hash=sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c \ + --hash=sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095 + # via h2 +hyperframe==6.0.1 \ + --hash=sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15 \ + --hash=sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914 + # via h2 +id==1.3.0 \ + --hash=sha256:c5dbb6048a469466054f065e92dba9b202a57d718cf12a0f24a082d0df988e18 \ + --hash=sha256:da320bc6d6e612a2c16364ca95bb905e87c74332d4fc9b34850a26c304790694 + # via sigstore +idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via + # email-validator + # requests +importlib-metadata==7.0.1 \ + --hash=sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e \ + --hash=sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc + # via + # keyring + # twine +jaraco-classes==3.3.0 \ + --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \ + --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621 + # via keyring +jeepney==0.8.0 \ + --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ + --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 + # via + # keyring + # secretstorage +keyring==24.3.0 \ + --hash=sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836 \ + --hash=sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25 + # via twine +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +more-itertools==10.2.0 \ + --hash=sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684 \ + --hash=sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1 + # via jaraco-classes +multidict==6.0.4 \ + --hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \ + --hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \ + --hash=sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03 \ + --hash=sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710 \ + --hash=sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161 \ + --hash=sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664 \ + --hash=sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569 \ + --hash=sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067 \ + --hash=sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313 \ + --hash=sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706 \ + --hash=sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2 \ + --hash=sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636 \ + --hash=sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49 \ + --hash=sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93 \ + --hash=sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603 \ + --hash=sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0 \ + --hash=sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60 \ + --hash=sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4 \ + --hash=sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e \ + --hash=sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1 \ + --hash=sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60 \ + --hash=sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951 \ + --hash=sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc \ + --hash=sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe \ + --hash=sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95 \ + --hash=sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d \ + --hash=sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8 \ + --hash=sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed \ + --hash=sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2 \ + --hash=sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775 \ + --hash=sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87 \ + --hash=sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c \ + --hash=sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2 \ + --hash=sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98 \ + --hash=sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3 \ + --hash=sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe \ + --hash=sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78 \ + --hash=sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660 \ + --hash=sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176 \ + --hash=sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e \ + --hash=sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988 \ + --hash=sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c \ + --hash=sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c \ + --hash=sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0 \ + --hash=sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449 \ + --hash=sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f \ + --hash=sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde \ + --hash=sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5 \ + --hash=sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d \ + --hash=sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac \ + --hash=sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a \ + --hash=sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9 \ + --hash=sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca \ + --hash=sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11 \ + --hash=sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35 \ + --hash=sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063 \ + --hash=sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b \ + --hash=sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982 \ + --hash=sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258 \ + --hash=sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1 \ + --hash=sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52 \ + --hash=sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480 \ + --hash=sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7 \ + --hash=sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461 \ + --hash=sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d \ + --hash=sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc \ + --hash=sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779 \ + --hash=sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a \ + --hash=sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547 \ + --hash=sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0 \ + --hash=sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171 \ + --hash=sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf \ + --hash=sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d \ + --hash=sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba + # via grpclib +nh3==0.2.15 \ + --hash=sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770 \ + --hash=sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf \ + --hash=sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305 \ + --hash=sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601 \ + --hash=sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28 \ + --hash=sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7 \ + --hash=sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3 \ + --hash=sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911 \ + --hash=sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf \ + --hash=sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0 \ + --hash=sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5 \ + --hash=sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97 \ + --hash=sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d \ + --hash=sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e \ + --hash=sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3 \ + --hash=sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6 + # via readme-renderer +pkginfo==1.9.6 \ + --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \ + --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046 + # via twine +pycparser==2.21 \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + # via cffi +pydantic[email]==2.5.3 \ + --hash=sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a \ + --hash=sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4 + # via + # id + # sigstore + # sigstore-rekor-types +pydantic-core==2.14.6 \ + --hash=sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556 \ + --hash=sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e \ + --hash=sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411 \ + --hash=sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245 \ + --hash=sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c \ + --hash=sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66 \ + --hash=sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd \ + --hash=sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d \ + --hash=sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b \ + --hash=sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06 \ + --hash=sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948 \ + --hash=sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341 \ + --hash=sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0 \ + --hash=sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f \ + --hash=sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a \ + --hash=sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2 \ + --hash=sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51 \ + --hash=sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80 \ + --hash=sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8 \ + --hash=sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d \ + --hash=sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8 \ + --hash=sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb \ + --hash=sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590 \ + --hash=sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87 \ + --hash=sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534 \ + --hash=sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b \ + --hash=sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145 \ + --hash=sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba \ + --hash=sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b \ + --hash=sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2 \ + --hash=sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e \ + --hash=sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052 \ + --hash=sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622 \ + --hash=sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab \ + --hash=sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b \ + --hash=sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66 \ + --hash=sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e \ + --hash=sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4 \ + --hash=sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e \ + --hash=sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec \ + --hash=sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c \ + --hash=sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed \ + --hash=sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937 \ + --hash=sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f \ + --hash=sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9 \ + --hash=sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4 \ + --hash=sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96 \ + --hash=sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277 \ + --hash=sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23 \ + --hash=sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7 \ + --hash=sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b \ + --hash=sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91 \ + --hash=sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d \ + --hash=sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e \ + --hash=sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1 \ + --hash=sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2 \ + --hash=sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160 \ + --hash=sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9 \ + --hash=sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670 \ + --hash=sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7 \ + --hash=sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c \ + --hash=sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb \ + --hash=sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42 \ + --hash=sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d \ + --hash=sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8 \ + --hash=sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1 \ + --hash=sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6 \ + --hash=sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8 \ + --hash=sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf \ + --hash=sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e \ + --hash=sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a \ + --hash=sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9 \ + --hash=sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1 \ + --hash=sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40 \ + --hash=sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2 \ + --hash=sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d \ + --hash=sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f \ + --hash=sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f \ + --hash=sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af \ + --hash=sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7 \ + --hash=sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda \ + --hash=sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a \ + --hash=sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95 \ + --hash=sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0 \ + --hash=sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60 \ + --hash=sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149 \ + --hash=sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975 \ + --hash=sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4 \ + --hash=sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe \ + --hash=sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94 \ + --hash=sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03 \ + --hash=sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c \ + --hash=sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b \ + --hash=sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a \ + --hash=sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24 \ + --hash=sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391 \ + --hash=sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c \ + --hash=sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab \ + --hash=sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd \ + --hash=sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786 \ + --hash=sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08 \ + --hash=sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8 \ + --hash=sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6 \ + --hash=sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0 \ + --hash=sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421 + # via pydantic +pygments==2.17.2 \ + --hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \ + --hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367 + # via + # readme-renderer + # rich +pyjwt==2.8.0 \ + --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ + --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 + # via sigstore +pyopenssl==23.3.0 \ + --hash=sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2 \ + --hash=sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12 + # via sigstore +python-dateutil==2.8.2 \ + --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ + --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 + # via betterproto +readme-renderer==42.0 \ + --hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \ + --hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1 + # via twine +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 + # via + # -r publish-requirements.in + # id + # requests-toolbelt + # sigstore + # tuf + # twine +requests-toolbelt==1.0.0 \ + --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ + --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 + # via twine +rfc3986==2.0.0 \ + --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ + --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c + # via twine +rich==13.7.0 \ + --hash=sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa \ + --hash=sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235 + # via + # sigstore + # twine +secretstorage==3.3.3 \ + --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ + --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 + # via keyring +securesystemslib==0.31.0 \ + --hash=sha256:549d70f7be6460252d016f03edc5ec0128fee56af55d2b863a5db14541ddbf18 \ + --hash=sha256:c1594afbcd5db198ec90c487e1720154afb71743d9f4bccf3dfda84de650c478 + # via + # sigstore + # tuf +sigstore==2.1.0 \ + --hash=sha256:68761c3078aca9bb97af8459602959ff47ce648bf722a8c2c868e45b46aad7e1 \ + --hash=sha256:7c64b4c6eccee0ec1b54d524d7be57dabc1f1f3651dd723cf195aa6b1f94b4f7 + # via -r publish-requirements.in +sigstore-protobuf-specs==0.2.2 \ + --hash=sha256:62c7beabc6910fb570dc4c600e33e81f2d2d683f785202ee109ca394bd829e94 \ + --hash=sha256:c05c1e7478a80af0c7dea9cc2d11f047826e4c029573d564137f788e11377391 + # via sigstore +sigstore-rekor-types==0.0.11 \ + --hash=sha256:791a696eccd5d07c933cc11d46dea22983efedaf5f1068734263ce0f25695bba \ + --hash=sha256:b63b4dc6dd70a3f69b236575146a18c357a3743172a03e8ceb18bbc25ef2563b + # via sigstore +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via python-dateutil +tuf==3.1.0 \ + --hash=sha256:3a4e9abba9d03c221842f62a9a687d51cc2b4a26c43ee7deb1ffb5fa2fb49374 \ + --hash=sha256:a8f055fbaf90d1477258c98fe29d23217e793ca0bdc5fb5a7d252ff5acecddc0 + # via sigstore +twine==4.0.2 \ + --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \ + --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8 + # via -r publish-requirements.in +typing-extensions==4.9.0 \ + --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ + --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd + # via + # pydantic + # pydantic-core +urllib3==2.1.0 \ + --hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \ + --hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54 + # via + # requests + # twine +zipp==3.17.0 \ + --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ + --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 + # via importlib-metadata diff --git a/.github/workflows/auto-close-stale.yml b/.github/workflows/auto-close-stale.yml index 46b4d3e2a9cf..de269c8aceac 100644 --- a/.github/workflows/auto-close-stale.yml +++ b/.github/workflows/auto-close-stale.yml @@ -13,7 +13,7 @@ jobs: if: github.repository_owner == 'pyca' runs-on: ubuntu-latest steps: - - uses: actions/stale@v8.0.0 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 with: only-labels: waiting-on-reporter days-before-stale: 3 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 1643a283b934..d494688db74f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -21,21 +21,24 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: persist-credentials: false path: "cryptography-pr" - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: repository: "pyca/cryptography" path: "cryptography-base" ref: "${{ github.base_ref }}" + - name: Clone test vectors + timeout-minutes: 2 + uses: ./cryptography-base/.github/actions/fetch-vectors - name: Setup python id: setup-python - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: "3.11" @@ -49,9 +52,9 @@ jobs: .venv-pr/bin/pip install -v -c ./cryptography-pr/ci-constraints-requirements.txt "./cryptography-pr[test]" ./cryptography-pr/vectors/ - name: Run benchmarks (base) - run: .venv-base/bin/pytest --benchmark-enable --benchmark-only ./cryptography-pr/tests/bench/ --benchmark-json=bench-base.json + run: .venv-base/bin/pytest --benchmark-enable --benchmark-only ./cryptography-pr/tests/bench/ --benchmark-json=bench-base.json --x509-limbo-root=x509-limbo/ - name: Run benchmarks (PR) - run: .venv-pr/bin/pytest --benchmark-enable --benchmark-only ./cryptography-pr/tests/bench/ --benchmark-json=bench-pr.json + 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 diff --git a/.github/workflows/boring-open-version-bump.yml b/.github/workflows/boring-open-version-bump.yml index 0c0036e11cac..9a6ba2ae81bc 100644 --- a/.github/workflows/boring-open-version-bump.yml +++ b/.github/workflows/boring-open-version-bump.yml @@ -13,7 +13,7 @@ jobs: if: github.repository_owner == 'pyca' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - id: check-sha-boring run: | SHA=$(git ls-remote https://boringssl.googlesource.com/boringssl refs/heads/master | cut -f1) @@ -51,14 +51,14 @@ jobs: sed -E -i "s/TYPE: \"openssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"openssl\", VERSION: \"${{ steps.check-sha-openssl.outputs.COMMIT_SHA }}\"/" .github/workflows/ci.yml git status if: steps.check-sha-openssl.outputs.COMMIT_SHA - - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + - 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 - name: Create Pull Request - uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 + uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 with: commit-message: "Bump BoringSSL and/or OpenSSL in CI" title: "Bump BoringSSL and/or OpenSSL in CI" diff --git a/.github/workflows/build_openssl.sh b/.github/workflows/build_openssl.sh index 42357abae9fc..013fcf42698a 100755 --- a/.github/workflows/build_openssl.sh +++ b/.github/workflows/build_openssl.sh @@ -72,13 +72,11 @@ elif [[ "${TYPE}" == "boringssl" ]]; then git clone https://boringssl.googlesource.com/boringssl pushd boringssl git checkout "${VERSION}" - mkdir build - pushd build - cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX="${OSSL_PATH}" - make -j"$(nproc)" - make install + cmake -B build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX="${OSSL_PATH}" + make -C build -j"$(nproc)" + make -C build install # delete binaries we don't need rm -rf "${OSSL_PATH}/bin" popd - popd + rm -rf boringssl/ fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0fe786454e4..4db52079b000 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ concurrency: env: CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + CARGO_INCREMENTAL: 0 jobs: linux: @@ -26,58 +27,61 @@ jobs: fail-fast: false matrix: PYTHON: - - {VERSION: "3.11", NOXSESSION: "flake"} - - {VERSION: "3.11", NOXSESSION: "rust"} - - {VERSION: "3.11", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.1.1"}} - - {VERSION: "pypy-3.8", NOXSESSION: "tests-nocoverage"} + - {VERSION: "3.12", NOXSESSION: "flake"} + - {VERSION: "3.12", NOXSESSION: "rust"} + - {VERSION: "3.12", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.2.1"}} - {VERSION: "pypy-3.9", NOXSESSION: "tests-nocoverage"} - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1u"}} - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.0.9"}} - - {VERSION: "3.11", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.1.1"}} - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.1", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.1", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} - - {VERSION: "3.11", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.1.1"}} - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.6.3"}} - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.7.3"}} - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.8.0"}} - - {VERSION: "3.11", NOXSESSION: "tests-randomorder"} - - {VERSION: "3.12-dev", NOXSESSION: "tests"} - # Latest commit on the BoringSSL master branch, as of May 27, 2023. - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "b0a026f8541c551854efd617021bb276f1fe5c23"}} - # Latest commit on the OpenSSL master branch, as of May 30, 2023. - - {VERSION: "3.11", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "36424806d699233b9a90a3a97fff3011828e2548"}} - # Builds with various Rust versions. Includes MSRV and potential - # future MSRV: - # 1.60 - pem 2.0.1 - - {VERSION: "3.11", NOXSESSION: "tests-nocoverage", RUST: "1.56.0"} - - {VERSION: "3.11", NOXSESSION: "rust,tests", RUST: "1.60.0"} - - {VERSION: "3.11", NOXSESSION: "rust,tests", RUST: "beta"} - - {VERSION: "3.11", NOXSESSION: "rust,tests", RUST: "nightly"} + - {VERSION: "pypy-3.10", NOXSESSION: "tests-nocoverage"} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1w"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.0.13"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.5"}} + - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.2.1"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.1", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.1", 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.5"}} + - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.2.1"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.7.3"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.8.2"}} + - {VERSION: "3.12", NOXSESSION: "tests-randomorder"} + # Latest commit on the BoringSSL master branch, as of Jan 23, 2024. + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "a4c3f8de4406c2382e43e88a638882fb1a32da32"}} + # Latest commit on the OpenSSL master branch, as of Jan 23, 2024. + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "5ed9a32a2aee89e10eb2891f5fb7a283e1b5199b"}} + # Builds with various Rust versions. Includes MSRV and next + # potential future MSRV: + # 1.64 - maturin, workspace inheritance + # 1.65 - Generic associated types (GATs) + - {VERSION: "3.12", NOXSESSION: "rust-noclippy,tests", RUST: "1.63.0"} + - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "1.64.0"} + - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "beta"} + - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "nightly"} timeout-minutes: 15 steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: persist-credentials: false - fetch-depth: 0 - - name: set mtimes for rust dirs - uses: ./.github/actions/mtime-fix - name: Setup python id: setup-python - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.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@52e69531e6f69a396bc9d1226284493a5db969ff + uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: ${{ matrix.PYTHON.RUST }} components: rustfmt,clippy if: matrix.PYTHON.RUST - run: rustup component add llvm-tools-preview - - name: Clone wycheproof + if: matrix.PYTHON.NOXSESSION != 'flake' && matrix.PYTHON.NOXSESSION != 'docs' + - name: Clone test vectors timeout-minutes: 2 - uses: ./.github/actions/wycheproof + uses: ./.github/actions/fetch-vectors + if: matrix.PYTHON.NOXSESSION != 'flake' && matrix.PYTHON.NOXSESSION != 'docs' && matrix.PYTHON.NOXSESSION != 'rust' - name: Compute config hash and set config vars run: | DEFAULT_CONFIG_FLAGS="shared no-ssl2 no-ssl3" @@ -91,14 +95,15 @@ jobs: CONFIG_FLAGS: ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }} if: matrix.PYTHON.OPENSSL - name: Load OpenSSL cache - uses: actions/cache@v3.3.1 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 id: ossl-cache timeout-minutes: 2 with: path: ${{ github.workspace }}/osslcache - # When altering the openssl build process you may need to increment the value on the end of this cache key - # so that you can prevent it from fetching the cache and skipping the build step. - key: ${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.OPENSSL_HASH }}-8 + # 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 }}-9 if: matrix.PYTHON.OPENSSL - name: Build custom OpenSSL/LibreSSL run: .github/workflows/build_openssl.sh @@ -121,7 +126,7 @@ jobs: # pypy3-3.8 and pypy3-3.9 -- both of them show up as 7.3.11. key: ${{ matrix.PYTHON.VERSION }}-${{ steps.setup-python.outputs.python-version }}-${{ matrix.PYTHON.NOXSESSION }}-${{ env.OPENSSL_HASH }} - - run: python -m pip install -c ci-constraints-requirements.txt 'nox' + - run: python -m pip install -c ci-constraints-requirements.txt 'nox' 'tomli; python_version < "3.11"' - name: Create nox environment run: | nox -v --install-only @@ -130,7 +135,7 @@ jobs: CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - name: Tests run: | - nox --no-install -- --color=yes --wycheproof-root=wycheproof ${{ matrix.PYTHON.NOXARGS }} + nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo ${{ matrix.PYTHON.NOXARGS }} env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} COLUMNS: 80 @@ -151,6 +156,7 @@ jobs: - {IMAGE: "buster", NOXSESSION: "tests-nocoverage", RUNNER: "ubuntu-latest"} - {IMAGE: "bullseye", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "bookworm", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} + - {IMAGE: "trixie", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "sid", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "ubuntu-focal", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "ubuntu-jammy", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} @@ -161,7 +167,7 @@ jobs: - {IMAGE: "centos-stream9-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} - {IMAGE: "ubuntu-jammy:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} - - {IMAGE: "alpine:aarch64", NOXSESSION: "tests-nocoverage", RUNNER: [self-hosted, Linux, ARM64]} + - {IMAGE: "alpine:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} timeout-minutes: 15 env: RUSTUP_HOME: /root/.rustup @@ -175,24 +181,18 @@ jobs: sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release if: matrix.IMAGE.IMAGE == 'alpine:aarch64' - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: persist-credentials: false - fetch-depth: 0 - - name: git config shenanigans - run: | - git config --global --add safe.directory $(pwd) # needed for the mtime fix since git doesn't think it owns the files due to being in containers - - name: set mtimes for rust dirs - uses: ./.github/actions/mtime-fix - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 with: key: ${{ matrix.IMAGE.IMAGE }} - - name: Clone wycheproof + - name: Clone test vectors timeout-minutes: 2 - uses: ./.github/actions/wycheproof + uses: ./.github/actions/fetch-vectors # When run in a docker container the home directory doesn't have the same owner as the # apparent user so pip refuses to create a cache dir - name: create pip cache dir @@ -200,14 +200,14 @@ jobs: - run: | echo "OPENSSL_FORCE_FIPS_MODE=1" >> $GITHUB_ENV if: matrix.IMAGE.FIPS - - run: /venv/bin/python -m pip install -c ci-constraints-requirements.txt 'nox' + - run: /venv/bin/python -m pip install -c ci-constraints-requirements.txt 'nox' '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 }} - - run: '/venv/bin/nox --no-install -- --color=yes --wycheproof-root="wycheproof"' + - run: '/venv/bin/nox --no-install -- --color=yes --wycheproof-root="wycheproof" --x509-limbo-root="x509-limbo"' env: COLUMNS: 80 # OPENSSL_ENABLE_SHA1_SIGNATURES is for CentOS 9 Stream @@ -221,24 +221,21 @@ jobs: fail-fast: false matrix: RUNNER: - - {OS: 'macos-12', ARCH: 'x86_64'} + - {OS: 'macos-13', ARCH: 'x86_64'} - {OS: [self-hosted, macos, ARM64, tart], ARCH: 'arm64'} PYTHON: - {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} - - {VERSION: "3.11", NOXSESSION: "tests"} + - {VERSION: "3.12", NOXSESSION: "tests"} exclude: # We only test latest Python on arm64. py37 won't work since there's no universal2 binary - PYTHON: {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} RUNNER: {OS: [self-hosted, macos, ARM64, tart], ARCH: 'arm64'} timeout-minutes: 15 steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: persist-credentials: false - fetch-depth: 0 - - name: set mtimes for rust dirs - uses: ./.github/actions/mtime-fix - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 @@ -246,19 +243,22 @@ jobs: key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.PYTHON.VERSION }} - name: Setup python - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: 'x64' # we force this right now so that it will install the universal2 on arm64 + cache: pip + cache-dependency-path: ci-constraints-requirements.txt + timeout-minutes: 3 - run: rustup component add llvm-tools-preview - - run: python -m pip install -c ci-constraints-requirements.txt 'nox' + - run: python -m pip install -c ci-constraints-requirements.txt 'nox' 'tomli; python_version < "3.11"' - - name: Clone wycheproof + - name: Clone test vectors timeout-minutes: 2 - uses: ./.github/actions/wycheproof + uses: ./.github/actions/fetch-vectors - - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 with: repo: pyca/infra workflow: build-macos-openssl.yml @@ -277,7 +277,7 @@ jobs: 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 + run: nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} COLUMNS: 80 @@ -294,31 +294,31 @@ jobs: - {ARCH: 'x64', WINDOWS: 'win64'} PYTHON: - {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} - - {VERSION: "3.11", NOXSESSION: "tests"} + - {VERSION: "3.12", NOXSESSION: "tests"} timeout-minutes: 15 steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: persist-credentials: false - fetch-depth: 0 - - name: set mtimes for rust dirs - uses: ./.github/actions/mtime-fix - name: Setup python id: setup-python - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} + cache: pip + cache-dependency-path: ci-constraints-requirements.txt + timeout-minutes: 3 - run: rustup component add llvm-tools-preview - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 with: key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.WINDOWS.ARCH }}-${{ steps.setup-python.outputs.python-version }} - - run: python -m pip install -c ci-constraints-requirements.txt "nox" + - run: python -m pip install -c ci-constraints-requirements.txt "nox" "tomli; python_version < '3.11'" - - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 with: repo: pyca/infra workflow: build-windows-openssl.yml @@ -332,9 +332,9 @@ jobs: echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV shell: bash - - name: Clone wycheproof + - name: Clone test vectors timeout-minutes: 2 - uses: ./.github/actions/wycheproof + uses: ./.github/actions/fetch-vectors - name: Build nox environment run: nox -v --install-only @@ -342,7 +342,7 @@ jobs: 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 + run: nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} COLUMNS: 80 @@ -367,26 +367,26 @@ jobs: - mitmproxy - scapy PYTHON: - - '3.11' + - '3.12' name: "Downstream tests for ${{ matrix.DOWNSTREAM }}" timeout-minutes: 15 steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: persist-credentials: false - fetch-depth: 0 - - name: set mtimes for rust dirs - uses: ./.github/actions/mtime-fix - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 - name: Setup python - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.PYTHON }} + cache: pip + cache-dependency-path: ci-constraints-requirements.txt + timeout-minutes: 3 - run: ./.github/downstream.d/${{ matrix.DOWNSTREAM }}.sh install - - run: pip install . + - run: pip install . setuptools 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 @@ -413,25 +413,29 @@ jobs: runs-on: ubuntu-latest needs: [linux, distros, macos, windows, linux-downstream] if: ${{ always() }} + timeout-minutes: 3 steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 timeout-minutes: 3 with: persist-credentials: false - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} - name: Setup python if: ${{ always() }} - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: - python-version: '3.11' + python-version: '3.12' + cache: pip + cache-dependency-path: ci-constraints-requirements.txt + timeout-minutes: 3 - run: pip install -c ci-constraints-requirements.txt coverage[toml] if: ${{ always() }} - name: Download coverage data if: ${{ always() }} - uses: actions/download-artifact@v3.0.2 + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: coverage-data - name: Combine coverage and fail if it's <100%. @@ -473,14 +477,14 @@ jobs: run: python -m coverage html if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} - name: Upload HTML report. - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: _html-report path: htmlcov if-no-files-found: ignore if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} - name: Upload rust HTML report. - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: _html-rust-report path: rust-coverage diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml index 9a11f2a9fc70..d4fb20e091f5 100644 --- a/.github/workflows/linkcheck.yml +++ b/.github/workflows/linkcheck.yml @@ -1,6 +1,9 @@ name: linkcheck on: - pull_request: {} + pull_request: + paths: + - docs/conf.py + - .github/workflows/linkcheck.yml push: branches: - main @@ -9,24 +12,20 @@ permissions: contents: read env: - CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + CARGO_INCREMENTAL: 0 jobs: docs-linkcheck: - if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && contains(github.event.pull_request.title, 'linkcheck')) runs-on: ubuntu-latest name: "linkcheck" timeout-minutes: 10 steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: persist-credentials: false - fetch-depth: 0 - - name: set mtimes for rust dirs - uses: ./.github/actions/mtime-fix - name: Setup python id: setup-python - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: 3.11 - name: Cache rust and pip @@ -43,4 +42,4 @@ jobs: env: CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} - name: linkcheck - run: nox --no-install -s docs-linkcheck -- --color=yes \ No newline at end of file + run: nox --no-install -s docs-linkcheck -- --color=yes diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 951b70546066..88379415f801 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -12,7 +12,7 @@ jobs: if: github.repository_owner == 'pyca' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@c1b35aecc5cdb1a34539d14196df55838bb2f836 + - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} issue-inactive-days: 90 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index eed42830ecc7..a7f75070628e 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -15,6 +15,12 @@ on: workflows: ["Wheel Builder"] types: [completed] +env: + PUBLISH_REQUIREMENTS_PATH: .github/requirements/publish-requirements.txt + +permissions: + contents: read + jobs: publish: runs-on: ubuntu-latest @@ -25,11 +31,23 @@ jobs: permissions: id-token: "write" steps: - - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + with: + python-version: "3.11" + - name: Get publish-requirements.txt from repository + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + sparse-checkout: | + ${{ env.PUBLISH_REQUIREMENTS_PATH }} + sparse-checkout-cone-mode: false + persist-credentials: false + - name: Install Python dependencies + run: pip install --require-hashes -r ${{ env.PUBLISH_REQUIREMENTS_PATH }} + + - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 with: path: dist/ run_id: ${{ github.event.inputs.run_id || github.event.workflow_run.id }} - - run: pip install twine requests sigstore - run: | echo "OIDC_AUDIENCE=pypi" >> $GITHUB_ENV @@ -57,7 +75,7 @@ jobs: response.raise_for_status() token = response.json()["value"] - response = requests.post(f"https://{os.environ['PYPI_DOMAIN']}/_/oidc/github/mint-token", json={"token": token}) + response = requests.post(f"https://{os.environ['PYPI_DOMAIN']}/_/oidc/mint-token", json={"token": token}) response.raise_for_status() pypi_token = response.json()["token"] diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index 677319b3fa5a..0d2c5774721f 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -15,19 +15,19 @@ on: pull_request: paths: - .github/workflows/wheel-builder.yml - - setup.py + - .github/requirements/** - pyproject.toml - vectors/pyproject.toml env: - CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + BUILD_REQUIREMENTS_PATH: .github/requirements/build-requirements.txt jobs: sdist: runs-on: ubuntu-latest name: sdists steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: # The tag to build or the tag received by the tag event ref: ${{ github.event.inputs.version || github.ref }} @@ -40,11 +40,11 @@ jobs: run: .venv/bin/python -m build --sdist - name: Make sdist and wheel (vectors) run: cd vectors/ && ../.venv/bin/python -m build - - uses: actions/upload-artifact@v3.1.2 + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: "cryptography-sdist" path: dist/cryptography* - - uses: actions/upload-artifact@v3.1.2 + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: "vectors-sdist-wheel" path: vectors/dist/cryptography* @@ -57,36 +57,49 @@ jobs: fail-fast: false matrix: PYTHON: - - { VERSION: "cp37-cp37m", ABI_VERSION: 'cp37' } - - { VERSION: "pp38-pypy38_pp73" } + - { VERSION: "cp311-cp311", ABI_VERSION: 'cp37' } + - { VERSION: "cp311-cp311", ABI_VERSION: 'cp39' } - { VERSION: "pp39-pypy39_pp73" } + - { VERSION: "pp310-pypy310_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: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1: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: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: [self-hosted, Linux, ARM64]} exclude: # There are no readily available musllinux PyPy distributions - - PYTHON: { VERSION: "pp38-pypy38_pp73" } - MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} - PYTHON: { VERSION: "pp39-pypy39_pp73" } MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} - - PYTHON: { VERSION: "pp38-pypy38_pp73" } - MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} - PYTHON: { VERSION: "pp39-pypy39_pp73" } MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} - # We also don't build pypy wheels for anything except the latest manylinux - - PYTHON: { VERSION: "pp38-pypy38_pp73" } - MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + + - PYTHON: { VERSION: "pp39-pypy39_pp73" } + MANYLINUX: { NAME: "musllinux_1_2_x86_64", CONTAINER: "cryptography-musllinux_1_2:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "musllinux_1_2_x86_64", CONTAINER: "cryptography-musllinux_1_2:x86_64", RUNNER: "ubuntu-latest"} + - PYTHON: { VERSION: "pp39-pypy39_pp73" } + MANYLINUX: { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: [self-hosted, Linux, ARM64]} + + # We also don't build pypy wheels for anything except the latest manylinux - PYTHON: { VERSION: "pp39-pypy39_pp73" } MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} - - PYTHON: { VERSION: "pp38-pypy38_pp73" } - MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} - PYTHON: { VERSION: "pp39-pypy39_pp73" } MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64]} + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64]} name: "${{ matrix.PYTHON.VERSION }} for ${{ matrix.MANYLINUX.NAME }}" steps: - name: Ridiculous alpine workaround for actions support on arm64 @@ -96,15 +109,24 @@ jobs: # then use a glibc nodejs, which works fine when gcompat # is installed in the container (which it is) sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release - if: matrix.MANYLINUX.NAME == 'musllinux_1_1_aarch64' + if: startsWith(matrix.MANYLINUX.NAME, 'musllinux') && endsWith(matrix.MANYLINUX.NAME, 'aarch64') - - uses: actions/download-artifact@v3.0.2 + - name: Get build-requirements.txt from repository + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: - name: cryptography-sdist - + # The tag to build or the tag received by the tag event + ref: ${{ github.event.inputs.version || github.ref }} + persist-credentials: false + sparse-checkout: | + ${{ env.BUILD_REQUIREMENTS_PATH }} + sparse-checkout-cone-mode: false - run: /opt/python/${{ matrix.PYTHON.VERSION }}/bin/python -m venv .venv - name: Install Python dependencies - run: .venv/bin/pip install -U pip wheel cffi setuptools-rust + run: .venv/bin/pip install --require-hashes -r ${{ env.BUILD_REQUIREMENTS_PATH }} + + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: cryptography-sdist - run: mkdir tmpwheelhouse - name: Build the wheel run: | @@ -131,14 +153,14 @@ jobs: .venv/bin/python -c "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" - run: mkdir cryptography-wheelhouse - run: mv wheelhouse/cryptography*.whl cryptography-wheelhouse/ - - uses: actions/upload-artifact@v3.1.2 + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: - name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.MANYLINUX.NAME }}-${{ matrix.PYTHON.VERSION }}" + name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.MANYLINUX.NAME }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION }}" path: cryptography-wheelhouse/ macos: needs: [sdist] - runs-on: macos-12 + runs-on: macos-13 strategy: fail-fast: false matrix: @@ -155,6 +177,18 @@ jobs: # This will change in the future as we change the base Python we # build against _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' + - VERSION: '3.11' + ABI_VERSION: 'cp39' + # Despite the name, this is built for the macOS 11 SDK on arm64 and 10.9+ on intel + DOWNLOAD_URL: 'https://www.python.org/ftp/python/3.11.3/python-3.11.3-macos11.pkg' + BIN_PATH: '/Library/Frameworks/Python.framework/Versions/3.11/bin/python3' + DEPLOYMENT_TARGET: '10.12' + # This archflags is default, but let's be explicit + ARCHFLAGS: '-arch x86_64 -arch arm64' + # See https://github.com/pypa/cibuildwheel/blob/c8876b5c54a6c6b08de5d4b1586906b56203bd9e/cibuildwheel/macos.py#L257-L269 + # This will change in the future as we change the base Python we + # build against + _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' - VERSION: '3.11' ABI_VERSION: 'cp37' DOWNLOAD_URL: 'https://www.python.org/ftp/python/3.11.3/python-3.11.3-macos11.pkg' @@ -165,22 +199,27 @@ jobs: # requires a 21.x pip) ARCHFLAGS: '-arch x86_64' _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' - - VERSION: 'pypy-3.8' + - VERSION: 'pypy-3.9' BIN_PATH: 'pypy3' DEPLOYMENT_TARGET: '10.12' _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' ARCHFLAGS: '-arch x86_64' - - VERSION: 'pypy-3.9' + - VERSION: 'pypy-3.10' BIN_PATH: 'pypy3' DEPLOYMENT_TARGET: '10.12' _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' ARCHFLAGS: '-arch x86_64' name: "${{ matrix.PYTHON.VERSION }} ABI ${{ matrix.PYTHON.ABI_VERSION }} macOS ${{ matrix.PYTHON.ARCHFLAGS }}" steps: - - uses: actions/download-artifact@v3.0.2 + - name: Get build-requirements.txt from repository + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: - name: cryptography-sdist - + # The tag to build or the tag received by the tag event + ref: ${{ github.event.inputs.version || github.ref }} + persist-credentials: false + sparse-checkout: | + ${{ env.BUILD_REQUIREMENTS_PATH }} + sparse-checkout-cone-mode: false - name: Setup python run: | curl "$PYTHON_DOWNLOAD_URL" -o python.pkg @@ -189,11 +228,11 @@ jobs: PYTHON_DOWNLOAD_URL: ${{ matrix.PYTHON.DOWNLOAD_URL }} if: contains(matrix.PYTHON.VERSION, 'pypy') == false - name: Setup pypy - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.PYTHON.VERSION }} if: contains(matrix.PYTHON.VERSION, 'pypy') - - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 with: repo: pyca/infra workflow: build-macos-openssl.yml @@ -202,14 +241,18 @@ jobs: name: openssl-macos-universal2 path: "../openssl-macos-universal2/" github_token: ${{ secrets.GITHUB_TOKEN }} - - uses: dtolnay/rust-toolchain@52e69531e6f69a396bc9d1226284493a5db969ff + - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: stable # Add the arm64 target in addition to the native arch (x86_64) target: aarch64-apple-darwin - - run: ${{ matrix.PYTHON.BIN_PATH }} -m venv venv - - run: venv/bin/pip install -U pip wheel cffi setuptools-rust + - name: Install Python dependencies + run: venv/bin/pip install --require-hashes -r ${{ env.BUILD_REQUIREMENTS_PATH }} + + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: cryptography-sdist - run: mkdir wheelhouse - name: Build the wheel run: | @@ -235,7 +278,7 @@ jobs: - run: mv wheelhouse/cryptography*.whl cryptography-wheelhouse/ - run: | echo "CRYPTOGRAPHY_WHEEL_NAME=$(basename $(ls cryptography-wheelhouse/cryptography*.whl))" >> $GITHUB_ENV - - uses: actions/upload-artifact@v3.1.2 + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: "${{ env.CRYPTOGRAPHY_WHEEL_NAME }}" path: cryptography-wheelhouse/ @@ -251,31 +294,42 @@ jobs: - {ARCH: 'x64', WINDOWS: 'win64', RUST_TRIPLE: 'x86_64-pc-windows-msvc'} PYTHON: - {VERSION: "3.11", "ABI_VERSION": "cp37"} - - {VERSION: "pypy-3.8"} + - {VERSION: "3.11", "ABI_VERSION": "cp39"} - {VERSION: "pypy-3.9"} + - {VERSION: "pypy-3.10"} exclude: # We need to exclude the below configuration because there is no 32-bit pypy3 - - WINDOWS: {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} - PYTHON: {VERSION: "pypy-3.8"} - WINDOWS: {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} PYTHON: {VERSION: "pypy-3.9"} + - WINDOWS: {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} + PYTHON: {VERSION: "pypy-3.10"} name: "${{ matrix.PYTHON.VERSION }} ${{ matrix.WINDOWS.WINDOWS }} ${{ matrix.PYTHON.ABI_VERSION }}" steps: - - uses: actions/download-artifact@v3.0.2 + - name: Get build-requirements.txt from repository + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + # The tag to build or the tag received by the tag event + ref: ${{ github.event.inputs.version || github.ref }} + persist-credentials: false + sparse-checkout: | + ${{ env.BUILD_REQUIREMENTS_PATH }} + sparse-checkout-cone-mode: false + + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: cryptography-sdist - name: Setup python - uses: actions/setup-python@v4.6.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} - - uses: dtolnay/rust-toolchain@52e69531e6f69a396bc9d1226284493a5db969ff + - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: stable target: ${{ matrix.WINDOWS.RUST_TRIPLE }} - - uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 + - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 with: repo: pyca/infra workflow: build-windows-openssl.yml @@ -289,9 +343,8 @@ jobs: echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV echo "OPENSSL_STATIC=1" >> $GITHUB_ENV shell: bash - - - run: python -m pip install -U pip wheel - - run: python -m pip install cffi setuptools-rust + - name: Install Python dependencies + run: python -m pip install --require-hashes -r ${{ env.BUILD_REQUIREMENTS_PATH }} - run: mkdir wheelhouse - run: | if [ -n "${{ matrix.PYTHON.ABI_VERSION }}" ]; then @@ -307,7 +360,7 @@ jobs: - run: mkdir cryptography-wheelhouse - run: move wheelhouse\cryptography*.whl cryptography-wheelhouse\ - - uses: actions/upload-artifact@v3.1.2 + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: - name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION}}" + name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION }}" path: cryptography-wheelhouse\ diff --git a/.github/workflows/x509-limbo-version-bump.yml b/.github/workflows/x509-limbo-version-bump.yml new file mode 100644 index 000000000000..e4a42bf3155f --- /dev/null +++ b/.github/workflows/x509-limbo-version-bump.yml @@ -0,0 +1,69 @@ +name: Bump x509-limbo and/or wycheproof +permissions: + contents: read + +on: + workflow_dispatch: + schedule: + # Run daily + - cron: "0 0 * * *" + +jobs: + bump: + if: github.repository_owner == 'pyca' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - id: check-sha-x509-limbo + run: | + SHA=$(git ls-remote https://github.com/C2SP/x509-limbo refs/heads/main | cut -f1) + LAST_COMMIT=$(grep x509-limbo-ref .github/actions/fetch-vectors/action.yml | grep -oE '[a-f0-9]{40}') + if ! grep -q "$SHA" .github/actions/fetch-vectors/action.yml; then + echo "COMMIT_SHA=${SHA}" >> $GITHUB_OUTPUT + echo "COMMIT_MSG<> $GITHUB_OUTPUT + echo -e "## x509-limbo\n[Commit: ${SHA}](https://github.com/C2SP/x509-limbo/commit/${SHA})\n\n[Diff](https://github.com/C2SP/x509-limbo/compare/${LAST_COMMIT}...${SHA}) between the last commit hash merged to this repository and the new commit." >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + - name: Update x509-limbo + run: | + set -xe + CURRENT_DATE=$(date "+%b %d, %Y") + sed -E -i "s/Latest commit on the x509-limbo main branch.*/Latest commit on the x509-limbo main branch, as of ${CURRENT_DATE}./" .github/actions/fetch-vectors/action.yml + sed -E -i "s/ref: \"[0-9a-f]{40}\" # x509-limbo-ref/ref: \"${{ steps.check-sha-x509-limbo.outputs.COMMIT_SHA }}\" # x509-limbo-ref/" .github/actions/fetch-vectors/action.yml + git status + if: steps.check-sha-x509-limbo.outputs.COMMIT_SHA + - id: check-sha-wycheproof + run: | + SHA=$(git ls-remote https://github.com/google/wycheproof refs/heads/master | cut -f1) + LAST_COMMIT=$(grep wycheproof-ref .github/actions/fetch-vectors/action.yml | grep -oE '[a-f0-9]{40}') + if ! grep -q "$SHA" .github/actions/fetch-vectors/action.yml; then + echo "COMMIT_SHA=${SHA}" >> $GITHUB_OUTPUT + echo "COMMIT_MSG<> $GITHUB_OUTPUT + echo -e "## wycheproof\n[Commit: ${SHA}](https://github.com/google/wycheproof/commit/${SHA})\n\n[Diff](https://github.com/google/wycheproof/compare/${LAST_COMMIT}...${SHA}) between the last commit hash merged to this repository and the new commit." >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + - name: Update wycheproof + run: | + set -xe + CURRENT_DATE=$(date "+%b %d, %Y") + sed -E -i "s/Latest commit on the wycheproof main branch.*/Latest commit on the wycheproof main branch, as of ${CURRENT_DATE}./" .github/actions/fetch-vectors/action.yml + sed -E -i "s/ref: \"[0-9a-f]{40}\" # wycheproof-ref/ref: \"${{ steps.check-sha-wycheproof.outputs.COMMIT_SHA }}\" # wycheproof-ref/" .github/actions/fetch-vectors/action.yml + git status + if: steps.check-sha-wycheproof.outputs.COMMIT_SHA + - 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-x509-limbo.outputs.COMMIT_SHA || steps.check-sha-wycheproof.outputs.COMMIT_SHA + - name: Create Pull Request + uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 + with: + commit-message: "Bump x509-limbo and/or wycheproof in CI" + title: "Bump x509-limbo and/or wycheproof in CI" + author: "pyca-boringbot[bot] " + body: | + ${{ steps.check-sha-x509-limbo.outputs.COMMIT_MSG }} + ${{ steps.check-sha-wycheproof.outputs.COMMIT_MSG }} + token: ${{ steps.generate-token.outputs.token }} + if: steps.check-sha-x509-limbo.outputs.COMMIT_SHA || steps.check-sha-wycheproof.outputs.COMMIT_SHA diff --git a/.readthedocs.yml b/.readthedocs.yml index 95b3c4f46e7c..8a37ec36404d 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -7,12 +7,15 @@ sphinx: # https://github.com/pyca/cryptography/issues/5863#issuecomment-817828152 builder: dirhtml +formats: + - pdf + build: # readdocs master now includes a rust toolchain os: "ubuntu-22.04" tools: python: "3.11" - rust: "1.64" + rust: "1.70" python: install: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 95e2ab25c0d9..393ba2aa8835 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,162 @@ Changelog ========= +.. _v42-0-2: + +42.0.2 - 2024-01-30 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.2.1. +* Fixed an issue that prevented the use of Python buffer protocol objects in + ``sign`` and ``verify`` methods on asymmetric keys. +* Fixed an issue with incorrect keyword-argument naming with ``EllipticCurvePrivateKey`` + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.exchange`, + ``X25519PrivateKey`` + :meth:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.exchange`, + ``X448PrivateKey`` + :meth:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.exchange`, + and ``DHPrivateKey`` + :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey.exchange`. + +.. _v42-0-1: + +42.0.1 - 2024-01-24 +~~~~~~~~~~~~~~~~~~~ + +* Fixed an issue with incorrect keyword-argument naming with ``EllipticCurvePrivateKey`` + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign`. +* Resolved compatibility issue with loading certain RSA public keys in + :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key`. + +.. _v42-0-0: + +42.0.0 - 2024-01-22 +~~~~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.7. +* **BACKWARDS INCOMPATIBLE:** Loading a PKCS7 with no content field using + :func:`~cryptography.hazmat.primitives.serialization.pkcs7.load_pem_pkcs7_certificates` + or + :func:`~cryptography.hazmat.primitives.serialization.pkcs7.load_der_pkcs7_certificates` + will now raise a ``ValueError`` rather than return an empty list. +* Parsing SSH certificates no longer permits malformed critical options with + values, as documented in the 41.0.2 release notes. +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.2.0. +* Updated the minimum supported Rust version (MSRV) to 1.63.0, from 1.56.0. +* We now publish both ``py37`` and ``py39`` ``abi3`` wheels. This should + resolve some errors relating to initializing a module multiple times per + process. +* Support :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` for + X.509 certificate signing requests and certificate revocation lists with the + keyword-only argument ``rsa_padding`` on the ``sign`` methods for + :class:`~cryptography.x509.CertificateSigningRequestBuilder` and + :class:`~cryptography.x509.CertificateRevocationListBuilder`. +* Added support for obtaining X.509 certificate signing request signature + algorithm parameters (including PSS) via + :meth:`~cryptography.x509.CertificateSigningRequest.signature_algorithm_parameters`. +* Added support for obtaining X.509 certificate revocation list signature + algorithm parameters (including PSS) via + :meth:`~cryptography.x509.CertificateRevocationList.signature_algorithm_parameters`. +* Added ``mgf`` property to + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. +* Added ``algorithm`` and ``mgf`` properties to + :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP`. +* Added the following properties that return timezone-aware ``datetime`` objects: + :meth:`~cryptography.x509.Certificate.not_valid_before_utc`, + :meth:`~cryptography.x509.Certificate.not_valid_after_utc`, + :meth:`~cryptography.x509.RevokedCertificate.revocation_date_utc`, + :meth:`~cryptography.x509.CertificateRevocationList.next_update_utc`, + :meth:`~cryptography.x509.CertificateRevocationList.last_update_utc`. + These are timezone-aware variants of existing properties that return naïve + ``datetime`` objects. +* Deprecated the following properties that return naïve ``datetime`` objects: + :meth:`~cryptography.x509.Certificate.not_valid_before`, + :meth:`~cryptography.x509.Certificate.not_valid_after`, + :meth:`~cryptography.x509.RevokedCertificate.revocation_date`, + :meth:`~cryptography.x509.CertificateRevocationList.next_update`, + :meth:`~cryptography.x509.CertificateRevocationList.last_update` + in favor of the new timezone-aware variants mentioned above. +* Added support for + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20` + on LibreSSL. +* Added support for RSA PSS signatures in PKCS7 with + :meth:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7SignatureBuilder.add_signer`. +* In the next release (43.0.0) of cryptography, loading an X.509 certificate + with a negative serial number will raise an exception. This has been + deprecated since 36.0.0. +* Added support for + :class:`~cryptography.hazmat.primitives.ciphers.aead.AESGCMSIV` when using + OpenSSL 3.2.0+. +* Added the :mod:`X.509 path validation ` APIs + for :class:`~cryptography.x509.Certificate` chains. These APIs should be + considered unstable and not subject to our stability guarantees until + documented as such in a future release. +* Added support for + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SM4` + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` + when using OpenSSL 3.0 or greater. + +.. _v41-0-7: + +41.0.7 - 2023-11-27 +~~~~~~~~~~~~~~~~~~~ + +* Fixed compilation when using LibreSSL 3.8.2. + +.. _v41-0-6: + +41.0.6 - 2023-11-27 +~~~~~~~~~~~~~~~~~~~ + +* Fixed a null-pointer-dereference and segfault that could occur when loading + certificates from a PKCS#7 bundle. Credit to **pkuzco** for reporting the + issue. **CVE-2023-49083** + +.. _v41-0-5: + +41.0.5 - 2023-10-24 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.4. +* Added a function to support an upcoming ``pyOpenSSL`` release. + +.. _v41-0-4: + +41.0.4 - 2023-09-19 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.3. + +.. _v41-0-3: + +41.0.3 - 2023-08-01 +~~~~~~~~~~~~~~~~~~~ + +* Fixed performance regression loading DH public keys. +* Fixed a memory leak when using + :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`. +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.2. + +.. _v41-0-2: + +41.0.2 - 2023-07-10 +~~~~~~~~~~~~~~~~~~~ + +* Fixed bugs in creating and parsing SSH certificates where critical options + with values were handled incorrectly. Certificates are now created correctly + and parsing accepts correct values as well as the previously generated + invalid forms with a warning. In the next release, support for parsing these + invalid forms will be removed. + +.. _v41-0-1: + +41.0.1 - 2023-06-01 +~~~~~~~~~~~~~~~~~~~ + +* Temporarily allow invalid ECDSA signature algorithm parameters in X.509 + certificates, which are generated by older versions of Java. +* Allow null bytes in pass phrases when serializing private keys. + .. _v41-0-0: 41.0.0 - 2023-05-30 diff --git a/README.rst b/README.rst index d71765b8dba3..3e573ae0a272 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ pyca/cryptography ``cryptography`` is a package which provides cryptographic recipes and primitives to Python developers. Our goal is for it to be your "cryptographic -standard library". It supports Python 3.7+ and PyPy3 7.3.10+. +standard library". It supports Python 3.7+ and PyPy3 7.3.11+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and diff --git a/ci-constraints-requirements.txt b/ci-constraints-requirements.txt index 009faa5e0bdc..19110a231d8e 100644 --- a/ci-constraints-requirements.txt +++ b/ci-constraints-requirements.txt @@ -5,95 +5,72 @@ # and then manually massaged to add version specifiers to packages whose # versions vary by Python version -alabaster==0.7.13 +alabaster==0.7.16 # via sphinx -argcomplete==3.0.8 +argcomplete==3.2.1; python_version >= "3.8" # via nox -babel==2.12.1 +babel==2.14.0 # via sphinx -black==23.3.0 - # via cryptography (pyproject.toml) -bleach==6.0.0 - # via readme-renderer -build==0.10.0 +build==1.0.3 # via # check-sdist # cryptography (pyproject.toml) -certifi==2023.5.7 +certifi==2023.11.17 # via requests -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests -check-sdist==0.1.2 +check-sdist==0.1.3 + # via cryptography (pyproject.toml) +click==8.1.7 # via cryptography (pyproject.toml) -click==8.1.3 - # via black -colorlog==6.7.0 +colorlog==6.8.0 # via nox -coverage==7.2.7 - # via pytest-cov -distlib==0.3.6 +coverage==7.4.0; python_version >= "3.8" + # via + # coverage + # pytest-cov +distlib==0.3.8 # via virtualenv -docutils==0.18.1 +docutils==0.20.1 # via # readme-renderer # sphinx # sphinx-rtd-theme -exceptiongroup==1.1.1 +exceptiongroup==1.2.0 # via pytest -execnet==1.9.0 +execnet==2.0.2 # via pytest-xdist -filelock==3.12.0 +filelock==3.13.1; python_version >= "3.8" # via virtualenv -idna==3.4 +idna==3.6 # via requests imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 - # via - # keyring - # twine iniconfig==2.0.0 # via pytest -jaraco-classes==3.2.3 - # via keyring -jinja2==3.1.2 +jinja2==3.1.3 # via sphinx -keyring==23.13.1 - # via twine -markdown-it-py==2.2.0 - # via rich -markupsafe==2.1.2 +markupsafe==2.1.4 # via jinja2 -mdurl==0.1.2 - # via markdown-it-py -more-itertools==9.1.0 - # via jaraco-classes -mypy==1.3.0 +mypy==1.8.0 # via cryptography (pyproject.toml) mypy-extensions==1.0.0 - # via - # black - # mypy + # via mypy +nh3==0.2.15 + # via readme-renderer nox==2023.4.22 # via cryptography (pyproject.toml) -packaging==23.1 +packaging==23.2 # via - # black # build # nox # pytest # sphinx -pathspec==0.11.1 - # via - # black - # check-sdist -pkginfo==1.9.6 - # via twine -platformdirs==3.5.1 - # via - # black - # virtualenv -pluggy==1.0.0 +pathspec==0.12.1 + # via check-sdist +platformdirs==4.1.0; python_version >= "3.8" + # via virtualenv +pluggy==1.3.0; python_version >= "3.8" # via pytest pretend==1.0.9 # via cryptography (pyproject.toml) @@ -103,14 +80,13 @@ pyenchant==3.2.2 # via # cryptography (pyproject.toml) # sphinxcontrib-spelling -pygments==2.15.1 +pygments==2.17.2 # via # readme-renderer - # rich # sphinx pyproject-hooks==1.0.0 # via build -pytest==7.3.1 +pytest==7.4.4 # via # cryptography (pyproject.toml) # pytest-benchmark @@ -121,76 +97,61 @@ pytest-benchmark==4.0.0 # via cryptography (pyproject.toml) pytest-cov==4.1.0 # via cryptography (pyproject.toml) -pytest-randomly==3.12.0 +pytest-randomly==3.15.0 + # via cryptography (pyproject.toml) +pytest-xdist==3.5.0 # via cryptography (pyproject.toml) -pytest-xdist==3.3.1 +readme-renderer==42.0 # via cryptography (pyproject.toml) -readme-renderer==37.3 - # via twine requests==2.31.0 - # via - # requests-toolbelt - # sphinx - # twine -requests-toolbelt==1.0.0 - # via twine -rfc3986==2.0.0 - # via twine -rich==13.3.5 - # via twine -ruff==0.0.270 + # via sphinx +ruff==0.1.14 # via cryptography (pyproject.toml) -six==1.16.0 - # via bleach snowballstemmer==2.2.0 # via sphinx -sphinx==6.2.1 +sphinx==7.2.6 # via # cryptography (pyproject.toml) # sphinx-rtd-theme + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp # sphinxcontrib-jquery + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml # sphinxcontrib-spelling -sphinx-rtd-theme==1.2.1 +sphinx-rtd-theme==2.0.0 # via cryptography (pyproject.toml) -sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-applehelp==1.0.8 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.6 # via sphinx -sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-htmlhelp==2.0.5 # via sphinx sphinxcontrib-jquery==4.1 # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.7 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.10 # via sphinx sphinxcontrib-spelling==8.0.0 # via cryptography (pyproject.toml) tomli==2.0.1 # via - # black # build # check-manifest # coverage # mypy # pyproject-hooks # pytest -twine==4.0.2 - # via cryptography (pyproject.toml) -typing-extensions==4.6.2 +typing-extensions==4.9.0; python_version >= "3.8" # via mypy -urllib3==2.0.2 - # via - # requests - # twine -virtualenv==20.23.0 +urllib3==2.1.0 + # via requests +virtualenv==20.25.0 # via nox -webencodings==0.5.1 - # via bleach -zipp==3.15.0 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # cffi diff --git a/docs/api-stability.rst b/docs/api-stability.rst index eafbd1d9506e..0ed03dc2f605 100644 --- a/docs/api-stability.rst +++ b/docs/api-stability.rst @@ -66,9 +66,9 @@ entirely. In that case, here's how the process will work: * In ``cryptography X.0.0`` the feature exists. * In ``cryptography (X + 1).0.0`` using that feature will emit a - ``UserWarning``. + ``CryptographyDeprecationWarning`` (base class ``UserWarning``). * In ``cryptography (X + 2).0.0`` using that feature will emit a - ``UserWarning``. + ``CryptographyDeprecationWarning``. * In ``cryptography (X + 3).0.0`` the feature will be removed or changed. In short, code that runs without warnings will always continue to work for a diff --git a/docs/conf.py b/docs/conf.py index 4cbbde37b7ce..cf0f25abcaa9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -# # Cryptography documentation build configuration file, created by # sphinx-quickstart on Tue Aug 6 19:19:14 2013. # @@ -72,7 +71,7 @@ # General information about the project. project = "Cryptography" -copyright = "2013-2023, Individual Contributors" +copyright = "2013-2024, 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 @@ -196,12 +195,15 @@ linkcheck_ignore = [ # Insecure renegotiation settings r"https://info.isl.ntt.co.jp/crypt/eng/camellia/", - # Inconsistent small DH params they seem incapable of fixing - r"https://www.secg.org/sec1-v2.pdf", - # Incomplete cert chain - r"https://www.oscca.gov.cn", # Cloudflare returns 403s for all non-browser requests r"https://speakerdeck.com", + r"https://\w+.stackexchange.com", + r"https://stackoverflow.com", + # GitHub changed how they do page renders so anchor detection + # no longer works in source view + r"https://github.com/.*/blob/.*#L\d+", + # Kuleuven struggles with the endless forward march of time + r"https://www.cosic.esat.kuleuven.be", ] autosectionlabel_prefix_document = True diff --git a/docs/development/custom-vectors/aes-192-gcm-siv.rst b/docs/development/custom-vectors/aes-192-gcm-siv.rst new file mode 100644 index 000000000000..1900eb87959d --- /dev/null +++ b/docs/development/custom-vectors/aes-192-gcm-siv.rst @@ -0,0 +1,28 @@ +AES-GCM-SIV vector creation +=========================== + +This page documents the code that was used to generate the AES-GCM-SIV test +vectors for key lengths not available in the OpenSSL test vectors. All the +vectors were generated using OpenSSL and verified with Rust. + +Creation +-------- + +The following Python script was run to generate the vector files. The OpenSSL +test vectors were used as a base and modified to have 192-bit key length. + +.. literalinclude:: /development/custom-vectors/aes-192-gcm-siv/generate_aes192gcmsiv.py + +Download link: :download:`generate_aes192gcmsiv.py +` + + +Verification +------------ + +The following Rust program was used to verify the vectors. + +.. literalinclude:: /development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/src/main.rs + +Download link: :download:`main.rs +` diff --git a/docs/development/custom-vectors/aes-192-gcm-siv/generate_aes192gcmsiv.py b/docs/development/custom-vectors/aes-192-gcm-siv/generate_aes192gcmsiv.py new file mode 100644 index 000000000000..a9d48198bd56 --- /dev/null +++ b/docs/development/custom-vectors/aes-192-gcm-siv/generate_aes192gcmsiv.py @@ -0,0 +1,86 @@ +# 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 + +from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV + + +def convert_key_to_192_bits(key: str) -> str: + """ + This takes existing 128 and 256-bit keys from test vectors from OpenSSL + and makes them 192-bit by either appending 0 or truncating the key. + """ + new_key = binascii.unhexlify(key) + if len(new_key) == 16: + new_key += b"\x00" * 8 + elif len(new_key) == 32: + new_key = new_key[0:24] + else: + raise RuntimeError( + "Unexpected key length. OpenSSL AES-GCM-SIV test vectors only " + "contain 128-bit and 256-bit keys" + ) + + return binascii.hexlify(new_key).decode("ascii") + + +def encrypt(key: str, iv: str, plaintext: str, aad: str) -> (str, str): + aesgcmsiv = AESGCMSIV(binascii.unhexlify(key)) + encrypted_output = aesgcmsiv.encrypt( + binascii.unhexlify(iv), + binascii.unhexlify(plaintext), + binascii.unhexlify(aad) if aad else None, + ) + ciphertext, tag = encrypted_output[:-16], encrypted_output[-16:] + + return ( + binascii.hexlify(ciphertext).decode("ascii"), + binascii.hexlify(tag).decode("ascii"), + ) + + +def build_vectors(filename): + count = 0 + output = [] + key = None + iv = None + aad = None + plaintext = None + + with open(filename) as vector_file: + for line in vector_file: + line = line.strip() + if line.startswith("Key"): + if count != 0: + ciphertext, tag = encrypt(key, iv, plaintext, aad) + output.append(f"Tag = {tag}\nCiphertext = {ciphertext}\n") + output.append(f"\nCOUNT = {count}") + count += 1 + aad = None + _, key = line.split(" = ") + key = convert_key_to_192_bits(key) + output.append(f"Key = {key}") + elif line.startswith("IV"): + _, iv = line.split(" = ") + output.append(f"IV = {iv}") + elif line.startswith("AAD"): + _, aad = line.split(" = ") + output.append(f"AAD = {aad}") + elif line.startswith("Plaintext"): + _, plaintext = line.split(" = ") + output.append(f"Plaintext = {plaintext}") + + ciphertext, tag = encrypt(key, iv, plaintext, aad) + output.append(f"Tag = {tag}\nCiphertext = {ciphertext}\n") + return "\n".join(output) + + +def write_file(data, filename): + with open(filename, "w") as f: + f.write(data) + + +path = "vectors/cryptography_vectors/ciphers/AES/GCM-SIV/openssl.txt" +write_file(build_vectors(path), "aes-192-gcm-siv.txt") diff --git a/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/Cargo.toml b/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/Cargo.toml new file mode 100644 index 000000000000..cbda93468545 --- /dev/null +++ b/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "verify-aes192gcmsiv" +version = "0.1.0" +edition = "2021" + +[dependencies] +aes-gcm-siv = "0.11.1" +aes = "0.8.1" +hex = "0.4.3" diff --git a/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/src/main.rs b/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/src/main.rs new file mode 100644 index 000000000000..d4dfdf99b0e6 --- /dev/null +++ b/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/src/main.rs @@ -0,0 +1,116 @@ +use aes_gcm_siv::{ + aead::{Aead, KeyInit}, + AesGcmSiv, Nonce, +}; + +use aes::Aes192; +use aes_gcm_siv::aead::generic_array::GenericArray; +use aes_gcm_siv::aead::Payload; +use std::fs::File; +use std::io; +use std::io::BufRead; +use std::path::Path; + +pub type Aes192GcmSiv = AesGcmSiv; + +struct VectorArgs { + nonce: String, + key: String, + aad: String, + tag: String, + plaintext: String, + ciphertext: String, +} + +fn validate(v: &VectorArgs) { + let key_bytes = hex::decode(&v.key).unwrap(); + let nonce_bytes = hex::decode(&v.nonce).unwrap(); + let aad_bytes = hex::decode(&v.aad).unwrap(); + let plaintext_bytes = hex::decode(&v.plaintext).unwrap(); + let expected_ciphertext_bytes = hex::decode(&v.ciphertext).unwrap(); + let expected_tag_bytes = hex::decode(&v.tag).unwrap(); + + let key_array: [u8; 24] = key_bytes.try_into().unwrap(); + let cipher = Aes192GcmSiv::new(&GenericArray::from(key_array)); + + let payload = Payload { + msg: plaintext_bytes.as_slice(), + aad: aad_bytes.as_slice(), + }; + let encrypted_bytes = cipher + .encrypt(Nonce::from_slice(nonce_bytes.as_slice()), payload) + .unwrap(); + let (ciphertext_bytes, tag_bytes) = encrypted_bytes.split_at(plaintext_bytes.len()); + assert_eq!(ciphertext_bytes, expected_ciphertext_bytes); + assert_eq!(tag_bytes, expected_tag_bytes); +} + +fn validate_vectors(filename: &Path) { + let file = File::open(filename).expect("Failed to open file"); + let reader = io::BufReader::new(file); + + let mut vector: Option = None; + + for line in reader.lines() { + let line = line.expect("Failed to read line"); + let segments: Vec<&str> = line.splitn(2, " = ").collect(); + + match segments.first() { + Some(&"COUNT") => { + if let Some(v) = vector.take() { + validate(&v); + } + vector = Some(VectorArgs { + nonce: String::new(), + key: String::new(), + aad: String::new(), + tag: String::new(), + plaintext: String::new(), + ciphertext: String::new(), + }); + } + Some(&"IV") => { + if let Some(v) = &mut vector { + v.nonce = segments[1].parse().expect("Failed to parse IV"); + } + } + Some(&"Key") => { + if let Some(v) = &mut vector { + v.key = segments[1].to_string(); + } + } + Some(&"AAD") => { + if let Some(v) = &mut vector { + v.aad = segments[1].to_string(); + } + } + Some(&"Tag") => { + if let Some(v) = &mut vector { + v.tag = segments[1].to_string(); + } + } + Some(&"Plaintext") => { + if let Some(v) = &mut vector { + v.plaintext = segments[1].to_string(); + } + } + Some(&"Ciphertext") => { + if let Some(v) = &mut vector { + v.ciphertext = segments[1].to_string(); + } + } + _ => {} + } + } + + if let Some(v) = vector { + validate(&v); + } +} + +fn main() { + validate_vectors(Path::new( + "vectors/cryptography_vectors/ciphers/AES/GCM-SIV/aes-192-gcm-siv.txt", + )); + println!("AES-192-GCM-SIV OK.") +} diff --git a/docs/development/custom-vectors/arc4/generate_arc4.py b/docs/development/custom-vectors/arc4/generate_arc4.py index 2ca919b40858..208d18585ac6 100644 --- a/docs/development/custom-vectors/arc4/generate_arc4.py +++ b/docs/development/custom-vectors/arc4/generate_arc4.py @@ -69,9 +69,7 @@ def _build_vectors(): for offset in _RFC6229_OFFSETS: if offset % 16 != 0: raise ValueError( - "Offset {} is not evenly divisible by 16".format( - offset - ) + f"Offset {offset} is not evenly divisible by 16" ) while current_offset < offset: encryptor.update(plaintext) diff --git a/docs/development/custom-vectors/cast5/generate_cast5.py b/docs/development/custom-vectors/cast5/generate_cast5.py index 27fb4634e295..38eddbf187fe 100644 --- a/docs/development/custom-vectors/cast5/generate_cast5.py +++ b/docs/development/custom-vectors/cast5/generate_cast5.py @@ -31,20 +31,18 @@ def build_vectors(mode, filename): if line.startswith("KEY"): if count != 0: output.append( - "CIPHERTEXT = {}".format( - encrypt(mode, key, iv, plaintext) - ) + f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) output.append(f"\nCOUNT = {count}") count += 1 - name, key = line.split(" = ") + _, key = line.split(" = ") output.append(f"KEY = {key}") elif line.startswith("IV"): - name, iv = line.split(" = ") + _, iv = line.split(" = ") iv = iv[0:16] output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): - name, plaintext = line.split(" = ") + _, plaintext = line.split(" = ") output.append(f"PLAINTEXT = {plaintext}") output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) diff --git a/docs/development/custom-vectors/chacha20.rst b/docs/development/custom-vectors/chacha20.rst new file mode 100644 index 000000000000..5fee0c360e35 --- /dev/null +++ b/docs/development/custom-vectors/chacha20.rst @@ -0,0 +1,29 @@ +ChaCha20 vector creation +======================== + +This page documents the code that was used to generate the vectors +to test the counter overflow behavior in ChaCha20 as well as code +used to verify them against another implementation. + +Creation +-------- + +The following Python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/chacha20/generate_chacha20_overflow.py + +Download link: :download:`generate_chacha20_overflow.py +` + + +Verification +------------ + +The following Python script was used to verify the vectors. The +counter overflow is handled manually to avoid relying on the same +code that generated the vectors. + +.. literalinclude:: /development/custom-vectors/chacha20/verify_chacha20_overflow.py + +Download link: :download:`verify_chacha20_overflow.py +` diff --git a/docs/development/custom-vectors/chacha20/generate_chacha20_overflow.py b/docs/development/custom-vectors/chacha20/generate_chacha20_overflow.py new file mode 100644 index 000000000000..c8ed339f4074 --- /dev/null +++ b/docs/development/custom-vectors/chacha20/generate_chacha20_overflow.py @@ -0,0 +1,47 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import binascii +import struct + +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import algorithms + +_N_BLOCKS = [1, 1.5, 2, 2.5, 3] +_INITIAL_COUNTERS = [2**32 - 1, 2**64 - 1] + + +def _build_vectors(): + count = 0 + output = [] + key = "0" * 64 + nonce = "0" * 16 + for blocks in _N_BLOCKS: + plaintext = binascii.unhexlify("0" * int(128 * blocks)) + for counter in _INITIAL_COUNTERS: + full_nonce = struct.pack(" bytes: + full_nonce = struct.pack("`). These are +handled by the use of ``nox``, which can be installed with ``pip``. .. code-block:: console $ # Create a virtualenv and activate it $ # Set up your cryptography build environment $ pip install nox - $ # Specify your Python version here. - $ nox -e tests -p py310 + $ nox -e local OpenSSL on macOS ~~~~~~~~~~~~~~~~ -You must have installed `OpenSSL`_ (via `Homebrew`_ , `MacPorts`_, or a custom -build) and must configure the build `as documented here`_ before calling -``nox`` or else pip will fail to compile. +You must have installed `OpenSSL`_ (via `Homebrew`_ , `MacPorts`_) before +invoking ``nox`` or else pip will fail to compile. Running tests ------------- ``cryptography`` unit tests are found in the ``tests/`` directory and are -designed to be run using `pytest`_. ``nox`` automatically invokes ``pytest``: +designed to be run using `pytest`_. ``nox`` automatically invokes ``pytest`` +and other required checks for ``cryptography``: .. code-block:: console - $ nox -e tests -p py310 - ... - 62746 passed in 220.43 seconds + $ nox -e local -Building documentation ----------------------- - -``cryptography`` documentation is stored in the ``docs/`` directory. It is -written in `reStructured Text`_ and rendered using `Sphinx`_. - -Use `nox`_ to build the documentation. For example: +You can also specify a subset of tests to run as positional arguments: .. code-block:: console - $ nox -e docs - ... - nox > Session docs was successful. + $ # run the whole x509 testsuite, plus the fernet tests + $ nox -e local -- tests/x509/ tests/test_fernet.py -The HTML documentation index can now be found at -``docs/_build/html/index.html``. .. _`Homebrew`: https://brew.sh .. _`MacPorts`: https://www.macports.org @@ -61,6 +49,4 @@ The HTML documentation index can now be found at .. _`nox`: https://pypi.org/project/nox/ .. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _`pip`: https://pypi.org/project/pip/ -.. _`sphinx`: https://pypi.org/project/Sphinx/ -.. _`reStructured Text`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html -.. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic \ No newline at end of file +.. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst index 6148419ce134..147de318e40f 100644 --- a/docs/development/submitting-patches.rst +++ b/docs/development/submitting-patches.rst @@ -19,10 +19,10 @@ Code ---- When in doubt, refer to :pep:`8` for Python code. You can check if your code -meets our automated requirements by formatting it with ``black`` and running -``ruff`` against it. If you've installed the development requirements this -will automatically use our configuration. You can also run the ``nox`` job with -``nox -e flake``. +meets our automated requirements by formatting it with ``ruff format`` and +running ``ruff`` against it. If you've installed the development requirements +this will automatically use our configuration. You can also run the ``nox`` +job with ``nox -e flake``. `Write comments as complete sentences.`_ @@ -61,12 +61,12 @@ whether the signature was valid. .. code-block:: python # This is bad. - def verify(sig): + def verify(sig: bytes) -> bool: # ... return is_valid # Good! - def verify(sig): + def verify(sig: bytes) -> None: # ... if not is_valid: raise InvalidSignature diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 56bc9361c555..0b1f238ffaa2 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -22,7 +22,7 @@ for various cryptographic algorithms. These are not included in the repository continuous integration environments. We have ensured all test vectors are used as of commit -``b063b4aedae951c69df014cd25fa6d69ae9e8cb9``. +``d9f6ec7d8bd8c96da05368999094e4a75ba5cb3d``. Asymmetric ciphers ~~~~~~~~~~~~~~~~~~ @@ -33,7 +33,7 @@ Asymmetric ciphers * FIPS 186-2 and FIPS 186-3 DSA test vectors from `NIST CAVP`_. * FIPS 186-2 and FIPS 186-3 ECDSA test vectors from `NIST CAVP`_. * DH and ECDH and ECDH+KDF(17.4) test vectors from `NIST CAVP`_. -* Ed25519 test vectors from the `Ed25519 website_`. +* Ed25519 test vectors from the `Ed25519 website`_. * OpenSSL PEM RSA serialization vectors from the `OpenSSL example key`_ and `GnuTLS key parsing tests`_. * ``asymmetric/PEM_Serialization/rsa-bad-1025-q-is-2.pem`` from `badkeys`_. @@ -72,12 +72,18 @@ Custom asymmetric vectors * ``asymmetric/PEM_Serialization/ec_public_key.pem`` and ``asymmetric/DER_Serialization/ec_public_key.der``- Contains the public key corresponding to ``ec_private_key.pem``, generated using OpenSSL. +* ``asymmetric/PEM_Serialization/ec_public_key_rsa_delimiter.pem`` - Contains + the public key corresponding to ``ec_private_key.pem``, but with the wrong PEM + delimiter (``RSA PUBLIC KEY`` when it should be ``PUBLIC KEY``). * ``asymmetric/PEM_Serialization/rsa_private_key.pem`` - Contains an RSA 2048 bit key generated using OpenSSL, protected by the secret "123456" with DES3 encryption. * ``asymmetric/PEM_Serialization/rsa_public_key.pem`` and ``asymmetric/DER_Serialization/rsa_public_key.der``- Contains an RSA 2048 bit public generated using OpenSSL from ``rsa_private_key.pem``. +* ``asymmetric/PEM_Serialization/rsa_wrong_delimiter_public_key.pem`` - Contains + an RSA 2048 bit public key generated from ``rsa_private_key.pem``, but with + the wrong PEM delimiter (``RSA PUBLIC KEY`` when it should be ``PUBLIC KEY``). * ``asymmetric/PEM_Serialization/dsa_4096.pem`` - Contains a 4096-bit DSA private key generated using OpenSSL. * ``asymmetric/PEM_Serialization/dsaparam.pem`` - Contains 2048-bit DSA @@ -107,12 +113,29 @@ Custom asymmetric vectors ``asymmetric/public/PKCS1/rsa.pub.der`` are PKCS1 conversions of the public key from ``asymmetric/PKCS8/unenc-rsa-pkcs8.pem`` using PEM and DER encoding. * ``x509/custom/ca/ca_key.pem`` - An unencrypted PCKS8 ``secp256r1`` key. It is - the private key for the certificate ``x509/custom/ca/ca.pem``. This key is + the private key for the certificate ``x509/custom/ca/ca.pem``. +* ``pkcs12/ca/ca_key.pem`` - An unencrypted PCKS8 ``secp256r1`` key. It is + the private key for the certificate ``pkcs12/ca/ca.pem``. This key is encoded in several of the PKCS12 custom vectors. * ``x509/custom/ca/rsa_key.pem`` - An unencrypted PCKS8 4096 bit RSA key. It is the private key for the certificate ``x509/custom/ca/rsa_ca.pem``. * ``asymmetric/EC/compressed_points.txt`` - Contains compressed public points generated using OpenSSL. +* ``asymmetric/EC/explicit_parameters_private_key.pem`` - Contains an EC + private key with an curve defined by explicit parameters. +* ``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/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 + ``sect163k1``. +* ``asymmetric/EC/sect163r2-spki.pem`` - Contains an EC SPKI on the curve + ``sect163r2``. +* ``asymmetric/EC/sect233k1-spki.pem`` - Contains an EC SPKI on the curve + ``sect233k1``. +* ``asymmetric/EC/sect233r1-spki.pem`` - Contains an EC SPKI on the curve + ``sect233r1``. * ``asymmetric/X448/x448-pkcs8-enc.pem`` and ``asymmetric/X448/x448-pkcs8-enc.der`` contain an X448 key encrypted with AES 256 CBC with the password ``password``. @@ -230,7 +253,7 @@ X.509 ``cryptography.io.pem`` certificate. * ``cryptography.io.precert.pem`` - A pre-certificate with the CT poison extension for the cryptography website. -* ``cryptography-scts.io.pem`` - A leaf certificate issued by Let's Encrypt for +* ``cryptography-scts.pem`` - A leaf certificate issued by Let's Encrypt for the cryptography website which contains signed certificate timestamps. * ``wildcard_san.pem`` - A leaf certificate issued by a public CA for ``langui.sh`` that contains wildcard entries in the SAN extension. @@ -464,8 +487,10 @@ Custom X.509 Vectors information access extension with both a CA repository entry and a custom OID entry. * ``ca/ca.pem`` - A self-signed certificate with ``basicConstraints`` set to - true. Its private key is ``ca/ca_key.pem``. This certificate is encoded in - several of the PKCS12 custom vectors. + true. Its private key is ``ca/ca_key.pem``. +* ``pkcs12/ca/ca.pem`` - A self-signed certificate with ``basicConstraints`` + set to true. Its private key is ``pkcs12/ca/ca_key.pem``. This key is + encoded in several of the PKCS12 custom vectors. * ``negative_serial.pem`` - A certificate with a serial number that is a negative number. * ``rsa_pss.pem`` - A certificate with an RSA PSS signature. @@ -499,6 +524,10 @@ Custom X.509 Vectors * ``rsa_pss_sha256_no_null.pem`` - A certificate with an RSA PSS signature with no encoded ``NULL`` for the PSS hash algorithm parameters. This certificate was generated by LibreSSL. +* ``ecdsa_null_alg.pem`` - A certificate with an ECDSA signature with ``NULL`` + algorithm parameters. This encoding is invalid, but was generated by Java 11. +* ``dsa_null_alg_params.pem`` - A certificate with a DSA signature with ``NULL`` + algorithm parameters. This encoding is invalid, but was generated by Java 20. Custom X.509 Request Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -682,90 +711,90 @@ Custom X.509 OCSP Test Vectors Custom PKCS12 Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``pkcs12/cert-key-aes256cbc.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) both encrypted with AES 256 CBC with the password ``cryptography``. * ``pkcs12/cert-none-key-none.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with no encryption. The password (used for integrity checking only) is ``cryptography``. * ``pkcs12/cert-rc2-key-3des.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) encrypted with RC2 and key - (``x509/custom/ca/ca_key.pem``) encrypted via 3DES with the password + (``pkcs12/ca/ca.pem``) encrypted with RC2 and key + (``pkcs12/ca/ca_key.pem``) encrypted via 3DES with the password ``cryptography``. * ``pkcs12/no-password.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) with no + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with no encryption and no password. * ``pkcs12/no-cert-key-aes256cbc.p12`` - A PKCS12 file containing a key - (``x509/custom/ca/ca_key.pem``) encrypted via AES 256 CBC with the + (``pkcs12/ca/ca_key.pem``) encrypted via AES 256 CBC with the password ``cryptography`` and no certificate. * ``pkcs12/cert-aes256cbc-no-key.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) encrypted via AES 256 CBC with the + (``pkcs12/ca/ca.pem``) encrypted via AES 256 CBC with the password ``cryptography`` and no private key. * ``pkcs12/no-name-no-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``). * ``pkcs12/name-all-no-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3``, respectively. * ``pkcs12/name-1-no-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``). * ``pkcs12/name-2-3-no-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3``, respectively. * ``pkcs12/name-2-no-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the first having friendly name ``name2``. * ``pkcs12/name-3-no-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the latter having friendly name ``name3``. * ``pkcs12/name-unicode-no-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``☺``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``ä`` and ``ç``, respectively. * ``pkcs12/no-name-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-all-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3`` respectively, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-1-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-2-3-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2` and ``name3`` respectively, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-2-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the first having friendly name ``name2``, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-3-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``), + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the latter having friendly name ``name2``, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-unicode-pwd.p12`` - A PKCS12 file containing a cert - (``x509/custom/ca/ca.pem``) and key (``x509/custom/ca/ca_key.pem``) + (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``☺``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``ä`` and ``ç`` respectively, encrypted via @@ -864,6 +893,10 @@ Custom OpenSSH Certificate Test Vectors critical option. * ``p256-p256-non-lexical-crit-opts.pub`` - A certificate with critical options in non-lexical order. +* ``p256-ed25519-non-singular-crit-opt-val.pub`` - A certificate with + a critical option that contains more than one value. +* ``p256-ed25519-non-singular-ext-val.pub`` - A certificate with + an extension that contains more than one value. * ``dsa-p256.pub`` - A certificate with a DSA public key signed by a P256 CA. * ``p256-dsa.pub`` - A certificate with a P256 public key signed by a DSA @@ -931,6 +964,9 @@ Symmetric ciphers * AES (CBC, CFB, ECB, GCM, OFB, CCM) from `NIST CAVP`_. * AES CTR from :rfc:`3686`. +* AES-GCM-SIV (KEY-LENGTH: 128, 256) from OpenSSL's `evpciph_aes_gcm_siv.txt`_. +* AES-GCM-SIV (KEY-LENGTH: 192) generated by this project. + See :doc:`/development/custom-vectors/aes-192-gcm-siv` * AES OCB3 from :rfc:`7253`, `dkg's additional OCB3 vectors`_, and `OpenSSL's OCB vectors`_. * AES SIV from OpenSSL's `evpciph_aes_siv.txt`_. * 3DES (CBC, CFB, ECB, OFB) from `NIST CAVP`_. @@ -943,7 +979,8 @@ Symmetric ciphers * CAST5 (ECB) from :rfc:`2144`. * CAST5 (CBC, CFB, OFB) generated by this project. See: :doc:`/development/custom-vectors/cast5` -* ChaCha20 from :rfc:`7539`. +* ChaCha20 from :rfc:`7539` and generated by this project. + See: :doc:`/development/custom-vectors/chacha20` * ChaCha20Poly1305 from :rfc:`7539`, `OpenSSL's evpciph.txt`_, and the `BoringSSL ChaCha20Poly1305 tests`_. * IDEA (ECB) from the `NESSIE IDEA vectors`_ created by `NESSIE`_. @@ -953,6 +990,8 @@ Symmetric ciphers * SEED (CBC) from :rfc:`4196`. * SEED (CFB, OFB) generated by this project. See: :doc:`/development/custom-vectors/seed` +* SM4 (CBC, CFB, CTR, ECB, OFB) from `draft-ribose-cfrg-sm4-10`_. +* SM4 (GCM) from :rfc:`8998`. Two factor authentication ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -983,8 +1022,10 @@ Created Vectors .. toctree:: :maxdepth: 1 + custom-vectors/aes-192-gcm-siv custom-vectors/arc4 custom-vectors/cast5 + custom-vectors/chacha20 custom-vectors/idea custom-vectors/seed custom-vectors/hkdf @@ -1015,7 +1056,7 @@ header format (substituting the correct information): .. _`BoringSSL ChaCha20Poly1305 tests`: https://boringssl.googlesource.com/boringssl/+/2e2a226ac9201ac411a84b5e79ac3a7333d8e1c9/crypto/cipher_extra/test/chacha20_poly1305_tests.txt .. _`BoringSSL evp tests`: https://boringssl.googlesource.com/boringssl/+/ce3773f9fe25c3b54390bc51d72572f251c7d7e6/crypto/evp/evp_tests.txt .. _`RIPEMD website`: https://homes.esat.kuleuven.be/~bosselae/ripemd160.html -.. _`draft RFC`: https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01 +.. _`draft RFC`: https://datatracker.ietf.org/doc/html/draft-josefsson-scrypt-kdf-01 .. _`Specification repository`: https://github.com/fernet/spec .. _`errata`: https://www.rfc-editor.org/errata_search.php?rfc=6238 .. _`OpenSSL example key`: https://github.com/openssl/openssl/blob/d02b48c63a58ea4367a0e905979f140b7d090f86/test/testrsa.pem @@ -1028,8 +1069,9 @@ header format (substituting the correct information): .. _`GnuTLS example keys`: https://gitlab.com/gnutls/gnutls/-/commit/ad2061deafdd7db78fd405f9d143b0a7c579da7b .. _`NESSIE IDEA vectors`: https://www.cosic.esat.kuleuven.be/nessie/testvectors/bc/idea/Idea-128-64.verified.test-vectors .. _`NESSIE`: https://en.wikipedia.org/wiki/NESSIE +.. _`draft-ribose-cfrg-sm4-10`: https://datatracker.ietf.org/doc/html/draft-ribose-cfrg-sm4-10 .. _`Ed25519 website`: https://ed25519.cr.yp.to/software.html -.. _`NIST SP-800-38B`: https://csrc.nist.gov/publications/detail/sp/800-38b/archive/2005-05-01 +.. _`NIST SP-800-38B`: https://csrc.nist.gov/pubs/sp/800/38/b/final .. _`NIST PKI Testing`: https://csrc.nist.gov/Projects/PKI-Testing .. _`testx509.pem`: https://github.com/openssl/openssl/blob/master/test/testx509.pem .. _`DigiCert Global Root G3`: http://cacerts.digicert.com/DigiCertGlobalRootG3.crt @@ -1045,6 +1087,7 @@ header format (substituting the correct information): .. _`root-ed25519.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/root-ed25519.pem .. _`server-ed25519-cert.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/server-ed25519-cert.pem .. _`server-ed448-cert.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/server-ed448-cert.pem +.. _`evpciph_aes_gcm_siv.txt`: https://github.com/openssl/openssl/blob/a2b1ab6100d5f0fb50b61d241471eea087415632/test/recipes/30-test_evp_data/evpciph_aes_gcm_siv.txt .. _`evpciph_aes_siv.txt`: https://github.com/openssl/openssl/blob/d830526c711074fdcd82c70c24c31444366a1ed8/test/recipes/30-test_evp_data/evpciph_aes_siv.txt .. _`dkg's additional OCB3 vectors`: https://gitlab.com/dkg/ocb-test-vectors .. _`OpenSSL's OCB vectors`: https://github.com/openssl/openssl/commit/2f19ab18a29cf9c82cdd68bc8c7e5be5061b19be diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index c7e82ffb4df2..cad1f3e312fe 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -50,10 +50,10 @@ Performing the release ---------------------- The commit that merged the version number bump is now the official release -commit for this release. You will need to have ``gpg`` installed and a ``gpg`` -key in order to do a release. Once this has happened: +commit for this release. You will need to have ``git`` configured to perform +signed tags. Once this has happened: -* Run ``python release.py release {version}``. +* Run ``python release.py release``. The release should now be available on PyPI and a tag should be available in the repository. @@ -100,4 +100,4 @@ Post-release tasks .. _`upgrading OpenSSL issue template`: https://github.com/pyca/cryptography/issues/new?template=openssl-release.md .. _`milestone`: https://github.com/pyca/cryptography/milestones .. _`mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev -.. _`python-announce`: https://mail.python.org/mailman/listinfo/python-announce-list +.. _`python-announce`: https://mail.python.org/mailman3/lists/python-announce-list.python.org/ diff --git a/docs/faq.rst b/docs/faq.rst index ac7f4152c731..f66cfba867d0 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -97,17 +97,6 @@ as secure as possible while retaining the advantages of OpenSSL, so we've chosen to rewrite non-cryptographic operations (such as ASN.1 parsing) in a high performance memory safe language: Rust. -Installing ``cryptography`` produces a ``fatal error: 'openssl/opensslv.h' file not found`` error -------------------------------------------------------------------------------------------------- - -``cryptography`` provides wheels which include a statically linked copy of -OpenSSL. If you see this error it is likely because your copy of ``pip`` is too -old to find our wheel files. Upgrade your ``pip`` with ``pip install -U pip`` -and then try to install ``cryptography`` again. - -Users on unusual CPU architectures will need to compile ``cryptography`` -themselves. Please view our :doc:`/installation` documentation. - ``cryptography`` raised an ``InternalError`` and I'm not sure what to do? ------------------------------------------------------------------------- diff --git a/docs/hazmat/primitives/aead.rst b/docs/hazmat/primitives/aead.rst index db9ef96d1ab7..9c80c3a62049 100644 --- a/docs/hazmat/primitives/aead.rst +++ b/docs/hazmat/primitives/aead.rst @@ -164,6 +164,79 @@ also support providing integrity for associated data which is not encrypted. when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. +.. class:: AESGCMSIV(key) + + .. versionadded:: 42.0.0 + + The AES-GCM-SIV construction is defined in :rfc:`8452` and is composed of + the :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` block + cipher utilizing Galois Counter Mode (GCM) and a synthetic initialization + vector (SIV). + + :param key: A 128, 192, or 256-bit key. This **must** be kept secret. + :type key: :term:`bytes-like` + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of + OpenSSL does not support AES-GCM-SIV. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV + >>> data = b"a secret message" + >>> aad = b"authenticated but unencrypted data" + >>> key = AESGCMSIV.generate_key(bit_length=128) + >>> aesgcmsiv = AESGCMSIV(key) + >>> nonce = os.urandom(12) + >>> ct = aesgcmsiv.encrypt(nonce, data, aad) + >>> aesgcmsiv.decrypt(nonce, ct, aad) + b'a secret message' + + .. classmethod:: generate_key(bit_length) + + Securely generates a random AES-GCM-SIV key. + + :param bit_length: The bit length of the key to generate. Must be + 128, 192, or 256. + + :returns bytes: The generated key. + + .. method:: encrypt(nonce, data, associated_data) + + Encrypts and authenticates the ``data`` provided as well as + authenticating the ``associated_data``. The output of this can be + passed directly to the ``decrypt`` method. + + :param nonce: A 12-byte value. + :type nonce: :term:`bytes-like` + :param data: The data to encrypt. + :type data: :term:`bytes-like` + :param associated_data: Additional data that should be + authenticated with the key, but is not encrypted. Can be ``None``. + :type associated_data: :term:`bytes-like` + :returns bytes: The ciphertext bytes with the 16 byte tag appended. + :raises OverflowError: If ``data`` or ``associated_data`` is larger + than 2\ :sup:`32` - 1 bytes. + + .. method:: decrypt(nonce, data, associated_data) + + Decrypts the ``data`` and authenticates the ``associated_data``. If you + called encrypt with ``associated_data`` you must pass the same + ``associated_data`` in decrypt or the integrity check will fail. + + :param nonce: A 12-byte value. + :type nonce: :term:`bytes-like` + :param data: The data to decrypt (with tag appended). + :type data: :term:`bytes-like` + :param associated_data: Additional data to authenticate. Can be + ``None`` if none was passed during encryption. + :type associated_data: :term:`bytes-like` + :returns bytes: The original plaintext. + :raises cryptography.exceptions.InvalidTag: If the authentication tag + doesn't validate this exception will be raised. This will occur + when the ciphertext has been changed, but will also occur when the + key, nonce, or associated data are wrong. + .. class:: AESOCB3(key) .. versionadded:: 36.0.0 @@ -420,4 +493,4 @@ also support providing integrity for associated data which is not encrypted. when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. -.. _`recommends a 96-bit IV length`: https://csrc.nist.gov/publications/detail/sp/800-38d/final +.. _`recommends a 96-bit IV length`: https://csrc.nist.gov/pubs/sp/800/38/d/final diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 5df80149bb9b..b159a09116ff 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -289,7 +289,8 @@ Key interfaces Sign one block of data which can be verified later by others using the public key. - :param bytes data: The message string to sign. + :param data: The message string to sign. + :type data: :term:`bytes-like` :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or @@ -391,9 +392,11 @@ Key interfaces Verify one block of data was signed by the private key associated with this public key. - :param bytes signature: The signature to verify. + :param signature: The signature to verify. + :type signature: :term:`bytes-like` - :param bytes data: The message string that was signed. + :param data: The message string that was signed. + :type data: :term:`bytes-like` :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or @@ -409,4 +412,4 @@ Key interfaces .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography .. _`FIPS 186-4`: https://csrc.nist.gov/publications/detail/fips/186/4/final .. _`at least 2048`: https://www.cosic.esat.kuleuven.be/ecrypt/ecrypt2/documents/D.SPA.20.pdf -.. _`ongoing protestations`: https://buttondown.email/cryptography-dispatches/archive/cryptography-dispatches-dsa-is-past-its-prime/ +.. _`ongoing protestations`: https://words.filippo.io/dispatches/dsa/ diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 5842e9ca1667..75165b6a4536 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -220,8 +220,8 @@ Elliptic Curve Key Exchange algorithm .. versionadded:: 1.1 - The Elliptic Curve Diffie-Hellman Key Exchange algorithm first standardized - in NIST publication `800-56A`_, and later in `800-56Ar2`_. + The Elliptic Curve Diffie-Hellman Key Exchange algorithm standardized + in NIST publication `800-56A`_. For most applications the ``shared_key`` should be passed to a key derivation function. This allows mixing of additional information into the @@ -569,7 +569,8 @@ Key Interfaces Sign one block of data which can be verified later by others using the public key. - :param bytes data: The message string to sign. + :param data: The message string to sign. + :type data: :term:`bytes-like` :param signature_algorithm: An instance of :class:`EllipticCurveSignatureAlgorithm`, such as :class:`ECDSA`. @@ -678,12 +679,14 @@ Key Interfaces Verify one block of data was signed by the private key associated with this public key. - :param bytes signature: The DER-encoded signature to verify. + :param signature: The DER-encoded signature to verify. A raw signature may be DER-encoded by splitting it into the ``r`` and ``s`` components and passing them into :func:`~cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature`. + :type signature: :term:`bytes-like` - :param bytes data: The message string that was signed. + :param data: The message string that was signed. + :type data: :term:`bytes-like` :param signature_algorithm: An instance of :class:`EllipticCurveSignatureAlgorithm`. @@ -909,10 +912,9 @@ Elliptic Curve Object Identifiers :raises LookupError: Raised if no elliptic curve is found that matches the provided object identifier. -.. _`FIPS 186-3`: https://csrc.nist.gov/csrc/media/publications/fips/186/3/archive/2009-06-25/documents/fips_186-3.pdf -.. _`FIPS 186-4`: https://csrc.nist.gov/publications/detail/fips/186/4/final -.. _`800-56A`: https://csrc.nist.gov/publications/detail/sp/800-56a/revised/archive/2007-03-14 -.. _`800-56Ar2`: https://csrc.nist.gov/publications/detail/sp/800-56a/rev-2/final +.. _`FIPS 186-3`: https://csrc.nist.gov/files/pubs/fips/186-3/final/docs/fips_186-3.pdf +.. _`FIPS 186-4`: https://csrc.nist.gov/pubs/fips/186-4/final +.. _`800-56A`: https://csrc.nist.gov/pubs/sp/800/56/a/r3/final .. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters .. _`less than 224 bits`: https://www.cosic.esat.kuleuven.be/ecrypt/ecrypt2/documents/D.SPA.20.pdf .. _`elliptic curve diffie-hellman is faster than diffie-hellman`: https://digitalcommons.unl.edu/cgi/viewcontent.cgi?article=1100&context=cseconfwork diff --git a/docs/hazmat/primitives/asymmetric/ed25519.rst b/docs/hazmat/primitives/asymmetric/ed25519.rst index 1ca06fc1b9f2..8d4b910ca115 100644 --- a/docs/hazmat/primitives/asymmetric/ed25519.rst +++ b/docs/hazmat/primitives/asymmetric/ed25519.rst @@ -67,7 +67,8 @@ Key interfaces .. method:: sign(data) - :param bytes data: The data to sign. + :param data: The data to sign. + :type data: :term:`bytes-like` :returns bytes: The 64 byte signature. @@ -192,9 +193,11 @@ Key interfaces .. method:: verify(signature, data) - :param bytes signature: The signature to verify. + :param signature: The signature to verify. + :type signature: :term:`bytes-like` - :param bytes data: The data to verify. + :param data: The data to verify. + :type data: :term:`bytes-like` :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the diff --git a/docs/hazmat/primitives/asymmetric/ed448.rst b/docs/hazmat/primitives/asymmetric/ed448.rst index efe245d568e9..27a8092db59c 100644 --- a/docs/hazmat/primitives/asymmetric/ed448.rst +++ b/docs/hazmat/primitives/asymmetric/ed448.rst @@ -47,7 +47,8 @@ Key interfaces .. method:: sign(data) - :param bytes data: The data to sign. + :param data: The data to sign. + :type data: :term:`bytes-like` :returns bytes: The 114 byte signature. @@ -146,9 +147,11 @@ Key interfaces .. method:: verify(signature, data) - :param bytes signature: The signature to verify. + :param signature: The signature to verify. + :type signature: :term:`bytes-like` - :param bytes data: The data to verify. + :param data: The data to verify. + :type data: :term:`bytes-like` :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index 23401f52793a..35230f7e982d 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -317,6 +317,14 @@ Padding Pass this attribute to ``salt_length`` to automatically determine the salt length when verifying. Raises ``ValueError`` if used when signing. + .. attribute:: mgf + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF` + + .. versionadded:: 42.0.0 + + The padding's mask generation function (MGF). + .. class:: OAEP(mgf, algorithm, label) .. versionadded:: 0.4 @@ -335,6 +343,22 @@ Padding :param bytes label: A label to apply. This is a rarely used field and should typically be set to ``None`` or ``b""``, which are equivalent. + .. attribute:: algorithm + + :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + .. versionadded:: 42.0.0 + + The padding's hash algorithm. + + .. attribute:: mgf + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF` + + .. versionadded:: 42.0.0 + + The padding's mask generation function (MGF). + .. class:: PKCS1v15() .. versionadded:: 0.3 @@ -369,6 +393,11 @@ Padding Mask generation functions ------------------------- +.. class:: MGF + + .. versionadded:: 37.0.0 + + .. class:: MGF1(algorithm) .. versionadded:: 0.3 @@ -591,7 +620,8 @@ Key interfaces Sign one block of data which can be verified later by others using the public key. - :param bytes data: The message string to sign. + :param data: The message string to sign. + :type data: :term:`bytes-like` :param padding: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. @@ -710,9 +740,11 @@ Key interfaces Verify one block of data was signed by the private key associated with this public key. - :param bytes signature: The signature to verify. + :param signature: The signature to verify. + :type signature: :term:`bytes-like` - :param bytes data: The message string that was signed. + :param data: The message string that was signed. + :type data: :term:`bytes-like` :param padding: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index c60accca6b40..402915c45540 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -1127,7 +1127,7 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :param data: The data to be hashed and signed. :type data: :term:`bytes-like` - .. method:: add_signer(certificate, private_key, hash_algorithm) + .. method:: add_signer(certificate, private_key, hash_algorithm, *, rsa_padding=None) :param certificate: The :class:`~cryptography.x509.Certificate`. @@ -1142,6 +1142,18 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, will be used to generate the signature. This must be one of the types in :data:`PKCS7HashTypes`. + :param rsa_padding: + + .. versionadded:: 42.0.0 + + This is a keyword-only argument. If ``private_key`` is an + ``RSAPrivateKey`` then this can be set to either + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign + with those respective paddings. If this is ``None`` then RSA + keys will default to ``PKCS1v15`` padding. All other key types **must** + not pass a value other than ``None``. + .. method:: add_certificate(certificate) Add an additional certificate (typically used to help build a diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst index b6c889df4a81..c1c29cad27c6 100644 --- a/docs/hazmat/primitives/cryptographic-hashes.rst +++ b/docs/hazmat/primitives/cryptographic-hashes.rst @@ -290,7 +290,7 @@ Interfaces .. _`Lifetimes of cryptographic hash functions`: https://valerieaurora.org/hash.html -.. _`BLAKE2`: https://blake2.net +.. _`BLAKE2`: https://www.blake2.net/ .. _`length-extension attacks`: https://en.wikipedia.org/wiki/Length_extension_attack .. _`GM/T 0004-2012`: https://www.oscca.gov.cn/sca/xxgk/2010-12/17/1002389/files/302a3ada057c4a73830536d03e683110.pdf .. _`draft-sca-cfrg-sm3`: https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3 diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 7c5c643e2218..2715e3e56c5d 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -247,7 +247,7 @@ ConcatKDF .. versionadded:: 1.0 ConcatKDFHash (Concatenation Key Derivation Function) is defined by the - NIST Special Publication `NIST SP 800-56Ar2`_ document, to be used to + NIST Special Publication `NIST SP 800-56Ar3`_ document, to be used to derive keys for use after a Key Exchange negotiation operation. .. warning:: @@ -1025,9 +1025,9 @@ Interface .. [#nist] See `NIST SP 800-132`_. -.. _`NIST SP 800-132`: https://csrc.nist.gov/publications/detail/sp/800-132/final -.. _`NIST SP 800-108`: https://csrc.nist.gov/publications/detail/sp/800-108/final -.. _`NIST SP 800-56Ar2`: https://csrc.nist.gov/publications/detail/sp/800-56a/rev-2/final +.. _`NIST SP 800-132`: https://csrc.nist.gov/pubs/sp/800/132/final +.. _`NIST SP 800-108`: https://csrc.nist.gov/pubs/sp/800/108/r1/final +.. _`NIST SP 800-56Ar3`: https://csrc.nist.gov/pubs/sp/800/56/a/r3/final .. _`ANSI X9.63:2001`: https://webstore.ansi.org .. _`SEC 1 v2.0`: https://www.secg.org/sec1-v2.pdf .. _`more detailed description`: https://security.stackexchange.com/a/3993/43116 @@ -1036,6 +1036,6 @@ Interface .. _`HKDF`: https://en.wikipedia.org/wiki/HKDF .. _`HKDF paper`: https://eprint.iacr.org/2010/264 .. _`here`: https://stackoverflow.com/a/30308723/1170681 -.. _`recommends`: https://tools.ietf.org/html/rfc7914#section-2 +.. _`recommends`: https://datatracker.ietf.org/doc/html/rfc7914#section-2 .. _`The scrypt paper`: https://www.tarsnap.com/scrypt/scrypt.pdf .. _`understanding HKDF`: https://soatok.blog/2021/11/17/understanding-hkdf/ diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 2bf7a88cb0a4..e12ccac6ecf5 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -140,38 +140,44 @@ Algorithms :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` does this for you. - ChaCha20 is a stream cipher used in several IETF protocols. It is - standardized in :rfc:`7539`. + ChaCha20 is a stream cipher used in several IETF protocols. While it is + standardized in :rfc:`7539`, **this implementation is not RFC-compliant**. + This implementation uses a ``64`` :term:`bits` counter and a ``64`` + :term:`bits` nonce as defined in the `original version`_ of the algorithm, + rather than the ``32/96`` counter/nonce split defined in :rfc:`7539`. :param key: The secret key. This must be kept secret. ``256`` :term:`bits` (32 bytes) in length. :type key: :term:`bytes-like` :param nonce: Should be unique, a :term:`nonce`. It is - critical to never reuse a ``nonce`` with a given key. Any reuse of a + critical to never reuse a ``nonce`` with a given key. Any reuse of a nonce with the same key compromises the security of every message encrypted with that key. The nonce does not need to be kept secret and may be included with the ciphertext. This must be ``128`` - :term:`bits` in length. The 128-bit value is a concatenation of 4-byte - little-endian counter and the 12-byte nonce (as described in - :rfc:`7539`). + :term:`bits` in length. The 128-bit value is a concatenation of the + 8-byte little-endian counter and the 8-byte nonce. :type nonce: :term:`bytes-like` - .. note:: + .. note:: + + In the `original version`_ of the algorithm the nonce is defined as a + 64-bit value that is later concatenated with a block counter (encoded + as a 64-bit little-endian). If you have a separate nonce and block + counter you will need to concatenate it yourself before passing it. + For example, if you have an initial block counter of 2 and a 64-bit + nonce the concatenated nonce would be + ``struct.pack(">> import struct, os >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - >>> nonce = os.urandom(16) - >>> algorithm = algorithms.ChaCha20(key, nonce) + >>> nonce = os.urandom(8) + >>> counter = 0 + >>> full_nonce = struct.pack(">> algorithm = algorithms.ChaCha20(key, full_nonce) >>> cipher = Cipher(algorithm, mode=None) >>> encryptor = cipher.encryptor() >>> ct = encryptor.update(b"a secret message") @@ -840,13 +846,14 @@ Exceptions .. _`described by Colin Percival`: https://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html -.. _`recommends a 96-bit IV length`: https://csrc.nist.gov/publications/detail/sp/800-38d/final +.. _`recommends a 96-bit IV length`: https://csrc.nist.gov/pubs/sp/800/38/d/final .. _`NIST SP-800-38D`: https://csrc.nist.gov/publications/detail/sp/800-38d/final .. _`Communications Security Establishment`: https://www.cse-cst.gc.ca .. _`encrypt`: https://ssd.eff.org/en/module/what-should-i-know-about-encryption -.. _`CRYPTREC`: https://www.cryptrec.go.jp/english/ +.. _`CRYPTREC`: https://www.cryptrec.go.jp/en/ +.. _`original version`: https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant .. _`significant patterns in the output`: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_(ECB) .. _`International Data Encryption Algorithm`: https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm .. _`OpenPGP`: https://www.openpgp.org/ .. _`disk encryption`: https://en.wikipedia.org/wiki/Disk_encryption_theory#XTS -.. _`draft-ribose-cfrg-sm4-10`: https://tools.ietf.org/html/draft-ribose-cfrg-sm4-10 +.. _`draft-ribose-cfrg-sm4-10`: https://datatracker.ietf.org/doc/html/draft-ribose-cfrg-sm4-10 diff --git a/docs/installation.rst b/docs/installation.rst index f35f270effea..d24d8062c8ad 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -13,20 +13,18 @@ single most common cause of installation problems. Supported platforms ------------------- -Currently we test ``cryptography`` on Python 3.7+ and PyPy3 7.3.10+ on these +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 Fedora (latest) -* x86-64 macOS 12 Monterey -* ARM64 macOS 13 Ventura +* x86-64 macOS 13 Ventura and ARM64 macOS 14 Sonoma * x86-64 Ubuntu 20.04, 22.04, rolling * ARM64 Ubuntu 22.04 -* x86-64 Debian Buster (10.x), Bullseye (11.x), Bookworm (12.x) - and Sid (unstable) -* x86-64 Alpine (latest) -* ARM64 Alpine (latest) +* x86-64 Debian Buster (10.x), Bullseye (11.x), Bookworm (12.x), + Trixie (13.x), and Sid (unstable) +* x86-64 and ARM64 Alpine (latest) * 32-bit and 64-bit Python on 64-bit Windows Server 2022 We test compiling with ``clang`` as well as ``gcc`` and use the following @@ -105,7 +103,7 @@ Alpine .. warning:: - The Rust available by default in Alpine < 3.15 is older than the minimum + 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. @@ -120,10 +118,9 @@ Debian/Ubuntu .. warning:: - The Rust available in most Debian versions is older than the minimum - supported version. Debian Bookworm is sufficiently new, but otherwise - please see the :ref:`Rust installation instructions ` - for information about installing a newer Rust. + 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. .. code-block:: console @@ -135,8 +132,8 @@ Fedora/RHEL/CentOS .. warning:: - For RHEL and CentOS you must be on version 8.6 or newer for the command - below to install a sufficiently new Rust. If your Rust is less than 1.56.0 + 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.63.0 please see the :ref:`Rust installation instructions ` for information about installing a newer Rust. @@ -314,7 +311,7 @@ Rust a Rust toolchain. Building ``cryptography`` requires having a working Rust toolchain. The current -minimum supported Rust version is 1.56.0. **This is newer than the Rust some +minimum supported Rust version is 1.63.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/security.rst b/docs/security.rst index e1fba3a1ecec..3c750b805683 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -5,6 +5,13 @@ We take the security of ``cryptography`` seriously. The following are a set of policies we have adopted to ensure that security issues are addressed in a timely fashion. +Known vulnerabilities +--------------------- + +A list of all known vulnerabilities in ``cryptography`` can be found on +`osv.dev`_, as well as other ecosystem vulnerability databases. They can +automatically be scanned for using tools such as `pip-audit`_ or `osv-scan`_. + Infrastructure -------------- @@ -87,5 +94,8 @@ The steps for issuing a security release are described in our :doc:`/doing-a-release` documentation. +.. _`osv.dev`: https://osv.dev/list?ecosystem=PyPI&q=cryptography +.. _`pip-audit`: https://pypi.org/project/pip-audit/ +.. _`osv-scan`: https://google.github.io/osv-scanner/ .. _`security advisory page`: https://github.com/pyca/cryptography/security/advisories/new .. _`main`: https://github.com/pyca/cryptography diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 62a62fb96e34..933e781308ed 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -38,6 +38,7 @@ decrypted decrypting deprecations DER +dereference deserialize deserialized Deserialization @@ -114,8 +115,11 @@ Schneier scrypt serializer Serializers +setuptools SHA Solaris +Sonoma +SPKI Sur syscall Tanja @@ -124,6 +128,7 @@ Thawte timestamp timestamps toolchain +Trixie tunable Ubuntu unencrypted diff --git a/docs/x509/certificate-transparency.rst b/docs/x509/certificate-transparency.rst index 33933384e19f..0e04ef3c5cab 100644 --- a/docs/x509/certificate-transparency.rst +++ b/docs/x509/certificate-transparency.rst @@ -125,4 +125,4 @@ issued. .. attribute:: ECDSA -.. _`Certificate Transparency`: https://www.certificate-transparency.org/ +.. _`Certificate Transparency`: https://certificate.transparency.dev/ diff --git a/docs/x509/index.rst b/docs/x509/index.rst index ef51fbf6220f..6e26846f6747 100644 --- a/docs/x509/index.rst +++ b/docs/x509/index.rst @@ -11,6 +11,7 @@ certificates are commonly used in protocols like `TLS`_. tutorial certificate-transparency ocsp + verification reference .. _`public key infrastructure`: https://en.wikipedia.org/wiki/Public_key_infrastructure diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst index 76bfc023f15f..94605c2e499f 100644 --- a/docs/x509/ocsp.rst +++ b/docs/x509/ocsp.rst @@ -340,7 +340,11 @@ Creating Responses :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` and an instance of a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` - otherwise. + otherwise. Please note that + :class:`~cryptography.hazmat.primitives.hashes.SHA1` + can not be used here, regardless of if it was used for + :meth:`~cryptography.x509.ocsp.OCSPResponseBuilder.add_response` + or not. :returns: A new :class:`~cryptography.x509.ocsp.OCSPResponse`. diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index e14c8ffc1093..166c01f9a58a 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -368,6 +368,13 @@ X.509 Certificate Object :type: :class:`datetime.datetime` + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.Certificate.not_valid_before_utc`. + + A naïve datetime representing the beginning of the validity period for the certificate in UTC. This value is inclusive. @@ -376,10 +383,30 @@ X.509 Certificate Object >>> cert.not_valid_before datetime.datetime(2010, 1, 1, 8, 30) + .. attribute:: not_valid_before_utc + + .. versionadded:: 42.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the beginning of the validity + period for the certificate in UTC. This value is inclusive. + + .. doctest:: + + >>> cert.not_valid_before_utc + datetime.datetime(2010, 1, 1, 8, 30, tzinfo=datetime.timezone.utc) + .. attribute:: not_valid_after :type: :class:`datetime.datetime` + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.Certificate.not_valid_after_utc`. + A naïve datetime representing the end of the validity period for the certificate in UTC. This value is inclusive. @@ -388,6 +415,20 @@ X.509 Certificate Object >>> cert.not_valid_after datetime.datetime(2030, 12, 31, 8, 30) + .. attribute:: not_valid_after_utc + + .. versionadded:: 42.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the end of the validity period + for the certificate in UTC. This value is inclusive. + + .. doctest:: + + >>> cert.not_valid_after_utc + datetime.datetime(2030, 12, 31, 8, 30, tzinfo=datetime.timezone.utc) + .. attribute:: issuer .. versionadded:: 0.8 @@ -675,6 +716,27 @@ X.509 CRL (Certificate Revocation List) Object >>> crl.signature_algorithm_oid + .. attribute:: signature_algorithm_parameters + + .. versionadded:: 42.0.0 + + Returns the parameters of the signature algorithm used to sign the + certificate revocation list. For RSA signatures it will return either a + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` object. + + For ECDSA signatures it will + return an :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA`. + + For EdDSA and DSA signatures it will return ``None``. + + These objects can be used to verify the CRL signature. + + :returns: None, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`, or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` + .. attribute:: issuer :type: :class:`Name` @@ -690,6 +752,12 @@ X.509 CRL (Certificate Revocation List) Object :type: :class:`datetime.datetime` + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.CertificateRevocationList.next_update_utc`. + A naïve datetime representing when the next update to this CRL is expected. @@ -698,10 +766,30 @@ X.509 CRL (Certificate Revocation List) Object >>> crl.next_update datetime.datetime(2016, 1, 1, 0, 0) + .. attribute:: next_update_utc + + .. versionadded:: 42.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing when the next update to this + CRL is expected. + + .. doctest:: + + >>> crl.next_update_utc + datetime.datetime(2016, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) + .. attribute:: last_update :type: :class:`datetime.datetime` + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.CertificateRevocationList.last_update_utc`. + A naïve datetime representing when this CRL was last updated. .. doctest:: @@ -709,6 +797,19 @@ X.509 CRL (Certificate Revocation List) Object >>> crl.last_update datetime.datetime(2015, 1, 1, 0, 0) + .. attribute:: last_update_utc + + .. versionadded:: 42.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing when this CRL was last updated. + + .. doctest:: + + >>> crl.last_update_utc + datetime.datetime(2015, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) + .. attribute:: extensions :type: :class:`Extensions` @@ -782,10 +883,10 @@ X.509 Certificate Builder >>> public_key = private_key.public_key() >>> builder = x509.CertificateBuilder() >>> builder = builder.subject_name(x509.Name([ - ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'), ... ])) >>> builder = builder.issuer_name(x509.Name([ - ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'), ... ])) >>> builder = builder.not_valid_before(datetime.datetime.today() - one_day) >>> builder = builder.not_valid_after(datetime.datetime.today() + (one_day * 30)) @@ -793,7 +894,7 @@ X.509 Certificate Builder >>> builder = builder.public_key(public_key) >>> builder = builder.add_extension( ... x509.SubjectAlternativeName( - ... [x509.DNSName(u'cryptography.io')] + ... [x509.DNSName('cryptography.io')] ... ), ... critical=False ... ) @@ -970,6 +1071,27 @@ X.509 CSR (Certificate Signing Request) Object >>> csr.signature_algorithm_oid + .. attribute:: signature_algorithm_parameters + + .. versionadded:: 42.0.0 + + Returns the parameters of the signature algorithm used to sign the + certificate signing request. For RSA signatures it will return either a + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` object. + + For ECDSA signatures it will + return an :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA`. + + For EdDSA and DSA signatures it will return ``None``. + + These objects can be used to verify signatures on the signing request. + + :returns: None, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`, or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` + .. attribute:: extensions :type: :class:`Extensions` @@ -1049,7 +1171,7 @@ X.509 Certificate Revocation List Builder ... ) >>> builder = x509.CertificateRevocationListBuilder() >>> builder = builder.issuer_name(x509.Name([ - ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io CA'), + ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io CA'), ... ])) >>> builder = builder.last_update(datetime.datetime.today()) >>> builder = builder.next_update(datetime.datetime.today() + one_day) @@ -1111,7 +1233,7 @@ X.509 Certificate Revocation List Builder obtained from an existing CRL or created with :class:`~cryptography.x509.RevokedCertificateBuilder`. - .. method:: sign(private_key, algorithm) + .. method:: sign(private_key, algorithm, *, rsa_padding=None) Sign this CRL using the CA's private key. @@ -1130,6 +1252,22 @@ X.509 Certificate Revocation List Builder :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. + :param rsa_padding: + + .. versionadded:: 42.0.0 + + This is a keyword-only argument. If ``private_key`` is an + ``RSAPrivateKey`` then this can be set to either + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign + with those respective paddings. If this is ``None`` then RSA + keys will default to ``PKCS1v15`` padding. All other key types **must** + not pass a value other than ``None``. + + :type rsa_padding: ``None``, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, + or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + :returns: :class:`~cryptography.x509.CertificateRevocationList` X.509 Revoked Certificate Object @@ -1155,6 +1293,12 @@ X.509 Revoked Certificate Object :type: :class:`datetime.datetime` + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.RevokedCertificate.revocation_date_utc`. + A naïve datetime representing the date this certificates was revoked. .. doctest:: @@ -1162,6 +1306,20 @@ X.509 Revoked Certificate Object >>> revoked_certificate.revocation_date datetime.datetime(2015, 1, 1, 0, 0) + .. attribute:: revocation_date_utc + + .. versionadded:: 42.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the date this certificates was + revoked. + + .. doctest:: + + >>> revoked_certificate.revocation_date_utc + datetime.datetime(2015, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) + .. attribute:: extensions :type: :class:`Extensions` @@ -1248,7 +1406,7 @@ X.509 CSR (Certificate Signing Request) Builder Object ... ) >>> builder = x509.CertificateSigningRequestBuilder() >>> builder = builder.subject_name(x509.Name([ - ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'), ... ])) >>> builder = builder.add_extension( ... x509.BasicConstraints(ca=False, path_length=None), critical=True, @@ -1288,7 +1446,7 @@ X.509 CSR (Certificate Signing Request) Builder Object :returns: A new :class:`~cryptography.x509.CertificateSigningRequestBuilder`. - .. method:: sign(private_key, algorithm) + .. method:: sign(private_key, algorithm, *, rsa_padding=None) :param private_key: The private key that will be used to sign the request. When the request is @@ -1307,6 +1465,22 @@ X.509 CSR (Certificate Signing Request) Builder Object :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. + :param rsa_padding: + + .. versionadded:: 42.0.0 + + This is a keyword-only argument. If ``private_key`` is an + ``RSAPrivateKey`` then this can be set to either + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign + with those respective paddings. If this is ``None`` then RSA + keys will default to ``PKCS1v15`` padding. All other key types **must** + not pass a value other than ``None``. + + :type rsa_padding: ``None``, + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, + or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + :returns: A new :class:`~cryptography.x509.CertificateSigningRequest`. @@ -3076,6 +3250,12 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"2.5.4.9"``. + .. attribute:: ORGANIZATION_IDENTIFIER + + .. versionadded:: 42.0.0 + + Corresponds to the dotted string ``"2.5.4.97"``. + .. attribute:: ORGANIZATION_NAME Corresponds to the dotted string ``"2.5.4.10"``. @@ -3738,6 +3918,6 @@ Exceptions types can be found in `RFC 5280 section 4.2.1.6`_. -.. _`RFC 5280 section 4.2.1.1`: https://tools.ietf.org/html/rfc5280#section-4.2.1.1 -.. _`RFC 5280 section 4.2.1.6`: https://tools.ietf.org/html/rfc5280#section-4.2.1.6 +.. _`RFC 5280 section 4.2.1.1`: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1 +.. _`RFC 5280 section 4.2.1.6`: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 .. _`CABForum Guidelines`: https://cabforum.org/baseline-requirements-documents/ diff --git a/docs/x509/tutorial.rst b/docs/x509/tutorial.rst index f5ca416ceb9f..45729f28ce15 100644 --- a/docs/x509/tutorial.rst +++ b/docs/x509/tutorial.rst @@ -60,17 +60,17 @@ a few details: >>> # Generate a CSR >>> csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ ... # Provide various details about who we are. - ... x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"), - ... x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), - ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"), - ... x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), + ... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), + ... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), + ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), + ... x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), ... ])).add_extension( ... x509.SubjectAlternativeName([ ... # Describe what sites we want this certificate for. - ... x509.DNSName(u"mysite.com"), - ... x509.DNSName(u"www.mysite.com"), - ... x509.DNSName(u"subdomain.mysite.com"), + ... x509.DNSName("mysite.com"), + ... x509.DNSName("www.mysite.com"), + ... x509.DNSName("subdomain.mysite.com"), ... ]), ... critical=False, ... # Sign the CSR with our private key. @@ -119,11 +119,11 @@ Then we generate the certificate itself: >>> # Various details about who we are. For a self-signed certificate the >>> # subject and issuer are always the same. >>> subject = issuer = x509.Name([ - ... x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"), - ... x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), - ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"), - ... x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), + ... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), + ... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), + ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), + ... x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), ... ]) >>> cert = x509.CertificateBuilder().subject_name( ... subject @@ -134,12 +134,12 @@ Then we generate the certificate itself: ... ).serial_number( ... x509.random_serial_number() ... ).not_valid_before( - ... datetime.datetime.utcnow() + ... datetime.datetime.now(datetime.timezone.utc) ... ).not_valid_after( ... # Our certificate will be valid for 10 days - ... datetime.datetime.utcnow() + datetime.timedelta(days=10) + ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10) ... ).add_extension( - ... x509.SubjectAlternativeName([x509.DNSName(u"localhost")]), + ... x509.SubjectAlternativeName([x509.DNSName("localhost")]), ... critical=False, ... # Sign our certificate with our private key ... ).sign(key, hashes.SHA256()) diff --git a/docs/x509/verification.rst b/docs/x509/verification.rst new file mode 100644 index 000000000000..6afc75f289e5 --- /dev/null +++ b/docs/x509/verification.rst @@ -0,0 +1,211 @@ +X.509 Verification +================== + +.. currentmodule:: cryptography.x509.verification + +.. module:: cryptography.x509.verification + +Support for X.509 certificate verification, also known as path validation +or chain building. + +.. note:: + While usable, these APIs should be considered unstable and not yet + subject to our backwards compatibility policy. + +Example usage, with `certifi `_ providing +the root of trust: + +.. testsetup:: + + from cryptography.x509 import load_pem_x509_certificate, load_pem_x509_certificates + from datetime import datetime + + peer = load_pem_x509_certificate(b""" + -----BEGIN CERTIFICATE----- + MIIDgTCCAwegAwIBAgISBJUzlK20QGqPf5xI0aoE8OIBMAoGCCqGSM49BAMDMDIx + CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF + MTAeFw0yMzExMjIyMDUyNDBaFw0yNDAyMjAyMDUyMzlaMBoxGDAWBgNVBAMTD2Ny + eXB0b2dyYXBoeS5pbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAh2A0yuOByJ + lxK3ps5vbSOT6ZmvAlflGLn8kEseeodIAockm0ISTb/NGSpu/SY4ITefAOSaulKn + BzDgmqjGRKujggITMIICDzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYB + BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJu7f03HjjwJ + MU6rfwDBzxySTrs5MB8GA1UdIwQYMBaAFFrz7Sv8NsI3eblSMOpUb89Vyy6sMFUG + CCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2UxLm8ubGVuY3Iub3Jn + MCIGCCsGAQUFBzAChhZodHRwOi8vZTEuaS5sZW5jci5vcmcvMBoGA1UdEQQTMBGC + D2NyeXB0b2dyYXBoeS5pbzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQYGCisGAQQB + 1nkCBAIEgfcEgfQA8gB3AEiw42vapkc0D+VqAvqdMOscUgHLVt0sgdm7v6s52IRz + AAABi/kFXv4AAAQDAEgwRgIhAI9uF526YzU/DEfpmWRA28fn9gryrWMUCXQnEejQ + K/trAiEA12ePSql3sGJ/QgXc6ceQB/XAdwzwDB+2CHr6T14vvvUAdwDuzdBk1dsa + zsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYv5BV8kAAAEAwBIMEYCIQD1mqTn + b1hOpZWAUlwVM4EJLYA9HtlOvF70bfrGHpAX4gIhAI8pktDxrUwfTXPuA+eMFPbC + QraG6dMkB+HOmTz+hgKyMAoGCCqGSM49BAMDA2gAMGUCMQC+PwiHciKMaJyRJkGa + KFjT/1ICAUsCm8o5h4Xxm0LoOCJVggaXeamDEYnPWbxGETgCME5TJzLIDuF3z6vX + 1SLZDdvHEHLKfOL8/h8KctkjLQ8OJycxwIc+zK+xexVoIuxRhA== + -----END CERTIFICATE----- + """ + ) + + untrusted_intermediates = load_pem_x509_certificates(b""" + -----BEGIN CERTIFICATE----- + MIICxjCCAk2gAwIBAgIRALO93/inhFu86QOgQTWzSkUwCgYIKoZIzj0EAwMwTzEL + MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo + IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjAwOTA0MDAwMDAwWhcN + MjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5j + cnlwdDELMAkGA1UEAxMCRTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQkXC2iKv0c + S6Zdl3MnMayyoGli72XoprDwrEuf/xwLcA/TmC9N/A8AmzfwdAVXMpcuBe8qQyWj + +240JxP2T35p0wKZXuskR5LBJJvmsSGPwSSB/GjMH2m6WPUZIvd0xhajggEIMIIB + BDAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB + MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFrz7Sv8NsI3eblSMOpUb89V + yy6sMB8GA1UdIwQYMBaAFHxClq7eS0g7+pL4nozPbYupcjeVMDIGCCsGAQUFBwEB + BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gyLmkubGVuY3Iub3JnLzAnBgNVHR8E + IDAeMBygGqAYhhZodHRwOi8veDIuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYG + Z4EMAQIBMA0GCysGAQQBgt8TAQEBMAoGCCqGSM49BAMDA2cAMGQCMHt01VITjWH+ + Dbo/AwCd89eYhNlXLr3pD5xcSAQh8suzYHKOl9YST8pE9kLJ03uGqQIwWrGxtO3q + YJkgsTgDyj2gJrjubi1K9sZmHzOa25JK1fUpE8ZwYii6I4zPPS/Lgul/ + -----END CERTIFICATE----- + """) + + verification_time = datetime.fromisoformat("2024-01-12T00:00:00Z") + +.. doctest:: + + >>> from cryptography.x509 import Certificate, DNSName, load_pem_x509_certificates + >>> from cryptography.x509.verification import PolicyBuilder, Store + >>> import certifi + >>> from datetime import datetime + >>> with open(certifi.where(), "rb") as pems: + ... store = Store(load_pem_x509_certificates(pems.read())) + >>> builder = PolicyBuilder().store(store) + >>> builder = builder.time(verification_time) + >>> verifier = builder.build_server_verifier(DNSName("cryptography.io")) + >>> # NOTE: peer and untrusted_intermediates are Certificate and + >>> # list[Certificate] respectively, and should be loaded from the + >>> # application context that needs them verified, such as a + >>> # TLS socket. + >>> chain = verifier.verify(peer, untrusted_intermediates) + +.. class:: Store(certs) + + .. versionadded:: 42.0.0 + + A Store is an opaque set of public keys and subject identifiers that are + considered trusted *a priori*. Stores are typically created from the host + OS's root of trust, from a well-known source such as a browser CA bundle, + or from a small set of manually pre-trusted entities. + + :param certs: A list of one or more :class:`cryptography.x509.Certificate` + instances. + +.. class:: Subject + + .. versionadded:: 42.0.0 + + Type alias: A union of all subject types supported: + :class:`cryptography.x509.general_name.DNSName`, + :class:`cryptography.x509.general_name.IPAddress`. + +.. class:: ServerVerifier + + .. versionadded:: 42.0.0 + + A ServerVerifier verifies server certificates. + + It contains and describes various pieces of configurable path + validation logic, such as which subject to expect, how deep prospective + validation chains may go, which signature algorithms are allowed, and + so forth. + + ServerVerifier instances cannot be constructed directly; + :class:`PolicyBuilder` must be used. + + .. attribute:: subject + + :type: :class:`Subject` + + The verifier's subject. + + .. attribute:: validation_time + + :type: :class:`datetime.datetime` + + The verifier's validation time. + + .. attribute:: max_chain_depth + + :type: :class:`int` + + The verifier's maximum intermediate CA chain depth. + + .. attribute:: store + + :type: :class:`Store` + + The verifier's trust store. + + .. method:: verify(leaf, intermediates) + + Performs path validation on ``leaf``, returning a valid path + if one exists. The path is returned in leaf-first order: + the first member is ``leaf``, followed by the intermediates used + (if any), followed by a member of the ``store``. + + :param leaf: The leaf :class:`~cryptography.x509.Certificate` to validate + :param intermediates: A :class:`list` of intermediate :class:`~cryptography.x509.Certificate` to attempt to use + + :returns: A list containing a valid chain from ``leaf`` to a member of :class:`ServerVerifier.store`. + + :raises VerificationError: If a valid chain cannot be constructed + +.. class:: VerificationError + + .. versionadded:: 42.0.0 + + The error raised when path validation fails. + +.. class:: PolicyBuilder + + .. versionadded:: 42.0.0 + + A PolicyBuilder provides a builder-style interface for constructing a + Verifier. + + .. method:: time(new_time) + + Sets the verifier's verification time. + + If not called explicitly, this is set to :meth:`datetime.datetime.now` + when :meth:`build_server_verifier` is called. + + :param new_time: The :class:`datetime.datetime` to use in the verifier + + :returns: A new instance of :class:`PolicyBuilder` + + .. method:: store(new_store) + + Sets the verifier's trust store. + + :param new_store: The :class:`Store` to use in the verifier + + :returns: A new instance of :class:`PolicyBuilder` + + .. method:: max_chain_depth(new_max_chain_depth) + + Sets the verifier's maximum chain building depth. + + This depth behaves tracks the length of the intermediate CA + chain: a maximum depth of zero means that the leaf must be directly + issued by a member of the store, a depth of one means no more than + 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 + + :returns: A new instance of :class:`PolicyBuilder` + + .. method:: build_server_verifier(subject) + + Builds a verifier for verifying server certificates. + + :param subject: A :class:`Subject` to use in the verifier + + :returns: An instance of :class:`ServerVerifier` diff --git a/noxfile.py b/noxfile.py index 86a6a68b61a8..f1117d7fee3b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -10,24 +10,34 @@ import pathlib import re import sys -import typing import uuid import nox +try: + import tomllib +except ImportError: + import tomli as tomllib # type: ignore[import-not-found,no-redef] + nox.options.reuse_existing_virtualenvs = True -def install(session: nox.Session, *args: str) -> None: +def install(session: nox.Session, *args: str, silent: bool = False) -> None: + if not silent: + args += ("-v",) session.install( - "-v", "-c", "ci-constraints-requirements.txt", *args, - silent=False, + silent=silent, ) +def load_pyproject_toml() -> dict: + with (pathlib.Path(__file__).parent / "pyproject.toml").open("rb") as f: + return tomllib.load(f) + + @nox.session @nox.session(name="tests-ssh") @nox.session(name="tests-randomorder") @@ -64,6 +74,11 @@ def tests(session: nox.Session) -> None: else: cov_args = [] + if session.posargs: + tests = session.posargs + else: + tests = ["tests/"] + session.run( "pytest", "-n", @@ -71,8 +86,7 @@ def tests(session: nox.Session) -> None: "--dist=worksteal", *cov_args, "--durations=10", - *session.posargs, - "tests/", + *tests, ) if session.name != "tests-nocoverage": @@ -132,10 +146,9 @@ def docs(session: nox.Session) -> None: "docs/_build/html", ) - # This is in the docs job because `twine check` verifies that the README - # is valid reStructuredText. - session.run("python", "-m", "build", "--sdist") - session.run("twine", "check", "dist/*") + session.run( + "python3", "-m", "readme_renderer", "README.rst", "-o", "/dev/null" + ) @nox.session(name="docs-linkcheck") @@ -149,11 +162,21 @@ def docs_linkcheck(session: nox.Session) -> None: @nox.session def flake(session: nox.Session) -> None: - install(session, ".[pep8test,test,ssh,nox]") + # TODO: Ideally there'd be a pip flag to install just our dependencies, + # but not install us. + pyproject_data = load_pyproject_toml() + install( + session, + *pyproject_data["build-system"]["requires"], + *pyproject_data["project"]["optional-dependencies"]["pep8test"], + *pyproject_data["project"]["optional-dependencies"]["test"], + *pyproject_data["project"]["optional-dependencies"]["ssh"], + *pyproject_data["project"]["optional-dependencies"]["nox"], + ) + install(session, "-e", "vectors/") session.run("ruff", ".") - session.run("black", "--check", ".") - session.run("check-sdist") + session.run("ruff", "format", "--check", ".") session.run( "mypy", "src/cryptography/", @@ -162,9 +185,11 @@ def flake(session: nox.Session) -> None: "release.py", "noxfile.py", ) + session.run("check-sdist", "--no-isolation") @nox.session +@nox.session(name="rust-noclippy") def rust(session: nox.Session) -> None: prof_location = ( pathlib.Path(".") / ".rust-cov" / str(uuid.uuid4()) @@ -177,11 +202,23 @@ def rust(session: nox.Session) -> None: } ) - install(session, ".") + # TODO: Ideally there'd be a pip flag to install just our dependencies, + # but not install us. + pyproject_data = load_pyproject_toml() + install(session, *pyproject_data["build-system"]["requires"]) with session.chdir("src/rust/"): session.run("cargo", "fmt", "--all", "--", "--check", external=True) - session.run("cargo", "clippy", "--", "-D", "warnings", external=True) + if session.name != "rust-noclippy": + session.run( + "cargo", + "clippy", + "--all", + "--", + "-D", + "warnings", + external=True, + ) build_output = session.run( "cargo", @@ -210,6 +247,69 @@ def rust(session: nox.Session) -> None: process_rust_coverage(session, rust_tests, prof_location) +@nox.session +def local(session): + pyproject_data = load_pyproject_toml() + install( + session, + *pyproject_data["build-system"]["requires"], + *pyproject_data["project"]["optional-dependencies"]["pep8test"], + *pyproject_data["project"]["optional-dependencies"]["test"], + *pyproject_data["project"]["optional-dependencies"]["ssh"], + *pyproject_data["project"]["optional-dependencies"]["nox"], + "flit", + silent=True, + ) + with session.cd("vectors/"): + session.run("flit", "install", "-s", silent=True) + + session.run("ruff", "format", ".") + session.run("ruff", ".") + + with session.chdir("src/rust/"): + session.run("cargo", "fmt", "--all", external=True) + session.run("cargo", "check", "--all", "--tests", external=True) + session.run( + "cargo", + "clippy", + "--all", + "--", + "-D", + "warnings", + external=True, + ) + + session.run( + "mypy", + "src/cryptography/", + "vectors/cryptography_vectors/", + "tests/", + "release.py", + "noxfile.py", + ) + + install(session, ".[test]") + + if session.posargs: + tests = session.posargs + else: + tests = ["tests/"] + + session.run( + "pytest", + "-n", + "auto", + "--dist=worksteal", + "--durations=10", + *tests, + ) + + with session.chdir("src/rust/"): + session.run( + "cargo", "test", "--no-default-features", "--all", external=True + ) + + LCOV_SOURCEFILE_RE = re.compile( r"^SF:.*[\\/]src[\\/]rust[\\/](.*)$", flags=re.MULTILINE ) @@ -218,7 +318,7 @@ def rust(session: nox.Session) -> None: def process_rust_coverage( session: nox.Session, - rust_binaries: typing.List[str], + rust_binaries: list[str], prof_raw_location: pathlib.Path, ) -> None: # Hitting weird issues merging Windows and Linux Rust coverage, so just diff --git a/pyproject.toml b/pyproject.toml index c9de27381328..c9a7979bdcc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,18 @@ [build-system] +# These requirements must be kept sync with the requirements on ./github/requirements/build-requirements files requires = [ # First version of setuptools to support pyproject.toml configuration "setuptools>=61.0.0", "wheel", # Must be kept in sync with `project.dependencies` "cffi>=1.12; platform_python_implementation != 'PyPy'", - "setuptools-rust>=0.11.4", + "setuptools-rust>=1.7.0", ] build-backend = "setuptools.build_meta" [project] name = "cryptography" -version = "41.0.0" +version = "42.0.2" authors = [ {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} ] @@ -37,6 +38,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Security :: Cryptography", @@ -44,7 +46,7 @@ classifiers = [ requires-python = ">=3.7" dependencies = [ # Must be kept in sync with `build-system.requires` - "cffi >=1.12", + "cffi>=1.12; platform_python_implementation != 'PyPy'", ] [project.urls] @@ -73,16 +75,21 @@ test = [ "pytest-cov", "pytest-xdist", "pretend", + "certifi", ] test-randomorder = ["pytest-randomly"] docs = ["sphinx >=5.3.0", "sphinx-rtd-theme >=1.1.1"] -docstest = ["pyenchant >=1.6.11", "twine >=1.12.0", "sphinxcontrib-spelling >=4.0.1"] +docstest = ["pyenchant >=1.6.11", "readme-renderer", "sphinxcontrib-spelling >=4.0.1"] sdist = ["build"] -pep8test = ["black", "ruff", "mypy", "check-sdist"] +# `click` included because its needed to type check `release.py` +pep8test = ["ruff", "mypy", "check-sdist", "click"] + +[[tool.setuptools-rust.ext-modules]] +target = "cryptography.hazmat.bindings._rust" +path = "src/rust/Cargo.toml" +py-limited-api = "auto" +rust-version = ">=1.63.0" -[tool.black] -line-length = 79 -target-version = ["py37"] [tool.pytest.ini_options] addopts = "-r s --capture=no --strict-markers --benchmark-disable" @@ -135,10 +142,8 @@ exclude_lines = [ ] [tool.ruff] -# UP006: Minimum Python 3.9 -# UP007, UP038: Minimum Python 3.10 -ignore = ['N818', 'UP006', 'UP007', 'UP038'] -select = ['E', 'F', 'I', 'N', 'W', 'UP'] +ignore = ['N818'] +select = ['E', 'F', 'I', 'N', 'W', 'UP', 'RUF'] line-length = 79 [tool.ruff.isort] @@ -151,4 +156,4 @@ git-only = [ "ci-constraints-requirements.txt", ".gitattributes", ".gitignore", -] \ No newline at end of file +] diff --git a/release.py b/release.py index b4844a12a5e5..78b894fe1d44 100644 --- a/release.py +++ b/release.py @@ -7,6 +7,8 @@ import subprocess import click +import tomllib +from packaging.version import Version def run(*args: str) -> None: @@ -20,21 +22,26 @@ def cli(): @cli.command() -@click.argument("version") -def release(version: str) -> None: - """ - ``version`` should be a string like '0.4' or '1.0'. - """ +def release() -> None: + base_dir = pathlib.Path(__file__).parent + with (base_dir / "pyproject.toml").open("rb") as f: + pyproject = tomllib.load(f) + version = pyproject["project"]["version"] + + if Version(version).is_prerelease: + raise RuntimeError( + f"Can't release, pyproject.toml version is pre-release: {version}" + ) + # Tag and push the tag (this will trigger the wheel builder in Actions) run("git", "tag", "-s", version, "-m", f"{version} release") - run("git", "push", "--tags") + run("git", "push", "--tags", "git@github.com:pyca/cryptography.git") def replace_version( p: pathlib.Path, variable_name: str, new_version: str ) -> None: - with p.open() as f: - content = f.read() + content = p.read_text() pattern = rf"^{variable_name}\s*=\s*.*$" match = re.search(pattern, content, re.MULTILINE) @@ -45,9 +52,7 @@ def replace_version( content[:start] + f'{variable_name} = "{new_version}"' + content[end:] ) - # Write back to file - with p.open("w") as f: - f.write(new_content) + p.write_text(new_content) @cli.command() diff --git a/setup.py b/setup.py deleted file mode 100644 index 4fe0c027c17c..000000000000 --- a/setup.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python - -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -import os -import platform -import re -import shutil -import subprocess -import sys -import warnings - -from setuptools import setup - -try: - from setuptools_rust import RustExtension -except ImportError: - print( - """ - =============================DEBUG ASSISTANCE========================== - If you are seeing an error here please try the following to - successfully install cryptography: - - Upgrade to the latest pip and try again. This will fix errors for most - users. See: https://pip.pypa.io/en/stable/installing/#upgrading-pip - =============================DEBUG ASSISTANCE========================== - """ - ) - raise - - -# distutils emits this warning if you pass `setup()` an unknown option. This -# is what happens if you somehow run this file without `cffi` installed: -# `cffi_modules` is an unknown option. -warnings.filterwarnings("error", message="Unknown distribution option") - -base_dir = os.path.dirname(__file__) -src_dir = os.path.join(base_dir, "src") - -# When executing the setup.py, we need to be able to import ourselves, this -# means that we need to add the src/ directory to the sys.path. -sys.path.insert(0, src_dir) - -if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (7, 3, 10): - raise RuntimeError("cryptography is not compatible with PyPy3 < 7.3.10") - -try: - # See pyproject.toml for most of the config metadata. - setup( - rust_extensions=[ - RustExtension( - "cryptography.hazmat.bindings._rust", - "src/rust/Cargo.toml", - py_limited_api=True, - rust_version=">=1.56.0", - ) - ], - ) -except: # noqa: E722 - # Note: This is a bare exception that re-raises so that we don't interfere - # with anything the installation machinery might want to do. Because we - # print this for any exception this msg can appear (e.g. in verbose logs) - # even if there's no failure. For example, SetupRequirementsError is raised - # during PEP517 building and prints this text. setuptools raises SystemExit - # when compilation fails right now, but it's possible this isn't stable - # or a public API commitment so we'll remain ultra conservative. - - import pkg_resources - - print( - """ - =============================DEBUG ASSISTANCE============================= - If you are seeing a compilation error please try the following steps to - successfully install cryptography: - 1) Upgrade to the latest pip and try again. This will fix errors for most - users. See: https://pip.pypa.io/en/stable/installing/#upgrading-pip - 2) Read https://cryptography.io/en/latest/installation/ for specific - instructions for your platform. - 3) Check our frequently asked questions for more information: - https://cryptography.io/en/latest/faq/ - 4) Ensure you have a recent Rust toolchain installed: - https://cryptography.io/en/latest/installation/#rust - """ - ) - print(f" Python: {'.'.join(str(v) for v in sys.version_info[:3])}") - print(f" platform: {platform.platform()}") - for dist in ["pip", "setuptools", "setuptools_rust"]: - try: - version = pkg_resources.get_distribution(dist).version - except pkg_resources.DistributionNotFound: - version = "n/a" - print(f" {dist}: {version}") - version = "n/a" - if shutil.which("rustc") is not None: - try: - # If for any reason `rustc --version` fails, silently ignore it - rustc_output = subprocess.run( - ["rustc", "--version"], - capture_output=True, - timeout=0.5, - encoding="utf8", - check=True, - ).stdout - version = re.sub("^rustc ", "", rustc_output.strip()) - except subprocess.SubprocessError: - pass - print(f" rustc: {version}") - - print( - """\ - =============================DEBUG ASSISTANCE============================= - """ - ) - raise diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index 6c4fd90e143b..6065e7aeed37 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -26,17 +26,13 @@ "asn1", "bignum", "bio", - "cmac", "crypto", "dh", "dsa", "ec", - "ecdsa", "engine", "err", "evp", - "evp_aead", - "fips", "nid", "objects", "opensslv", @@ -50,7 +46,6 @@ "x509v3", "x509_vfy", "pkcs7", - "callbacks", ], ) diff --git a/src/_cffi_src/openssl/bignum.py b/src/_cffi_src/openssl/bignum.py index 044403325582..4c7a9593d51c 100644 --- a/src/_cffi_src/openssl/bignum.py +++ b/src/_cffi_src/openssl/bignum.py @@ -9,56 +9,24 @@ """ TYPES = """ -static const long Cryptography_HAS_BN_FLAGS; +static const long Cryptography_HAS_PRIME_CHECKS; typedef ... BN_CTX; -typedef ... BN_MONT_CTX; typedef ... BIGNUM; typedef int... BN_ULONG; """ FUNCTIONS = """ -#define BN_FLG_CONSTTIME ... - -void BN_set_flags(BIGNUM *, int); - BIGNUM *BN_new(void); void BN_free(BIGNUM *); -void BN_clear_free(BIGNUM *); int BN_rand_range(BIGNUM *, const BIGNUM *); -BN_CTX *BN_CTX_new(void); -void BN_CTX_free(BN_CTX *); - -void BN_CTX_start(BN_CTX *); -BIGNUM *BN_CTX_get(BN_CTX *); -void BN_CTX_end(BN_CTX *); - -BN_MONT_CTX *BN_MONT_CTX_new(void); -int BN_MONT_CTX_set(BN_MONT_CTX *, const BIGNUM *, BN_CTX *); -void BN_MONT_CTX_free(BN_MONT_CTX *); - int BN_set_word(BIGNUM *, BN_ULONG); char *BN_bn2hex(const BIGNUM *); int BN_hex2bn(BIGNUM **, const char *); -int BN_bn2bin(const BIGNUM *, unsigned char *); -BIGNUM *BN_bin2bn(const unsigned char *, int, BIGNUM *); - -int BN_num_bits(const BIGNUM *); - -int BN_is_negative(const BIGNUM *); -int BN_is_odd(const BIGNUM *); -int BN_mod_exp_mont(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *, BN_MONT_CTX *); -int BN_mod_exp_mont_consttime(BIGNUM *, const BIGNUM *, const BIGNUM *, - const BIGNUM *, BN_CTX *, BN_MONT_CTX *); -BIGNUM *BN_mod_inverse(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); - -int BN_num_bytes(const BIGNUM *); - /* The following 3 prime methods are exposed for Tribler. */ int BN_generate_prime_ex(BIGNUM *, int, int, const BIGNUM *, const BIGNUM *, BN_GENCB *); @@ -68,12 +36,9 @@ CUSTOMIZATIONS = """ #if CRYPTOGRAPHY_IS_BORINGSSL -static const long Cryptography_HAS_BN_FLAGS = 0; - -static const int BN_FLG_CONSTTIME = 0; -void (*BN_set_flags)(BIGNUM *, int) = NULL; +static const long Cryptography_HAS_PRIME_CHECKS = 0; int (*BN_prime_checks_for_size)(int) = NULL; #else -static const long Cryptography_HAS_BN_FLAGS = 1; +static const long Cryptography_HAS_PRIME_CHECKS = 1; #endif """ diff --git a/src/_cffi_src/openssl/callbacks.py b/src/_cffi_src/openssl/callbacks.py deleted file mode 100644 index ddb764283920..000000000000 --- a/src/_cffi_src/openssl/callbacks.py +++ /dev/null @@ -1,52 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -INCLUDES = """ -#include -""" - -TYPES = """ -typedef struct { - char *password; - int length; - int called; - int error; - int maxsize; -} CRYPTOGRAPHY_PASSWORD_DATA; -""" - -FUNCTIONS = """ -int Cryptography_pem_password_cb(char *, int, int, void *); -""" - -CUSTOMIZATIONS = """ -typedef struct { - char *password; - int length; - int called; - int error; - int maxsize; -} CRYPTOGRAPHY_PASSWORD_DATA; - -int Cryptography_pem_password_cb(char *buf, int size, - int rwflag, void *userdata) { - /* The password cb is only invoked if OpenSSL decides the private - key is encrypted. So this path only occurs if it needs a password */ - CRYPTOGRAPHY_PASSWORD_DATA *st = (CRYPTOGRAPHY_PASSWORD_DATA *)userdata; - st->called += 1; - st->maxsize = size; - if (st->length == 0) { - st->error = -1; - return 0; - } else if (st->length < size) { - memcpy(buf, st->password, (size_t)st->length); - return st->length; - } else { - st->error = -2; - return 0; - } -} -""" diff --git a/src/_cffi_src/openssl/cmac.py b/src/_cffi_src/openssl/cmac.py deleted file mode 100644 index 7095066dac54..000000000000 --- a/src/_cffi_src/openssl/cmac.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -INCLUDES = """ -#if !defined(OPENSSL_NO_CMAC) -#include -#endif -""" - -TYPES = """ -typedef ... CMAC_CTX; -""" - -FUNCTIONS = """ -CMAC_CTX *CMAC_CTX_new(void); -int CMAC_Init(CMAC_CTX *, const void *, size_t, const EVP_CIPHER *, ENGINE *); -int CMAC_Update(CMAC_CTX *, const void *, size_t); -int CMAC_Final(CMAC_CTX *, unsigned char *, size_t *); -int CMAC_CTX_copy(CMAC_CTX *, const CMAC_CTX *); -void CMAC_CTX_free(CMAC_CTX *); -""" - -CUSTOMIZATIONS = """ -""" diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index f5fcb04405b5..b3543ade73cb 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -42,36 +42,34 @@ #define CRYPTOGRAPHY_IS_BORINGSSL 0 #endif -#if CRYPTOGRAPHY_IS_LIBRESSL -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 \ - (LIBRESSL_VERSION_NUMBER < 0x3070000f) - -#else -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 (0) -#endif - #if OPENSSL_VERSION_NUMBER < 0x10101040 #error "pyca/cryptography MUST be linked with Openssl 1.1.1d or later" #endif #define CRYPTOGRAPHY_OPENSSL_300_OR_GREATER \ (OPENSSL_VERSION_NUMBER >= 0x30000000 && !CRYPTOGRAPHY_IS_LIBRESSL) +#define CRYPTOGRAPHY_OPENSSL_320_OR_GREATER \ + (OPENSSL_VERSION_NUMBER >= 0x30200000 && !CRYPTOGRAPHY_IS_LIBRESSL) #define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E \ (OPENSSL_VERSION_NUMBER < 0x10101050 || CRYPTOGRAPHY_IS_LIBRESSL) -/* Ed25519 support is in all supported OpenSSLs as well as LibreSSL 3.7.0. */ -#define CRYPTOGRAPHY_HAS_WORKING_ED25519 \ - (!CRYPTOGRAPHY_IS_LIBRESSL || \ - (CRYPTOGRAPHY_IS_LIBRESSL && !CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370)) + +#if CRYPTOGRAPHY_IS_LIBRESSL +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_380 \ + (LIBRESSL_VERSION_NUMBER < 0x3080000f) + +#else +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_380 (0) +#endif """ TYPES = """ static const int CRYPTOGRAPHY_OPENSSL_300_OR_GREATER; +static const int CRYPTOGRAPHY_OPENSSL_320_OR_GREATER; static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E; -static const int CRYPTOGRAPHY_HAS_WORKING_ED25519; -static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370; +static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_380; static const int CRYPTOGRAPHY_IS_LIBRESSL; static const int CRYPTOGRAPHY_IS_BORINGSSL; diff --git a/src/_cffi_src/openssl/dh.py b/src/_cffi_src/openssl/dh.py index b4a42e7f6058..a3bf23335dc1 100644 --- a/src/_cffi_src/openssl/dh.py +++ b/src/_cffi_src/openssl/dh.py @@ -10,8 +10,6 @@ TYPES = """ typedef ... DH; - -const long DH_NOT_SUITABLE_GENERATOR; """ FUNCTIONS = """ diff --git a/src/_cffi_src/openssl/dsa.py b/src/_cffi_src/openssl/dsa.py index d91076393582..2188939948ed 100644 --- a/src/_cffi_src/openssl/dsa.py +++ b/src/_cffi_src/openssl/dsa.py @@ -16,15 +16,7 @@ int DSA_generate_key(DSA *); DSA *DSA_new(void); void DSA_free(DSA *); -DSA *DSAparams_dup(DSA *); -int DSA_size(const DSA *); -int DSA_sign(int, const unsigned char *, int, unsigned char *, unsigned int *, - DSA *); -int DSA_verify(int, const unsigned char *, int, const unsigned char *, int, - DSA *); -int DSA_set0_pqg(DSA *, BIGNUM *, BIGNUM *, BIGNUM *); -int DSA_set0_key(DSA *, BIGNUM *, BIGNUM *); int DSA_generate_parameters_ex(DSA *, int, unsigned char *, int, int *, unsigned long *, BN_GENCB *); """ diff --git a/src/_cffi_src/openssl/ec.py b/src/_cffi_src/openssl/ec.py index 0e3604d1d29a..8b9558f8d311 100644 --- a/src/_cffi_src/openssl/ec.py +++ b/src/_cffi_src/openssl/ec.py @@ -10,8 +10,6 @@ """ TYPES = """ -static const int OPENSSL_EC_NAMED_CURVE; - typedef ... EC_KEY; typedef ... EC_GROUP; typedef ... EC_POINT; @@ -19,58 +17,16 @@ int nid; const char *comment; } EC_builtin_curve; -typedef enum { - POINT_CONVERSION_COMPRESSED, - POINT_CONVERSION_UNCOMPRESSED, - ... -} point_conversion_form_t; """ FUNCTIONS = """ -void EC_GROUP_free(EC_GROUP *); - -EC_GROUP *EC_GROUP_new_by_curve_name(int); - -int EC_GROUP_get_curve_name(const EC_GROUP *); - size_t EC_get_builtin_curves(EC_builtin_curve *, size_t); void EC_KEY_free(EC_KEY *); EC_KEY *EC_KEY_new_by_curve_name(int); -const EC_GROUP *EC_KEY_get0_group(const EC_KEY *); -const BIGNUM *EC_KEY_get0_private_key(const EC_KEY *); -int EC_KEY_set_private_key(EC_KEY *, const BIGNUM *); -const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *); -int EC_KEY_set_public_key(EC_KEY *, const EC_POINT *); -void EC_KEY_set_asn1_flag(EC_KEY *, int); -int EC_KEY_generate_key(EC_KEY *); - -EC_POINT *EC_POINT_new(const EC_GROUP *); -void EC_POINT_free(EC_POINT *); -int EC_POINT_cmp(const EC_GROUP *, const EC_POINT *, const EC_POINT *, - BN_CTX *); - -int EC_POINT_set_affine_coordinates(const EC_GROUP *, EC_POINT *, - const BIGNUM *, const BIGNUM *, BN_CTX *); -int EC_POINT_get_affine_coordinates(const EC_GROUP *, const EC_POINT *, - BIGNUM *, BIGNUM *, BN_CTX *); - -size_t EC_POINT_point2oct(const EC_GROUP *, const EC_POINT *, - point_conversion_form_t, - unsigned char *, size_t, BN_CTX *); - -int EC_POINT_oct2point(const EC_GROUP *, EC_POINT *, - const unsigned char *, size_t, BN_CTX *); - -int EC_POINT_is_at_infinity(const EC_GROUP *, const EC_POINT *); - -int EC_POINT_mul(const EC_GROUP *, EC_POINT *, const BIGNUM *, - const EC_POINT *, const BIGNUM *, BN_CTX *); const char *EC_curve_nid2nist(int); - -int EC_GROUP_get_asn1_flag(const EC_GROUP *); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/ecdsa.py b/src/_cffi_src/openssl/ecdsa.py deleted file mode 100644 index 716b5d03016f..000000000000 --- a/src/_cffi_src/openssl/ecdsa.py +++ /dev/null @@ -1,24 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -INCLUDES = """ -#include -""" - -TYPES = """ -""" - -FUNCTIONS = """ -int ECDSA_sign(int, const unsigned char *, int, unsigned char *, - unsigned int *, EC_KEY *); -int ECDSA_verify(int, const unsigned char *, int, const unsigned char *, int, - EC_KEY *); -int ECDSA_size(const EC_KEY *); - -""" - -CUSTOMIZATIONS = """ -""" diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py index 609313ec57ae..9629a2c8f929 100644 --- a/src/_cffi_src/openssl/engine.py +++ b/src/_cffi_src/openssl/engine.py @@ -42,18 +42,20 @@ typedef void UI_METHOD; #endif -/* Despite being OPENSSL_NO_ENGINE, BoringSSL defines these symbols. */ -#if !CRYPTOGRAPHY_IS_BORINGSSL +/* Despite being OPENSSL_NO_ENGINE, BoringSSL/LibreSSL define these symbols. */ +#if !CRYPTOGRAPHY_IS_BORINGSSL && !CRYPTOGRAPHY_IS_LIBRESSL int (*ENGINE_free)(ENGINE *) = NULL; void (*ENGINE_load_builtin_engines)(void) = NULL; #endif -ENGINE *(*ENGINE_by_id)(const char *) = NULL; -int (*ENGINE_init)(ENGINE *) = NULL; -int (*ENGINE_finish)(ENGINE *) = NULL; ENGINE *(*ENGINE_get_default_RAND)(void) = NULL; int (*ENGINE_set_default_RAND)(ENGINE *) = NULL; void (*ENGINE_unregister_RAND)(ENGINE *) = NULL; + +#if !CRYPTOGRAPHY_IS_LIBRESSL +ENGINE *(*ENGINE_by_id)(const char *) = NULL; +int (*ENGINE_init)(ENGINE *) = NULL; +int (*ENGINE_finish)(ENGINE *) = NULL; int (*ENGINE_ctrl_cmd)(ENGINE *, const char *, long, void *, void (*)(void), int) = NULL; @@ -66,6 +68,7 @@ void *) = NULL; EVP_PKEY *(*ENGINE_load_public_key)(ENGINE *, const char *, UI_METHOD *, void *) = NULL; +#endif #else static const long Cryptography_HAS_ENGINE = 1; diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py index ce54fd9fe931..54f5388b83d0 100644 --- a/src/_cffi_src/openssl/evp.py +++ b/src/_cffi_src/openssl/evp.py @@ -20,13 +20,11 @@ static const int EVP_PKEY_RSA_PSS; static const int EVP_PKEY_DSA; static const int EVP_PKEY_DH; -static const int EVP_PKEY_DHX; static const int EVP_PKEY_EC; static const int EVP_PKEY_X25519; static const int EVP_PKEY_ED25519; static const int EVP_PKEY_X448; static const int EVP_PKEY_ED448; -static const int EVP_PKEY_POLY1305; static const int EVP_MAX_MD_SIZE; static const int EVP_CTRL_AEAD_SET_IVLEN; static const int EVP_CTRL_AEAD_GET_TAG; @@ -34,12 +32,8 @@ static const int Cryptography_HAS_SCRYPT; static const int Cryptography_HAS_EVP_PKEY_DHX; -static const long Cryptography_HAS_RAW_KEY; -static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF; static const long Cryptography_HAS_300_FIPS; static const long Cryptography_HAS_300_EVP_CIPHER; -static const long Cryptography_HAS_EVP_PKEY_DH; -static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX; """ FUNCTIONS = """ @@ -58,7 +52,6 @@ void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *); int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *, int); -int EVP_DigestFinalXOF(EVP_MD_CTX *, unsigned char *, size_t); const EVP_MD *EVP_get_digestbyname(const char *); EVP_PKEY *EVP_PKEY_new(void); @@ -67,11 +60,6 @@ int EVP_PKEY_size(EVP_PKEY *); RSA *EVP_PKEY_get1_RSA(EVP_PKEY *); -int EVP_PKEY_encrypt(EVP_PKEY_CTX *, unsigned char *, size_t *, - const unsigned char *, size_t); -int EVP_PKEY_decrypt(EVP_PKEY_CTX *, unsigned char *, size_t *, - const unsigned char *, size_t); - 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 *); @@ -82,30 +70,8 @@ EVP_PKEY *); -EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *, ENGINE *); -void EVP_PKEY_CTX_free(EVP_PKEY_CTX *); -int EVP_PKEY_sign_init(EVP_PKEY_CTX *); -int EVP_PKEY_sign(EVP_PKEY_CTX *, unsigned char *, size_t *, - const unsigned char *, size_t); -int EVP_PKEY_verify_init(EVP_PKEY_CTX *); -int EVP_PKEY_verify(EVP_PKEY_CTX *, const unsigned char *, size_t, - const unsigned char *, size_t); -int EVP_PKEY_verify_recover_init(EVP_PKEY_CTX *); -int EVP_PKEY_verify_recover(EVP_PKEY_CTX *, unsigned char *, - size_t *, const unsigned char *, size_t); -int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *); -int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *); - int EVP_PKEY_set1_RSA(EVP_PKEY *, RSA *); int EVP_PKEY_set1_DSA(EVP_PKEY *, DSA *); -int EVP_PKEY_set1_DH(EVP_PKEY *, DH *); - -int EVP_PKEY_cmp(const EVP_PKEY *, const EVP_PKEY *); - -int EVP_PKEY_derive_init(EVP_PKEY_CTX *); -int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *, EVP_PKEY *); -int EVP_PKEY_derive_set_peer_ex(EVP_PKEY_CTX *, EVP_PKEY *, int); -int EVP_PKEY_derive(EVP_PKEY_CTX *, unsigned char *, size_t *); int EVP_PKEY_id(const EVP_PKEY *); @@ -116,21 +82,8 @@ int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); -EC_KEY *EVP_PKEY_get1_EC_KEY(EVP_PKEY *); -int EVP_PKEY_set1_EC_KEY(EVP_PKEY *, EC_KEY *); - int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *, int, int, void *); -int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX *, const EVP_MD *); - -EVP_PKEY *EVP_PKEY_new_raw_private_key(int, ENGINE *, const unsigned char *, - size_t); -EVP_PKEY *EVP_PKEY_new_raw_public_key(int, ENGINE *, const unsigned char *, - size_t); -int EVP_PKEY_get_raw_private_key(const EVP_PKEY *, unsigned char *, size_t *); -int EVP_PKEY_get_raw_public_key(const EVP_PKEY *, unsigned char *, size_t *); - -int EVP_default_properties_is_fips_enabled(OSSL_LIB_CTX *); int EVP_default_properties_enable_fips(OSSL_LIB_CTX *, int); """ @@ -139,7 +92,6 @@ const long Cryptography_HAS_EVP_PKEY_DHX = 1; #else const long Cryptography_HAS_EVP_PKEY_DHX = 0; -const long EVP_PKEY_DHX = -1; #endif #if CRYPTOGRAPHY_IS_LIBRESSL || defined(OPENSSL_NO_SCRYPT) @@ -148,42 +100,6 @@ static const long Cryptography_HAS_SCRYPT = 1; #endif -#if CRYPTOGRAPHY_IS_LIBRESSL -static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 0; -int (*EVP_DigestFinalXOF)(EVP_MD_CTX *, unsigned char *, size_t) = NULL; -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 -static const long Cryptography_HAS_RAW_KEY = 0; -EVP_PKEY *(*EVP_PKEY_new_raw_private_key)(int, ENGINE *, const unsigned char *, - size_t) = NULL; -EVP_PKEY *(*EVP_PKEY_new_raw_public_key)(int, ENGINE *, const unsigned char *, - size_t) = NULL; -int (*EVP_PKEY_get_raw_private_key)(const EVP_PKEY *, unsigned char *, - size_t *) = NULL; -int (*EVP_PKEY_get_raw_public_key)(const EVP_PKEY *, unsigned char *, - size_t *) = NULL; -#else -static const long Cryptography_HAS_RAW_KEY = 1; -#endif -#else -static const long Cryptography_HAS_RAW_KEY = 1; -static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 1; -#endif - -#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER -static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX = 1; -#else -static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX = 0; -int (*EVP_PKEY_derive_set_peer_ex)(EVP_PKEY_CTX *, EVP_PKEY *, int) = NULL; -#endif - -/* This is tied to X25519 support so we reuse the Cryptography_HAS_X25519 - conditional to remove it. OpenSSL 1.1.0 didn't have this define, but - 1.1.1 will when it is released. We can remove this in the distant - future when we drop 1.1.0 support. */ -#ifndef EVP_PKEY_X25519 -#define EVP_PKEY_X25519 NID_X25519 -#endif - /* This is tied to X448 support so we reuse the Cryptography_HAS_X448 conditional to remove it. OpenSSL 1.1.1 adds this define. We can remove this in the distant future when we drop 1.1.0 support. */ @@ -191,22 +107,10 @@ #define EVP_PKEY_X448 NID_X448 #endif -/* This is tied to ED25519 support so we reuse the Cryptography_HAS_ED25519 - conditional to remove it. */ -#ifndef EVP_PKEY_ED25519 -#define EVP_PKEY_ED25519 NID_ED25519 -#endif - /* This is tied to ED448 support so we reuse the Cryptography_HAS_ED448 conditional to remove it. */ #ifndef EVP_PKEY_ED448 -#define EVP_PKEY_ED448 NID_ED448 -#endif - -/* This is tied to poly1305 support so we reuse the Cryptography_HAS_POLY1305 - conditional to remove it. */ -#ifndef EVP_PKEY_POLY1305 -#define EVP_PKEY_POLY1305 NID_poly1305 +#define EVP_PKEY_ED448 0 #endif #if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER @@ -215,17 +119,9 @@ #else static const long Cryptography_HAS_300_FIPS = 0; static const long Cryptography_HAS_300_EVP_CIPHER = 0; -int (*EVP_default_properties_is_fips_enabled)(OSSL_LIB_CTX *) = NULL; int (*EVP_default_properties_enable_fips)(OSSL_LIB_CTX *, int) = NULL; EVP_CIPHER * (*EVP_CIPHER_fetch)(OSSL_LIB_CTX *, const char *, const char *) = NULL; void (*EVP_CIPHER_free)(EVP_CIPHER *) = NULL; #endif - -#if CRYPTOGRAPHY_IS_BORINGSSL -static const long Cryptography_HAS_EVP_PKEY_DH = 0; -int (*EVP_PKEY_set1_DH)(EVP_PKEY *, DH *) = NULL; -#else -static const long Cryptography_HAS_EVP_PKEY_DH = 1; -#endif """ diff --git a/src/_cffi_src/openssl/evp_aead.py b/src/_cffi_src/openssl/evp_aead.py deleted file mode 100644 index a748bcd7a6a8..000000000000 --- a/src/_cffi_src/openssl/evp_aead.py +++ /dev/null @@ -1,88 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -INCLUDES = """ -#if CRYPTOGRAPHY_IS_BORINGSSL -#include -#endif -""" - -TYPES = """ -typedef ... EVP_AEAD; -typedef ... EVP_AEAD_CTX; -static const size_t EVP_AEAD_DEFAULT_TAG_LENGTH; - -static const long Cryptography_HAS_EVP_AEAD; -""" - -FUNCTIONS = """ -const EVP_AEAD *EVP_aead_chacha20_poly1305(void); -void EVP_AEAD_CTX_free(EVP_AEAD_CTX *); -int EVP_AEAD_CTX_seal(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, - const uint8_t *, size_t, const uint8_t *, size_t, - const uint8_t *, size_t); -int EVP_AEAD_CTX_open(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, - const uint8_t *, size_t, const uint8_t *, size_t, - const uint8_t *, size_t); -size_t EVP_AEAD_max_overhead(const EVP_AEAD *); -/* The function EVP_AEAD_CTX_NEW() has different signatures in BoringSSL and - LibreSSL, so we cannot declare it here. We define a wrapper for it instead. -*/ -EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *, - const uint8_t *, size_t, - size_t); -""" - -CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_LIBRESSL -static const long Cryptography_HAS_EVP_AEAD = 1; -#else -static const long Cryptography_HAS_EVP_AEAD = 0; -#endif - -#if CRYPTOGRAPHY_IS_BORINGSSL -EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *aead, - const uint8_t *key, - size_t key_len, size_t tag_len) { - return EVP_AEAD_CTX_new(aead, key, key_len, tag_len); -} -#elif CRYPTOGRAPHY_IS_LIBRESSL -EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *aead, - const uint8_t *key, - size_t key_len, size_t tag_len) { - EVP_AEAD_CTX *ctx = EVP_AEAD_CTX_new(); - if (ctx == NULL) { - return NULL; - } - - /* This mimics BoringSSL's behavior: any error here is pushed onto - the stack. - */ - int result = EVP_AEAD_CTX_init(ctx, aead, key, key_len, tag_len, NULL); - if (result != 1) { - return NULL; - } - - return ctx; -} -#else -typedef void EVP_AEAD; -typedef void EVP_AEAD_CTX; -static const size_t EVP_AEAD_DEFAULT_TAG_LENGTH = 0; -const EVP_AEAD *(*EVP_aead_chacha20_poly1305)(void) = NULL; -void (*EVP_AEAD_CTX_free)(EVP_AEAD_CTX *) = NULL; -int (*EVP_AEAD_CTX_seal)(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, - const uint8_t *, size_t, const uint8_t *, size_t, - const uint8_t *, size_t) = NULL; -int (*EVP_AEAD_CTX_open)(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, - const uint8_t *, size_t, const uint8_t *, size_t, - const uint8_t *, size_t) = NULL; -size_t (*EVP_AEAD_max_overhead)(const EVP_AEAD *) = NULL; -EVP_AEAD_CTX *(*Cryptography_EVP_AEAD_CTX_new)(const EVP_AEAD *, - const uint8_t *, size_t, - size_t) = NULL; -#endif -""" diff --git a/src/_cffi_src/openssl/fips.py b/src/_cffi_src/openssl/fips.py deleted file mode 100644 index 9e3ce9524b44..000000000000 --- a/src/_cffi_src/openssl/fips.py +++ /dev/null @@ -1,28 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -INCLUDES = """ -#include -""" - -TYPES = """ -static const long Cryptography_HAS_FIPS; -""" - -FUNCTIONS = """ -int FIPS_mode_set(int); -int FIPS_mode(void); -""" - -CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER -static const long Cryptography_HAS_FIPS = 0; -int (*FIPS_mode_set)(int) = NULL; -int (*FIPS_mode)(void) = NULL; -#else -static const long Cryptography_HAS_FIPS = 1; -#endif -""" diff --git a/src/_cffi_src/openssl/nid.py b/src/_cffi_src/openssl/nid.py index 7f6cb62303af..0a38fe038da7 100644 --- a/src/_cffi_src/openssl/nid.py +++ b/src/_cffi_src/openssl/nid.py @@ -10,15 +10,11 @@ TYPES = """ static const int Cryptography_HAS_ED448; -static const int Cryptography_HAS_ED25519; static const int Cryptography_HAS_POLY1305; static const int NID_undef; static const int NID_aes_256_cbc; static const int NID_pbe_WithSHA1And3_Key_TripleDES_CBC; -static const int NID_ED25519; -static const int NID_ED448; -static const int NID_poly1305; static const int NID_subject_alt_name; static const int NID_crl_reason; @@ -30,21 +26,13 @@ """ CUSTOMIZATIONS = """ -#ifndef NID_ED25519 -static const long Cryptography_HAS_ED25519 = 0; -static const int NID_ED25519 = 0; -#else -static const long Cryptography_HAS_ED25519 = 1; -#endif #ifndef NID_ED448 static const long Cryptography_HAS_ED448 = 0; -static const int NID_ED448 = 0; #else static const long Cryptography_HAS_ED448 = 1; #endif #ifndef NID_poly1305 static const long Cryptography_HAS_POLY1305 = 0; -static const int NID_poly1305 = 0; #else static const long Cryptography_HAS_POLY1305 = 1; #endif diff --git a/src/_cffi_src/openssl/objects.py b/src/_cffi_src/openssl/objects.py index 5f9bdb3361d0..cf79cfa208ae 100644 --- a/src/_cffi_src/openssl/objects.py +++ b/src/_cffi_src/openssl/objects.py @@ -15,7 +15,6 @@ const char *OBJ_nid2ln(int); const char *OBJ_nid2sn(int); int OBJ_obj2nid(const ASN1_OBJECT *); -int OBJ_sn2nid(const char *); int OBJ_txt2nid(const char *); """ diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py index 950bd3780c9c..e069d6126999 100644 --- a/src/_cffi_src/openssl/pem.py +++ b/src/_cffi_src/openssl/pem.py @@ -22,17 +22,8 @@ EVP_PKEY *PEM_read_bio_PrivateKey(BIO *, EVP_PKEY **, pem_password_cb *, void *); -int PEM_write_bio_PKCS8PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *, - char *, int, pem_password_cb *, void *); - -int i2d_PKCS8PrivateKey_bio(BIO *, EVP_PKEY *, const EVP_CIPHER *, - char *, int, pem_password_cb *, void *); - PKCS7 *d2i_PKCS7_bio(BIO *, PKCS7 **); -EVP_PKEY *d2i_PKCS8PrivateKey_bio(BIO *, EVP_PKEY **, pem_password_cb *, - void *); - int PEM_write_bio_X509_REQ(BIO *, X509_REQ *); X509_REQ *PEM_read_bio_X509_REQ(BIO *, X509_REQ **, pem_password_cb *, void *); @@ -45,23 +36,8 @@ DH *PEM_read_bio_DHparams(BIO *, DH **, pem_password_cb *, void *); -int PEM_write_bio_DSAPrivateKey(BIO *, DSA *, const EVP_CIPHER *, - unsigned char *, int, - pem_password_cb *, void *); - -int PEM_write_bio_RSAPrivateKey(BIO *, RSA *, const EVP_CIPHER *, - unsigned char *, int, - pem_password_cb *, void *); - -RSA *PEM_read_bio_RSAPublicKey(BIO *, RSA **, pem_password_cb *, void *); - -int PEM_write_bio_RSAPublicKey(BIO *, const RSA *); - EVP_PKEY *PEM_read_bio_PUBKEY(BIO *, EVP_PKEY **, pem_password_cb *, void *); int PEM_write_bio_PUBKEY(BIO *, EVP_PKEY *); -int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *, - unsigned char *, int, pem_password_cb *, - void *); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/pkcs7.py b/src/_cffi_src/openssl/pkcs7.py index ef75157a80da..cce06c6ec0c8 100644 --- a/src/_cffi_src/openssl/pkcs7.py +++ b/src/_cffi_src/openssl/pkcs7.py @@ -13,16 +13,10 @@ typedef struct { Cryptography_STACK_OF_X509 *cert; - Cryptography_STACK_OF_X509_CRL *crl; ...; } PKCS7_SIGNED; -typedef struct { - Cryptography_STACK_OF_X509 *cert; - Cryptography_STACK_OF_X509_CRL *crl; - ...; -} PKCS7_SIGN_ENVELOPE; - +typedef ... PKCS7_SIGN_ENVELOPE; typedef ... PKCS7_DIGEST; typedef ... PKCS7_ENCRYPT; typedef ... PKCS7_ENVELOPE; @@ -48,43 +42,20 @@ FUNCTIONS = """ void PKCS7_free(PKCS7 *); -int SMIME_write_PKCS7(BIO *, PKCS7 *, BIO *, int); -int PEM_write_bio_PKCS7_stream(BIO *, PKCS7 *, BIO *, int); -PKCS7_SIGNER_INFO *PKCS7_sign_add_signer(PKCS7 *, X509 *, EVP_PKEY *, - const EVP_MD *, int); -int PKCS7_final(PKCS7 *, BIO *, int); /* Included verify due to external consumer, see https://github.com/pyca/cryptography/issues/5433 */ int PKCS7_verify(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, BIO *, int); PKCS7 *SMIME_read_PKCS7(BIO *, BIO **); -/* Included due to external consumer, see - https://github.com/pyca/pyopenssl/issues/1031 */ -Cryptography_STACK_OF_X509 *PKCS7_get0_signers(PKCS7 *, - Cryptography_STACK_OF_X509 *, - int); - -int PKCS7_type_is_signed(PKCS7 *); -int PKCS7_type_is_enveloped(PKCS7 *); -int PKCS7_type_is_signedAndEnveloped(PKCS7 *); -int PKCS7_type_is_data(PKCS7 *); """ CUSTOMIZATIONS = """ #if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_PKCS7_FUNCS = 0; -int (*SMIME_write_PKCS7)(BIO *, PKCS7 *, BIO *, int) = NULL; -int (*PEM_write_bio_PKCS7_stream)(BIO *, PKCS7 *, BIO *, int) = NULL; -PKCS7_SIGNER_INFO *(*PKCS7_sign_add_signer)(PKCS7 *, X509 *, EVP_PKEY *, - const EVP_MD *, int) = NULL; -int (*PKCS7_final)(PKCS7 *, BIO *, int); int (*PKCS7_verify)(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, BIO *, int) = NULL; PKCS7 *(*SMIME_read_PKCS7)(BIO *, BIO **) = NULL; -Cryptography_STACK_OF_X509 *(*PKCS7_get0_signers)(PKCS7 *, - Cryptography_STACK_OF_X509 *, - int) = NULL; #else static const long Cryptography_HAS_PKCS7_FUNCS = 1; #endif diff --git a/src/_cffi_src/openssl/rsa.py b/src/_cffi_src/openssl/rsa.py index eea6e396e3fb..89e46470de38 100644 --- a/src/_cffi_src/openssl/rsa.py +++ b/src/_cffi_src/openssl/rsa.py @@ -11,11 +11,7 @@ TYPES = """ typedef ... RSA; typedef ... BN_GENCB; -static const int RSA_PKCS1_PADDING; -static const int RSA_PKCS1_OAEP_PADDING; -static const int RSA_PKCS1_PSS_PADDING; static const int RSA_F4; -static const int RSA_PSS_SALTLEN_AUTO; static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION; """ @@ -25,33 +21,10 @@ void RSA_free(RSA *); int RSA_generate_key_ex(RSA *, int, BIGNUM *, BN_GENCB *); int RSA_check_key(const RSA *); -RSA *RSAPublicKey_dup(RSA *); -int RSA_blinding_on(RSA *, BN_CTX *); int RSA_print(BIO *, const RSA *, int); - -int RSA_set0_key(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); -int RSA_set0_factors(RSA *, BIGNUM *, BIGNUM *); -int RSA_set0_crt_params(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); -void RSA_get0_key(const RSA *, const BIGNUM **, const BIGNUM **, - const BIGNUM **); -void RSA_get0_factors(const RSA *, const BIGNUM **, const BIGNUM **); -void RSA_get0_crt_params(const RSA *, const BIGNUM **, const BIGNUM **, - const BIGNUM **); -int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *, int); -int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX *, int); -int EVP_PKEY_CTX_set_rsa_mgf1_md(EVP_PKEY_CTX *, EVP_MD *); -int EVP_PKEY_CTX_set0_rsa_oaep_label(EVP_PKEY_CTX *, unsigned char *, int); - -int EVP_PKEY_CTX_set_rsa_oaep_md(EVP_PKEY_CTX *, EVP_MD *); """ CUSTOMIZATIONS = """ -// BoringSSL doesn't define this constant, but the value is used for -// automatic salt length computation as in OpenSSL and LibreSSL -#if !defined(RSA_PSS_SALTLEN_AUTO) -#define RSA_PSS_SALTLEN_AUTO -2 -#endif - #if defined(EVP_PKEY_CTRL_RSA_IMPLICIT_REJECTION) static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION = 1; #else diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py index dfab7f651341..c78d681dca8d 100644 --- a/src/_cffi_src/openssl/ssl.py +++ b/src/_cffi_src/openssl/ssl.py @@ -72,6 +72,7 @@ static const long SSL_OP_ALL; static const long SSL_OP_SINGLE_ECDH_USE; static const long SSL_OP_IGNORE_UNEXPECTED_EOF; +static const long SSL_OP_LEGACY_SERVER_CONNECT; static const long SSL_VERIFY_PEER; static const long SSL_VERIFY_FAIL_IF_NO_PEER_CERT; static const long SSL_VERIFY_CLIENT_ONCE; @@ -107,6 +108,7 @@ static const long SSL_CB_HANDSHAKE_DONE; static const long SSL_MODE_RELEASE_BUFFERS; static const long SSL_MODE_ENABLE_PARTIAL_WRITE; +static const long SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; static const long SSL_MODE_AUTO_RETRY; static const long TLS_ST_BEFORE; static const long TLS_ST_OK; @@ -140,6 +142,7 @@ const char *SSL_state_string_long(const SSL *); SSL_SESSION *SSL_get1_session(SSL *); int SSL_set_session(SSL *, SSL_SESSION *); +int SSL_session_reused(const SSL *); SSL *SSL_new(SSL_CTX *); void SSL_free(SSL *); int SSL_set_fd(SSL *, int); diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index 66e8592042fd..b43593543cee 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -24,11 +24,7 @@ typedef ... Cryptography_STACK_OF_X509_CRL; typedef ... Cryptography_STACK_OF_X509_REVOKED; -typedef struct { - ASN1_OBJECT *algorithm; - ...; -} X509_ALGOR; - +typedef ... X509_ALGOR; typedef ... X509_ATTRIBUTE; typedef ... X509_EXTENSION; typedef ... X509_EXTENSIONS; @@ -142,11 +138,6 @@ const char *X509_get_default_cert_dir_env(void); const char *X509_get_default_cert_file_env(void); -int i2d_RSAPrivateKey_bio(BIO *, RSA *); -RSA *d2i_RSAPublicKey_bio(BIO *, RSA **); -int i2d_RSAPublicKey_bio(BIO *, RSA *); -int i2d_DSAPrivateKey_bio(BIO *, DSA *); - int X509_get_ext_count(const X509 *); X509_EXTENSION *X509_get_ext(const X509 *, int); X509_NAME *X509_get_subject_name(const X509 *); @@ -191,10 +182,11 @@ int X509_CRL_set1_lastUpdate(X509_CRL *, const ASN1_TIME *); int X509_CRL_set1_nextUpdate(X509_CRL *, const ASN1_TIME *); -int i2d_ECPrivateKey_bio(BIO *, EC_KEY *); - 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 *); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/x509_vfy.py b/src/_cffi_src/openssl/x509_vfy.py index f1ea8ee6af82..26eed9974f82 100644 --- a/src/_cffi_src/openssl/x509_vfy.py +++ b/src/_cffi_src/openssl/x509_vfy.py @@ -19,8 +19,6 @@ """ TYPES = """ -static const long Cryptography_HAS_X509_STORE_CTX_GET_ISSUER; - typedef ... Cryptography_STACK_OF_ASN1_OBJECT; typedef ... Cryptography_STACK_OF_X509_OBJECT; @@ -103,7 +101,6 @@ static const long X509_V_FLAG_POLICY_CHECK; static const long X509_V_FLAG_EXPLICIT_POLICY; static const long X509_V_FLAG_INHIBIT_MAP; -static const long X509_V_FLAG_NOTIFY_POLICY; static const long X509_V_FLAG_CHECK_SS_SIGNATURE; static const long X509_V_FLAG_PARTIAL_CHAIN; @@ -125,8 +122,6 @@ static const long X509_PURPOSE_ANY; static const long X509_PURPOSE_OCSP_HELPER; static const long X509_PURPOSE_TIMESTAMP_SIGN; -static const long X509_PURPOSE_MIN; -static const long X509_PURPOSE_MAX; """ FUNCTIONS = """ @@ -175,16 +170,7 @@ Cryptography_STACK_OF_X509_OBJECT *X509_STORE_get0_objects(X509_STORE *); X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *); -void X509_STORE_set_get_issuer(X509_STORE *, X509_STORE_CTX_get_issuer_fn); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_LIBRESSL -static const long Cryptography_HAS_X509_STORE_CTX_GET_ISSUER = 0; -typedef void *X509_STORE_CTX_get_issuer_fn; -void (*X509_STORE_set_get_issuer)(X509_STORE *, - X509_STORE_CTX_get_issuer_fn) = NULL; -#else -static const long Cryptography_HAS_X509_STORE_CTX_GET_ISSUER = 1; -#endif """ diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index dae98da1bf4e..7f04a2cbce35 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -30,7 +30,7 @@ static const int GEN_DNS; static const int GEN_URI; -typedef struct stack_st_GENERAL_NAME GENERAL_NAMES; +typedef ... GENERAL_NAMES; /* Only include the one union element used by pyOpenSSL. */ typedef struct { @@ -41,6 +41,8 @@ } d; ...; } GENERAL_NAME; + +static const long X509V3_EXT_ERROR_UNKNOWN; """ @@ -54,8 +56,8 @@ void X509V3_set_ctx_nodb(X509V3_CTX *); -int sk_GENERAL_NAME_num(struct stack_st_GENERAL_NAME *); -GENERAL_NAME *sk_GENERAL_NAME_value(struct stack_st_GENERAL_NAME *, int); +int sk_GENERAL_NAME_num(GENERAL_NAMES *); +GENERAL_NAME *sk_GENERAL_NAME_value(GENERAL_NAMES *, int); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/utils.py b/src/_cffi_src/utils.py index b5fba37091d9..e942eddd4630 100644 --- a/src/_cffi_src/utils.py +++ b/src/_cffi_src/utils.py @@ -7,13 +7,12 @@ import os import platform import sys -import typing from cffi import FFI # Load the cryptography __about__ to get the current package version base_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -about = {} +about: dict = {} with open(os.path.join(base_src, "cryptography", "__about__.py")) as f: exec(f.read(), about) @@ -21,7 +20,7 @@ def build_ffi_for_binding( module_name: str, module_prefix: str, - modules: typing.List[str], + modules: list[str], ): """ Modules listed in ``modules`` should have the following attributes: diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py index b66e23c606ba..5b1b9783549c 100644 --- a/src/cryptography/__about__.py +++ b/src/cryptography/__about__.py @@ -10,8 +10,8 @@ "__copyright__", ] -__version__ = "41.0.0" +__version__ = "42.0.2" __author__ = "The Python Cryptographic Authority and individual contributors" -__copyright__ = f"Copyright 2013-2023 {__author__}" +__copyright__ = f"Copyright 2013-2024 {__author__}" diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index 47fdd18eeeb2..fe125ea9a763 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -15,9 +15,7 @@ class UnsupportedAlgorithm(Exception): - def __init__( - self, message: str, reason: typing.Optional[_Reasons] = None - ) -> None: + def __init__(self, message: str, reason: _Reasons | None = None) -> None: super().__init__(message) self._reason = reason @@ -44,7 +42,7 @@ class InvalidSignature(Exception): class InternalError(Exception): def __init__( - self, msg: str, err_code: typing.List[rust_openssl.OpenSSLError] + self, msg: str, err_code: list[rust_openssl.OpenSSLError] ) -> None: super().__init__(msg) self.err_code = err_code diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py index ad8fb40b9d44..35ce1131a921 100644 --- a/src/cryptography/fernet.py +++ b/src/cryptography/fernet.py @@ -27,7 +27,7 @@ class InvalidToken(Exception): class Fernet: def __init__( self, - key: typing.Union[bytes, str], + key: bytes | str, backend: typing.Any = None, ) -> None: try: @@ -80,9 +80,7 @@ def _encrypt_from_parts( hmac = h.finalize() return base64.urlsafe_b64encode(basic_parts + hmac) - def decrypt( - self, token: typing.Union[bytes, str], ttl: typing.Optional[int] = None - ) -> bytes: + def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes: timestamp, data = Fernet._get_unverified_token_data(token) if ttl is None: time_info = None @@ -91,7 +89,7 @@ def decrypt( return self._decrypt_data(data, timestamp, time_info) def decrypt_at_time( - self, token: typing.Union[bytes, str], ttl: int, current_time: int + self, token: bytes | str, ttl: int, current_time: int ) -> bytes: if ttl is None: raise ValueError( @@ -100,16 +98,14 @@ def decrypt_at_time( timestamp, data = Fernet._get_unverified_token_data(token) return self._decrypt_data(data, timestamp, (ttl, current_time)) - def extract_timestamp(self, token: typing.Union[bytes, str]) -> int: + def extract_timestamp(self, token: bytes | str) -> int: timestamp, data = Fernet._get_unverified_token_data(token) # Verify the token was not tampered with. self._verify_signature(data) return timestamp @staticmethod - def _get_unverified_token_data( - token: typing.Union[bytes, str] - ) -> typing.Tuple[int, bytes]: + def _get_unverified_token_data(token: bytes | str) -> tuple[int, bytes]: if not isinstance(token, (str, bytes)): raise TypeError("token must be bytes or str") @@ -139,7 +135,7 @@ def _decrypt_data( self, data: bytes, timestamp: int, - time_info: typing.Optional[typing.Tuple[int, int]], + time_info: tuple[int, int] | None, ) -> bytes: if time_info is not None: ttl, current_time = time_info @@ -186,7 +182,7 @@ def encrypt(self, msg: bytes) -> bytes: def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes: return self._fernets[0].encrypt_at_time(msg, current_time) - def rotate(self, msg: typing.Union[bytes, str]) -> bytes: + def rotate(self, msg: bytes | str) -> bytes: timestamp, data = Fernet._get_unverified_token_data(msg) for f in self._fernets: try: @@ -200,9 +196,7 @@ def rotate(self, msg: typing.Union[bytes, str]) -> bytes: iv = os.urandom(16) return self._fernets[0]._encrypt_from_parts(p, timestamp, iv) - def decrypt( - self, msg: typing.Union[bytes, str], ttl: typing.Optional[int] = None - ) -> bytes: + def decrypt(self, msg: bytes | str, ttl: int | None = None) -> bytes: for f in self._fernets: try: return f.decrypt(msg, ttl) @@ -211,7 +205,7 @@ def decrypt( raise InvalidToken def decrypt_at_time( - self, msg: typing.Union[bytes, str], ttl: int, current_time: int + self, msg: bytes | str, ttl: int, current_time: int ) -> bytes: for f in self._fernets: try: diff --git a/src/cryptography/hazmat/_oid.py b/src/cryptography/hazmat/_oid.py index 01d4b3406062..c5d062c1374a 100644 --- a/src/cryptography/hazmat/_oid.py +++ b/src/cryptography/hazmat/_oid.py @@ -4,8 +4,6 @@ from __future__ import annotations -import typing - from cryptography.hazmat.bindings._rust import ( ObjectIdentifier as ObjectIdentifier, ) @@ -60,6 +58,7 @@ class NameOID: LOCALITY_NAME = ObjectIdentifier("2.5.4.7") STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") STREET_ADDRESS = ObjectIdentifier("2.5.4.9") + ORGANIZATION_IDENTIFIER = ObjectIdentifier("2.5.4.97") ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") @@ -123,9 +122,7 @@ class SignatureAlgorithmOID: GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") -_SIG_OIDS_TO_HASH: typing.Dict[ - ObjectIdentifier, typing.Optional[hashes.HashAlgorithm] -] = { +_SIG_OIDS_TO_HASH: dict[ObjectIdentifier, hashes.HashAlgorithm | None] = { SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), @@ -282,7 +279,7 @@ class AttributeOID: ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", ExtensionOID.FRESHEST_CRL: "freshestCRL", ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", - ExtensionOID.ISSUING_DISTRIBUTION_POINT: ("issuingDistributionPoint"), + ExtensionOID.ISSUING_DISTRIBUTION_POINT: "issuingDistributionPoint", ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", diff --git a/src/cryptography/hazmat/backends/openssl/aead.py b/src/cryptography/hazmat/backends/openssl/aead.py index b36f535f3f8f..f1d990106474 100644 --- a/src/cryptography/hazmat/backends/openssl/aead.py +++ b/src/cryptography/hazmat/backends/openssl/aead.py @@ -13,213 +13,41 @@ from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, - AESOCB3, - AESSIV, - ChaCha20Poly1305, ) - _AEADTypes = typing.Union[ - AESCCM, AESGCM, AESOCB3, AESSIV, ChaCha20Poly1305 - ] - - -def _is_evp_aead_supported_cipher( - backend: Backend, cipher: _AEADTypes -) -> bool: - """ - Checks whether the given cipher is supported through - EVP_AEAD rather than the normal OpenSSL EVP_CIPHER API. - """ - from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 - - return backend._lib.Cryptography_HAS_EVP_AEAD and isinstance( - cipher, ChaCha20Poly1305 - ) + _AEADTypes = typing.Union[AESCCM, AESGCM] def _aead_cipher_supported(backend: Backend, cipher: _AEADTypes) -> bool: - if _is_evp_aead_supported_cipher(backend, cipher): - return True - else: - cipher_name = _evp_cipher_cipher_name(cipher) - if backend._fips_enabled and cipher_name not in backend._fips_aead: - return False - # SIV isn't loaded through get_cipherbyname but instead a new fetch API - # only available in 3.0+. But if we know we're on 3.0+ then we know - # it's supported. - if cipher_name.endswith(b"-siv"): - return backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER == 1 - else: - return ( - backend._lib.EVP_get_cipherbyname(cipher_name) - != backend._ffi.NULL - ) - - -def _aead_create_ctx( - backend: Backend, - cipher: _AEADTypes, - key: bytes, -): - if _is_evp_aead_supported_cipher(backend, cipher): - return _evp_aead_create_ctx(backend, cipher, key) - else: - return _evp_cipher_create_ctx(backend, cipher, key) - + cipher_name = _evp_cipher_cipher_name(cipher) -def _encrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any = None, -) -> bytes: - if _is_evp_aead_supported_cipher(backend, cipher): - return _evp_aead_encrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) - else: - return _evp_cipher_encrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) + return backend._lib.EVP_get_cipherbyname(cipher_name) != backend._ffi.NULL -def _decrypt( +def _encrypt( backend: Backend, cipher: _AEADTypes, nonce: bytes, data: bytes, - associated_data: typing.List[bytes], + associated_data: list[bytes], tag_length: int, - ctx: typing.Any = None, ) -> bytes: - if _is_evp_aead_supported_cipher(backend, cipher): - return _evp_aead_decrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) - else: - return _evp_cipher_decrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) - - -def _evp_aead_create_ctx( - backend: Backend, - cipher: _AEADTypes, - key: bytes, - tag_len: typing.Optional[int] = None, -): - aead_cipher = _evp_aead_get_cipher(backend, cipher) - assert aead_cipher is not None - key_ptr = backend._ffi.from_buffer(key) - tag_len = ( - backend._lib.EVP_AEAD_DEFAULT_TAG_LENGTH - if tag_len is None - else tag_len - ) - ctx = backend._lib.Cryptography_EVP_AEAD_CTX_new( - aead_cipher, key_ptr, len(key), tag_len - ) - backend.openssl_assert(ctx != backend._ffi.NULL) - ctx = backend._ffi.gc(ctx, backend._lib.EVP_AEAD_CTX_free) - return ctx - - -def _evp_aead_get_cipher(backend: Backend, cipher: _AEADTypes): - from cryptography.hazmat.primitives.ciphers.aead import ( - ChaCha20Poly1305, + return _evp_cipher_encrypt( + backend, cipher, nonce, data, associated_data, tag_length ) - # Currently only ChaCha20-Poly1305 is supported using this API - assert isinstance(cipher, ChaCha20Poly1305) - return backend._lib.EVP_aead_chacha20_poly1305() - -def _evp_aead_encrypt( +def _decrypt( backend: Backend, cipher: _AEADTypes, nonce: bytes, data: bytes, - associated_data: typing.List[bytes], + associated_data: list[bytes], tag_length: int, - ctx: typing.Any, ) -> bytes: - assert ctx is not None - - aead_cipher = _evp_aead_get_cipher(backend, cipher) - assert aead_cipher is not None - - out_len = backend._ffi.new("size_t *") - # max_out_len should be in_len plus the result of - # EVP_AEAD_max_overhead. - max_out_len = len(data) + backend._lib.EVP_AEAD_max_overhead(aead_cipher) - out_buf = backend._ffi.new("uint8_t[]", max_out_len) - data_ptr = backend._ffi.from_buffer(data) - nonce_ptr = backend._ffi.from_buffer(nonce) - aad = b"".join(associated_data) - aad_ptr = backend._ffi.from_buffer(aad) - - res = backend._lib.EVP_AEAD_CTX_seal( - ctx, - out_buf, - out_len, - max_out_len, - nonce_ptr, - len(nonce), - data_ptr, - len(data), - aad_ptr, - len(aad), + return _evp_cipher_decrypt( + backend, cipher, nonce, data, associated_data, tag_length ) - backend.openssl_assert(res == 1) - encrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] - return encrypted_data - - -def _evp_aead_decrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any, -) -> bytes: - if len(data) < tag_length: - raise InvalidTag - - assert ctx is not None - - out_len = backend._ffi.new("size_t *") - # max_out_len should at least in_len - max_out_len = len(data) - out_buf = backend._ffi.new("uint8_t[]", max_out_len) - data_ptr = backend._ffi.from_buffer(data) - nonce_ptr = backend._ffi.from_buffer(nonce) - aad = b"".join(associated_data) - aad_ptr = backend._ffi.from_buffer(aad) - - res = backend._lib.EVP_AEAD_CTX_open( - ctx, - out_buf, - out_len, - max_out_len, - nonce_ptr, - len(nonce), - data_ptr, - len(data), - aad_ptr, - len(aad), - ) - - if res == 0: - backend._consume_errors() - raise InvalidTag - - decrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] - return decrypted_data _ENCRYPT = 1 @@ -230,69 +58,27 @@ def _evp_cipher_cipher_name(cipher: _AEADTypes) -> bytes: from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, - AESOCB3, - AESSIV, - ChaCha20Poly1305, ) - if isinstance(cipher, ChaCha20Poly1305): - return b"chacha20-poly1305" - elif isinstance(cipher, AESCCM): + if isinstance(cipher, AESCCM): return f"aes-{len(cipher._key) * 8}-ccm".encode("ascii") - elif isinstance(cipher, AESOCB3): - return f"aes-{len(cipher._key) * 8}-ocb".encode("ascii") - elif isinstance(cipher, AESSIV): - return f"aes-{len(cipher._key) * 8 // 2}-siv".encode("ascii") else: assert isinstance(cipher, AESGCM) return f"aes-{len(cipher._key) * 8}-gcm".encode("ascii") def _evp_cipher(cipher_name: bytes, backend: Backend): - if cipher_name.endswith(b"-siv"): - evp_cipher = backend._lib.EVP_CIPHER_fetch( - backend._ffi.NULL, - cipher_name, - backend._ffi.NULL, - ) - backend.openssl_assert(evp_cipher != backend._ffi.NULL) - evp_cipher = backend._ffi.gc(evp_cipher, backend._lib.EVP_CIPHER_free) - else: - evp_cipher = backend._lib.EVP_get_cipherbyname(cipher_name) - backend.openssl_assert(evp_cipher != backend._ffi.NULL) - + evp_cipher = backend._lib.EVP_get_cipherbyname(cipher_name) + backend.openssl_assert(evp_cipher != backend._ffi.NULL) return evp_cipher -def _evp_cipher_create_ctx( - backend: Backend, - cipher: _AEADTypes, - key: bytes, -): - ctx = backend._lib.EVP_CIPHER_CTX_new() - backend.openssl_assert(ctx != backend._ffi.NULL) - ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) - cipher_name = _evp_cipher_cipher_name(cipher) - evp_cipher = _evp_cipher(cipher_name, backend) - key_ptr = backend._ffi.from_buffer(key) - res = backend._lib.EVP_CipherInit_ex( - ctx, - evp_cipher, - backend._ffi.NULL, - key_ptr, - backend._ffi.NULL, - 0, - ) - backend.openssl_assert(res != 0) - return ctx - - def _evp_cipher_aead_setup( backend: Backend, cipher_name: bytes, key: bytes, nonce: bytes, - tag: typing.Optional[bytes], + tag: bytes | None, tag_len: int, operation: int, ): @@ -350,21 +136,6 @@ def _evp_cipher_set_tag(backend, ctx, tag: bytes) -> None: backend.openssl_assert(res != 0) -def _evp_cipher_set_nonce_operation( - backend, ctx, nonce: bytes, operation: int -) -> None: - nonce_ptr = backend._ffi.from_buffer(nonce) - res = backend._lib.EVP_CipherInit_ex( - ctx, - backend._ffi.NULL, - backend._ffi.NULL, - backend._ffi.NULL, - nonce_ptr, - int(operation == _ENCRYPT), - ) - backend.openssl_assert(res != 0) - - def _evp_cipher_set_length(backend: Backend, ctx, data_len: int) -> None: intptr = backend._ffi.new("int *") res = backend._lib.EVP_CipherUpdate( @@ -389,10 +160,7 @@ def _evp_cipher_process_data(backend: Backend, ctx, data: bytes) -> bytes: buf = backend._ffi.new("unsigned char[]", len(data)) data_ptr = backend._ffi.from_buffer(data) res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data_ptr, len(data)) - if res == 0: - # AES SIV can error here if the data is invalid on decrypt - backend._consume_errors() - raise InvalidTag + backend.openssl_assert(res != 0) return backend._ffi.buffer(buf, outlen[0])[:] @@ -401,25 +169,21 @@ def _evp_cipher_encrypt( cipher: _AEADTypes, nonce: bytes, data: bytes, - associated_data: typing.List[bytes], + associated_data: list[bytes], tag_length: int, - ctx: typing.Any = None, ) -> bytes: - from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV - - if ctx is None: - cipher_name = _evp_cipher_cipher_name(cipher) - ctx = _evp_cipher_aead_setup( - backend, - cipher_name, - cipher._key, - nonce, - None, - tag_length, - _ENCRYPT, - ) - else: - _evp_cipher_set_nonce_operation(backend, ctx, nonce, _ENCRYPT) + from cryptography.hazmat.primitives.ciphers.aead import AESCCM + + cipher_name = _evp_cipher_cipher_name(cipher) + ctx = _evp_cipher_aead_setup( + backend, + cipher_name, + cipher._key, + nonce, + None, + tag_length, + _ENCRYPT, + ) # CCM requires us to pass the length of the data before processing # anything. @@ -445,14 +209,7 @@ def _evp_cipher_encrypt( backend.openssl_assert(res != 0) tag = backend._ffi.buffer(tag_buf)[:] - if isinstance(cipher, AESSIV): - # RFC 5297 defines the output as IV || C, where the tag we generate - # is the "IV" and C is the ciphertext. This is the opposite of our - # other AEADs, which are Ciphertext || Tag - backend.openssl_assert(len(tag) == 16) - return tag + processed_data - else: - return processed_data + tag + return processed_data + tag def _evp_cipher_decrypt( @@ -460,38 +217,26 @@ def _evp_cipher_decrypt( cipher: _AEADTypes, nonce: bytes, data: bytes, - associated_data: typing.List[bytes], + associated_data: list[bytes], tag_length: int, - ctx: typing.Any = None, ) -> bytes: - from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV + from cryptography.hazmat.primitives.ciphers.aead import AESCCM if len(data) < tag_length: raise InvalidTag - if isinstance(cipher, AESSIV): - # RFC 5297 defines the output as IV || C, where the tag we generate - # is the "IV" and C is the ciphertext. This is the opposite of our - # other AEADs, which are Ciphertext || Tag - tag = data[:tag_length] - data = data[tag_length:] - else: - tag = data[-tag_length:] - data = data[:-tag_length] - if ctx is None: - cipher_name = _evp_cipher_cipher_name(cipher) - ctx = _evp_cipher_aead_setup( - backend, - cipher_name, - cipher._key, - nonce, - tag, - tag_length, - _DECRYPT, - ) - else: - _evp_cipher_set_nonce_operation(backend, ctx, nonce, _DECRYPT) - _evp_cipher_set_tag(backend, ctx, tag) + tag = data[-tag_length:] + data = data[:-tag_length] + cipher_name = _evp_cipher_cipher_name(cipher) + ctx = _evp_cipher_aead_setup( + backend, + cipher_name, + cipher._key, + nonce, + tag, + tag_length, + _DECRYPT, + ) # CCM requires us to pass the length of the data before processing # anything. diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 02d51094cfe5..5d9eb2768dfb 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -8,35 +8,17 @@ import contextlib import itertools import typing -from contextlib import contextmanager from cryptography import utils, x509 -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.openssl import aead from cryptography.hazmat.backends.openssl.ciphers import _CipherContext -from cryptography.hazmat.backends.openssl.cmac import _CMACContext -from cryptography.hazmat.backends.openssl.ec import ( - _EllipticCurvePrivateKey, - _EllipticCurvePublicKey, -) -from cryptography.hazmat.backends.openssl.rsa import ( - _RSAPrivateKey, - _RSAPublicKey, -) from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl import binding from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding -from cryptography.hazmat.primitives.asymmetric import ( - dh, - dsa, - ec, - ed448, - ed25519, - rsa, - x448, - x25519, -) +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.asymmetric.padding import ( MGF1, OAEP, @@ -45,10 +27,8 @@ ) from cryptography.hazmat.primitives.asymmetric.types import ( PrivateKeyTypes, - PublicKeyTypes, ) from cryptography.hazmat.primitives.ciphers import ( - BlockCipherAlgorithm, CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.algorithms import ( @@ -76,7 +56,6 @@ XTS, Mode, ) -from cryptography.hazmat.primitives.serialization import ssh from cryptography.hazmat.primitives.serialization.pkcs12 import ( PBES, PKCS12Certificate, @@ -104,7 +83,7 @@ class Backend: # disallowed algorithms are still present in OpenSSL. They just error if # you try to use them. To avoid that we allowlist the algorithms in # FIPS 140-3. This isn't ideal, but FIPS 140-3 is trash so here we are. - _fips_aead = { + _fips_aead: typing.ClassVar[set[bytes]] = { b"aes-128-ccm", b"aes-192-ccm", b"aes-256-ccm", @@ -149,14 +128,11 @@ def __init__(self) -> None: self._lib = self._binding.lib self._fips_enabled = rust_openssl.is_fips_enabled() - self._cipher_registry: typing.Dict[ - typing.Tuple[typing.Type[CipherAlgorithm], typing.Type[Mode]], + self._cipher_registry: dict[ + tuple[type[CipherAlgorithm], type[Mode]], typing.Callable, ] = {} self._register_default_ciphers() - self._dh_types = [self._lib.EVP_PKEY_DH] - if self._lib.Cryptography_HAS_EVP_PKEY_DHX: - self._dh_types.append(self._lib.EVP_PKEY_DHX) def __repr__(self) -> str: return "".format( @@ -168,9 +144,9 @@ def __repr__(self) -> str: def openssl_assert( self, ok: bool, - errors: typing.Optional[typing.List[rust_openssl.OpenSSLError]] = None, + errors: list[rust_openssl.OpenSSLError] | None = None, ) -> None: - return binding._openssl_assert(self._lib, ok, errors=errors) + return binding._openssl_assert(ok, errors=errors) def _enable_fips(self) -> None: # This function enables FIPS mode for OpenSSL 3.0.0 on installs that @@ -194,10 +170,10 @@ def openssl_version_number(self) -> int: return self._lib.OpenSSL_version_num() def _evp_md_from_algorithm(self, algorithm: hashes.HashAlgorithm): - if algorithm.name == "blake2b" or algorithm.name == "blake2s": - alg = "{}{}".format( - algorithm.name, algorithm.digest_size * 8 - ).encode("ascii") + if algorithm.name in ("blake2b", "blake2s"): + alg = f"{algorithm.name}{algorithm.digest_size * 8}".encode( + "ascii" + ) else: alg = algorithm.name.encode("ascii") @@ -255,9 +231,7 @@ def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: def register_cipher_adapter(self, cipher_cls, mode_cls, adapter) -> None: if (cipher_cls, mode_cls) in self._cipher_registry: raise ValueError( - "Duplicate registration for: {} {}.".format( - cipher_cls, mode_cls - ) + f"Duplicate registration for: {cipher_cls} {mode_cls}." ) self._cipher_registry[cipher_cls, mode_cls] = adapter @@ -284,11 +258,17 @@ def _register_default_ciphers(self) -> None: self.register_cipher_adapter( TripleDES, ECB, GetCipherByName("des-ede3") ) + # ChaCha20 uses the Long Name "chacha20" in OpenSSL, but in LibreSSL + # it uses "chacha" self.register_cipher_adapter( - ChaCha20, type(None), GetCipherByName("chacha20") + ChaCha20, + type(None), + GetCipherByName( + "chacha" if self._lib.CRYPTOGRAPHY_IS_LIBRESSL else "chacha20" + ), ) self.register_cipher_adapter(AES, XTS, _get_xts_cipher) - for mode_cls in [ECB, CBC, OFB, CFB, CTR]: + for mode_cls in [ECB, CBC, OFB, CFB, CTR, GCM]: self.register_cipher_adapter( SM4, mode_cls, GetCipherByName("sm4-{mode.name}") ) @@ -342,56 +322,9 @@ def create_symmetric_decryption_ctx( def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: return self.hmac_supported(algorithm) - def _consume_errors(self) -> typing.List[rust_openssl.OpenSSLError]: + def _consume_errors(self) -> list[rust_openssl.OpenSSLError]: return rust_openssl.capture_error_stack() - def _bn_to_int(self, bn) -> int: - assert bn != self._ffi.NULL - self.openssl_assert(not self._lib.BN_is_negative(bn)) - - bn_num_bytes = self._lib.BN_num_bytes(bn) - bin_ptr = self._ffi.new("unsigned char[]", bn_num_bytes) - bin_len = self._lib.BN_bn2bin(bn, bin_ptr) - # A zero length means the BN has value 0 - self.openssl_assert(bin_len >= 0) - val = int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") - return val - - def _int_to_bn(self, num: int): - """ - Converts a python integer to a BIGNUM. The returned BIGNUM will not - be garbage collected (to support adding them to structs that take - ownership of the object). Be sure to register it for GC if it will - be discarded after use. - """ - binary = num.to_bytes(int(num.bit_length() / 8.0 + 1), "big") - bn_ptr = self._lib.BN_bin2bn(binary, len(binary), self._ffi.NULL) - self.openssl_assert(bn_ptr != self._ffi.NULL) - return bn_ptr - - def generate_rsa_private_key( - self, public_exponent: int, key_size: int - ) -> rsa.RSAPrivateKey: - rsa._verify_rsa_parameters(public_exponent, key_size) - - rsa_cdata = self._lib.RSA_new() - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - - bn = self._int_to_bn(public_exponent) - bn = self._ffi.gc(bn, self._lib.BN_free) - - res = self._lib.RSA_generate_key_ex( - rsa_cdata, key_size, bn, self._ffi.NULL - ) - self.openssl_assert(res == 1) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - - # We can skip RSA key validation here since we just generated the key - return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, unsafe_skip_rsa_key_validation=True - ) - def generate_rsa_parameters_supported( self, public_exponent: int, key_size: int ) -> bool: @@ -401,74 +334,6 @@ def generate_rsa_parameters_supported( and key_size >= 512 ) - def load_rsa_private_numbers( - self, - numbers: rsa.RSAPrivateNumbers, - unsafe_skip_rsa_key_validation: bool, - ) -> rsa.RSAPrivateKey: - rsa._check_private_key_components( - numbers.p, - numbers.q, - numbers.d, - numbers.dmp1, - numbers.dmq1, - numbers.iqmp, - numbers.public_numbers.e, - numbers.public_numbers.n, - ) - rsa_cdata = self._lib.RSA_new() - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - p = self._int_to_bn(numbers.p) - q = self._int_to_bn(numbers.q) - d = self._int_to_bn(numbers.d) - dmp1 = self._int_to_bn(numbers.dmp1) - dmq1 = self._int_to_bn(numbers.dmq1) - iqmp = self._int_to_bn(numbers.iqmp) - e = self._int_to_bn(numbers.public_numbers.e) - n = self._int_to_bn(numbers.public_numbers.n) - res = self._lib.RSA_set0_factors(rsa_cdata, p, q) - self.openssl_assert(res == 1) - res = self._lib.RSA_set0_key(rsa_cdata, n, e, d) - self.openssl_assert(res == 1) - res = self._lib.RSA_set0_crt_params(rsa_cdata, dmp1, dmq1, iqmp) - self.openssl_assert(res == 1) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - - return _RSAPrivateKey( - self, - rsa_cdata, - evp_pkey, - unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, - ) - - def load_rsa_public_numbers( - self, numbers: rsa.RSAPublicNumbers - ) -> rsa.RSAPublicKey: - rsa._check_public_key_components(numbers.e, numbers.n) - rsa_cdata = self._lib.RSA_new() - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - e = self._int_to_bn(numbers.e) - n = self._int_to_bn(numbers.n) - res = self._lib.RSA_set0_key(rsa_cdata, n, e, self._ffi.NULL) - self.openssl_assert(res == 1) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - - def _create_evp_pkey_gc(self): - evp_pkey = self._lib.EVP_PKEY_new() - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return evp_pkey - - def _rsa_cdata_to_evp_pkey(self, rsa_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_RSA(evp_pkey, rsa_cdata) - self.openssl_assert(res == 1) - return evp_pkey - def _bytes_to_bio(self, data: bytes) -> _MemoryBIO: """ Return a _MemoryBIO namedtuple of (BIO, char*). @@ -504,145 +369,6 @@ def _read_mem_bio(self, bio) -> bytes: bio_data = self._ffi.buffer(buf[0], buf_len)[:] return bio_data - def _evp_pkey_to_private_key( - self, evp_pkey, unsafe_skip_rsa_key_validation: bool - ) -> PrivateKeyTypes: - """ - Return the appropriate type of PrivateKey given an evp_pkey cdata - pointer. - """ - - key_type = self._lib.EVP_PKEY_id(evp_pkey) - - if key_type == self._lib.EVP_PKEY_RSA: - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - return _RSAPrivateKey( - self, - rsa_cdata, - evp_pkey, - unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, - ) - elif ( - key_type == self._lib.EVP_PKEY_RSA_PSS - and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E - ): - # At the moment the way we handle RSA PSS keys is to strip the - # PSS constraints from them and treat them as normal RSA keys - # Unfortunately the RSA * itself tracks this data so we need to - # extract, serialize, and reload it without the constraints. - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - bio = self._create_mem_bio_gc() - res = self._lib.i2d_RSAPrivateKey_bio(bio, rsa_cdata) - self.openssl_assert(res == 1) - return self.load_der_private_key( - self._read_mem_bio(bio), - password=None, - unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, - ) - elif key_type == self._lib.EVP_PKEY_DSA: - return rust_openssl.dsa.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_EC: - ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) - self.openssl_assert(ec_cdata != self._ffi.NULL) - ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - elif key_type in self._dh_types: - return rust_openssl.dh.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed25519.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.x448.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_X25519: - return rust_openssl.x25519.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed448.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - else: - raise UnsupportedAlgorithm("Unsupported key type.") - - def _evp_pkey_to_public_key(self, evp_pkey) -> PublicKeyTypes: - """ - Return the appropriate type of PublicKey given an evp_pkey cdata - pointer. - """ - - key_type = self._lib.EVP_PKEY_id(evp_pkey) - - if key_type == self._lib.EVP_PKEY_RSA: - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - elif ( - key_type == self._lib.EVP_PKEY_RSA_PSS - and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E - ): - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - bio = self._create_mem_bio_gc() - res = self._lib.i2d_RSAPublicKey_bio(bio, rsa_cdata) - self.openssl_assert(res == 1) - return self.load_der_public_key(self._read_mem_bio(bio)) - elif key_type == self._lib.EVP_PKEY_DSA: - return rust_openssl.dsa.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_EC: - ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) - if ec_cdata == self._ffi.NULL: - errors = self._consume_errors() - raise ValueError("Unable to load EC key", errors) - ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) - return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - elif key_type in self._dh_types: - return rust_openssl.dh.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed25519.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.x448.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_X25519: - return rust_openssl.x25519.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed448.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - else: - raise UnsupportedAlgorithm("Unsupported key type.") - def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and isinstance(algorithm, hashes.SHA1): return False @@ -683,43 +409,6 @@ def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool: else: return self.rsa_padding_supported(padding) - def generate_dsa_parameters(self, key_size: int) -> dsa.DSAParameters: - if key_size not in (1024, 2048, 3072, 4096): - raise ValueError( - "Key size must be 1024, 2048, 3072, or 4096 bits." - ) - - return rust_openssl.dsa.generate_parameters(key_size) - - def generate_dsa_private_key( - self, parameters: dsa.DSAParameters - ) -> dsa.DSAPrivateKey: - return parameters.generate_private_key() - - def generate_dsa_private_key_and_parameters( - self, key_size: int - ) -> dsa.DSAPrivateKey: - parameters = self.generate_dsa_parameters(key_size) - return self.generate_dsa_private_key(parameters) - - def load_dsa_private_numbers( - self, numbers: dsa.DSAPrivateNumbers - ) -> dsa.DSAPrivateKey: - dsa._check_dsa_private_numbers(numbers) - return rust_openssl.dsa.from_private_numbers(numbers) - - def load_dsa_public_numbers( - self, numbers: dsa.DSAPublicNumbers - ) -> dsa.DSAPublicKey: - dsa._check_dsa_parameters(numbers.parameter_numbers) - return rust_openssl.dsa.from_public_numbers(numbers) - - def load_dsa_parameter_numbers( - self, numbers: dsa.DSAParameterNumbers - ) -> dsa.DSAParameters: - dsa._check_dsa_parameters(numbers) - return rust_openssl.dsa.from_parameter_numbers(numbers) - def dsa_supported(self) -> bool: return ( not self._lib.CRYPTOGRAPHY_IS_BORINGSSL and not self._fips_enabled @@ -735,133 +424,6 @@ def cmac_algorithm_supported(self, algorithm) -> bool: algorithm, CBC(b"\x00" * algorithm.block_size) ) - def create_cmac_ctx(self, algorithm: BlockCipherAlgorithm) -> _CMACContext: - return _CMACContext(self, algorithm) - - def load_pem_private_key( - self, - data: bytes, - password: typing.Optional[bytes], - unsafe_skip_rsa_key_validation: bool, - ) -> PrivateKeyTypes: - return self._load_key( - self._lib.PEM_read_bio_PrivateKey, - data, - password, - unsafe_skip_rsa_key_validation, - ) - - def load_pem_public_key(self, data: bytes) -> PublicKeyTypes: - mem_bio = self._bytes_to_bio(data) - # In OpenSSL 3.0.x the PEM_read_bio_PUBKEY function will invoke - # the default password callback if you pass an encrypted private - # key. This is very, very, very bad as the default callback can - # trigger an interactive console prompt, which will hang the - # Python process. We therefore provide our own callback to - # catch this and error out properly. - userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") - evp_pkey = self._lib.PEM_read_bio_PUBKEY( - mem_bio.bio, - self._ffi.NULL, - self._ffi.addressof( - self._lib._original_lib, "Cryptography_pem_password_cb" - ), - userdata, - ) - if evp_pkey != self._ffi.NULL: - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return self._evp_pkey_to_public_key(evp_pkey) - else: - # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still - # need to check to see if it is a pure PKCS1 RSA public key (not - # embedded in a subjectPublicKeyInfo) - self._consume_errors() - res = self._lib.BIO_reset(mem_bio.bio) - self.openssl_assert(res == 1) - rsa_cdata = self._lib.PEM_read_bio_RSAPublicKey( - mem_bio.bio, - self._ffi.NULL, - self._ffi.addressof( - self._lib._original_lib, "Cryptography_pem_password_cb" - ), - userdata, - ) - if rsa_cdata != self._ffi.NULL: - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - else: - self._handle_key_loading_error() - - def load_pem_parameters(self, data: bytes) -> dh.DHParameters: - return rust_openssl.dh.from_pem_parameters(data) - - def load_der_private_key( - self, - data: bytes, - password: typing.Optional[bytes], - unsafe_skip_rsa_key_validation: bool, - ) -> PrivateKeyTypes: - # OpenSSL has a function called d2i_AutoPrivateKey that in theory - # handles this automatically, however it doesn't handle encrypted - # private keys. Instead we try to load the key two different ways. - # First we'll try to load it as a traditional key. - bio_data = self._bytes_to_bio(data) - key = self._evp_pkey_from_der_traditional_key(bio_data, password) - if key: - return self._evp_pkey_to_private_key( - key, unsafe_skip_rsa_key_validation - ) - else: - # Finally we try to load it with the method that handles encrypted - # PKCS8 properly. - return self._load_key( - self._lib.d2i_PKCS8PrivateKey_bio, - data, - password, - unsafe_skip_rsa_key_validation, - ) - - def _evp_pkey_from_der_traditional_key(self, bio_data, password): - key = self._lib.d2i_PrivateKey_bio(bio_data.bio, self._ffi.NULL) - if key != self._ffi.NULL: - key = self._ffi.gc(key, self._lib.EVP_PKEY_free) - if password is not None: - raise TypeError( - "Password was given but private key is not encrypted." - ) - - return key - else: - self._consume_errors() - return None - - def load_der_public_key(self, data: bytes) -> PublicKeyTypes: - mem_bio = self._bytes_to_bio(data) - evp_pkey = self._lib.d2i_PUBKEY_bio(mem_bio.bio, self._ffi.NULL) - if evp_pkey != self._ffi.NULL: - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return self._evp_pkey_to_public_key(evp_pkey) - else: - # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still - # need to check to see if it is a pure PKCS1 RSA public key (not - # embedded in a subjectPublicKeyInfo) - self._consume_errors() - res = self._lib.BIO_reset(mem_bio.bio) - self.openssl_assert(res == 1) - rsa_cdata = self._lib.d2i_RSAPublicKey_bio( - mem_bio.bio, self._ffi.NULL - ) - if rsa_cdata != self._ffi.NULL: - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - else: - self._handle_key_loading_error() - - def load_der_parameters(self, data: bytes) -> dh.DHParameters: - return rust_openssl.dh.from_der_parameters(data) - def _cert2ossl(self, cert: x509.Certificate) -> typing.Any: data = cert.public_bytes(serialization.Encoding.DER) mem_bio = self._bytes_to_bio(data) @@ -891,61 +453,9 @@ def _key2ossl(self, key: PKCS12PrivateKeyTypes) -> typing.Any: self.openssl_assert(evp_pkey != self._ffi.NULL) return self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - def _load_key( - self, openssl_read_func, data, password, unsafe_skip_rsa_key_validation - ) -> PrivateKeyTypes: - mem_bio = self._bytes_to_bio(data) - - userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") - if password is not None: - utils._check_byteslike("password", password) - password_ptr = self._ffi.from_buffer(password) - userdata.password = password_ptr - userdata.length = len(password) - - evp_pkey = openssl_read_func( - mem_bio.bio, - self._ffi.NULL, - self._ffi.addressof( - self._lib._original_lib, "Cryptography_pem_password_cb" - ), - userdata, - ) - - if evp_pkey == self._ffi.NULL: - if userdata.error != 0: - self._consume_errors() - if userdata.error == -1: - raise TypeError( - "Password was not given but private key is encrypted" - ) - else: - assert userdata.error == -2 - raise ValueError( - "Passwords longer than {} bytes are not supported " - "by this backend.".format(userdata.maxsize - 1) - ) - else: - self._handle_key_loading_error() - - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - if password is not None and userdata.called == 0: - raise TypeError( - "Password was given but private key is not encrypted." - ) - - assert ( - password is not None and userdata.called == 1 - ) or password is None - - return self._evp_pkey_to_private_key( - evp_pkey, unsafe_skip_rsa_key_validation - ) - - def _handle_key_loading_error(self) -> typing.NoReturn: - errors = self._consume_errors() - + def _handle_key_loading_error( + self, errors: list[rust_openssl.OpenSSLError] + ) -> typing.NoReturn: if not errors: raise ValueError( "Could not deserialize key data. The data may be in an " @@ -990,20 +500,12 @@ def _handle_key_loading_error(self) -> typing.NoReturn: ) def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: - try: - curve_nid = self._elliptic_curve_to_nid(curve) - except UnsupportedAlgorithm: - curve_nid = self._lib.NID_undef - - group = self._lib.EC_GROUP_new_by_curve_name(curve_nid) - - if group == self._ffi.NULL: - self._consume_errors() + if self._fips_enabled and not isinstance( + curve, self._fips_ecdh_curves + ): return False - else: - self.openssl_assert(curve_nid != self._lib.NID_undef) - self._lib.EC_GROUP_free(group) - return True + + return rust_openssl.ec.curve_supported(curve) def elliptic_curve_signature_algorithm_supported( self, @@ -1014,515 +516,39 @@ def elliptic_curve_signature_algorithm_supported( if not isinstance(signature_algorithm, ec.ECDSA): return False - return self.elliptic_curve_supported(curve) - - def generate_elliptic_curve_private_key( - self, curve: ec.EllipticCurve - ) -> ec.EllipticCurvePrivateKey: - """ - Generate a new private key on the named curve. - """ - - if self.elliptic_curve_supported(curve): - ec_cdata = self._ec_key_new_by_curve(curve) - - res = self._lib.EC_KEY_generate_key(ec_cdata) - self.openssl_assert(res == 1) - - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - else: - raise UnsupportedAlgorithm( - f"Backend object does not support {curve.name}.", - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, - ) - - def load_elliptic_curve_private_numbers( - self, numbers: ec.EllipticCurvePrivateNumbers - ) -> ec.EllipticCurvePrivateKey: - public = numbers.public_numbers - - ec_cdata = self._ec_key_new_by_curve(public.curve) - - private_value = self._ffi.gc( - self._int_to_bn(numbers.private_value), self._lib.BN_clear_free + return self.elliptic_curve_supported(curve) and ( + isinstance(signature_algorithm.algorithm, asym_utils.Prehashed) + or self.hash_supported(signature_algorithm.algorithm) ) - res = self._lib.EC_KEY_set_private_key(ec_cdata, private_value) - if res != 1: - self._consume_errors() - raise ValueError("Invalid EC key.") - - with self._tmp_bn_ctx() as bn_ctx: - self._ec_key_set_public_key_affine_coordinates( - ec_cdata, public.x, public.y, bn_ctx - ) - # derive the expected public point and compare it to the one we - # just set based on the values we were given. If they don't match - # this isn't a valid key pair. - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - set_point = backend._lib.EC_KEY_get0_public_key(ec_cdata) - self.openssl_assert(set_point != self._ffi.NULL) - computed_point = self._lib.EC_POINT_new(group) - self.openssl_assert(computed_point != self._ffi.NULL) - computed_point = self._ffi.gc( - computed_point, self._lib.EC_POINT_free - ) - res = self._lib.EC_POINT_mul( - group, - computed_point, - private_value, - self._ffi.NULL, - self._ffi.NULL, - bn_ctx, - ) - self.openssl_assert(res == 1) - if ( - self._lib.EC_POINT_cmp( - group, set_point, computed_point, bn_ctx - ) - != 0 - ): - raise ValueError("Invalid EC key.") - - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - - def load_elliptic_curve_public_numbers( - self, numbers: ec.EllipticCurvePublicNumbers - ) -> ec.EllipticCurvePublicKey: - ec_cdata = self._ec_key_new_by_curve(numbers.curve) - with self._tmp_bn_ctx() as bn_ctx: - self._ec_key_set_public_key_affine_coordinates( - ec_cdata, numbers.x, numbers.y, bn_ctx - ) - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - - def load_elliptic_curve_public_bytes( - self, curve: ec.EllipticCurve, point_bytes: bytes - ) -> ec.EllipticCurvePublicKey: - ec_cdata = self._ec_key_new_by_curve(curve) - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - point = self._lib.EC_POINT_new(group) - self.openssl_assert(point != self._ffi.NULL) - point = self._ffi.gc(point, self._lib.EC_POINT_free) - with self._tmp_bn_ctx() as bn_ctx: - res = self._lib.EC_POINT_oct2point( - group, point, point_bytes, len(point_bytes), bn_ctx - ) - if res != 1: - self._consume_errors() - raise ValueError("Invalid public bytes for the given curve") - - res = self._lib.EC_KEY_set_public_key(ec_cdata, point) - self.openssl_assert(res == 1) - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - - def derive_elliptic_curve_private_key( - self, private_value: int, curve: ec.EllipticCurve - ) -> ec.EllipticCurvePrivateKey: - ec_cdata = self._ec_key_new_by_curve(curve) - - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - - point = self._lib.EC_POINT_new(group) - self.openssl_assert(point != self._ffi.NULL) - point = self._ffi.gc(point, self._lib.EC_POINT_free) - - value = self._int_to_bn(private_value) - value = self._ffi.gc(value, self._lib.BN_clear_free) - - with self._tmp_bn_ctx() as bn_ctx: - res = self._lib.EC_POINT_mul( - group, point, value, self._ffi.NULL, self._ffi.NULL, bn_ctx - ) - self.openssl_assert(res == 1) - - bn_x = self._lib.BN_CTX_get(bn_ctx) - bn_y = self._lib.BN_CTX_get(bn_ctx) - - res = self._lib.EC_POINT_get_affine_coordinates( - group, point, bn_x, bn_y, bn_ctx - ) - if res != 1: - self._consume_errors() - raise ValueError("Unable to derive key from private_value") - - res = self._lib.EC_KEY_set_public_key(ec_cdata, point) - self.openssl_assert(res == 1) - private = self._int_to_bn(private_value) - private = self._ffi.gc(private, self._lib.BN_clear_free) - res = self._lib.EC_KEY_set_private_key(ec_cdata, private) - self.openssl_assert(res == 1) - - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - - def _ec_key_new_by_curve(self, curve: ec.EllipticCurve): - curve_nid = self._elliptic_curve_to_nid(curve) - return self._ec_key_new_by_curve_nid(curve_nid) - - def _ec_key_new_by_curve_nid(self, curve_nid: int): - ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) - self.openssl_assert(ec_cdata != self._ffi.NULL) - return self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) def elliptic_curve_exchange_algorithm_supported( self, algorithm: ec.ECDH, curve: ec.EllipticCurve ) -> bool: - if self._fips_enabled and not isinstance( - curve, self._fips_ecdh_curves - ): - return False - return self.elliptic_curve_supported(curve) and isinstance( algorithm, ec.ECDH ) - def _ec_cdata_to_evp_pkey(self, ec_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_EC_KEY(evp_pkey, ec_cdata) - self.openssl_assert(res == 1) - return evp_pkey - - def _elliptic_curve_to_nid(self, curve: ec.EllipticCurve) -> int: - """ - Get the NID for a curve name. - """ - - curve_aliases = {"secp192r1": "prime192v1", "secp256r1": "prime256v1"} - - curve_name = curve_aliases.get(curve.name, curve.name) - - curve_nid = self._lib.OBJ_sn2nid(curve_name.encode()) - if curve_nid == self._lib.NID_undef: - raise UnsupportedAlgorithm( - f"{curve.name} is not a supported elliptic curve", - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, - ) - return curve_nid - - @contextmanager - def _tmp_bn_ctx(self): - bn_ctx = self._lib.BN_CTX_new() - self.openssl_assert(bn_ctx != self._ffi.NULL) - bn_ctx = self._ffi.gc(bn_ctx, self._lib.BN_CTX_free) - self._lib.BN_CTX_start(bn_ctx) - try: - yield bn_ctx - finally: - self._lib.BN_CTX_end(bn_ctx) - - def _ec_key_set_public_key_affine_coordinates( - self, - ec_cdata, - x: int, - y: int, - bn_ctx, - ) -> None: - """ - Sets the public key point in the EC_KEY context to the affine x and y - values. - """ - - if x < 0 or y < 0: - raise ValueError( - "Invalid EC key. Both x and y must be non-negative." - ) - - x = self._ffi.gc(self._int_to_bn(x), self._lib.BN_free) - y = self._ffi.gc(self._int_to_bn(y), self._lib.BN_free) - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - point = self._lib.EC_POINT_new(group) - self.openssl_assert(point != self._ffi.NULL) - point = self._ffi.gc(point, self._lib.EC_POINT_free) - res = self._lib.EC_POINT_set_affine_coordinates( - group, point, x, y, bn_ctx - ) - if res != 1: - self._consume_errors() - raise ValueError("Invalid EC key.") - res = self._lib.EC_KEY_set_public_key(ec_cdata, point) - self.openssl_assert(res == 1) - - def _private_key_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - key, - evp_pkey, - cdata, - ) -> bytes: - # validate argument types - if not isinstance(encoding, serialization.Encoding): - raise TypeError("encoding must be an item from the Encoding enum") - if not isinstance(format, serialization.PrivateFormat): - raise TypeError( - "format must be an item from the PrivateFormat enum" - ) - if not isinstance( - encryption_algorithm, serialization.KeySerializationEncryption - ): - raise TypeError( - "Encryption algorithm must be a KeySerializationEncryption " - "instance" - ) - - # validate password - if isinstance(encryption_algorithm, serialization.NoEncryption): - password = b"" - elif isinstance( - encryption_algorithm, serialization.BestAvailableEncryption - ): - password = encryption_algorithm.password - if len(password) > 1023: - raise ValueError( - "Passwords longer than 1023 bytes are not supported by " - "this backend" - ) - elif ( - isinstance( - encryption_algorithm, serialization._KeySerializationEncryption - ) - and encryption_algorithm._format - is format - is serialization.PrivateFormat.OpenSSH - ): - password = encryption_algorithm.password - else: - raise ValueError("Unsupported encryption type") - - # PKCS8 + PEM/DER - if format is serialization.PrivateFormat.PKCS8: - if encoding is serialization.Encoding.PEM: - write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey - elif encoding is serialization.Encoding.DER: - write_bio = self._lib.i2d_PKCS8PrivateKey_bio - else: - raise ValueError("Unsupported encoding for PKCS8") - return self._private_key_bytes_via_bio( - write_bio, evp_pkey, password - ) - - # TraditionalOpenSSL + PEM/DER - if format is serialization.PrivateFormat.TraditionalOpenSSL: - if self._fips_enabled and not isinstance( - encryption_algorithm, serialization.NoEncryption - ): - raise ValueError( - "Encrypted traditional OpenSSL format is not " - "supported in FIPS mode." - ) - key_type = self._lib.EVP_PKEY_id(evp_pkey) - - if encoding is serialization.Encoding.PEM: - if key_type == self._lib.EVP_PKEY_RSA: - write_bio = self._lib.PEM_write_bio_RSAPrivateKey - else: - assert key_type == self._lib.EVP_PKEY_EC - write_bio = self._lib.PEM_write_bio_ECPrivateKey - return self._private_key_bytes_via_bio( - write_bio, cdata, password - ) - - if encoding is serialization.Encoding.DER: - if password: - raise ValueError( - "Encryption is not supported for DER encoded " - "traditional OpenSSL keys" - ) - if key_type == self._lib.EVP_PKEY_RSA: - write_bio = self._lib.i2d_RSAPrivateKey_bio - else: - assert key_type == self._lib.EVP_PKEY_EC - write_bio = self._lib.i2d_ECPrivateKey_bio - return self._bio_func_output(write_bio, cdata) - - raise ValueError("Unsupported encoding for TraditionalOpenSSL") - - # OpenSSH + PEM - if format is serialization.PrivateFormat.OpenSSH: - if encoding is serialization.Encoding.PEM: - return ssh._serialize_ssh_private_key( - key, password, encryption_algorithm - ) - - raise ValueError( - "OpenSSH private key format can only be used" - " with PEM encoding" - ) - - # Anything that key-specific code was supposed to handle earlier, - # like Raw. - raise ValueError("format is invalid with this key") - - def _private_key_bytes_via_bio( - self, write_bio, evp_pkey, password - ) -> bytes: - if not password: - evp_cipher = self._ffi.NULL - else: - # This is a curated value that we will update over time. - evp_cipher = self._lib.EVP_get_cipherbyname(b"aes-256-cbc") - - return self._bio_func_output( - write_bio, - evp_pkey, - evp_cipher, - password, - len(password), - self._ffi.NULL, - self._ffi.NULL, - ) - - def _bio_func_output(self, write_bio, *args) -> bytes: - bio = self._create_mem_bio_gc() - res = write_bio(bio, *args) - self.openssl_assert(res == 1) - return self._read_mem_bio(bio) - - def _public_key_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - key, - evp_pkey, - cdata, - ) -> bytes: - if not isinstance(encoding, serialization.Encoding): - raise TypeError("encoding must be an item from the Encoding enum") - if not isinstance(format, serialization.PublicFormat): - raise TypeError( - "format must be an item from the PublicFormat enum" - ) - - # SubjectPublicKeyInfo + PEM/DER - if format is serialization.PublicFormat.SubjectPublicKeyInfo: - if encoding is serialization.Encoding.PEM: - write_bio = self._lib.PEM_write_bio_PUBKEY - elif encoding is serialization.Encoding.DER: - write_bio = self._lib.i2d_PUBKEY_bio - else: - raise ValueError( - "SubjectPublicKeyInfo works only with PEM or DER encoding" - ) - return self._bio_func_output(write_bio, evp_pkey) - - # PKCS1 + PEM/DER - if format is serialization.PublicFormat.PKCS1: - # Only RSA is supported here. - key_type = self._lib.EVP_PKEY_id(evp_pkey) - if key_type != self._lib.EVP_PKEY_RSA: - raise ValueError("PKCS1 format is supported only for RSA keys") - - if encoding is serialization.Encoding.PEM: - write_bio = self._lib.PEM_write_bio_RSAPublicKey - elif encoding is serialization.Encoding.DER: - write_bio = self._lib.i2d_RSAPublicKey_bio - else: - raise ValueError("PKCS1 works only with PEM or DER encoding") - return self._bio_func_output(write_bio, cdata) - - # OpenSSH + OpenSSH - if format is serialization.PublicFormat.OpenSSH: - if encoding is serialization.Encoding.OpenSSH: - return ssh.serialize_ssh_public_key(key) - - raise ValueError( - "OpenSSH format must be used with OpenSSH encoding" - ) - - # Anything that key-specific code was supposed to handle earlier, - # like Raw, CompressedPoint, UncompressedPoint - raise ValueError("format is invalid with this key") - def dh_supported(self) -> bool: return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - def generate_dh_parameters( - self, generator: int, key_size: int - ) -> dh.DHParameters: - return rust_openssl.dh.generate_parameters(generator, key_size) - - def generate_dh_private_key( - self, parameters: dh.DHParameters - ) -> dh.DHPrivateKey: - return parameters.generate_private_key() - - def generate_dh_private_key_and_parameters( - self, generator: int, key_size: int - ) -> dh.DHPrivateKey: - return self.generate_dh_private_key( - self.generate_dh_parameters(generator, key_size) - ) - - def load_dh_private_numbers( - self, numbers: dh.DHPrivateNumbers - ) -> dh.DHPrivateKey: - return rust_openssl.dh.from_private_numbers(numbers) - - def load_dh_public_numbers( - self, numbers: dh.DHPublicNumbers - ) -> dh.DHPublicKey: - return rust_openssl.dh.from_public_numbers(numbers) - - def load_dh_parameter_numbers( - self, numbers: dh.DHParameterNumbers - ) -> dh.DHParameters: - return rust_openssl.dh.from_parameter_numbers(numbers) - - def dh_parameters_supported( - self, p: int, g: int, q: typing.Optional[int] = None - ) -> bool: - try: - rust_openssl.dh.from_parameter_numbers( - dh.DHParameterNumbers(p=p, g=g, q=q) - ) - except ValueError: - return False - else: - return True - def dh_x942_serialization_supported(self) -> bool: return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 - def x25519_load_public_bytes(self, data: bytes) -> x25519.X25519PublicKey: - return rust_openssl.x25519.from_public_bytes(data) - - def x25519_load_private_bytes( - self, data: bytes - ) -> x25519.X25519PrivateKey: - return rust_openssl.x25519.from_private_bytes(data) - - def x25519_generate_key(self) -> x25519.X25519PrivateKey: - return rust_openssl.x25519.generate_key() - def x25519_supported(self) -> bool: - if self._fips_enabled: + # Beginning with OpenSSL 3.2.0, X25519 is considered FIPS. + if ( + self._fips_enabled + and not self._lib.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER + ): return False - return not self._lib.CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 - - def x448_load_public_bytes(self, data: bytes) -> x448.X448PublicKey: - return rust_openssl.x448.from_public_bytes(data) - - def x448_load_private_bytes(self, data: bytes) -> x448.X448PrivateKey: - return rust_openssl.x448.from_private_bytes(data) - - def x448_generate_key(self) -> x448.X448PrivateKey: - return rust_openssl.x448.generate_key() + return True def x448_supported(self) -> bool: - if self._fips_enabled: + # Beginning with OpenSSL 3.2.0, X448 is considered FIPS. + if ( + self._fips_enabled + and not self._lib.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER + ): return False return ( not self._lib.CRYPTOGRAPHY_IS_LIBRESSL @@ -1532,20 +558,7 @@ def x448_supported(self) -> bool: def ed25519_supported(self) -> bool: if self._fips_enabled: return False - return self._lib.CRYPTOGRAPHY_HAS_WORKING_ED25519 - - def ed25519_load_public_bytes( - self, data: bytes - ) -> ed25519.Ed25519PublicKey: - return rust_openssl.ed25519.from_public_bytes(data) - - def ed25519_load_private_bytes( - self, data: bytes - ) -> ed25519.Ed25519PrivateKey: - return rust_openssl.ed25519.from_private_bytes(data) - - def ed25519_generate_key(self) -> ed25519.Ed25519PrivateKey: - return rust_openssl.ed25519.generate_key() + return True def ed448_supported(self) -> bool: if self._fips_enabled: @@ -1555,15 +568,6 @@ def ed448_supported(self) -> bool: and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL ) - def ed448_load_public_bytes(self, data: bytes) -> ed448.Ed448PublicKey: - return rust_openssl.ed448.from_public_bytes(data) - - def ed448_load_private_bytes(self, data: bytes) -> ed448.Ed448PrivateKey: - return rust_openssl.ed448.from_private_bytes(data) - - def ed448_generate_key(self) -> ed448.Ed448PrivateKey: - return rust_openssl.ed448.generate_key() - def aead_cipher_supported(self, cipher) -> bool: return aead._aead_cipher_supported(self, cipher) @@ -1598,11 +602,11 @@ def _zeroed_null_terminated_buf(self, data): self._zero_data(self._ffi.cast("uint8_t *", buf), data_len) def load_key_and_certificates_from_pkcs12( - self, data: bytes, password: typing.Optional[bytes] - ) -> typing.Tuple[ - typing.Optional[PrivateKeyTypes], - typing.Optional[x509.Certificate], - typing.List[x509.Certificate], + self, data: bytes, password: bytes | None + ) -> tuple[ + PrivateKeyTypes | None, + x509.Certificate | None, + list[x509.Certificate], ]: pkcs12 = self.load_pkcs12(data, password) return ( @@ -1612,7 +616,7 @@ def load_key_and_certificates_from_pkcs12( ) def load_pkcs12( - self, data: bytes, password: typing.Optional[bytes] + self, data: bytes, password: bytes | None ) -> PKCS12KeyAndCertificates: if password is not None: utils._check_byteslike("password", password) @@ -1643,8 +647,9 @@ def load_pkcs12( evp_pkey = self._ffi.gc(evp_pkey_ptr[0], self._lib.EVP_PKEY_free) # We don't support turning off RSA key validation when loading # PKCS12 keys - key = self._evp_pkey_to_private_key( - evp_pkey, unsafe_skip_rsa_key_validation=False + key = rust_openssl.keys.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)), + unsafe_skip_rsa_key_validation=False, ) if x509_ptr[0] != self._ffi.NULL: @@ -1688,10 +693,10 @@ def load_pkcs12( def serialize_key_and_certificates_to_pkcs12( self, - name: typing.Optional[bytes], - key: typing.Optional[PKCS12PrivateKeyTypes], - cert: typing.Optional[x509.Certificate], - cas: typing.Optional[typing.List[_PKCS12CATypes]], + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: list[_PKCS12CATypes] | None, encryption_algorithm: serialization.KeySerializationEncryption, ) -> bytes: password = None @@ -1847,60 +852,17 @@ def serialize_key_and_certificates_to_pkcs12( def poly1305_supported(self) -> bool: if self._fips_enabled: return False - return self._lib.Cryptography_HAS_POLY1305 == 1 + elif ( + self._lib.CRYPTOGRAPHY_IS_BORINGSSL + or self._lib.CRYPTOGRAPHY_IS_LIBRESSL + ): + return True + else: + return self._lib.Cryptography_HAS_POLY1305 == 1 def pkcs7_supported(self) -> bool: return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - def load_pem_pkcs7_certificates( - self, data: bytes - ) -> typing.List[x509.Certificate]: - utils._check_bytes("data", data) - bio = self._bytes_to_bio(data) - p7 = self._lib.PEM_read_bio_PKCS7( - bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL - ) - if p7 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to parse PKCS7 data") - - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - return self._load_pkcs7_certificates(p7) - - def load_der_pkcs7_certificates( - self, data: bytes - ) -> typing.List[x509.Certificate]: - utils._check_bytes("data", data) - bio = self._bytes_to_bio(data) - p7 = self._lib.d2i_PKCS7_bio(bio.bio, self._ffi.NULL) - if p7 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to parse PKCS7 data") - - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - return self._load_pkcs7_certificates(p7) - - def _load_pkcs7_certificates(self, p7) -> typing.List[x509.Certificate]: - nid = self._lib.OBJ_obj2nid(p7.type) - self.openssl_assert(nid != self._lib.NID_undef) - if nid != self._lib.NID_pkcs7_signed: - raise UnsupportedAlgorithm( - "Only basic signed structures are currently supported. NID" - " for this data was {}".format(nid), - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - sk_x509 = p7.d.sign.cert - num = self._lib.sk_X509_num(sk_x509) - certs = [] - for i in range(num): - x509 = self._lib.sk_X509_value(sk_x509, i) - self.openssl_assert(x509 != self._ffi.NULL) - cert = self._ossl2cert(x509) - certs.append(cert) - - return certs - class GetCipherByName: def __init__(self, fmt: str): diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py index bc42adbd49a5..3916b1a510ad 100644 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -17,14 +17,14 @@ class _CipherContext: _ENCRYPT = 1 _DECRYPT = 0 - _MAX_CHUNK_SIZE = 2**30 - 1 + _MAX_CHUNK_SIZE = 2**29 def __init__(self, backend: Backend, cipher, mode, operation: int) -> None: self._backend = backend self._cipher = cipher self._mode = mode self._operation = operation - self._tag: typing.Optional[bytes] = None + self._tag: bytes | None = None if isinstance(self._cipher, ciphers.BlockCipherAlgorithm): self._block_size_bytes = self._cipher.block_size // 8 @@ -149,8 +149,9 @@ def update_into(self, data: bytes, buf: bytes) -> int: total_data_len = len(data) if len(buf) < (total_data_len + self._block_size_bytes - 1): raise ValueError( - "buffer must be at least {} bytes for this " - "payload".format(len(data) + self._block_size_bytes - 1) + "buffer must be at least {} bytes for this payload".format( + len(data) + self._block_size_bytes - 1 + ) ) data_processed = 0 @@ -277,5 +278,5 @@ def authenticate_additional_data(self, data: bytes) -> None: self._backend.openssl_assert(res != 0) @property - def tag(self) -> typing.Optional[bytes]: + def tag(self) -> bytes | None: return self._tag diff --git a/src/cryptography/hazmat/backends/openssl/cmac.py b/src/cryptography/hazmat/backends/openssl/cmac.py deleted file mode 100644 index bdd7fec611d1..000000000000 --- a/src/cryptography/hazmat/backends/openssl/cmac.py +++ /dev/null @@ -1,89 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.primitives import constant_time -from cryptography.hazmat.primitives.ciphers.modes import CBC - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - from cryptography.hazmat.primitives import ciphers - - -class _CMACContext: - def __init__( - self, - backend: Backend, - algorithm: ciphers.BlockCipherAlgorithm, - ctx=None, - ) -> None: - if not backend.cmac_algorithm_supported(algorithm): - raise UnsupportedAlgorithm( - "This backend does not support CMAC.", - _Reasons.UNSUPPORTED_CIPHER, - ) - - self._backend = backend - self._key = algorithm.key - self._algorithm = algorithm - self._output_length = algorithm.block_size // 8 - - if ctx is None: - registry = self._backend._cipher_registry - adapter = registry[type(algorithm), CBC] - - evp_cipher = adapter(self._backend, algorithm, CBC) - - ctx = self._backend._lib.CMAC_CTX_new() - - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free) - - key_ptr = self._backend._ffi.from_buffer(self._key) - res = self._backend._lib.CMAC_Init( - ctx, - key_ptr, - len(self._key), - evp_cipher, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(res == 1) - - self._ctx = ctx - - def update(self, data: bytes) -> None: - res = self._backend._lib.CMAC_Update(self._ctx, data, len(data)) - self._backend.openssl_assert(res == 1) - - def finalize(self) -> bytes: - buf = self._backend._ffi.new("unsigned char[]", self._output_length) - length = self._backend._ffi.new("size_t *", self._output_length) - res = self._backend._lib.CMAC_Final(self._ctx, buf, length) - self._backend.openssl_assert(res == 1) - - self._ctx = None - - return self._backend._ffi.buffer(buf)[:] - - def copy(self) -> _CMACContext: - copied_ctx = self._backend._lib.CMAC_CTX_new() - copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.CMAC_CTX_free - ) - res = self._backend._lib.CMAC_CTX_copy(copied_ctx, self._ctx) - self._backend.openssl_assert(res == 1) - return _CMACContext(self._backend, self._algorithm, ctx=copied_ctx) - - def verify(self, signature: bytes) -> None: - digest = self.finalize() - if not constant_time.bytes_eq(digest, signature): - raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py deleted file mode 100644 index 9821bd193e29..000000000000 --- a/src/cryptography/hazmat/backends/openssl/ec.py +++ /dev/null @@ -1,328 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, - _evp_pkey_derive, -) -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import ec - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _check_signature_algorithm( - signature_algorithm: ec.EllipticCurveSignatureAlgorithm, -) -> None: - if not isinstance(signature_algorithm, ec.ECDSA): - raise UnsupportedAlgorithm( - "Unsupported elliptic curve signature algorithm.", - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, - ) - - -def _ec_key_curve_sn(backend: Backend, ec_key) -> str: - group = backend._lib.EC_KEY_get0_group(ec_key) - backend.openssl_assert(group != backend._ffi.NULL) - - nid = backend._lib.EC_GROUP_get_curve_name(group) - # The following check is to find EC keys with unnamed curves and raise - # an error for now. - if nid == backend._lib.NID_undef: - raise ValueError( - "ECDSA keys with explicit parameters are unsupported at this time" - ) - - # This is like the above check, but it also catches the case where you - # explicitly encoded a curve with the same parameters as a named curve. - # Don't do that. - if ( - not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - and backend._lib.EC_GROUP_get_asn1_flag(group) == 0 - ): - raise ValueError( - "ECDSA keys with explicit parameters are unsupported at this time" - ) - - curve_name = backend._lib.OBJ_nid2sn(nid) - backend.openssl_assert(curve_name != backend._ffi.NULL) - - sn = backend._ffi.string(curve_name).decode("ascii") - return sn - - -def _mark_asn1_named_ec_curve(backend: Backend, ec_cdata): - """ - Set the named curve flag on the EC_KEY. This causes OpenSSL to - serialize EC keys along with their curve OID which makes - deserialization easier. - """ - - backend._lib.EC_KEY_set_asn1_flag( - ec_cdata, backend._lib.OPENSSL_EC_NAMED_CURVE - ) - - -def _check_key_infinity(backend: Backend, ec_cdata) -> None: - point = backend._lib.EC_KEY_get0_public_key(ec_cdata) - backend.openssl_assert(point != backend._ffi.NULL) - group = backend._lib.EC_KEY_get0_group(ec_cdata) - backend.openssl_assert(group != backend._ffi.NULL) - if backend._lib.EC_POINT_is_at_infinity(group, point): - raise ValueError( - "Cannot load an EC public key where the point is at infinity" - ) - - -def _sn_to_elliptic_curve(backend: Backend, sn: str) -> ec.EllipticCurve: - try: - return ec._CURVE_TYPES[sn]() - except KeyError: - raise UnsupportedAlgorithm( - f"{sn} is not a supported elliptic curve", - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, - ) - - -def _ecdsa_sig_sign( - backend: Backend, private_key: _EllipticCurvePrivateKey, data: bytes -) -> bytes: - max_size = backend._lib.ECDSA_size(private_key._ec_key) - backend.openssl_assert(max_size > 0) - - sigbuf = backend._ffi.new("unsigned char[]", max_size) - siglen_ptr = backend._ffi.new("unsigned int[]", 1) - res = backend._lib.ECDSA_sign( - 0, data, len(data), sigbuf, siglen_ptr, private_key._ec_key - ) - backend.openssl_assert(res == 1) - return backend._ffi.buffer(sigbuf)[: siglen_ptr[0]] - - -def _ecdsa_sig_verify( - backend: Backend, - public_key: _EllipticCurvePublicKey, - signature: bytes, - data: bytes, -) -> None: - res = backend._lib.ECDSA_verify( - 0, data, len(data), signature, len(signature), public_key._ec_key - ) - if res != 1: - backend._consume_errors() - raise InvalidSignature - - -class _EllipticCurvePrivateKey(ec.EllipticCurvePrivateKey): - def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): - self._backend = backend - self._ec_key = ec_key_cdata - self._evp_pkey = evp_pkey - - sn = _ec_key_curve_sn(backend, ec_key_cdata) - self._curve = _sn_to_elliptic_curve(backend, sn) - _mark_asn1_named_ec_curve(backend, ec_key_cdata) - _check_key_infinity(backend, ec_key_cdata) - - @property - def curve(self) -> ec.EllipticCurve: - return self._curve - - @property - def key_size(self) -> int: - return self.curve.key_size - - def exchange( - self, algorithm: ec.ECDH, peer_public_key: ec.EllipticCurvePublicKey - ) -> bytes: - if not ( - self._backend.elliptic_curve_exchange_algorithm_supported( - algorithm, self.curve - ) - ): - raise UnsupportedAlgorithm( - "This backend does not support the ECDH algorithm.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, - ) - - if peer_public_key.curve.name != self.curve.name: - raise ValueError( - "peer_public_key and self are not on the same curve" - ) - - return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) - - def public_key(self) -> ec.EllipticCurvePublicKey: - group = self._backend._lib.EC_KEY_get0_group(self._ec_key) - self._backend.openssl_assert(group != self._backend._ffi.NULL) - - curve_nid = self._backend._lib.EC_GROUP_get_curve_name(group) - public_ec_key = self._backend._ec_key_new_by_curve_nid(curve_nid) - - point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) - self._backend.openssl_assert(point != self._backend._ffi.NULL) - - res = self._backend._lib.EC_KEY_set_public_key(public_ec_key, point) - self._backend.openssl_assert(res == 1) - - evp_pkey = self._backend._ec_cdata_to_evp_pkey(public_ec_key) - - return _EllipticCurvePublicKey(self._backend, public_ec_key, evp_pkey) - - def private_numbers(self) -> ec.EllipticCurvePrivateNumbers: - bn = self._backend._lib.EC_KEY_get0_private_key(self._ec_key) - private_value = self._backend._bn_to_int(bn) - return ec.EllipticCurvePrivateNumbers( - private_value=private_value, - public_numbers=self.public_key().public_numbers(), - ) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._ec_key, - ) - - def sign( - self, - data: bytes, - signature_algorithm: ec.EllipticCurveSignatureAlgorithm, - ) -> bytes: - _check_signature_algorithm(signature_algorithm) - data, _ = _calculate_digest_and_algorithm( - data, - signature_algorithm.algorithm, - ) - return _ecdsa_sig_sign(self._backend, self, data) - - -class _EllipticCurvePublicKey(ec.EllipticCurvePublicKey): - def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): - self._backend = backend - self._ec_key = ec_key_cdata - self._evp_pkey = evp_pkey - - sn = _ec_key_curve_sn(backend, ec_key_cdata) - self._curve = _sn_to_elliptic_curve(backend, sn) - _mark_asn1_named_ec_curve(backend, ec_key_cdata) - _check_key_infinity(backend, ec_key_cdata) - - @property - def curve(self) -> ec.EllipticCurve: - return self._curve - - @property - def key_size(self) -> int: - return self.curve.key_size - - def __eq__(self, other: object) -> bool: - if not isinstance(other, _EllipticCurvePublicKey): - return NotImplemented - - return ( - self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) - == 1 - ) - - def public_numbers(self) -> ec.EllipticCurvePublicNumbers: - group = self._backend._lib.EC_KEY_get0_group(self._ec_key) - self._backend.openssl_assert(group != self._backend._ffi.NULL) - - point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) - self._backend.openssl_assert(point != self._backend._ffi.NULL) - - with self._backend._tmp_bn_ctx() as bn_ctx: - bn_x = self._backend._lib.BN_CTX_get(bn_ctx) - bn_y = self._backend._lib.BN_CTX_get(bn_ctx) - - res = self._backend._lib.EC_POINT_get_affine_coordinates( - group, point, bn_x, bn_y, bn_ctx - ) - self._backend.openssl_assert(res == 1) - - x = self._backend._bn_to_int(bn_x) - y = self._backend._bn_to_int(bn_y) - - return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=self._curve) - - def _encode_point(self, format: serialization.PublicFormat) -> bytes: - if format is serialization.PublicFormat.CompressedPoint: - conversion = self._backend._lib.POINT_CONVERSION_COMPRESSED - else: - assert format is serialization.PublicFormat.UncompressedPoint - conversion = self._backend._lib.POINT_CONVERSION_UNCOMPRESSED - - group = self._backend._lib.EC_KEY_get0_group(self._ec_key) - self._backend.openssl_assert(group != self._backend._ffi.NULL) - point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) - self._backend.openssl_assert(point != self._backend._ffi.NULL) - with self._backend._tmp_bn_ctx() as bn_ctx: - buflen = self._backend._lib.EC_POINT_point2oct( - group, point, conversion, self._backend._ffi.NULL, 0, bn_ctx - ) - self._backend.openssl_assert(buflen > 0) - buf = self._backend._ffi.new("char[]", buflen) - res = self._backend._lib.EC_POINT_point2oct( - group, point, conversion, buf, buflen, bn_ctx - ) - self._backend.openssl_assert(buflen == res) - - return self._backend._ffi.buffer(buf)[:] - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - if ( - encoding is serialization.Encoding.X962 - or format is serialization.PublicFormat.CompressedPoint - or format is serialization.PublicFormat.UncompressedPoint - ): - if encoding is not serialization.Encoding.X962 or format not in ( - serialization.PublicFormat.CompressedPoint, - serialization.PublicFormat.UncompressedPoint, - ): - raise ValueError( - "X962 encoding must be used with CompressedPoint or " - "UncompressedPoint format" - ) - - return self._encode_point(format) - else: - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def verify( - self, - signature: bytes, - data: bytes, - signature_algorithm: ec.EllipticCurveSignatureAlgorithm, - ) -> None: - _check_signature_algorithm(signature_algorithm) - data, _ = _calculate_digest_and_algorithm( - data, - signature_algorithm.algorithm, - ) - _ecdsa_sig_verify(self._backend, self, signature, data) diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py deleted file mode 100644 index ef27d4ead570..000000000000 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ /dev/null @@ -1,599 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import threading -import typing - -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, -) -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import utils as asym_utils -from cryptography.hazmat.primitives.asymmetric.padding import ( - MGF1, - OAEP, - PSS, - AsymmetricPadding, - PKCS1v15, - _Auto, - _DigestLength, - _MaxLength, - calculate_max_pss_salt_length, -) -from cryptography.hazmat.primitives.asymmetric.rsa import ( - RSAPrivateKey, - RSAPrivateNumbers, - RSAPublicKey, - RSAPublicNumbers, -) - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _get_rsa_pss_salt_length( - backend: Backend, - pss: PSS, - key: typing.Union[RSAPrivateKey, RSAPublicKey], - hash_algorithm: hashes.HashAlgorithm, -) -> int: - salt = pss._salt_length - - if isinstance(salt, _MaxLength): - return calculate_max_pss_salt_length(key, hash_algorithm) - elif isinstance(salt, _DigestLength): - return hash_algorithm.digest_size - elif isinstance(salt, _Auto): - if isinstance(key, RSAPrivateKey): - raise ValueError( - "PSS salt length can only be set to AUTO when verifying" - ) - return backend._lib.RSA_PSS_SALTLEN_AUTO - else: - return salt - - -def _enc_dec_rsa( - backend: Backend, - key: typing.Union[_RSAPrivateKey, _RSAPublicKey], - data: bytes, - padding: AsymmetricPadding, -) -> bytes: - if not isinstance(padding, AsymmetricPadding): - raise TypeError("Padding must be an instance of AsymmetricPadding.") - - if isinstance(padding, PKCS1v15): - padding_enum = backend._lib.RSA_PKCS1_PADDING - elif isinstance(padding, OAEP): - padding_enum = backend._lib.RSA_PKCS1_OAEP_PADDING - - if not isinstance(padding._mgf, MGF1): - raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend.", - _Reasons.UNSUPPORTED_MGF, - ) - - if not backend.rsa_padding_supported(padding): - raise UnsupportedAlgorithm( - "This combination of padding and hash algorithm is not " - "supported by this backend.", - _Reasons.UNSUPPORTED_PADDING, - ) - - else: - raise UnsupportedAlgorithm( - f"{padding.name} is not supported by this backend.", - _Reasons.UNSUPPORTED_PADDING, - ) - - return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding) - - -def _enc_dec_rsa_pkey_ctx( - backend: Backend, - key: typing.Union[_RSAPrivateKey, _RSAPublicKey], - data: bytes, - padding_enum: int, - padding: AsymmetricPadding, -) -> bytes: - init: typing.Callable[[typing.Any], int] - crypt: typing.Callable[[typing.Any, typing.Any, int, bytes, int], int] - if isinstance(key, _RSAPublicKey): - init = backend._lib.EVP_PKEY_encrypt_init - crypt = backend._lib.EVP_PKEY_encrypt - else: - init = backend._lib.EVP_PKEY_decrypt_init - crypt = backend._lib.EVP_PKEY_decrypt - - pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL) - backend.openssl_assert(pkey_ctx != backend._ffi.NULL) - pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) - res = init(pkey_ctx) - backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) - backend.openssl_assert(res > 0) - buf_size = backend._lib.EVP_PKEY_size(key._evp_pkey) - backend.openssl_assert(buf_size > 0) - if isinstance(padding, OAEP): - mgf1_md = backend._evp_md_non_null_from_algorithm( - padding._mgf._algorithm - ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md) - backend.openssl_assert(res > 0) - oaep_md = backend._evp_md_non_null_from_algorithm(padding._algorithm) - res = backend._lib.EVP_PKEY_CTX_set_rsa_oaep_md(pkey_ctx, oaep_md) - backend.openssl_assert(res > 0) - - if ( - isinstance(padding, OAEP) - and padding._label is not None - and len(padding._label) > 0 - ): - # set0_rsa_oaep_label takes ownership of the char * so we need to - # copy it into some new memory - labelptr = backend._lib.OPENSSL_malloc(len(padding._label)) - backend.openssl_assert(labelptr != backend._ffi.NULL) - backend._ffi.memmove(labelptr, padding._label, len(padding._label)) - res = backend._lib.EVP_PKEY_CTX_set0_rsa_oaep_label( - pkey_ctx, labelptr, len(padding._label) - ) - backend.openssl_assert(res == 1) - - outlen = backend._ffi.new("size_t *", buf_size) - buf = backend._ffi.new("unsigned char[]", buf_size) - # Everything from this line onwards is written with the goal of being as - # constant-time as is practical given the constraints of Python and our - # API. See Bleichenbacher's '98 attack on RSA, and its many many variants. - # As such, you should not attempt to change this (particularly to "clean it - # up") without understanding why it was written this way (see - # Chesterton's Fence), and without measuring to verify you have not - # introduced observable time differences. - res = crypt(pkey_ctx, buf, outlen, data, len(data)) - resbuf = backend._ffi.buffer(buf)[: outlen[0]] - backend._lib.ERR_clear_error() - if res <= 0: - raise ValueError("Encryption/decryption failed.") - return resbuf - - -def _rsa_sig_determine_padding( - backend: Backend, - key: typing.Union[_RSAPrivateKey, _RSAPublicKey], - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], -) -> int: - if not isinstance(padding, AsymmetricPadding): - raise TypeError("Expected provider of AsymmetricPadding.") - - pkey_size = backend._lib.EVP_PKEY_size(key._evp_pkey) - backend.openssl_assert(pkey_size > 0) - - if isinstance(padding, PKCS1v15): - # Hash algorithm is ignored for PKCS1v15-padding, may be None. - padding_enum = backend._lib.RSA_PKCS1_PADDING - elif isinstance(padding, PSS): - if not isinstance(padding._mgf, MGF1): - raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend.", - _Reasons.UNSUPPORTED_MGF, - ) - - # PSS padding requires a hash algorithm - if not isinstance(algorithm, hashes.HashAlgorithm): - raise TypeError("Expected instance of hashes.HashAlgorithm.") - - # Size of key in bytes - 2 is the maximum - # PSS signature length (salt length is checked later) - if pkey_size - algorithm.digest_size - 2 < 0: - raise ValueError( - "Digest too large for key size. Use a larger " - "key or different digest." - ) - - padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING - else: - raise UnsupportedAlgorithm( - f"{padding.name} is not supported by this backend.", - _Reasons.UNSUPPORTED_PADDING, - ) - - return padding_enum - - -# Hash algorithm can be absent (None) to initialize the context without setting -# any message digest algorithm. This is currently only valid for the PKCS1v15 -# padding type, where it means that the signature data is encoded/decoded -# as provided, without being wrapped in a DigestInfo structure. -def _rsa_sig_setup( - backend: Backend, - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], - key: typing.Union[_RSAPublicKey, _RSAPrivateKey], - init_func: typing.Callable[[typing.Any], int], -): - padding_enum = _rsa_sig_determine_padding(backend, key, padding, algorithm) - pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL) - backend.openssl_assert(pkey_ctx != backend._ffi.NULL) - pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) - res = init_func(pkey_ctx) - if res != 1: - errors = backend._consume_errors() - raise ValueError("Unable to sign/verify with this key", errors) - - if algorithm is not None: - evp_md = backend._evp_md_non_null_from_algorithm(algorithm) - res = backend._lib.EVP_PKEY_CTX_set_signature_md(pkey_ctx, evp_md) - if res <= 0: - backend._consume_errors() - raise UnsupportedAlgorithm( - "{} is not supported by this backend for RSA signing.".format( - algorithm.name - ), - _Reasons.UNSUPPORTED_HASH, - ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) - if res <= 0: - backend._consume_errors() - raise UnsupportedAlgorithm( - "{} is not supported for the RSA signature operation.".format( - padding.name - ), - _Reasons.UNSUPPORTED_PADDING, - ) - if isinstance(padding, PSS): - assert isinstance(algorithm, hashes.HashAlgorithm) - res = backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( - pkey_ctx, - _get_rsa_pss_salt_length(backend, padding, key, algorithm), - ) - backend.openssl_assert(res > 0) - - mgf1_md = backend._evp_md_non_null_from_algorithm( - padding._mgf._algorithm - ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md) - backend.openssl_assert(res > 0) - - return pkey_ctx - - -def _rsa_sig_sign( - backend: Backend, - padding: AsymmetricPadding, - algorithm: hashes.HashAlgorithm, - private_key: _RSAPrivateKey, - data: bytes, -) -> bytes: - pkey_ctx = _rsa_sig_setup( - backend, - padding, - algorithm, - private_key, - backend._lib.EVP_PKEY_sign_init, - ) - buflen = backend._ffi.new("size_t *") - res = backend._lib.EVP_PKEY_sign( - pkey_ctx, backend._ffi.NULL, buflen, data, len(data) - ) - backend.openssl_assert(res == 1) - buf = backend._ffi.new("unsigned char[]", buflen[0]) - res = backend._lib.EVP_PKEY_sign(pkey_ctx, buf, buflen, data, len(data)) - if res != 1: - errors = backend._consume_errors() - raise ValueError( - "Digest or salt length too long for key size. Use a larger key " - "or shorter salt length if you are specifying a PSS salt", - errors, - ) - - return backend._ffi.buffer(buf)[:] - - -def _rsa_sig_verify( - backend: Backend, - padding: AsymmetricPadding, - algorithm: hashes.HashAlgorithm, - public_key: _RSAPublicKey, - signature: bytes, - data: bytes, -) -> None: - pkey_ctx = _rsa_sig_setup( - backend, - padding, - algorithm, - public_key, - backend._lib.EVP_PKEY_verify_init, - ) - res = backend._lib.EVP_PKEY_verify( - pkey_ctx, signature, len(signature), data, len(data) - ) - # The previous call can return negative numbers in the event of an - # error. This is not a signature failure but we need to fail if it - # occurs. - backend.openssl_assert(res >= 0) - if res == 0: - backend._consume_errors() - raise InvalidSignature - - -def _rsa_sig_recover( - backend: Backend, - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], - public_key: _RSAPublicKey, - signature: bytes, -) -> bytes: - pkey_ctx = _rsa_sig_setup( - backend, - padding, - algorithm, - public_key, - backend._lib.EVP_PKEY_verify_recover_init, - ) - - # Attempt to keep the rest of the code in this function as constant/time - # as possible. See the comment in _enc_dec_rsa_pkey_ctx. Note that the - # buflen parameter is used even though its value may be undefined in the - # error case. Due to the tolerant nature of Python slicing this does not - # trigger any exceptions. - maxlen = backend._lib.EVP_PKEY_size(public_key._evp_pkey) - backend.openssl_assert(maxlen > 0) - buf = backend._ffi.new("unsigned char[]", maxlen) - buflen = backend._ffi.new("size_t *", maxlen) - res = backend._lib.EVP_PKEY_verify_recover( - pkey_ctx, buf, buflen, signature, len(signature) - ) - resbuf = backend._ffi.buffer(buf)[: buflen[0]] - backend._lib.ERR_clear_error() - # Assume that all parameter errors are handled during the setup phase and - # any error here is due to invalid signature. - if res != 1: - raise InvalidSignature - return resbuf - - -class _RSAPrivateKey(RSAPrivateKey): - _evp_pkey: object - _rsa_cdata: object - _key_size: int - - def __init__( - self, - backend: Backend, - rsa_cdata, - evp_pkey, - *, - unsafe_skip_rsa_key_validation: bool, - ): - res: int - # RSA_check_key is slower in OpenSSL 3.0.0 due to improved - # primality checking. In normal use this is unlikely to be a problem - # since users don't load new keys constantly, but for TESTING we've - # added an init arg that allows skipping the checks. You should not - # use this in production code unless you understand the consequences. - if not unsafe_skip_rsa_key_validation: - res = backend._lib.RSA_check_key(rsa_cdata) - if res != 1: - errors = backend._consume_errors() - raise ValueError("Invalid private key", errors) - # 2 is prime and passes an RSA key check, so we also check - # if p and q are odd just to be safe. - p = backend._ffi.new("BIGNUM **") - q = backend._ffi.new("BIGNUM **") - backend._lib.RSA_get0_factors(rsa_cdata, p, q) - backend.openssl_assert(p[0] != backend._ffi.NULL) - backend.openssl_assert(q[0] != backend._ffi.NULL) - p_odd = backend._lib.BN_is_odd(p[0]) - q_odd = backend._lib.BN_is_odd(q[0]) - if p_odd != 1 or q_odd != 1: - errors = backend._consume_errors() - raise ValueError("Invalid private key", errors) - - self._backend = backend - self._rsa_cdata = rsa_cdata - self._evp_pkey = evp_pkey - # Used for lazy blinding - self._blinded = False - self._blinding_lock = threading.Lock() - - n = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key( - self._rsa_cdata, - n, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(n[0]) - - def _enable_blinding(self) -> None: - # If you call blind on an already blinded RSA key OpenSSL will turn - # it off and back on, which is a performance hit we want to avoid. - if not self._blinded: - with self._blinding_lock: - self._non_threadsafe_enable_blinding() - - def _non_threadsafe_enable_blinding(self) -> None: - # This is only a separate function to allow for testing to cover both - # branches. It should never be invoked except through _enable_blinding. - # Check if it's not True again in case another thread raced past the - # first non-locked check. - if not self._blinded: - res = self._backend._lib.RSA_blinding_on( - self._rsa_cdata, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res == 1) - self._blinded = True - - @property - def key_size(self) -> int: - return self._key_size - - def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: - self._enable_blinding() - key_size_bytes = (self.key_size + 7) // 8 - if key_size_bytes != len(ciphertext): - raise ValueError("Ciphertext length must be equal to key size.") - - return _enc_dec_rsa(self._backend, self, ciphertext, padding) - - def public_key(self) -> RSAPublicKey: - ctx = self._backend._lib.RSAPublicKey_dup(self._rsa_cdata) - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc(ctx, self._backend._lib.RSA_free) - evp_pkey = self._backend._rsa_cdata_to_evp_pkey(ctx) - return _RSAPublicKey(self._backend, ctx, evp_pkey) - - def private_numbers(self) -> RSAPrivateNumbers: - n = self._backend._ffi.new("BIGNUM **") - e = self._backend._ffi.new("BIGNUM **") - d = self._backend._ffi.new("BIGNUM **") - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - dmp1 = self._backend._ffi.new("BIGNUM **") - dmq1 = self._backend._ffi.new("BIGNUM **") - iqmp = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key(self._rsa_cdata, n, e, d) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(e[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(d[0] != self._backend._ffi.NULL) - self._backend._lib.RSA_get0_factors(self._rsa_cdata, p, q) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend._lib.RSA_get0_crt_params( - self._rsa_cdata, dmp1, dmq1, iqmp - ) - self._backend.openssl_assert(dmp1[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(dmq1[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(iqmp[0] != self._backend._ffi.NULL) - return RSAPrivateNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - d=self._backend._bn_to_int(d[0]), - dmp1=self._backend._bn_to_int(dmp1[0]), - dmq1=self._backend._bn_to_int(dmq1[0]), - iqmp=self._backend._bn_to_int(iqmp[0]), - public_numbers=RSAPublicNumbers( - e=self._backend._bn_to_int(e[0]), - n=self._backend._bn_to_int(n[0]), - ), - ) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._rsa_cdata, - ) - - def sign( - self, - data: bytes, - padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], - ) -> bytes: - self._enable_blinding() - data, algorithm = _calculate_digest_and_algorithm(data, algorithm) - return _rsa_sig_sign(self._backend, padding, algorithm, self, data) - - -class _RSAPublicKey(RSAPublicKey): - _evp_pkey: object - _rsa_cdata: object - _key_size: int - - def __init__(self, backend: Backend, rsa_cdata, evp_pkey): - self._backend = backend - self._rsa_cdata = rsa_cdata - self._evp_pkey = evp_pkey - - n = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key( - self._rsa_cdata, - n, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(n[0]) - - @property - def key_size(self) -> int: - return self._key_size - - def __eq__(self, other: object) -> bool: - if not isinstance(other, _RSAPublicKey): - return NotImplemented - - return ( - self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) - == 1 - ) - - def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: - return _enc_dec_rsa(self._backend, self, plaintext, padding) - - def public_numbers(self) -> RSAPublicNumbers: - n = self._backend._ffi.new("BIGNUM **") - e = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key( - self._rsa_cdata, n, e, self._backend._ffi.NULL - ) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(e[0] != self._backend._ffi.NULL) - return RSAPublicNumbers( - e=self._backend._bn_to_int(e[0]), - n=self._backend._bn_to_int(n[0]), - ) - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, self._rsa_cdata - ) - - def verify( - self, - signature: bytes, - data: bytes, - padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], - ) -> None: - data, algorithm = _calculate_digest_and_algorithm(data, algorithm) - _rsa_sig_verify( - self._backend, padding, algorithm, self, signature, data - ) - - def recover_data_from_signature( - self, - signature: bytes, - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], - ) -> bytes: - if isinstance(algorithm, asym_utils.Prehashed): - raise TypeError( - "Prehashed is only supported in the sign and verify methods. " - "It cannot be used with recover_data_from_signature." - ) - return _rsa_sig_recover( - self._backend, padding, algorithm, self, signature - ) diff --git a/src/cryptography/hazmat/backends/openssl/utils.py b/src/cryptography/hazmat/backends/openssl/utils.py deleted file mode 100644 index 5b404defde33..000000000000 --- a/src/cryptography/hazmat/backends/openssl/utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric.utils import Prehashed - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _evp_pkey_derive(backend: Backend, evp_pkey, peer_public_key) -> bytes: - ctx = backend._lib.EVP_PKEY_CTX_new(evp_pkey, backend._ffi.NULL) - backend.openssl_assert(ctx != backend._ffi.NULL) - ctx = backend._ffi.gc(ctx, backend._lib.EVP_PKEY_CTX_free) - res = backend._lib.EVP_PKEY_derive_init(ctx) - backend.openssl_assert(res == 1) - - if backend._lib.Cryptography_HAS_EVP_PKEY_SET_PEER_EX: - res = backend._lib.EVP_PKEY_derive_set_peer_ex( - ctx, peer_public_key._evp_pkey, 0 - ) - else: - res = backend._lib.EVP_PKEY_derive_set_peer( - ctx, peer_public_key._evp_pkey - ) - backend.openssl_assert(res == 1) - - keylen = backend._ffi.new("size_t *") - res = backend._lib.EVP_PKEY_derive(ctx, backend._ffi.NULL, keylen) - backend.openssl_assert(res == 1) - backend.openssl_assert(keylen[0] > 0) - buf = backend._ffi.new("unsigned char[]", keylen[0]) - res = backend._lib.EVP_PKEY_derive(ctx, buf, keylen) - if res != 1: - errors = backend._consume_errors() - raise ValueError("Error computing shared key.", errors) - - return backend._ffi.buffer(buf, keylen[0])[:] - - -def _calculate_digest_and_algorithm( - data: bytes, - algorithm: typing.Union[Prehashed, hashes.HashAlgorithm], -) -> typing.Tuple[bytes, hashes.HashAlgorithm]: - if not isinstance(algorithm, Prehashed): - hash_ctx = hashes.Hash(algorithm) - hash_ctx.update(data) - data = hash_ctx.finalize() - else: - algorithm = algorithm._algorithm - - if len(data) != algorithm.digest_size: - raise ValueError( - "The provided data must be the same length as the hash " - "algorithm's digest size." - ) - - return (data, algorithm) diff --git a/src/cryptography/hazmat/bindings/_rust/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/__init__.pyi index 94a37a20aa96..18a6fb87b628 100644 --- a/src/cryptography/hazmat/bindings/_rust/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import types import typing def check_pkcs7_padding(data: bytes) -> bool: ... @@ -16,19 +15,3 @@ class ObjectIdentifier: def _name(self) -> str: ... T = typing.TypeVar("T") - -class FixedPool(typing.Generic[T]): - def __init__( - self, - create: typing.Callable[[], T], - ) -> None: ... - def acquire(self) -> PoolAcquisition[T]: ... - -class PoolAcquisition(typing.Generic[T]): - def __enter__(self) -> T: ... - def __exit__( - self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc_value: typing.Optional[BaseException], - exc_tb: typing.Optional[types.TracebackType], - ) -> None: ... diff --git a/src/cryptography/hazmat/bindings/_rust/asn1.pyi b/src/cryptography/hazmat/bindings/_rust/asn1.pyi index a8369ba8383e..35652c6ada1c 100644 --- a/src/cryptography/hazmat/bindings/_rust/asn1.pyi +++ b/src/cryptography/hazmat/bindings/_rust/asn1.pyi @@ -2,15 +2,13 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import typing - class TestCertificate: not_after_tag: int not_before_tag: int - issuer_value_tags: typing.List[int] - subject_value_tags: typing.List[int] + issuer_value_tags: list[int] + subject_value_tags: list[int] -def decode_dss_signature(signature: bytes) -> typing.Tuple[int, int]: ... +def decode_dss_signature(signature: bytes) -> tuple[int, int]: ... def encode_dss_signature(r: int, s: int) -> bytes: ... def parse_spki_for_data(data: bytes) -> bytes: ... def test_parse_certificate(data: bytes) -> TestCertificate: ... diff --git a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi index 4671eb9ba34d..b15628f8d46b 100644 --- a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi +++ b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -2,8 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import typing - from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes from cryptography.x509.ocsp import ( @@ -19,7 +17,7 @@ def load_der_ocsp_response(data: bytes) -> OCSPResponse: ... def create_ocsp_request(builder: OCSPRequestBuilder) -> OCSPRequest: ... def create_ocsp_response( status: OCSPResponseStatus, - builder: typing.Optional[OCSPResponseBuilder], - private_key: typing.Optional[PrivateKeyTypes], - hash_algorithm: typing.Optional[hashes.HashAlgorithm], + builder: OCSPResponseBuilder | None, + private_key: PrivateKeyTypes | None, + hash_algorithm: hashes.HashAlgorithm | None, ) -> OCSPResponse: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi index 82f30d20b0ab..9cdb4d6a5c6e 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -5,14 +5,19 @@ import typing from cryptography.hazmat.bindings._rust.openssl import ( + aead, + cmac, dh, dsa, + ec, ed448, ed25519, hashes, hmac, kdf, + keys, poly1305, + rsa, x448, x25519, ) @@ -20,13 +25,18 @@ from cryptography.hazmat.bindings._rust.openssl import ( __all__ = [ "openssl_version", "raise_openssl_error", + "aead", + "cmac", "dh", "dsa", + "ec", "hashes", "hmac", "kdf", + "keys", "ed448", "ed25519", + "rsa", "poly1305", "x448", "x25519", @@ -34,7 +44,7 @@ __all__ = [ def openssl_version() -> int: ... def raise_openssl_error() -> typing.NoReturn: ... -def capture_error_stack() -> typing.List[OpenSSLError]: ... +def capture_error_stack() -> list[OpenSSLError]: ... def is_fips_enabled() -> bool: ... class OpenSSLError: diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi new file mode 100644 index 000000000000..81e801e30bb5 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi @@ -0,0 +1,69 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +class ChaCha20Poly1305: + def __init__(self, key: bytes) -> None: ... + @staticmethod + def generate_key() -> bytes: ... + def encrypt( + self, + nonce: bytes, + data: bytes, + associated_data: bytes | None, + ) -> bytes: ... + def decrypt( + self, + nonce: bytes, + data: bytes, + associated_data: bytes | None, + ) -> bytes: ... + +class AESSIV: + def __init__(self, key: bytes) -> None: ... + @staticmethod + def generate_key(key_size: int) -> bytes: ... + def encrypt( + self, + data: bytes, + associated_data: list[bytes] | None, + ) -> bytes: ... + def decrypt( + self, + data: bytes, + associated_data: list[bytes] | None, + ) -> bytes: ... + +class AESOCB3: + def __init__(self, key: bytes) -> None: ... + @staticmethod + def generate_key(key_size: int) -> bytes: ... + def encrypt( + self, + nonce: bytes, + data: bytes, + associated_data: bytes | None, + ) -> bytes: ... + def decrypt( + self, + nonce: bytes, + data: bytes, + associated_data: bytes | None, + ) -> bytes: ... + +class AESGCMSIV: + def __init__(self, key: bytes) -> None: ... + @staticmethod + def generate_key(key_size: int) -> bytes: ... + def encrypt( + self, + nonce: bytes, + data: bytes, + associated_data: bytes | None, + ) -> bytes: ... + def decrypt( + self, + nonce: bytes, + data: bytes, + associated_data: bytes | None, + ) -> bytes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi new file mode 100644 index 000000000000..9c03508bc89b --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi @@ -0,0 +1,18 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers + +class CMAC: + def __init__( + self, + algorithm: ciphers.BlockCipherAlgorithm, + backend: typing.Any = None, + ) -> None: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> CMAC: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi index bfd005d99fec..08733d745c3d 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi @@ -2,6 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import typing + from cryptography.hazmat.primitives.asymmetric import dh MIN_MODULUS_SIZE: int @@ -10,13 +12,40 @@ class DHPrivateKey: ... class DHPublicKey: ... class DHParameters: ... -def generate_parameters(generator: int, key_size: int) -> dh.DHParameters: ... -def private_key_from_ptr(ptr: int) -> dh.DHPrivateKey: ... -def public_key_from_ptr(ptr: int) -> dh.DHPublicKey: ... -def from_pem_parameters(data: bytes) -> dh.DHParameters: ... -def from_der_parameters(data: bytes) -> dh.DHParameters: ... -def from_private_numbers(numbers: dh.DHPrivateNumbers) -> dh.DHPrivateKey: ... -def from_public_numbers(numbers: dh.DHPublicNumbers) -> dh.DHPublicKey: ... -def from_parameter_numbers( - numbers: dh.DHParameterNumbers, +class DHPrivateNumbers: + def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: ... + def private_key(self, backend: typing.Any = None) -> dh.DHPrivateKey: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DHPublicNumbers: ... + +class DHPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DHParameterNumbers + ) -> None: ... + def public_key(self, backend: typing.Any = None) -> dh.DHPublicKey: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DHParameterNumbers: ... + +class DHParameterNumbers: + def __init__(self, p: int, g: int, q: int | None = None) -> None: ... + def parameters(self, backend: typing.Any = None) -> dh.DHParameters: ... + @property + def p(self) -> int: ... + @property + def g(self) -> int: ... + @property + def q(self) -> int | None: ... + +def generate_parameters( + generator: int, key_size: int, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_pem_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_der_parameters( + data: bytes, backend: typing.Any = None ) -> dh.DHParameters: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi index 5a56f256d52d..0922a4c4041a 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi @@ -2,19 +2,40 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import typing + from cryptography.hazmat.primitives.asymmetric import dsa class DSAPrivateKey: ... class DSAPublicKey: ... class DSAParameters: ... +class DSAPrivateNumbers: + def __init__(self, x: int, public_numbers: DSAPublicNumbers) -> None: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DSAPublicNumbers: ... + def private_key(self, backend: typing.Any = None) -> dsa.DSAPrivateKey: ... + +class DSAPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DSAParameterNumbers + ) -> None: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DSAParameterNumbers: ... + def public_key(self, backend: typing.Any = None) -> dsa.DSAPublicKey: ... + +class DSAParameterNumbers: + def __init__(self, p: int, q: int, g: int) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def g(self) -> int: ... + def parameters(self, backend: typing.Any = None) -> dsa.DSAParameters: ... + def generate_parameters(key_size: int) -> dsa.DSAParameters: ... -def private_key_from_ptr(ptr: int) -> dsa.DSAPrivateKey: ... -def public_key_from_ptr(ptr: int) -> dsa.DSAPublicKey: ... -def from_private_numbers( - numbers: dsa.DSAPrivateNumbers, -) -> dsa.DSAPrivateKey: ... -def from_public_numbers(numbers: dsa.DSAPublicNumbers) -> dsa.DSAPublicKey: ... -def from_parameter_numbers( - numbers: dsa.DSAParameterNumbers, -) -> dsa.DSAParameters: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ec.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ec.pyi new file mode 100644 index 000000000000..5c3b7bf6e4a9 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ec.pyi @@ -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. + +import typing + +from cryptography.hazmat.primitives.asymmetric import ec + +class ECPrivateKey: ... +class ECPublicKey: ... + +class EllipticCurvePrivateNumbers: + def __init__( + self, private_value: int, public_numbers: EllipticCurvePublicNumbers + ) -> None: ... + def private_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePrivateKey: ... + @property + def private_value(self) -> int: ... + @property + def public_numbers(self) -> EllipticCurvePublicNumbers: ... + +class EllipticCurvePublicNumbers: + def __init__(self, x: int, y: int, curve: ec.EllipticCurve) -> None: ... + def public_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePublicKey: ... + @property + def x(self) -> int: ... + @property + def y(self) -> int: ... + @property + def curve(self) -> ec.EllipticCurve: ... + def __eq__(self, other: object) -> bool: ... + +def curve_supported(curve: ec.EllipticCurve) -> bool: ... +def generate_private_key( + curve: ec.EllipticCurve, backend: typing.Any = None +) -> ec.EllipticCurvePrivateKey: ... +def from_private_numbers( + numbers: ec.EllipticCurvePrivateNumbers, +) -> ec.EllipticCurvePrivateKey: ... +def from_public_numbers( + numbers: ec.EllipticCurvePublicNumbers, +) -> ec.EllipticCurvePublicKey: ... +def from_public_bytes( + curve: ec.EllipticCurve, data: bytes +) -> ec.EllipticCurvePublicKey: ... +def derive_private_key( + private_value: int, curve: ec.EllipticCurve +) -> ec.EllipticCurvePrivateKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi index c7f127f0b157..5233f9a1d1c8 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi @@ -8,7 +8,5 @@ class Ed25519PrivateKey: ... class Ed25519PublicKey: ... def generate_key() -> ed25519.Ed25519PrivateKey: ... -def private_key_from_ptr(ptr: int) -> ed25519.Ed25519PrivateKey: ... -def public_key_from_ptr(ptr: int) -> ed25519.Ed25519PublicKey: ... def from_private_bytes(data: bytes) -> ed25519.Ed25519PrivateKey: ... def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi index 1cf5f1773a0b..7a06520380a0 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi @@ -8,7 +8,5 @@ class Ed448PrivateKey: ... class Ed448PublicKey: ... def generate_key() -> ed448.Ed448PrivateKey: ... -def private_key_from_ptr(ptr: int) -> ed448.Ed448PrivateKey: ... -def public_key_from_ptr(ptr: int) -> ed448.Ed448PublicKey: ... def from_private_bytes(data: bytes) -> ed448.Ed448PrivateKey: ... def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi new file mode 100644 index 000000000000..e312d51dc58b --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi @@ -0,0 +1,37 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric.types import ( + PrivateKeyTypes, + PublicKeyTypes, +) + +def private_key_from_ptr( + ptr: int, + unsafe_skip_rsa_key_validation: bool, +) -> PrivateKeyTypes: ... +def load_der_private_key( + data: bytes, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_pem_private_key( + data: bytes, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_der_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... +def load_pem_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi new file mode 100644 index 000000000000..ef7752ddb79d --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi @@ -0,0 +1,55 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import rsa + +class RSAPrivateKey: ... +class RSAPublicKey: ... + +class RSAPrivateNumbers: + def __init__( + self, + p: int, + q: int, + d: int, + dmp1: int, + dmq1: int, + iqmp: int, + public_numbers: RSAPublicNumbers, + ) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def d(self) -> int: ... + @property + def dmp1(self) -> int: ... + @property + def dmq1(self) -> int: ... + @property + def iqmp(self) -> int: ... + @property + def public_numbers(self) -> RSAPublicNumbers: ... + def private_key( + self, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, + ) -> rsa.RSAPrivateKey: ... + +class RSAPublicNumbers: + def __init__(self, e: int, n: int) -> None: ... + @property + def n(self) -> int: ... + @property + def e(self) -> int: ... + def public_key(self, backend: typing.Any = None) -> rsa.RSAPublicKey: ... + +def generate_private_key( + public_exponent: int, + key_size: int, +) -> rsa.RSAPrivateKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi index 90f7cbdda950..da0f3ec588b9 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi @@ -8,7 +8,5 @@ class X25519PrivateKey: ... class X25519PublicKey: ... def generate_key() -> x25519.X25519PrivateKey: ... -def private_key_from_ptr(ptr: int) -> x25519.X25519PrivateKey: ... -def public_key_from_ptr(ptr: int) -> x25519.X25519PublicKey: ... def from_private_bytes(data: bytes) -> x25519.X25519PrivateKey: ... def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi index d326c8d2d7c5..e51cfebe15f6 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi @@ -8,7 +8,5 @@ class X448PrivateKey: ... class X448PublicKey: ... def generate_key() -> x448.X448PrivateKey: ... -def private_key_from_ptr(ptr: int) -> x448.X448PrivateKey: ... -def public_key_from_ptr(ptr: int) -> x448.X448PublicKey: ... def from_private_bytes(data: bytes) -> x448.X448PrivateKey: ... def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi index 66bd850981a6..a84978246572 100644 --- a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi +++ b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -5,7 +5,7 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import pkcs7 def serialize_certificates( - certs: typing.List[x509.Certificate], + certs: list[x509.Certificate], encoding: serialization.Encoding, ) -> bytes: ... def sign_and_serialize( @@ -13,3 +13,9 @@ def sign_and_serialize( encoding: serialization.Encoding, options: typing.Iterable[pkcs7.PKCS7Options], ) -> bytes: ... +def load_pem_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... +def load_der_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi index 24b2f5e3a78c..418184f8a6fd 100644 --- a/src/cryptography/hazmat/bindings/_rust/x509.pyi +++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import datetime import typing from cryptography import x509 @@ -9,32 +10,46 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes -def load_pem_x509_certificate(data: bytes) -> x509.Certificate: ... +def load_pem_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... +def load_der_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... def load_pem_x509_certificates( data: bytes, -) -> typing.List[x509.Certificate]: ... -def load_der_x509_certificate(data: bytes) -> x509.Certificate: ... -def load_pem_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... -def load_der_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... -def load_pem_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ... -def load_der_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ... +) -> list[x509.Certificate]: ... +def load_pem_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_der_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_pem_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... +def load_der_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... def encode_name_bytes(name: x509.Name) -> bytes: ... def encode_extension_value(extension: x509.ExtensionType) -> bytes: ... def create_x509_certificate( builder: x509.CertificateBuilder, private_key: PrivateKeyTypes, - hash_algorithm: typing.Optional[hashes.HashAlgorithm], - padding: typing.Optional[typing.Union[PKCS1v15, PSS]], + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, ) -> x509.Certificate: ... def create_x509_csr( builder: x509.CertificateSigningRequestBuilder, private_key: PrivateKeyTypes, - hash_algorithm: typing.Optional[hashes.HashAlgorithm], + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, ) -> x509.CertificateSigningRequest: ... def create_x509_crl( builder: x509.CertificateRevocationListBuilder, private_key: PrivateKeyTypes, - hash_algorithm: typing.Optional[hashes.HashAlgorithm], + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, ) -> x509.CertificateRevocationList: ... class Sct: ... @@ -42,3 +57,32 @@ class Certificate: ... class RevokedCertificate: ... class CertificateRevocationList: ... class CertificateSigningRequest: ... + +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 build_server_verifier( + self, subject: x509.verification.Subject + ) -> ServerVerifier: ... + +class ServerVerifier: + @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: ... + def verify( + self, + leaf: x509.Certificate, + intermediates: list[x509.Certificate], + ) -> list[x509.Certificate]: ... + +class Store: + def __init__(self, certs: list[x509.Certificate]) -> None: ... + +class VerificationError(Exception): + pass diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index 5e8ecd04182c..30cc3bfa25ef 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -4,17 +4,15 @@ from __future__ import annotations -import typing - -def cryptography_has_set_cert_cb() -> typing.List[str]: +def cryptography_has_set_cert_cb() -> list[str]: return [ "SSL_CTX_set_cert_cb", "SSL_set_cert_cb", ] -def cryptography_has_ssl_st() -> typing.List[str]: +def cryptography_has_ssl_st() -> list[str]: return [ "SSL_ST_BEFORE", "SSL_ST_OK", @@ -23,72 +21,32 @@ def cryptography_has_ssl_st() -> typing.List[str]: ] -def cryptography_has_tls_st() -> typing.List[str]: +def cryptography_has_tls_st() -> list[str]: return [ "TLS_ST_BEFORE", "TLS_ST_OK", ] -def cryptography_has_evp_pkey_dhx() -> typing.List[str]: - return [ - "EVP_PKEY_DHX", - ] - - -def cryptography_has_mem_functions() -> typing.List[str]: +def cryptography_has_mem_functions() -> list[str]: return [ "Cryptography_CRYPTO_set_mem_functions", ] -def cryptography_has_x509_store_ctx_get_issuer() -> typing.List[str]: - return [ - "X509_STORE_set_get_issuer", - ] - - -def cryptography_has_ed448() -> typing.List[str]: +def cryptography_has_ed448() -> list[str]: return [ "EVP_PKEY_ED448", - "NID_ED448", - ] - - -def cryptography_has_ed25519() -> typing.List[str]: - return [ - "NID_ED25519", - "EVP_PKEY_ED25519", - ] - - -def cryptography_has_poly1305() -> typing.List[str]: - return [ - "NID_poly1305", - "EVP_PKEY_POLY1305", - ] - - -def cryptography_has_evp_digestfinal_xof() -> typing.List[str]: - return [ - "EVP_DigestFinalXOF", ] -def cryptography_has_fips() -> typing.List[str]: - return [ - "FIPS_mode_set", - "FIPS_mode", - ] - - -def cryptography_has_ssl_sigalgs() -> typing.List[str]: +def cryptography_has_ssl_sigalgs() -> list[str]: return [ "SSL_CTX_set1_sigalgs_list", ] -def cryptography_has_psk() -> typing.List[str]: +def cryptography_has_psk() -> list[str]: return [ "SSL_CTX_use_psk_identity_hint", "SSL_CTX_set_psk_server_callback", @@ -96,7 +54,7 @@ def cryptography_has_psk() -> typing.List[str]: ] -def cryptography_has_psk_tlsv13() -> typing.List[str]: +def cryptography_has_psk_tlsv13() -> list[str]: return [ "SSL_CTX_set_psk_find_session_callback", "SSL_CTX_set_psk_use_session_callback", @@ -108,7 +66,7 @@ def cryptography_has_psk_tlsv13() -> typing.List[str]: ] -def cryptography_has_custom_ext() -> typing.List[str]: +def cryptography_has_custom_ext() -> list[str]: return [ "SSL_CTX_add_client_custom_ext", "SSL_CTX_add_server_custom_ext", @@ -116,7 +74,7 @@ def cryptography_has_custom_ext() -> typing.List[str]: ] -def cryptography_has_tlsv13_functions() -> typing.List[str]: +def cryptography_has_tlsv13_functions() -> list[str]: return [ "SSL_VERIFY_POST_HANDSHAKE", "SSL_CTX_set_ciphersuites", @@ -130,16 +88,7 @@ def cryptography_has_tlsv13_functions() -> typing.List[str]: ] -def cryptography_has_raw_key() -> typing.List[str]: - return [ - "EVP_PKEY_new_raw_private_key", - "EVP_PKEY_new_raw_public_key", - "EVP_PKEY_get_raw_private_key", - "EVP_PKEY_get_raw_public_key", - ] - - -def cryptography_has_engine() -> typing.List[str]: +def cryptography_has_engine() -> list[str]: return [ "ENGINE_by_id", "ENGINE_init", @@ -158,13 +107,13 @@ def cryptography_has_engine() -> typing.List[str]: ] -def cryptography_has_verified_chain() -> typing.List[str]: +def cryptography_has_verified_chain() -> list[str]: return [ "SSL_get0_verified_chain", ] -def cryptography_has_srtp() -> typing.List[str]: +def cryptography_has_srtp() -> list[str]: return [ "SSL_CTX_set_tlsext_use_srtp", "SSL_set_tlsext_use_srtp", @@ -172,7 +121,7 @@ def cryptography_has_srtp() -> typing.List[str]: ] -def cryptography_has_providers() -> typing.List[str]: +def cryptography_has_providers() -> list[str]: return [ "OSSL_PROVIDER_load", "OSSL_PROVIDER_unload", @@ -182,26 +131,25 @@ def cryptography_has_providers() -> typing.List[str]: ] -def cryptography_has_op_no_renegotiation() -> typing.List[str]: +def cryptography_has_op_no_renegotiation() -> list[str]: return [ "SSL_OP_NO_RENEGOTIATION", ] -def cryptography_has_dtls_get_data_mtu() -> typing.List[str]: +def cryptography_has_dtls_get_data_mtu() -> list[str]: return [ "DTLS_get_data_mtu", ] -def cryptography_has_300_fips() -> typing.List[str]: +def cryptography_has_300_fips() -> list[str]: return [ - "EVP_default_properties_is_fips_enabled", "EVP_default_properties_enable_fips", ] -def cryptography_has_ssl_cookie() -> typing.List[str]: +def cryptography_has_ssl_cookie() -> list[str]: return [ "SSL_OP_COOKIE_EXCHANGE", "DTLSv1_listen", @@ -210,69 +158,41 @@ def cryptography_has_ssl_cookie() -> typing.List[str]: ] -def cryptography_has_pkcs7_funcs() -> typing.List[str]: +def cryptography_has_pkcs7_funcs() -> list[str]: return [ - "SMIME_write_PKCS7", - "PEM_write_bio_PKCS7_stream", - "PKCS7_sign_add_signer", - "PKCS7_final", "PKCS7_verify", "SMIME_read_PKCS7", - "PKCS7_get0_signers", ] -def cryptography_has_bn_flags() -> typing.List[str]: +def cryptography_has_prime_checks() -> list[str]: return [ - "BN_FLG_CONSTTIME", - "BN_set_flags", "BN_prime_checks_for_size", ] -def cryptography_has_evp_pkey_dh() -> typing.List[str]: - return [ - "EVP_PKEY_set1_DH", - ] - - -def cryptography_has_300_evp_cipher() -> typing.List[str]: +def cryptography_has_300_evp_cipher() -> list[str]: return ["EVP_CIPHER_fetch", "EVP_CIPHER_free"] -def cryptography_has_unexpected_eof_while_reading() -> typing.List[str]: +def cryptography_has_unexpected_eof_while_reading() -> list[str]: return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"] -def cryptography_has_pkcs12_set_mac() -> typing.List[str]: +def cryptography_has_pkcs12_set_mac() -> list[str]: return ["PKCS12_set_mac"] -def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]: +def cryptography_has_ssl_op_ignore_unexpected_eof() -> list[str]: return [ "SSL_OP_IGNORE_UNEXPECTED_EOF", ] -def cryptography_has_get_extms_support() -> typing.List[str]: +def cryptography_has_get_extms_support() -> list[str]: return ["SSL_get_extms_support"] -def cryptography_has_evp_pkey_set_peer_ex() -> typing.List[str]: - return ["EVP_PKEY_derive_set_peer_ex"] - - -def cryptography_has_evp_aead() -> typing.List[str]: - return [ - "EVP_aead_chacha20_poly1305", - "EVP_AEAD_CTX_free", - "EVP_AEAD_CTX_seal", - "EVP_AEAD_CTX_open", - "EVP_AEAD_max_overhead", - "Cryptography_EVP_AEAD_CTX_new", - ] - - # This is a mapping of # {condition: function-returning-names-dependent-on-that-condition} so we can # loop over them and delete unsupported names at runtime. It will be removed @@ -282,24 +202,13 @@ def cryptography_has_evp_aead() -> typing.List[str]: "Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb, "Cryptography_HAS_SSL_ST": cryptography_has_ssl_st, "Cryptography_HAS_TLS_ST": cryptography_has_tls_st, - "Cryptography_HAS_EVP_PKEY_DHX": cryptography_has_evp_pkey_dhx, "Cryptography_HAS_MEM_FUNCTIONS": cryptography_has_mem_functions, - "Cryptography_HAS_X509_STORE_CTX_GET_ISSUER": ( - cryptography_has_x509_store_ctx_get_issuer - ), "Cryptography_HAS_ED448": cryptography_has_ed448, - "Cryptography_HAS_ED25519": cryptography_has_ed25519, - "Cryptography_HAS_POLY1305": cryptography_has_poly1305, - "Cryptography_HAS_FIPS": cryptography_has_fips, "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs, "Cryptography_HAS_PSK": cryptography_has_psk, "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, - "Cryptography_HAS_RAW_KEY": cryptography_has_raw_key, - "Cryptography_HAS_EVP_DIGESTFINAL_XOF": ( - cryptography_has_evp_digestfinal_xof - ), "Cryptography_HAS_ENGINE": cryptography_has_engine, "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, @@ -311,8 +220,7 @@ def cryptography_has_evp_aead() -> typing.List[str]: "Cryptography_HAS_300_FIPS": cryptography_has_300_fips, "Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie, "Cryptography_HAS_PKCS7_FUNCS": cryptography_has_pkcs7_funcs, - "Cryptography_HAS_BN_FLAGS": cryptography_has_bn_flags, - "Cryptography_HAS_EVP_PKEY_DH": cryptography_has_evp_pkey_dh, + "Cryptography_HAS_PRIME_CHECKS": cryptography_has_prime_checks, "Cryptography_HAS_300_EVP_CIPHER": cryptography_has_300_evp_cipher, "Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": ( cryptography_has_unexpected_eof_while_reading @@ -322,8 +230,4 @@ def cryptography_has_evp_aead() -> typing.List[str]: cryptography_has_ssl_op_ignore_unexpected_eof ), "Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support, - "Cryptography_HAS_EVP_PKEY_SET_PEER_EX": ( - cryptography_has_evp_pkey_set_peer_ex - ), - "Cryptography_HAS_EVP_AEAD": (cryptography_has_evp_aead), } diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index b50d631518c1..40814f2a58a0 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -18,9 +18,8 @@ def _openssl_assert( - lib, ok: bool, - errors: typing.Optional[typing.List[openssl.OpenSSLError]] = None, + errors: list[openssl.OpenSSLError] | None = None, ) -> None: if not ok: if errors is None: @@ -33,7 +32,7 @@ def _openssl_assert( "OpenSSL try disabling it before reporting a bug. Otherwise " "please file an issue at https://github.com/pyca/cryptography/" "issues with information on how to reproduce " - "this. ({!r})".format(errors), + f"this. ({errors!r})", errors, ) @@ -51,7 +50,7 @@ def _legacy_provider_error(loaded: bool) -> None: def build_conditional_library( lib: typing.Any, - conditional_names: typing.Dict[str, typing.Callable[[], typing.List[str]]], + conditional_names: dict[str, typing.Callable[[], list[str]]], ) -> typing.Any: conditional_lib = types.ModuleType("lib") conditional_lib._original_lib = lib # type: ignore[attr-defined] @@ -86,18 +85,18 @@ def __init__(self) -> None: def _enable_fips(self) -> None: # This function enables FIPS mode for OpenSSL 3.0.0 on installs that # have the FIPS provider installed properly. - _openssl_assert(self.lib, self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER) + _openssl_assert(self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER) self._base_provider = self.lib.OSSL_PROVIDER_load( self.ffi.NULL, b"base" ) - _openssl_assert(self.lib, self._base_provider != self.ffi.NULL) + _openssl_assert(self._base_provider != self.ffi.NULL) self.lib._fips_provider = self.lib.OSSL_PROVIDER_load( self.ffi.NULL, b"fips" ) - _openssl_assert(self.lib, self.lib._fips_provider != self.ffi.NULL) + _openssl_assert(self.lib._fips_provider != self.ffi.NULL) res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1) - _openssl_assert(self.lib, res == 1) + _openssl_assert(res == 1) @classmethod def _ensure_ffi_initialized(cls) -> None: @@ -125,9 +124,7 @@ def _ensure_ffi_initialized(cls) -> None: cls._default_provider = cls.lib.OSSL_PROVIDER_load( cls.ffi.NULL, b"default" ) - _openssl_assert( - cls.lib, cls._default_provider != cls.ffi.NULL - ) + _openssl_assert(cls._default_provider != cls.ffi.NULL) @classmethod def init_static_locks(cls) -> None: @@ -157,7 +154,6 @@ def _verify_package_version(version: str) -> None: ) _openssl_assert( - _openssl.lib, _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), ) diff --git a/src/cryptography/hazmat/primitives/_cipheralgorithm.py b/src/cryptography/hazmat/primitives/_cipheralgorithm.py index 3b880b648849..9d7f5bc79c2b 100644 --- a/src/cryptography/hazmat/primitives/_cipheralgorithm.py +++ b/src/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing # This exists to break an import cycle. It is normally accessible from the # ciphers module. @@ -21,7 +20,7 @@ def name(self) -> str: @property @abc.abstractmethod - def key_sizes(self) -> typing.FrozenSet[int]: + def key_sizes(self) -> frozenset[int]: """ Valid key sizes for this algorithm in bits """ diff --git a/src/cryptography/hazmat/primitives/_serialization.py b/src/cryptography/hazmat/primitives/_serialization.py index 34f3fbc86026..46157721970b 100644 --- a/src/cryptography/hazmat/primitives/_serialization.py +++ b/src/cryptography/hazmat/primitives/_serialization.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing from cryptography import utils from cryptography.hazmat.primitives.hashes import HashAlgorithm @@ -78,9 +77,9 @@ def __init__( self, format: PrivateFormat, *, - _kdf_rounds: typing.Optional[int] = None, - _hmac_hash: typing.Optional[HashAlgorithm] = None, - _key_cert_algorithm: typing.Optional[PBES] = None, + _kdf_rounds: int | None = None, + _hmac_hash: HashAlgorithm | None = None, + _key_cert_algorithm: PBES | None = None, ) -> None: self._format = format @@ -158,9 +157,9 @@ def __init__( format: PrivateFormat, password: bytes, *, - kdf_rounds: typing.Optional[int], - hmac_hash: typing.Optional[HashAlgorithm], - key_cert_algorithm: typing.Optional[PBES], + kdf_rounds: int | None, + hmac_hash: HashAlgorithm | None, + key_cert_algorithm: PBES | None, ): self._format = format self.password = password diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py index 751bcc402e94..31c9748a91cd 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -5,142 +5,16 @@ from __future__ import annotations import abc -import typing from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +generate_parameters = rust_openssl.dh.generate_parameters -def generate_parameters( - generator: int, key_size: int, backend: typing.Any = None -) -> DHParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - return ossl.generate_dh_parameters(generator, key_size) - - -class DHParameterNumbers: - def __init__(self, p: int, g: int, q: typing.Optional[int] = None) -> None: - if not isinstance(p, int) or not isinstance(g, int): - raise TypeError("p and g must be integers") - if q is not None and not isinstance(q, int): - raise TypeError("q must be integer or None") - - if g < 2: - raise ValueError("DH generator must be 2 or greater") - - if p.bit_length() < rust_openssl.dh.MIN_MODULUS_SIZE: - raise ValueError( - f"p (modulus) must be at least " - f"{rust_openssl.dh.MIN_MODULUS_SIZE}-bit" - ) - - self._p = p - self._g = g - self._q = q - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DHParameterNumbers): - return NotImplemented - - return ( - self._p == other._p and self._g == other._g and self._q == other._q - ) - - def parameters(self, backend: typing.Any = None) -> DHParameters: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dh_parameter_numbers(self) - - @property - def p(self) -> int: - return self._p - - @property - def g(self) -> int: - return self._g - - @property - def q(self) -> typing.Optional[int]: - return self._q - - -class DHPublicNumbers: - def __init__(self, y: int, parameter_numbers: DHParameterNumbers) -> None: - if not isinstance(y, int): - raise TypeError("y must be an integer.") - - if not isinstance(parameter_numbers, DHParameterNumbers): - raise TypeError( - "parameters must be an instance of DHParameterNumbers." - ) - - self._y = y - self._parameter_numbers = parameter_numbers - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DHPublicNumbers): - return NotImplemented - - return ( - self._y == other._y - and self._parameter_numbers == other._parameter_numbers - ) - - def public_key(self, backend: typing.Any = None) -> DHPublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dh_public_numbers(self) - - @property - def y(self) -> int: - return self._y - - @property - def parameter_numbers(self) -> DHParameterNumbers: - return self._parameter_numbers - - -class DHPrivateNumbers: - def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: - if not isinstance(x, int): - raise TypeError("x must be an integer.") - - if not isinstance(public_numbers, DHPublicNumbers): - raise TypeError( - "public_numbers must be an instance of " "DHPublicNumbers." - ) - - self._x = x - self._public_numbers = public_numbers - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DHPrivateNumbers): - return NotImplemented - - return ( - self._x == other._x - and self._public_numbers == other._public_numbers - ) - - def private_key(self, backend: typing.Any = None) -> DHPrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dh_private_numbers(self) - - @property - def public_numbers(self) -> DHPublicNumbers: - return self._public_numbers - - @property - def x(self) -> int: - return self._x +DHPrivateNumbers = rust_openssl.dh.DHPrivateNumbers +DHPublicNumbers = rust_openssl.dh.DHPublicNumbers +DHParameterNumbers = rust_openssl.dh.DHParameterNumbers class DHParameters(metaclass=abc.ABCMeta): diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py index a8c52de4fb49..6dd34c0e09b0 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -54,7 +54,7 @@ def parameters(self) -> DSAParameters: def sign( self, data: bytes, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> bytes: """ Signs the data @@ -117,7 +117,7 @@ def verify( self, signature: bytes, data: bytes, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> None: """ Verifies the signature of the data. @@ -133,167 +133,22 @@ def __eq__(self, other: object) -> bool: DSAPublicKeyWithSerialization = DSAPublicKey DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) - -class DSAParameterNumbers: - def __init__(self, p: int, q: int, g: int): - if ( - not isinstance(p, int) - or not isinstance(q, int) - or not isinstance(g, int) - ): - raise TypeError( - "DSAParameterNumbers p, q, and g arguments must be integers." - ) - - self._p = p - self._q = q - self._g = g - - @property - def p(self) -> int: - return self._p - - @property - def q(self) -> int: - return self._q - - @property - def g(self) -> int: - return self._g - - def parameters(self, backend: typing.Any = None) -> DSAParameters: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dsa_parameter_numbers(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DSAParameterNumbers): - return NotImplemented - - return self.p == other.p and self.q == other.q and self.g == other.g - - def __repr__(self) -> str: - return ( - "".format(self=self) - ) - - -class DSAPublicNumbers: - def __init__(self, y: int, parameter_numbers: DSAParameterNumbers): - if not isinstance(y, int): - raise TypeError("DSAPublicNumbers y argument must be an integer.") - - if not isinstance(parameter_numbers, DSAParameterNumbers): - raise TypeError( - "parameter_numbers must be a DSAParameterNumbers instance." - ) - - self._y = y - self._parameter_numbers = parameter_numbers - - @property - def y(self) -> int: - return self._y - - @property - def parameter_numbers(self) -> DSAParameterNumbers: - return self._parameter_numbers - - def public_key(self, backend: typing.Any = None) -> DSAPublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dsa_public_numbers(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DSAPublicNumbers): - return NotImplemented - - return ( - self.y == other.y - and self.parameter_numbers == other.parameter_numbers - ) - - def __repr__(self) -> str: - return ( - "".format(self=self) - ) - - -class DSAPrivateNumbers: - def __init__(self, x: int, public_numbers: DSAPublicNumbers): - if not isinstance(x, int): - raise TypeError("DSAPrivateNumbers x argument must be an integer.") - - if not isinstance(public_numbers, DSAPublicNumbers): - raise TypeError( - "public_numbers must be a DSAPublicNumbers instance." - ) - self._public_numbers = public_numbers - self._x = x - - @property - def x(self) -> int: - return self._x - - @property - def public_numbers(self) -> DSAPublicNumbers: - return self._public_numbers - - def private_key(self, backend: typing.Any = None) -> DSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dsa_private_numbers(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DSAPrivateNumbers): - return NotImplemented - - return ( - self.x == other.x and self.public_numbers == other.public_numbers - ) +DSAPrivateNumbers = rust_openssl.dsa.DSAPrivateNumbers +DSAPublicNumbers = rust_openssl.dsa.DSAPublicNumbers +DSAParameterNumbers = rust_openssl.dsa.DSAParameterNumbers def generate_parameters( key_size: int, backend: typing.Any = None ) -> DSAParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl + if key_size not in (1024, 2048, 3072, 4096): + raise ValueError("Key size must be 1024, 2048, 3072, or 4096 bits.") - return ossl.generate_dsa_parameters(key_size) + return rust_openssl.dsa.generate_parameters(key_size) def generate_private_key( key_size: int, backend: typing.Any = None ) -> DSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.generate_dsa_private_key_and_parameters(key_size) - - -def _check_dsa_parameters(parameters: DSAParameterNumbers) -> None: - if parameters.p.bit_length() not in [1024, 2048, 3072, 4096]: - raise ValueError( - "p must be exactly 1024, 2048, 3072, or 4096 bits long" - ) - if parameters.q.bit_length() not in [160, 224, 256]: - raise ValueError("q must be exactly 160, 224, or 256 bits long") - - if not (1 < parameters.g < parameters.p): - raise ValueError("g, p don't satisfy 1 < g < p.") - - -def _check_dsa_private_numbers(numbers: DSAPrivateNumbers) -> None: - parameters = numbers.public_numbers.parameter_numbers - _check_dsa_parameters(parameters) - if numbers.x <= 0 or numbers.x >= parameters.q: - raise ValueError("x must be > 0 and < q.") - - if numbers.public_numbers.y != pow(parameters.g, numbers.x, parameters.p): - raise ValueError("y must be equal to (g ** x % p).") + parameters = generate_parameters(key_size) + return parameters.generate_private_key() diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index ddfaabf4f3e4..b612b40149d4 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -9,6 +9,7 @@ from cryptography import utils from cryptography.hazmat._oid import ObjectIdentifier +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 @@ -56,7 +57,7 @@ class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): @abc.abstractmethod def algorithm( self, - ) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]: + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: """ The digest algorithm used with this signature. """ @@ -121,6 +122,7 @@ def private_bytes( EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey +EllipticCurvePrivateKey.register(rust_openssl.ec.ECPrivateKey) class EllipticCurvePublicKey(metaclass=abc.ABCMeta): @@ -171,18 +173,13 @@ def from_encoded_point( ) -> EllipticCurvePublicKey: utils._check_bytes("data", data) - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must be an EllipticCurve instance") - if len(data) == 0: raise ValueError("data must not be an empty byte string") if data[0] not in [0x02, 0x03, 0x04]: raise ValueError("Unsupported elliptic curve point type") - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_elliptic_curve_public_bytes(curve, data) + return rust_openssl.ec.from_public_bytes(curve, data) @abc.abstractmethod def __eq__(self, other: object) -> bool: @@ -192,6 +189,10 @@ def __eq__(self, other: object) -> bool: EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey +EllipticCurvePublicKey.register(rust_openssl.ec.ECPublicKey) + +EllipticCurvePrivateNumbers = rust_openssl.ec.EllipticCurvePrivateNumbers +EllipticCurvePublicNumbers = rust_openssl.ec.EllipticCurvePublicNumbers class SECT571R1(EllipticCurve): @@ -289,51 +290,46 @@ class BrainpoolP512R1(EllipticCurve): key_size = 512 -_CURVE_TYPES: typing.Dict[str, typing.Type[EllipticCurve]] = { - "prime192v1": SECP192R1, - "prime256v1": SECP256R1, - "secp192r1": SECP192R1, - "secp224r1": SECP224R1, - "secp256r1": SECP256R1, - "secp384r1": SECP384R1, - "secp521r1": SECP521R1, - "secp256k1": SECP256K1, - "sect163k1": SECT163K1, - "sect233k1": SECT233K1, - "sect283k1": SECT283K1, - "sect409k1": SECT409K1, - "sect571k1": SECT571K1, - "sect163r2": SECT163R2, - "sect233r1": SECT233R1, - "sect283r1": SECT283R1, - "sect409r1": SECT409R1, - "sect571r1": SECT571R1, - "brainpoolP256r1": BrainpoolP256R1, - "brainpoolP384r1": BrainpoolP384R1, - "brainpoolP512r1": BrainpoolP512R1, +_CURVE_TYPES: dict[str, EllipticCurve] = { + "prime192v1": SECP192R1(), + "prime256v1": SECP256R1(), + "secp192r1": SECP192R1(), + "secp224r1": SECP224R1(), + "secp256r1": SECP256R1(), + "secp384r1": SECP384R1(), + "secp521r1": SECP521R1(), + "secp256k1": SECP256K1(), + "sect163k1": SECT163K1(), + "sect233k1": SECT233K1(), + "sect283k1": SECT283K1(), + "sect409k1": SECT409K1(), + "sect571k1": SECT571K1(), + "sect163r2": SECT163R2(), + "sect233r1": SECT233R1(), + "sect283r1": SECT283R1(), + "sect409r1": SECT409R1(), + "sect571r1": SECT571R1(), + "brainpoolP256r1": BrainpoolP256R1(), + "brainpoolP384r1": BrainpoolP384R1(), + "brainpoolP512r1": BrainpoolP512R1(), } class ECDSA(EllipticCurveSignatureAlgorithm): def __init__( self, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ): self._algorithm = algorithm @property def algorithm( self, - ) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]: + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: return self._algorithm -def generate_private_key( - curve: EllipticCurve, backend: typing.Any = None -) -> EllipticCurvePrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.generate_elliptic_curve_private_key(curve) +generate_private_key = rust_openssl.ec.generate_private_key def derive_private_key( @@ -341,116 +337,13 @@ def derive_private_key( curve: EllipticCurve, backend: typing.Any = None, ) -> EllipticCurvePrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - if not isinstance(private_value, int): raise TypeError("private_value must be an integer type.") if private_value <= 0: raise ValueError("private_value must be a positive integer.") - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must provide the EllipticCurve interface.") - - return ossl.derive_elliptic_curve_private_key(private_value, curve) - - -class EllipticCurvePublicNumbers: - def __init__(self, x: int, y: int, curve: EllipticCurve): - if not isinstance(x, int) or not isinstance(y, int): - raise TypeError("x and y must be integers.") - - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must provide the EllipticCurve interface.") - - self._y = y - self._x = x - self._curve = curve - - def public_key(self, backend: typing.Any = None) -> EllipticCurvePublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_elliptic_curve_public_numbers(self) - - @property - def curve(self) -> EllipticCurve: - return self._curve - - @property - def x(self) -> int: - return self._x - - @property - def y(self) -> int: - return self._y - - def __eq__(self, other: object) -> bool: - if not isinstance(other, EllipticCurvePublicNumbers): - return NotImplemented - - return ( - self.x == other.x - and self.y == other.y - and self.curve.name == other.curve.name - and self.curve.key_size == other.curve.key_size - ) - - def __hash__(self) -> int: - return hash((self.x, self.y, self.curve.name, self.curve.key_size)) - - def __repr__(self) -> str: - return ( - "".format(self) - ) - - -class EllipticCurvePrivateNumbers: - def __init__( - self, private_value: int, public_numbers: EllipticCurvePublicNumbers - ): - if not isinstance(private_value, int): - raise TypeError("private_value must be an integer.") - - if not isinstance(public_numbers, EllipticCurvePublicNumbers): - raise TypeError( - "public_numbers must be an EllipticCurvePublicNumbers " - "instance." - ) - - self._private_value = private_value - self._public_numbers = public_numbers - - def private_key( - self, backend: typing.Any = None - ) -> EllipticCurvePrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_elliptic_curve_private_numbers(self) - - @property - def private_value(self) -> int: - return self._private_value - - @property - def public_numbers(self) -> EllipticCurvePublicNumbers: - return self._public_numbers - - def __eq__(self, other: object) -> bool: - if not isinstance(other, EllipticCurvePrivateNumbers): - return NotImplemented - - return ( - self.private_value == other.private_value - and self.public_numbers == other.public_numbers - ) - - def __hash__(self) -> int: - return hash((self.private_value, self.public_numbers)) + return rust_openssl.ec.derive_private_key(private_value, curve) class ECDH: @@ -480,7 +373,7 @@ class ECDH: } -def get_curve_for_oid(oid: ObjectIdentifier) -> typing.Type[EllipticCurve]: +def get_curve_for_oid(oid: ObjectIdentifier) -> type[EllipticCurve]: try: return _OID_TO_CURVE[oid] except KeyError: diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py index f26e54d24ec5..3a26185d7dbc 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -22,7 +22,7 @@ def from_public_bytes(cls, data: bytes) -> Ed25519PublicKey: _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed25519_load_public_bytes(data) + return rust_openssl.ed25519.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -54,8 +54,7 @@ def __eq__(self, other: object) -> bool: """ -if hasattr(rust_openssl, "ed25519"): - Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) +Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) class Ed25519PrivateKey(metaclass=abc.ABCMeta): @@ -69,7 +68,7 @@ def generate(cls) -> Ed25519PrivateKey: _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed25519_generate_key() + return rust_openssl.ed25519.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> Ed25519PrivateKey: @@ -81,7 +80,7 @@ def from_private_bytes(cls, data: bytes) -> Ed25519PrivateKey: _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed25519_load_private_bytes(data) + return rust_openssl.ed25519.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> Ed25519PublicKey: @@ -114,5 +113,4 @@ def sign(self, data: bytes) -> bytes: """ -if hasattr(rust_openssl, "x25519"): - Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) +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 a9a34b251b01..78c82c4a3c45 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -22,7 +22,7 @@ def from_public_bytes(cls, data: bytes) -> Ed448PublicKey: _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed448_load_public_bytes(data) + return rust_openssl.ed448.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -68,7 +68,8 @@ def generate(cls) -> Ed448PrivateKey: "ed448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed448_generate_key() + + return rust_openssl.ed448.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> Ed448PrivateKey: @@ -80,7 +81,7 @@ def from_private_bytes(cls, data: bytes) -> Ed448PrivateKey: _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed448_load_private_bytes(data) + return rust_openssl.ed448.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> Ed448PublicKey: diff --git a/src/cryptography/hazmat/primitives/asymmetric/padding.py b/src/cryptography/hazmat/primitives/asymmetric/padding.py index 7198808effd0..b4babf44f79b 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/src/cryptography/hazmat/primitives/asymmetric/padding.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives._asymmetric import ( @@ -35,12 +34,12 @@ class PSS(AsymmetricPadding): AUTO = _Auto() DIGEST_LENGTH = _DigestLength() name = "EMSA-PSS" - _salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength] + _salt_length: int | _MaxLength | _Auto | _DigestLength def __init__( self, mgf: MGF, - salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength], + salt_length: int | _MaxLength | _Auto | _DigestLength, ) -> None: self._mgf = mgf @@ -57,6 +56,10 @@ def __init__( self._salt_length = salt_length + @property + def mgf(self) -> MGF: + return self._mgf + class OAEP(AsymmetricPadding): name = "EME-OAEP" @@ -65,7 +68,7 @@ def __init__( self, mgf: MGF, algorithm: hashes.HashAlgorithm, - label: typing.Optional[bytes], + label: bytes | None, ): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of hashes.HashAlgorithm.") @@ -74,6 +77,14 @@ def __init__( self._algorithm = algorithm self._label = label + @property + def algorithm(self) -> hashes.HashAlgorithm: + return self._algorithm + + @property + def mgf(self) -> MGF: + return self._mgf + class MGF(metaclass=abc.ABCMeta): _algorithm: hashes.HashAlgorithm @@ -90,7 +101,7 @@ def __init__(self, algorithm: hashes.HashAlgorithm): def calculate_max_pss_salt_length( - key: typing.Union[rsa.RSAPrivateKey, rsa.RSAPublicKey], + key: rsa.RSAPrivateKey | rsa.RSAPublicKey, hash_algorithm: hashes.HashAlgorithm, ) -> int: if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index b740f01f7c4c..6420434d82b7 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -8,6 +8,7 @@ import typing from math import gcd +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding from cryptography.hazmat.primitives.asymmetric import utils as asym_utils @@ -38,7 +39,7 @@ def sign( self, data: bytes, padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> bytes: """ Signs the data. @@ -63,6 +64,7 @@ def private_bytes( RSAPrivateKeyWithSerialization = RSAPrivateKey +RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey) class RSAPublicKey(metaclass=abc.ABCMeta): @@ -101,7 +103,7 @@ def verify( signature: bytes, data: bytes, padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> None: """ Verifies the signature of the data. @@ -112,7 +114,7 @@ def recover_data_from_signature( self, signature: bytes, padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], + algorithm: hashes.HashAlgorithm | None, ) -> bytes: """ Recovers the original data from the signature. @@ -126,6 +128,10 @@ def __eq__(self, other: object) -> bool: RSAPublicKeyWithSerialization = RSAPublicKey +RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey) + +RSAPrivateNumbers = rust_openssl.rsa.RSAPrivateNumbers +RSAPublicNumbers = rust_openssl.rsa.RSAPublicNumbers def generate_private_key( @@ -133,10 +139,8 @@ def generate_private_key( key_size: int, backend: typing.Any = None, ) -> RSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - _verify_rsa_parameters(public_exponent, key_size) - return ossl.generate_rsa_private_key(public_exponent, key_size) + return rust_openssl.rsa.generate_private_key(public_exponent, key_size) def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: @@ -150,64 +154,6 @@ def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: raise ValueError("key_size must be at least 512-bits.") -def _check_private_key_components( - p: int, - q: int, - private_exponent: int, - dmp1: int, - dmq1: int, - iqmp: int, - public_exponent: int, - modulus: int, -) -> None: - if modulus < 3: - raise ValueError("modulus must be >= 3.") - - if p >= modulus: - raise ValueError("p must be < modulus.") - - if q >= modulus: - raise ValueError("q must be < modulus.") - - if dmp1 >= modulus: - raise ValueError("dmp1 must be < modulus.") - - if dmq1 >= modulus: - raise ValueError("dmq1 must be < modulus.") - - if iqmp >= modulus: - raise ValueError("iqmp must be < modulus.") - - if private_exponent >= modulus: - raise ValueError("private_exponent must be < modulus.") - - if public_exponent < 3 or public_exponent >= modulus: - raise ValueError("public_exponent must be >= 3 and < modulus.") - - if public_exponent & 1 == 0: - raise ValueError("public_exponent must be odd.") - - if dmp1 & 1 == 0: - raise ValueError("dmp1 must be odd.") - - if dmq1 & 1 == 0: - raise ValueError("dmq1 must be odd.") - - if p * q != modulus: - raise ValueError("p*q must equal modulus.") - - -def _check_public_key_components(e: int, n: int) -> None: - if n < 3: - raise ValueError("n must be >= 3.") - - if e < 3 or e >= n: - raise ValueError("e must be >= 3 and < n.") - - if e & 1 == 0: - raise ValueError("e must be odd.") - - def _modinv(e: int, m: int) -> int: """ Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1 @@ -250,9 +196,7 @@ def rsa_crt_dmq1(private_exponent: int, q: int) -> int: _MAX_RECOVERY_ATTEMPTS = 1000 -def rsa_recover_prime_factors( - n: int, e: int, d: int -) -> typing.Tuple[int, int]: +def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: """ Compute factors p and q from the private exponent d. We assume that n has no more than two factors. This function is adapted from code in PyCrypto. @@ -293,147 +237,3 @@ def rsa_recover_prime_factors( assert r == 0 p, q = sorted((p, q), reverse=True) return (p, q) - - -class RSAPrivateNumbers: - def __init__( - self, - p: int, - q: int, - d: int, - dmp1: int, - dmq1: int, - iqmp: int, - public_numbers: RSAPublicNumbers, - ): - if ( - not isinstance(p, int) - or not isinstance(q, int) - or not isinstance(d, int) - or not isinstance(dmp1, int) - or not isinstance(dmq1, int) - or not isinstance(iqmp, int) - ): - raise TypeError( - "RSAPrivateNumbers p, q, d, dmp1, dmq1, iqmp arguments must" - " all be an integers." - ) - - if not isinstance(public_numbers, RSAPublicNumbers): - raise TypeError( - "RSAPrivateNumbers public_numbers must be an RSAPublicNumbers" - " instance." - ) - - self._p = p - self._q = q - self._d = d - self._dmp1 = dmp1 - self._dmq1 = dmq1 - self._iqmp = iqmp - self._public_numbers = public_numbers - - @property - def p(self) -> int: - return self._p - - @property - def q(self) -> int: - return self._q - - @property - def d(self) -> int: - return self._d - - @property - def dmp1(self) -> int: - return self._dmp1 - - @property - def dmq1(self) -> int: - return self._dmq1 - - @property - def iqmp(self) -> int: - return self._iqmp - - @property - def public_numbers(self) -> RSAPublicNumbers: - return self._public_numbers - - def private_key( - self, - backend: typing.Any = None, - *, - unsafe_skip_rsa_key_validation: bool = False, - ) -> RSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_rsa_private_numbers( - self, unsafe_skip_rsa_key_validation - ) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, RSAPrivateNumbers): - return NotImplemented - - return ( - self.p == other.p - and self.q == other.q - and self.d == other.d - and self.dmp1 == other.dmp1 - and self.dmq1 == other.dmq1 - and self.iqmp == other.iqmp - and self.public_numbers == other.public_numbers - ) - - def __hash__(self) -> int: - return hash( - ( - self.p, - self.q, - self.d, - self.dmp1, - self.dmq1, - self.iqmp, - self.public_numbers, - ) - ) - - -class RSAPublicNumbers: - def __init__(self, e: int, n: int): - if not isinstance(e, int) or not isinstance(n, int): - raise TypeError("RSAPublicNumbers arguments must be integers.") - - self._e = e - self._n = n - - @property - def e(self) -> int: - return self._e - - @property - def n(self) -> int: - return self._n - - def public_key(self, backend: typing.Any = None) -> RSAPublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_rsa_public_numbers(self) - - def __repr__(self) -> str: - return "".format(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, RSAPublicNumbers): - return NotImplemented - - return self.e == other.e and self.n == other.n - - def __hash__(self) -> int: - return hash((self.e, self.n)) diff --git a/src/cryptography/hazmat/primitives/asymmetric/x25519.py b/src/cryptography/hazmat/primitives/asymmetric/x25519.py index 699054c9689b..0cfa36e346ad 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -22,7 +22,7 @@ def from_public_bytes(cls, data: bytes) -> X25519PublicKey: _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x25519_load_public_bytes(data) + return rust_openssl.x25519.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -48,9 +48,7 @@ def __eq__(self, other: object) -> bool: """ -# For LibreSSL -if hasattr(rust_openssl, "x25519"): - X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) +X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) class X25519PrivateKey(metaclass=abc.ABCMeta): @@ -63,7 +61,7 @@ def generate(cls) -> X25519PrivateKey: "X25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x25519_generate_key() + return rust_openssl.x25519.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> X25519PrivateKey: @@ -75,12 +73,12 @@ def from_private_bytes(cls, data: bytes) -> X25519PrivateKey: _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x25519_load_private_bytes(data) + return rust_openssl.x25519.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> X25519PublicKey: """ - Returns the public key assosciated with this private key + Returns the public key associated with this private key """ @abc.abstractmethod @@ -108,6 +106,4 @@ def exchange(self, peer_public_key: X25519PublicKey) -> bytes: """ -# For LibreSSL -if hasattr(rust_openssl, "x25519"): - X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) +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 abf7848550b8..86086ab44855 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x448.py @@ -22,7 +22,7 @@ def from_public_bytes(cls, data: bytes) -> X448PublicKey: _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x448_load_public_bytes(data) + return rust_openssl.x448.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -62,7 +62,8 @@ def generate(cls) -> X448PrivateKey: "X448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x448_generate_key() + + return rust_openssl.x448.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> X448PrivateKey: @@ -74,7 +75,7 @@ def from_private_bytes(cls, data: bytes) -> X448PrivateKey: _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x448_load_private_bytes(data) + return rust_openssl.x448.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> X448PublicKey: diff --git a/src/cryptography/hazmat/primitives/ciphers/aead.py b/src/cryptography/hazmat/primitives/ciphers/aead.py index 957b2d221b62..40f1b9b74459 100644 --- a/src/cryptography/hazmat/primitives/ciphers/aead.py +++ b/src/cryptography/hazmat/primitives/ciphers/aead.py @@ -5,85 +5,25 @@ from __future__ import annotations import os -import typing from cryptography import exceptions, utils from cryptography.hazmat.backends.openssl import aead from cryptography.hazmat.backends.openssl.backend import backend -from cryptography.hazmat.bindings._rust import FixedPool +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +__all__ = [ + "ChaCha20Poly1305", + "AESCCM", + "AESGCM", + "AESGCMSIV", + "AESOCB3", + "AESSIV", +] -class ChaCha20Poly1305: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes): - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "ChaCha20Poly1305 is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - utils._check_byteslike("key", key) - - if len(key) != 32: - raise ValueError("ChaCha20Poly1305 key must be 32 bytes.") - - self._key = key - self._pool = FixedPool(self._create_fn) - - @classmethod - def generate_key(cls) -> bytes: - return os.urandom(32) - - def _create_fn(self): - return aead._aead_create_ctx(backend, self, self._key) - - def encrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - self._check_params(nonce, data, associated_data) - with self._pool.acquire() as ctx: - return aead._encrypt( - backend, self, nonce, data, [associated_data], 16, ctx - ) - - def decrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - with self._pool.acquire() as ctx: - return aead._decrypt( - backend, self, nonce, data, [associated_data], 16, ctx - ) - - def _check_params( - self, - nonce: bytes, - data: bytes, - associated_data: bytes, - ) -> None: - utils._check_byteslike("nonce", nonce) - utils._check_byteslike("data", data) - utils._check_byteslike("associated_data", associated_data) - if len(nonce) != 12: - raise ValueError("Nonce must be 12 bytes") +ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305 +AESSIV = rust_openssl.aead.AESSIV +AESOCB3 = rust_openssl.aead.AESOCB3 +AESGCMSIV = rust_openssl.aead.AESGCMSIV class AESCCM: @@ -123,7 +63,7 @@ def encrypt( self, nonce: bytes, data: bytes, - associated_data: typing.Optional[bytes], + associated_data: bytes | None, ) -> bytes: if associated_data is None: associated_data = b"" @@ -144,7 +84,7 @@ def decrypt( self, nonce: bytes, data: bytes, - associated_data: typing.Optional[bytes], + associated_data: bytes | None, ) -> bytes: if associated_data is None: associated_data = b"" @@ -195,7 +135,7 @@ def encrypt( self, nonce: bytes, data: bytes, - associated_data: typing.Optional[bytes], + associated_data: bytes | None, ) -> bytes: if associated_data is None: associated_data = b"" @@ -213,7 +153,7 @@ def decrypt( self, nonce: bytes, data: bytes, - associated_data: typing.Optional[bytes], + associated_data: bytes | None, ) -> bytes: if associated_data is None: associated_data = b"" @@ -232,147 +172,3 @@ def _check_params( utils._check_byteslike("associated_data", associated_data) if len(nonce) < 8 or len(nonce) > 128: raise ValueError("Nonce must be between 8 and 128 bytes") - - -class AESOCB3: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes): - utils._check_byteslike("key", key) - if len(key) not in (16, 24, 32): - raise ValueError("AESOCB3 key must be 128, 192, or 256 bits.") - - self._key = key - - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "OCB3 is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - - @classmethod - def generate_key(cls, bit_length: int) -> bytes: - if not isinstance(bit_length, int): - raise TypeError("bit_length must be an integer") - - if bit_length not in (128, 192, 256): - raise ValueError("bit_length must be 128, 192, or 256") - - return os.urandom(bit_length // 8) - - def encrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - self._check_params(nonce, data, associated_data) - return aead._encrypt(backend, self, nonce, data, [associated_data], 16) - - def decrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - return aead._decrypt(backend, self, nonce, data, [associated_data], 16) - - def _check_params( - self, - nonce: bytes, - data: bytes, - associated_data: bytes, - ) -> None: - utils._check_byteslike("nonce", nonce) - utils._check_byteslike("data", data) - utils._check_byteslike("associated_data", associated_data) - if len(nonce) < 12 or len(nonce) > 15: - raise ValueError("Nonce must be between 12 and 15 bytes") - - -class AESSIV: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes): - utils._check_byteslike("key", key) - if len(key) not in (32, 48, 64): - raise ValueError("AESSIV key must be 256, 384, or 512 bits.") - - self._key = key - - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "AES-SIV is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - - @classmethod - def generate_key(cls, bit_length: int) -> bytes: - if not isinstance(bit_length, int): - raise TypeError("bit_length must be an integer") - - if bit_length not in (256, 384, 512): - raise ValueError("bit_length must be 256, 384, or 512") - - return os.urandom(bit_length // 8) - - def encrypt( - self, - data: bytes, - associated_data: typing.Optional[typing.List[bytes]], - ) -> bytes: - if associated_data is None: - associated_data = [] - - self._check_params(data, associated_data) - - if len(data) > self._MAX_SIZE or any( - len(ad) > self._MAX_SIZE for ad in associated_data - ): - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - return aead._encrypt(backend, self, b"", data, associated_data, 16) - - def decrypt( - self, - data: bytes, - associated_data: typing.Optional[typing.List[bytes]], - ) -> bytes: - if associated_data is None: - associated_data = [] - - self._check_params(data, associated_data) - - return aead._decrypt(backend, self, b"", data, associated_data, 16) - - def _check_params( - self, - data: bytes, - associated_data: typing.List[bytes], - ) -> None: - utils._check_byteslike("data", data) - if len(data) == 0: - raise ValueError("data must not be zero length") - - if not isinstance(associated_data, list): - raise TypeError( - "associated_data must be a list of bytes-like objects or None" - ) - for x in associated_data: - utils._check_byteslike("associated_data elements", x) diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py index 4bfc5d840d67..000bdcba97a4 100644 --- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -18,9 +18,7 @@ def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: # Verify that the key size matches the expected key size if len(key) * 8 not in algorithm.key_sizes: raise ValueError( - "Invalid key size ({}) for {}.".format( - len(key) * 8, algorithm.name - ) + f"Invalid key size ({len(key) * 8}) for {algorithm.name}." ) return key @@ -106,7 +104,7 @@ def key_size(self) -> int: utils.deprecated( Blowfish, __name__, - "Blowfish has been deprecated", + "Blowfish has been deprecated and will be removed in a future release", utils.DeprecatedIn37, name="Blowfish", ) @@ -129,7 +127,7 @@ def key_size(self) -> int: utils.deprecated( CAST5, __name__, - "CAST5 has been deprecated", + "CAST5 has been deprecated and will be removed in a future release", utils.DeprecatedIn37, name="CAST5", ) @@ -164,7 +162,7 @@ def key_size(self) -> int: utils.deprecated( IDEA, __name__, - "IDEA has been deprecated", + "IDEA has been deprecated and will be removed in a future release", utils.DeprecatedIn37, name="IDEA", ) @@ -187,7 +185,7 @@ def key_size(self) -> int: utils.deprecated( SEED, __name__, - "SEED has been deprecated", + "SEED has been deprecated and will be removed in a future release", utils.DeprecatedIn37, name="SEED", ) diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index 38a2ebbe081e..2082df669a23 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -141,9 +141,7 @@ def decryptor(self): def _wrap_ctx( self, ctx: _BackendCipherContext, encrypt: bool - ) -> typing.Union[ - AEADEncryptionContext, AEADDecryptionContext, CipherContext - ]: + ) -> AEADEncryptionContext | AEADDecryptionContext | CipherContext: if isinstance(self.mode, modes.ModeWithAuthenticationTag): if encrypt: return _AEADEncryptionContext(ctx) @@ -165,7 +163,7 @@ def _wrap_ctx( class _CipherContext(CipherContext): - _ctx: typing.Optional[_BackendCipherContext] + _ctx: _BackendCipherContext | None def __init__(self, ctx: _BackendCipherContext) -> None: self._ctx = ctx @@ -189,8 +187,8 @@ def finalize(self) -> bytes: class _AEADCipherContext(AEADCipherContext): - _ctx: typing.Optional[_BackendCipherContext] - _tag: typing.Optional[bytes] + _ctx: _BackendCipherContext | None + _tag: bytes | None def __init__(self, ctx: _BackendCipherContext) -> None: self._ctx = ctx @@ -252,6 +250,11 @@ class _AEADDecryptionContext(_AEADCipherContext, AEADDecryptionContext): def finalize_with_tag(self, tag: bytes) -> bytes: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") + if self._ctx._tag is not None: + raise ValueError( + "tag provided both in mode and in call with finalize_with_tag:" + " tag should only be provided once" + ) data = self._ctx.finalize_with_tag(tag) self._tag = self._ctx.tag self._ctx = None diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index d8ea1888d67b..712ccd3f7945 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons @@ -62,7 +61,7 @@ def nonce(self) -> bytes: class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def tag(self) -> typing.Optional[bytes]: + def tag(self) -> bytes | None: """ The value of the tag supplied to the constructor of this mode. """ @@ -225,7 +224,7 @@ class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): def __init__( self, initialization_vector: bytes, - tag: typing.Optional[bytes] = None, + tag: bytes | None = None, min_tag_length: int = 16, ): # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive @@ -251,7 +250,7 @@ def __init__( self._min_tag_length = min_tag_length @property - def tag(self) -> typing.Optional[bytes]: + def tag(self) -> bytes | None: return self._tag @property diff --git a/src/cryptography/hazmat/primitives/cmac.py b/src/cryptography/hazmat/primitives/cmac.py index 8aa1d791acdd..2c67ce2206e4 100644 --- a/src/cryptography/hazmat/primitives/cmac.py +++ b/src/cryptography/hazmat/primitives/cmac.py @@ -4,62 +4,7 @@ from __future__ import annotations -import typing +from cryptography.hazmat.bindings._rust import openssl as rust_openssl -from cryptography import utils -from cryptography.exceptions import AlreadyFinalized -from cryptography.hazmat.primitives import ciphers - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.cmac import _CMACContext - - -class CMAC: - _ctx: typing.Optional[_CMACContext] - _algorithm: ciphers.BlockCipherAlgorithm - - def __init__( - self, - algorithm: ciphers.BlockCipherAlgorithm, - backend: typing.Any = None, - ctx: typing.Optional[_CMACContext] = None, - ) -> None: - if not isinstance(algorithm, ciphers.BlockCipherAlgorithm): - raise TypeError("Expected instance of BlockCipherAlgorithm.") - self._algorithm = algorithm - - if ctx is None: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - self._ctx = ossl.create_cmac_ctx(self._algorithm) - else: - self._ctx = ctx - - def update(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - utils._check_bytes("data", data) - self._ctx.update(data) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - digest = self._ctx.finalize() - self._ctx = None - return digest - - def verify(self, signature: bytes) -> None: - utils._check_bytes("signature", signature) - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - ctx, self._ctx = self._ctx, None - ctx.verify(signature) - - def copy(self) -> CMAC: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return CMAC(self._algorithm, ctx=self._ctx.copy()) +__all__ = ["CMAC"] +CMAC = rust_openssl.cmac.CMAC diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py index b6a7ff140e68..c5be0c8eadc0 100644 --- a/src/cryptography/hazmat/primitives/hashes.py +++ b/src/cryptography/hazmat/primitives/hashes.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing from cryptography.hazmat.bindings._rust import openssl as rust_openssl @@ -51,7 +50,7 @@ def digest_size(self) -> int: @property @abc.abstractmethod - def block_size(self) -> typing.Optional[int]: + def block_size(self) -> int | None: """ The internal block size of the hash function, or None if the hash function does not use blocks internally (e.g. SHA3). diff --git a/src/cryptography/hazmat/primitives/kdf/concatkdf.py b/src/cryptography/hazmat/primitives/kdf/concatkdf.py index d5ea58a94522..96d9d4c0df5e 100644 --- a/src/cryptography/hazmat/primitives/kdf/concatkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -19,7 +19,7 @@ def _int_to_u32be(n: int) -> bytes: def _common_args_checks( algorithm: hashes.HashAlgorithm, length: int, - otherinfo: typing.Optional[bytes], + otherinfo: bytes | None, ) -> None: max_length = algorithm.digest_size * (2**32 - 1) if length > max_length: @@ -56,7 +56,7 @@ def __init__( self, algorithm: hashes.HashAlgorithm, length: int, - otherinfo: typing.Optional[bytes], + otherinfo: bytes | None, backend: typing.Any = None, ): _common_args_checks(algorithm, length, otherinfo) @@ -87,8 +87,8 @@ def __init__( self, algorithm: hashes.HashAlgorithm, length: int, - salt: typing.Optional[bytes], - otherinfo: typing.Optional[bytes], + salt: bytes | None, + otherinfo: bytes | None, backend: typing.Any = None, ): _common_args_checks(algorithm, length, otherinfo) diff --git a/src/cryptography/hazmat/primitives/kdf/hkdf.py b/src/cryptography/hazmat/primitives/kdf/hkdf.py index d47689443631..ee562d2f4433 100644 --- a/src/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/hkdf.py @@ -17,8 +17,8 @@ def __init__( self, algorithm: hashes.HashAlgorithm, length: int, - salt: typing.Optional[bytes], - info: typing.Optional[bytes], + salt: bytes | None, + info: bytes | None, backend: typing.Any = None, ): self._algorithm = algorithm @@ -51,7 +51,7 @@ def __init__( self, algorithm: hashes.HashAlgorithm, length: int, - info: typing.Optional[bytes], + info: bytes | None, backend: typing.Any = None, ): self._algorithm = algorithm diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py index 967763828f3f..2f41db9260ec 100644 --- a/src/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -40,12 +40,12 @@ def __init__( mode: Mode, length: int, rlen: int, - llen: typing.Optional[int], + llen: int | None, location: CounterLocation, - break_location: typing.Optional[int], - label: typing.Optional[bytes], - context: typing.Optional[bytes], - fixed: typing.Optional[bytes], + break_location: int | None, + label: bytes | None, + context: bytes | None, + fixed: bytes | None, ): assert callable(prf) @@ -181,14 +181,14 @@ def __init__( mode: Mode, length: int, rlen: int, - llen: typing.Optional[int], + llen: int | None, location: CounterLocation, - label: typing.Optional[bytes], - context: typing.Optional[bytes], - fixed: typing.Optional[bytes], + label: bytes | None, + context: bytes | None, + fixed: bytes | None, backend: typing.Any = None, *, - break_location: typing.Optional[int] = None, + break_location: int | None = None, ): if not isinstance(algorithm, hashes.HashAlgorithm): raise UnsupportedAlgorithm( @@ -239,14 +239,14 @@ def __init__( mode: Mode, length: int, rlen: int, - llen: typing.Optional[int], + llen: int | None, location: CounterLocation, - label: typing.Optional[bytes], - context: typing.Optional[bytes], - fixed: typing.Optional[bytes], + label: bytes | None, + context: bytes | None, + fixed: bytes | None, backend: typing.Any = None, *, - break_location: typing.Optional[int] = None, + break_location: int | None = None, ): if not issubclass( algorithm, ciphers.BlockCipherAlgorithm @@ -257,7 +257,7 @@ def __init__( ) self._algorithm = algorithm - self._cipher: typing.Optional[ciphers.BlockCipherAlgorithm] = None + self._cipher: ciphers.BlockCipherAlgorithm | None = None self._deriver = _KBKDFDeriver( self._prf, diff --git a/src/cryptography/hazmat/primitives/kdf/x963kdf.py b/src/cryptography/hazmat/primitives/kdf/x963kdf.py index 17acc5174bb0..6e38366a996f 100644 --- a/src/cryptography/hazmat/primitives/kdf/x963kdf.py +++ b/src/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -21,7 +21,7 @@ def __init__( self, algorithm: hashes.HashAlgorithm, length: int, - sharedinfo: typing.Optional[bytes], + sharedinfo: bytes | None, backend: typing.Any = None, ): max_len = algorithm.digest_size * (2**32 - 1) diff --git a/src/cryptography/hazmat/primitives/keywrap.py b/src/cryptography/hazmat/primitives/keywrap.py index 59b0326c2a86..3ee152b7903a 100644 --- a/src/cryptography/hazmat/primitives/keywrap.py +++ b/src/cryptography/hazmat/primitives/keywrap.py @@ -15,7 +15,7 @@ def _wrap_core( wrapping_key: bytes, a: bytes, - r: typing.List[bytes], + r: list[bytes], ) -> bytes: # RFC 3394 Key Wrap - 2.2.1 (index method) encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() @@ -58,8 +58,8 @@ def aes_key_wrap( def _unwrap_core( wrapping_key: bytes, a: bytes, - r: typing.List[bytes], -) -> typing.Tuple[bytes, typing.List[bytes]]: + r: list[bytes], +) -> tuple[bytes, list[bytes]]: # Implement RFC 3394 Key Unwrap - 2.2.2 (index method) decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() n = len(r) diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index fde3094b00ae..baceaf381880 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -38,8 +38,8 @@ def _byte_padding_check(block_size: int) -> None: def _byte_padding_update( - buffer_: typing.Optional[bytes], data: bytes, block_size: int -) -> typing.Tuple[bytes, bytes]: + buffer_: bytes | None, data: bytes, block_size: int +) -> tuple[bytes, bytes]: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") @@ -56,7 +56,7 @@ def _byte_padding_update( def _byte_padding_pad( - buffer_: typing.Optional[bytes], + buffer_: bytes | None, block_size: int, paddingfn: typing.Callable[[int], bytes], ) -> bytes: @@ -68,8 +68,8 @@ def _byte_padding_pad( def _byte_unpadding_update( - buffer_: typing.Optional[bytes], data: bytes, block_size: int -) -> typing.Tuple[bytes, bytes]: + buffer_: bytes | None, data: bytes, block_size: int +) -> tuple[bytes, bytes]: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") @@ -86,7 +86,7 @@ def _byte_unpadding_update( def _byte_unpadding_check( - buffer_: typing.Optional[bytes], + buffer_: bytes | None, block_size: int, checkfn: typing.Callable[[bytes], int], ) -> bytes: @@ -118,7 +118,7 @@ def unpadder(self) -> PaddingContext: class _PKCS7PaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] + _buffer: bytes | None def __init__(self, block_size: int): self.block_size = block_size @@ -143,7 +143,7 @@ def finalize(self) -> bytes: class _PKCS7UnpaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] + _buffer: bytes | None def __init__(self, block_size: int): self.block_size = block_size @@ -177,7 +177,7 @@ def unpadder(self) -> PaddingContext: class _ANSIX923PaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] + _buffer: bytes | None def __init__(self, block_size: int): self.block_size = block_size @@ -202,7 +202,7 @@ def finalize(self) -> bytes: class _ANSIX923UnpaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] + _buffer: bytes | None def __init__(self, block_size: int): self.block_size = block_size diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py index 18a96ccfd5cd..e7c998b7f35b 100644 --- a/src/cryptography/hazmat/primitives/serialization/base.py +++ b/src/cryptography/hazmat/primitives/serialization/base.py @@ -2,72 +2,13 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import annotations +from cryptography.hazmat.bindings._rust import openssl as rust_openssl -import typing +load_pem_private_key = rust_openssl.keys.load_pem_private_key +load_der_private_key = rust_openssl.keys.load_der_private_key -from cryptography.hazmat.primitives.asymmetric import dh -from cryptography.hazmat.primitives.asymmetric.types import ( - PrivateKeyTypes, - PublicKeyTypes, -) +load_pem_public_key = rust_openssl.keys.load_pem_public_key +load_der_public_key = rust_openssl.keys.load_der_public_key - -def load_pem_private_key( - data: bytes, - password: typing.Optional[bytes], - backend: typing.Any = None, - *, - unsafe_skip_rsa_key_validation: bool = False, -) -> PrivateKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pem_private_key( - data, password, unsafe_skip_rsa_key_validation - ) - - -def load_pem_public_key( - data: bytes, backend: typing.Any = None -) -> PublicKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pem_public_key(data) - - -def load_pem_parameters( - data: bytes, backend: typing.Any = None -) -> dh.DHParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pem_parameters(data) - - -def load_der_private_key( - data: bytes, - password: typing.Optional[bytes], - backend: typing.Any = None, - *, - unsafe_skip_rsa_key_validation: bool = False, -) -> PrivateKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_der_private_key( - data, password, unsafe_skip_rsa_key_validation - ) - - -def load_der_public_key( - data: bytes, backend: typing.Any = None -) -> PublicKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_der_public_key(data) - - -def load_der_parameters( - data: bytes, backend: typing.Any = None -) -> dh.DHParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_der_parameters(data) +load_pem_parameters = rust_openssl.dh.from_pem_parameters +load_der_parameters = rust_openssl.dh.from_der_parameters diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py index 27133a3fa851..006a248bd244 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -41,7 +41,7 @@ class PKCS12Certificate: def __init__( self, cert: x509.Certificate, - friendly_name: typing.Optional[bytes], + friendly_name: bytes | None, ): if not isinstance(cert, x509.Certificate): raise TypeError("Expecting x509.Certificate object") @@ -51,7 +51,7 @@ def __init__( self._friendly_name = friendly_name @property - def friendly_name(self) -> typing.Optional[bytes]: + def friendly_name(self) -> bytes | None: return self._friendly_name @property @@ -79,9 +79,9 @@ def __repr__(self) -> str: class PKCS12KeyAndCertificates: def __init__( self, - key: typing.Optional[PrivateKeyTypes], - cert: typing.Optional[PKCS12Certificate], - additional_certs: typing.List[PKCS12Certificate], + key: PrivateKeyTypes | None, + cert: PKCS12Certificate | None, + additional_certs: list[PKCS12Certificate], ): if key is not None and not isinstance( key, @@ -112,15 +112,15 @@ def __init__( self._additional_certs = additional_certs @property - def key(self) -> typing.Optional[PrivateKeyTypes]: + def key(self) -> PrivateKeyTypes | None: return self._key @property - def cert(self) -> typing.Optional[PKCS12Certificate]: + def cert(self) -> PKCS12Certificate | None: return self._cert @property - def additional_certs(self) -> typing.List[PKCS12Certificate]: + def additional_certs(self) -> list[PKCS12Certificate]: return self._additional_certs def __eq__(self, other: object) -> bool: @@ -145,12 +145,12 @@ def __repr__(self) -> str: def load_key_and_certificates( data: bytes, - password: typing.Optional[bytes], + password: bytes | None, backend: typing.Any = None, -) -> typing.Tuple[ - typing.Optional[PrivateKeyTypes], - typing.Optional[x509.Certificate], - typing.List[x509.Certificate], +) -> tuple[ + PrivateKeyTypes | None, + x509.Certificate | None, + list[x509.Certificate], ]: from cryptography.hazmat.backends.openssl.backend import backend as ossl @@ -159,7 +159,7 @@ def load_key_and_certificates( def load_pkcs12( data: bytes, - password: typing.Optional[bytes], + password: bytes | None, backend: typing.Any = None, ) -> PKCS12KeyAndCertificates: from cryptography.hazmat.backends.openssl.backend import backend as ossl @@ -174,10 +174,10 @@ def load_pkcs12( def serialize_key_and_certificates( - name: typing.Optional[bytes], - key: typing.Optional[PKCS12PrivateKeyTypes], - cert: typing.Optional[x509.Certificate], - cas: typing.Optional[typing.Iterable[_PKCS12CATypes]], + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: typing.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 9998bcaa1131..bae35c5f5988 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -14,28 +14,14 @@ from cryptography import utils, x509 from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa from cryptography.utils import _check_byteslike +load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates -def load_pem_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]: - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_pem_pkcs7_certificates(data) - - -def load_der_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]: - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_der_pkcs7_certificates(data) - - -def serialize_certificates( - certs: typing.List[x509.Certificate], - encoding: serialization.Encoding, -) -> bytes: - return rust_pkcs7.serialize_certificates(certs, encoding) +load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates +serialize_certificates = rust_pkcs7.serialize_certificates PKCS7HashTypes = typing.Union[ hashes.SHA224, @@ -61,15 +47,16 @@ class PKCS7Options(utils.Enum): class PKCS7SignatureBuilder: def __init__( self, - data: typing.Optional[bytes] = None, - signers: typing.List[ - typing.Tuple[ + data: bytes | None = None, + signers: list[ + tuple[ x509.Certificate, PKCS7PrivateKeyTypes, PKCS7HashTypes, + padding.PSS | padding.PKCS1v15 | None, ] ] = [], - additional_certs: typing.List[x509.Certificate] = [], + additional_certs: list[x509.Certificate] = [], ): self._data = data self._signers = signers @@ -87,6 +74,8 @@ def add_signer( certificate: x509.Certificate, private_key: PKCS7PrivateKeyTypes, hash_algorithm: PKCS7HashTypes, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> PKCS7SignatureBuilder: if not isinstance( hash_algorithm, @@ -109,9 +98,18 @@ def add_signer( ): raise TypeError("Only RSA & EC keys are supported at this time.") + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + return PKCS7SignatureBuilder( self._data, - self._signers + [(certificate, private_key, hash_algorithm)], + [ + *self._signers, + (certificate, private_key, hash_algorithm, rsa_padding), + ], ) def add_certificate( @@ -121,7 +119,7 @@ def add_certificate( raise TypeError("certificate must be a x509.Certificate") return PKCS7SignatureBuilder( - self._data, self._signers, self._additional_certs + [certificate] + self._data, self._signers, [*self._additional_certs, certificate] ) def sign( diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index 7725c83543e8..f33edd55e0ea 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -87,21 +87,17 @@ def _bcrypt_kdf( @dataclass class _SSHCipher: - alg: typing.Type[algorithms.AES] + alg: type[algorithms.AES] key_len: int - mode: typing.Union[ - typing.Type[modes.CTR], - typing.Type[modes.CBC], - typing.Type[modes.GCM], - ] + mode: type[modes.CTR] | type[modes.CBC] | type[modes.GCM] block_len: int iv_len: int - tag_len: typing.Optional[int] + tag_len: int | None is_aead: bool # ciphers that are actually used in key wrapping -_SSH_CIPHERS: typing.Dict[bytes, _SSHCipher] = { +_SSH_CIPHERS: dict[bytes, _SSHCipher] = { b"aes256-ctr": _SSHCipher( alg=algorithms.AES, key_len=32, @@ -139,9 +135,7 @@ class _SSHCipher: } -def _get_ssh_key_type( - key: typing.Union[SSHPrivateKeyTypes, SSHPublicKeyTypes] -) -> bytes: +def _get_ssh_key_type(key: SSHPrivateKeyTypes | SSHPublicKeyTypes) -> bytes: if isinstance(key, ec.EllipticCurvePrivateKey): key_type = _ecdsa_key_type(key.public_key()) elif isinstance(key, ec.EllipticCurvePublicKey): @@ -192,10 +186,10 @@ def _check_empty(data: bytes) -> None: def _init_cipher( ciphername: bytes, - password: typing.Optional[bytes], + password: bytes | None, salt: bytes, rounds: int, -) -> Cipher[typing.Union[modes.CBC, modes.CTR, modes.GCM]]: +) -> Cipher[modes.CBC | modes.CTR | modes.GCM]: """Generate key + iv and return cipher.""" if not password: raise ValueError("Key is password-protected.") @@ -210,21 +204,21 @@ def _init_cipher( ) -def _get_u32(data: memoryview) -> typing.Tuple[int, memoryview]: +def _get_u32(data: memoryview) -> tuple[int, memoryview]: """Uint32""" if len(data) < 4: raise ValueError("Invalid data") return int.from_bytes(data[:4], byteorder="big"), data[4:] -def _get_u64(data: memoryview) -> typing.Tuple[int, memoryview]: +def _get_u64(data: memoryview) -> tuple[int, memoryview]: """Uint64""" if len(data) < 8: raise ValueError("Invalid data") return int.from_bytes(data[:8], byteorder="big"), data[8:] -def _get_sshstr(data: memoryview) -> typing.Tuple[memoryview, memoryview]: +def _get_sshstr(data: memoryview) -> tuple[memoryview, memoryview]: """Bytes with u32 length prefix""" n, data = _get_u32(data) if n > len(data): @@ -232,7 +226,7 @@ def _get_sshstr(data: memoryview) -> typing.Tuple[memoryview, memoryview]: return data[:n], data[n:] -def _get_mpint(data: memoryview) -> typing.Tuple[int, memoryview]: +def _get_mpint(data: memoryview) -> tuple[int, memoryview]: """Big integer.""" val, data = _get_sshstr(data) if val and val[0] > 0x7F: @@ -253,11 +247,9 @@ def _to_mpint(val: int) -> bytes: class _FragList: """Build recursive structure without data copy.""" - flist: typing.List[bytes] + flist: list[bytes] - def __init__( - self, init: typing.Optional[typing.List[bytes]] = None - ) -> None: + def __init__(self, init: list[bytes] | None = None) -> None: self.flist = [] if init: self.flist.extend(init) @@ -274,7 +266,7 @@ def put_u64(self, val: int) -> None: """Big-endian uint64""" self.flist.append(val.to_bytes(length=8, byteorder="big")) - def put_sshstr(self, val: typing.Union[bytes, _FragList]) -> None: + def put_sshstr(self, val: bytes | _FragList) -> None: """Bytes prefixed with u32 length""" if isinstance(val, (bytes, memoryview, bytearray)): self.put_u32(len(val)) @@ -323,7 +315,7 @@ def get_public(self, data: memoryview): def load_public( self, data: memoryview - ) -> typing.Tuple[rsa.RSAPublicKey, memoryview]: + ) -> tuple[rsa.RSAPublicKey, memoryview]: """Make RSA public key from data.""" (e, n), data = self.get_public(data) public_numbers = rsa.RSAPublicNumbers(e, n) @@ -332,7 +324,7 @@ def load_public( def load_private( self, data: memoryview, pubfields - ) -> typing.Tuple[rsa.RSAPrivateKey, memoryview]: + ) -> tuple[rsa.RSAPrivateKey, memoryview]: """Make RSA private key from data.""" n, data = _get_mpint(data) e, data = _get_mpint(data) @@ -385,9 +377,7 @@ class _SSHFormatDSA: mpint p, q, g, y, x """ - def get_public( - self, data: memoryview - ) -> typing.Tuple[typing.Tuple, memoryview]: + def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: """DSA public fields""" p, data = _get_mpint(data) q, data = _get_mpint(data) @@ -397,7 +387,7 @@ def get_public( def load_public( self, data: memoryview - ) -> typing.Tuple[dsa.DSAPublicKey, memoryview]: + ) -> tuple[dsa.DSAPublicKey, memoryview]: """Make DSA public key from data.""" (p, q, g, y), data = self.get_public(data) parameter_numbers = dsa.DSAParameterNumbers(p, q, g) @@ -408,7 +398,7 @@ def load_public( def load_private( self, data: memoryview, pubfields - ) -> typing.Tuple[dsa.DSAPrivateKey, memoryview]: + ) -> tuple[dsa.DSAPrivateKey, memoryview]: """Make DSA private key from data.""" (p, q, g, y), data = self.get_public(data) x, data = _get_mpint(data) @@ -464,9 +454,7 @@ def __init__(self, ssh_curve_name: bytes, curve: ec.EllipticCurve): self.ssh_curve_name = ssh_curve_name self.curve = curve - def get_public( - self, data: memoryview - ) -> typing.Tuple[typing.Tuple, memoryview]: + def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: """ECDSA public fields""" curve, data = _get_sshstr(data) point, data = _get_sshstr(data) @@ -478,9 +466,9 @@ def get_public( def load_public( self, data: memoryview - ) -> typing.Tuple[ec.EllipticCurvePublicKey, memoryview]: + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: """Make ECDSA public key from data.""" - (curve_name, point), data = self.get_public(data) + (_, point), data = self.get_public(data) public_key = ec.EllipticCurvePublicKey.from_encoded_point( self.curve, point.tobytes() ) @@ -488,7 +476,7 @@ def load_public( def load_private( self, data: memoryview, pubfields - ) -> typing.Tuple[ec.EllipticCurvePrivateKey, memoryview]: + ) -> tuple[ec.EllipticCurvePrivateKey, memoryview]: """Make ECDSA private key from data.""" (curve_name, point), data = self.get_public(data) secret, data = _get_mpint(data) @@ -529,16 +517,14 @@ class _SSHFormatEd25519: bytes secret_and_point """ - def get_public( - self, data: memoryview - ) -> typing.Tuple[typing.Tuple, memoryview]: + def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: """Ed25519 public fields""" point, data = _get_sshstr(data) return (point,), data def load_public( self, data: memoryview - ) -> typing.Tuple[ed25519.Ed25519PublicKey, memoryview]: + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: """Make Ed25519 public key from data.""" (point,), data = self.get_public(data) public_key = ed25519.Ed25519PublicKey.from_public_bytes( @@ -548,7 +534,7 @@ def load_public( def load_private( self, data: memoryview, pubfields - ) -> typing.Tuple[ed25519.Ed25519PrivateKey, memoryview]: + ) -> tuple[ed25519.Ed25519PrivateKey, memoryview]: """Make Ed25519 private key from data.""" (point,), data = self.get_public(data) keypair, data = _get_sshstr(data) @@ -615,7 +601,7 @@ def _lookup_kformat(key_type: bytes): def load_ssh_private_key( data: bytes, - password: typing.Optional[bytes], + password: bytes | None, backend: typing.Any = None, ) -> SSHPrivateKeyTypes: """Load private key from OpenSSH custom encoding.""" @@ -698,7 +684,8 @@ def load_ssh_private_key( if key_type != pub_key_type: raise ValueError("Corrupt data: key type mismatch") private_key, edata = kformat.load_private(edata, pubfields) - comment, edata = _get_sshstr(edata) + # We don't use the comment + _, edata = _get_sshstr(edata) # yes, SSH does padding check *after* all other parsing is done. # need to follow as it writes zero-byte padding too. @@ -820,11 +807,11 @@ def __init__( _serial: int, _cctype: int, _key_id: memoryview, - _valid_principals: typing.List[bytes], + _valid_principals: list[bytes], _valid_after: int, _valid_before: int, - _critical_options: typing.Dict[bytes, bytes], - _extensions: typing.Dict[bytes, bytes], + _critical_options: dict[bytes, bytes], + _extensions: dict[bytes, bytes], _sig_type: memoryview, _sig_key: memoryview, _inner_sig_type: memoryview, @@ -876,7 +863,7 @@ def key_id(self) -> bytes: return bytes(self._key_id) @property - def valid_principals(self) -> typing.List[bytes]: + def valid_principals(self) -> list[bytes]: return self._valid_principals @property @@ -888,11 +875,11 @@ def valid_after(self) -> int: return self._valid_after @property - def critical_options(self) -> typing.Dict[bytes, bytes]: + def critical_options(self) -> dict[bytes, bytes]: return self._critical_options @property - def extensions(self) -> typing.Dict[bytes, bytes]: + def extensions(self) -> dict[bytes, bytes]: return self._extensions def signature_key(self) -> SSHCertPublicKeyTypes: @@ -954,7 +941,7 @@ def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm: def _load_ssh_public_identity( data: bytes, _legacy_dsa_allowed=False, -) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: +) -> SSHCertificate | SSHPublicKeyTypes: utils._check_byteslike("data", data) m = _SSH_PUBKEY_RC.match(data) @@ -1048,12 +1035,12 @@ def _load_ssh_public_identity( def load_ssh_public_identity( data: bytes, -) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: +) -> SSHCertificate | SSHPublicKeyTypes: return _load_ssh_public_identity(data) -def _parse_exts_opts(exts_opts: memoryview) -> typing.Dict[bytes, bytes]: - result: typing.Dict[bytes, bytes] = {} +def _parse_exts_opts(exts_opts: memoryview) -> dict[bytes, bytes]: + result: dict[bytes, bytes] = {} last_name = None while exts_opts: name, exts_opts = _get_sshstr(exts_opts) @@ -1063,6 +1050,10 @@ def _parse_exts_opts(exts_opts: memoryview) -> typing.Dict[bytes, bytes]: if last_name is not None and bname < last_name: raise ValueError("Fields not lexically sorted") value, exts_opts = _get_sshstr(exts_opts) + if len(value) > 0: + value, extra = _get_sshstr(value) + if len(extra) > 0: + raise ValueError("Unexpected extra data after value") result[bname] = bytes(value) last_name = bname return result @@ -1123,16 +1114,16 @@ def serialize_ssh_public_key(public_key: SSHPublicKeyTypes) -> bytes: class SSHCertificateBuilder: def __init__( self, - _public_key: typing.Optional[SSHCertPublicKeyTypes] = None, - _serial: typing.Optional[int] = None, - _type: typing.Optional[SSHCertificateType] = None, - _key_id: typing.Optional[bytes] = None, - _valid_principals: typing.List[bytes] = [], + _public_key: SSHCertPublicKeyTypes | None = None, + _serial: int | None = None, + _type: SSHCertificateType | None = None, + _key_id: bytes | None = None, + _valid_principals: list[bytes] = [], _valid_for_all_principals: bool = False, - _valid_before: typing.Optional[int] = None, - _valid_after: typing.Optional[int] = None, - _critical_options: typing.List[typing.Tuple[bytes, bytes]] = [], - _extensions: typing.List[typing.Tuple[bytes, bytes]] = [], + _valid_before: int | None = None, + _valid_after: int | None = None, + _critical_options: list[tuple[bytes, bytes]] = [], + _extensions: list[tuple[bytes, bytes]] = [], ): self._public_key = _public_key self._serial = _serial @@ -1233,7 +1224,7 @@ def key_id(self, key_id: bytes) -> SSHCertificateBuilder: ) def valid_principals( - self, valid_principals: typing.List[bytes] + self, valid_principals: list[bytes] ) -> SSHCertificateBuilder: if self._valid_for_all_principals: raise ValueError( @@ -1290,9 +1281,7 @@ def valid_for_all_principals(self): _extensions=self._extensions, ) - def valid_before( - self, valid_before: typing.Union[int, float] - ) -> SSHCertificateBuilder: + def valid_before(self, valid_before: int | float) -> SSHCertificateBuilder: if not isinstance(valid_before, (int, float)): raise TypeError("valid_before must be an int or float") valid_before = int(valid_before) @@ -1314,9 +1303,7 @@ def valid_before( _extensions=self._extensions, ) - def valid_after( - self, valid_after: typing.Union[int, float] - ) -> SSHCertificateBuilder: + def valid_after(self, valid_after: int | float) -> SSHCertificateBuilder: if not isinstance(valid_after, (int, float)): raise TypeError("valid_after must be an int or float") valid_after = int(valid_after) @@ -1356,7 +1343,7 @@ def add_critical_option( _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, - _critical_options=self._critical_options + [(name, value)], + _critical_options=[*self._critical_options, (name, value)], _extensions=self._extensions, ) @@ -1379,7 +1366,7 @@ def add_extension( _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, - _extensions=self._extensions + [(name, value)], + _extensions=[*self._extensions, (name, value)], ) def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate: @@ -1450,12 +1437,22 @@ def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate: fcrit = _FragList() for name, value in self._critical_options: fcrit.put_sshstr(name) - fcrit.put_sshstr(value) + if len(value) > 0: + foptval = _FragList() + foptval.put_sshstr(value) + fcrit.put_sshstr(foptval.tobytes()) + else: + fcrit.put_sshstr(value) f.put_sshstr(fcrit.tobytes()) fext = _FragList() for name, value in self._extensions: fext.put_sshstr(name) - fext.put_sshstr(value) + if len(value) > 0: + fextval = _FragList() + fextval.put_sshstr(value) + fext.put_sshstr(fextval.tobytes()) + else: + fext.put_sshstr(value) f.put_sshstr(fext.tobytes()) f.put_sshstr(b"") # RESERVED FIELD # encode CA public key diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py index 2067108a63d6..af5ab6efe290 100644 --- a/src/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -19,8 +19,8 @@ def _generate_uri( hotp: HOTP, type_name: str, account_name: str, - issuer: typing.Optional[str], - extra_parameters: typing.List[typing.Tuple[str, int]], + issuer: str | None, + extra_parameters: list[tuple[str, int]], ) -> str: parameters = [ ("digits", hotp._length), @@ -85,7 +85,7 @@ def _dynamic_truncate(self, counter: int) -> int: return int.from_bytes(p, byteorder="big") & 0x7FFFFFFF def get_provisioning_uri( - self, account_name: str, counter: int, issuer: typing.Optional[str] + self, account_name: str, counter: int, issuer: str | None ) -> str: return _generate_uri( self, "hotp", account_name, issuer, [("counter", int(counter))] diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py index daddcea2f77e..68a5077468e3 100644 --- a/src/cryptography/hazmat/primitives/twofactor/totp.py +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -30,7 +30,7 @@ def __init__( key, length, algorithm, enforce_key_length=enforce_key_length ) - def generate(self, time: typing.Union[int, float]) -> bytes: + def generate(self, time: int | float) -> bytes: counter = int(time / self._time_step) return self._hotp.generate(counter) @@ -39,7 +39,7 @@ def verify(self, totp: bytes, time: int) -> None: raise InvalidToken("Supplied TOTP value does not match.") def get_provisioning_uri( - self, account_name: str, issuer: typing.Optional[str] + self, account_name: str, issuer: str | None ) -> str: return _generate_uri( self._hotp, diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 719168168440..a0ec7a3cd76d 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -12,7 +12,7 @@ # We use a UserWarning subclass, instead of DeprecationWarning, because CPython -# decided deprecation warnings should be invisble by default. +# decided deprecation warnings should be invisible by default. class CryptographyDeprecationWarning(UserWarning): pass @@ -24,6 +24,7 @@ class CryptographyDeprecationWarning(UserWarning): DeprecatedIn37 = CryptographyDeprecationWarning DeprecatedIn40 = CryptographyDeprecationWarning DeprecatedIn41 = CryptographyDeprecationWarning +DeprecatedIn42 = CryptographyDeprecationWarning def _check_bytes(name: str, value: bytes) -> None: @@ -38,13 +39,13 @@ def _check_byteslike(name: str, value: bytes) -> None: raise TypeError(f"{name} must be bytes-like") -def int_to_bytes(integer: int, length: typing.Optional[int] = None) -> bytes: +def int_to_bytes(integer: int, length: int | None = None) -> bytes: return integer.to_bytes( length or (integer.bit_length() + 7) // 8 or 1, "big" ) -def _extract_buffer_length(obj: typing.Any) -> typing.Tuple[typing.Any, int]: +def _extract_buffer_length(obj: typing.Any) -> tuple[typing.Any, int]: from cryptography.hazmat.bindings._rust import _openssl buf = _openssl.ffi.from_buffer(obj) @@ -85,15 +86,15 @@ def __delattr__(self, attr: str) -> None: delattr(self._module, attr) def __dir__(self) -> typing.Sequence[str]: - return ["_module"] + dir(self._module) + return ["_module", *dir(self._module)] def deprecated( value: object, module_name: str, message: str, - warning_class: typing.Type[Warning], - name: typing.Optional[str] = None, + warning_class: type[Warning], + name: str | None = None, ) -> _DeprecatedValue: module = sys.modules[module_name] if not isinstance(module, _ModuleWithDeprecations): diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index d77694a29906..931618aa49d1 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations -from cryptography.x509 import certificate_transparency +from cryptography.x509 import certificate_transparency, verification from cryptography.x509.base import ( Attribute, AttributeNotFound, @@ -171,6 +171,7 @@ __all__ = [ "certificate_transparency", + "verification", "load_pem_x509_certificate", "load_pem_x509_certificates", "load_der_x509_certificate", @@ -179,6 +180,7 @@ "load_pem_x509_crl", "load_der_x509_crl", "random_serial_number", + "verification", "Attribute", "AttributeNotFound", "Attributes", diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 576385e088d8..89a75a23ac36 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -8,6 +8,7 @@ import datetime import os import typing +import warnings from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 @@ -60,7 +61,7 @@ def __init__(self, msg: str, oid: ObjectIdentifier) -> None: def _reject_duplicate_extension( extension: Extension[ExtensionType], - extensions: typing.List[Extension[ExtensionType]], + extensions: list[Extension[ExtensionType]], ) -> None: # This is quadratic in the number of extensions for e in extensions: @@ -70,9 +71,7 @@ def _reject_duplicate_extension( def _reject_duplicate_attribute( oid: ObjectIdentifier, - attributes: typing.List[ - typing.Tuple[ObjectIdentifier, bytes, typing.Optional[int]] - ], + attributes: list[tuple[ObjectIdentifier, bytes, int | None]], ) -> None: # This is quadratic in the number of attributes for attr_oid, _, _ in attributes: @@ -195,6 +194,13 @@ def not_valid_before(self) -> datetime.datetime: Not before time (represented as UTC datetime) """ + @property + @abc.abstractmethod + def not_valid_before_utc(self) -> datetime.datetime: + """ + Not before time (represented as a non-naive UTC datetime) + """ + @property @abc.abstractmethod def not_valid_after(self) -> datetime.datetime: @@ -202,6 +208,13 @@ def not_valid_after(self) -> datetime.datetime: Not after time (represented as UTC datetime) """ + @property + @abc.abstractmethod + def not_valid_after_utc(self) -> datetime.datetime: + """ + Not after time (represented as a non-naive UTC datetime) + """ + @property @abc.abstractmethod def issuer(self) -> Name: @@ -220,7 +233,7 @@ def subject(self) -> Name: @abc.abstractmethod def signature_hash_algorithm( self, - ) -> typing.Optional[hashes.HashAlgorithm]: + ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. @@ -237,7 +250,7 @@ def signature_algorithm_oid(self) -> ObjectIdentifier: @abc.abstractmethod def signature_algorithm_parameters( self, - ) -> typing.Union[None, padding.PSS, padding.PKCS1v15, ec.ECDSA]: + ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA: """ Returns the signature algorithm parameters. """ @@ -317,6 +330,14 @@ def revocation_date(self) -> datetime.datetime: Returns the date of when this certificate was revoked. """ + @property + @abc.abstractmethod + def revocation_date_utc(self) -> datetime.datetime: + """ + Returns the date of when this certificate was revoked as a non-naive + UTC datetime. + """ + @property @abc.abstractmethod def extensions(self) -> Extensions: @@ -346,8 +367,18 @@ def serial_number(self) -> int: @property def revocation_date(self) -> datetime.datetime: + warnings.warn( + "Properties that return a naïve datetime object have been " + "deprecated. Please switch to revocation_date_utc.", + utils.DeprecatedIn42, + stacklevel=2, + ) return self._revocation_date + @property + def revocation_date_utc(self) -> datetime.datetime: + return self._revocation_date.replace(tzinfo=datetime.timezone.utc) + @property def extensions(self) -> Extensions: return self._extensions @@ -369,7 +400,7 @@ def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: @abc.abstractmethod def get_revoked_certificate_by_serial_number( self, serial_number: int - ) -> typing.Optional[RevokedCertificate]: + ) -> RevokedCertificate | None: """ Returns an instance of RevokedCertificate or None if the serial_number is not in the CRL. @@ -379,7 +410,7 @@ def get_revoked_certificate_by_serial_number( @abc.abstractmethod def signature_hash_algorithm( self, - ) -> typing.Optional[hashes.HashAlgorithm]: + ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. @@ -392,6 +423,15 @@ def signature_algorithm_oid(self) -> ObjectIdentifier: Returns the ObjectIdentifier of the signature algorithm. """ + @property + @abc.abstractmethod + def signature_algorithm_parameters( + self, + ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA: + """ + Returns the signature algorithm parameters. + """ + @property @abc.abstractmethod def issuer(self) -> Name: @@ -401,11 +441,19 @@ def issuer(self) -> Name: @property @abc.abstractmethod - def next_update(self) -> typing.Optional[datetime.datetime]: + def next_update(self) -> datetime.datetime | None: """ Returns the date of next update for this CRL. """ + @property + @abc.abstractmethod + def next_update_utc(self) -> datetime.datetime | None: + """ + Returns the date of next update for this CRL as a non-naive UTC + datetime. + """ + @property @abc.abstractmethod def last_update(self) -> datetime.datetime: @@ -413,6 +461,14 @@ def last_update(self) -> datetime.datetime: Returns the date of last update for this CRL. """ + @property + @abc.abstractmethod + def last_update_utc(self) -> datetime.datetime: + """ + Returns the date of last update for this CRL as a non-naive UTC + datetime. + """ + @property @abc.abstractmethod def extensions(self) -> Extensions: @@ -451,13 +507,13 @@ def __getitem__(self, idx: int) -> RevokedCertificate: ... @typing.overload - def __getitem__(self, idx: slice) -> typing.List[RevokedCertificate]: + def __getitem__(self, idx: slice) -> list[RevokedCertificate]: ... @abc.abstractmethod def __getitem__( - self, idx: typing.Union[int, slice] - ) -> typing.Union[RevokedCertificate, typing.List[RevokedCertificate]]: + self, idx: int | slice + ) -> RevokedCertificate | list[RevokedCertificate]: """ Returns a revoked certificate (or slice of revoked certificates). """ @@ -510,7 +566,7 @@ def subject(self) -> Name: @abc.abstractmethod def signature_hash_algorithm( self, - ) -> typing.Optional[hashes.HashAlgorithm]: + ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. @@ -523,6 +579,15 @@ def signature_algorithm_oid(self) -> ObjectIdentifier: Returns the ObjectIdentifier of the signature algorithm. """ + @property + @abc.abstractmethod + def signature_algorithm_parameters( + self, + ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA: + """ + Returns the signature algorithm parameters. + """ + @property @abc.abstractmethod def extensions(self) -> Extensions: @@ -576,60 +641,24 @@ def get_attribute_for_oid(self, oid: ObjectIdentifier) -> bytes: CertificateSigningRequest.register(rust_x509.CertificateSigningRequest) -# Backend argument preserved for API compatibility, but ignored. -def load_pem_x509_certificate( - data: bytes, backend: typing.Any = None -) -> Certificate: - return rust_x509.load_pem_x509_certificate(data) - +load_pem_x509_certificate = rust_x509.load_pem_x509_certificate +load_der_x509_certificate = rust_x509.load_der_x509_certificate -def load_pem_x509_certificates(data: bytes) -> typing.List[Certificate]: - return rust_x509.load_pem_x509_certificates(data) +load_pem_x509_certificates = rust_x509.load_pem_x509_certificates +load_pem_x509_csr = rust_x509.load_pem_x509_csr +load_der_x509_csr = rust_x509.load_der_x509_csr -# Backend argument preserved for API compatibility, but ignored. -def load_der_x509_certificate( - data: bytes, backend: typing.Any = None -) -> Certificate: - return rust_x509.load_der_x509_certificate(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_pem_x509_csr( - data: bytes, backend: typing.Any = None -) -> CertificateSigningRequest: - return rust_x509.load_pem_x509_csr(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_der_x509_csr( - data: bytes, backend: typing.Any = None -) -> CertificateSigningRequest: - return rust_x509.load_der_x509_csr(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_pem_x509_crl( - data: bytes, backend: typing.Any = None -) -> CertificateRevocationList: - return rust_x509.load_pem_x509_crl(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_der_x509_crl( - data: bytes, backend: typing.Any = None -) -> CertificateRevocationList: - return rust_x509.load_der_x509_crl(data) +load_pem_x509_crl = rust_x509.load_pem_x509_crl +load_der_x509_crl = rust_x509.load_der_x509_crl class CertificateSigningRequestBuilder: def __init__( self, - subject_name: typing.Optional[Name] = None, - extensions: typing.List[Extension[ExtensionType]] = [], - attributes: typing.List[ - typing.Tuple[ObjectIdentifier, bytes, typing.Optional[int]] - ] = [], + subject_name: Name | None = None, + extensions: list[Extension[ExtensionType]] = [], + attributes: list[tuple[ObjectIdentifier, bytes, int | None]] = [], ): """ Creates an empty X.509 certificate request (v1). @@ -664,7 +693,7 @@ def add_extension( return CertificateSigningRequestBuilder( self._subject_name, - self._extensions + [extension], + [*self._extensions, extension], self._attributes, ) @@ -673,7 +702,7 @@ def add_attribute( oid: ObjectIdentifier, value: bytes, *, - _tag: typing.Optional[_ASN1Type] = None, + _tag: _ASN1Type | None = None, ) -> CertificateSigningRequestBuilder: """ Adds an X.509 attribute with an OID and associated value. @@ -697,35 +726,46 @@ def add_attribute( return CertificateSigningRequestBuilder( self._subject_name, self._extensions, - self._attributes + [(oid, value, tag)], + [*self._attributes, (oid, value, tag)], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[_AllowedHashTypes], + algorithm: _AllowedHashTypes | None, backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> CertificateSigningRequest: """ Signs the request using the requestor's private key. """ if self._subject_name is None: raise ValueError("A CertificateSigningRequest must have a subject") - return rust_x509.create_x509_csr(self, private_key, algorithm) + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + return rust_x509.create_x509_csr( + self, private_key, algorithm, rsa_padding + ) class CertificateBuilder: - _extensions: typing.List[Extension[ExtensionType]] + _extensions: list[Extension[ExtensionType]] def __init__( self, - issuer_name: typing.Optional[Name] = None, - subject_name: typing.Optional[Name] = None, - public_key: typing.Optional[CertificatePublicKeyTypes] = None, - serial_number: typing.Optional[int] = None, - not_valid_before: typing.Optional[datetime.datetime] = None, - not_valid_after: typing.Optional[datetime.datetime] = None, - extensions: typing.List[Extension[ExtensionType]] = [], + issuer_name: Name | None = None, + subject_name: Name | None = None, + public_key: CertificatePublicKeyTypes | None = None, + serial_number: int | None = None, + not_valid_before: datetime.datetime | None = None, + not_valid_after: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], ) -> None: self._version = Version.v3 self._issuer_name = issuer_name @@ -916,18 +956,16 @@ def add_extension( self._serial_number, self._not_valid_before, self._not_valid_after, - self._extensions + [extension], + [*self._extensions, extension], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[_AllowedHashTypes], + algorithm: _AllowedHashTypes | None, backend: typing.Any = None, *, - rsa_padding: typing.Optional[ - typing.Union[padding.PSS, padding.PKCS1v15] - ] = None, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> Certificate: """ Signs the certificate using the CA's private key. @@ -962,16 +1000,16 @@ def sign( class CertificateRevocationListBuilder: - _extensions: typing.List[Extension[ExtensionType]] - _revoked_certificates: typing.List[RevokedCertificate] + _extensions: list[Extension[ExtensionType]] + _revoked_certificates: list[RevokedCertificate] def __init__( self, - issuer_name: typing.Optional[Name] = None, - last_update: typing.Optional[datetime.datetime] = None, - next_update: typing.Optional[datetime.datetime] = None, - extensions: typing.List[Extension[ExtensionType]] = [], - revoked_certificates: typing.List[RevokedCertificate] = [], + issuer_name: Name | None = None, + last_update: datetime.datetime | None = None, + next_update: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + revoked_certificates: list[RevokedCertificate] = [], ): self._issuer_name = issuer_name self._last_update = last_update @@ -1057,7 +1095,7 @@ def add_extension( self._issuer_name, self._last_update, self._next_update, - self._extensions + [extension], + [*self._extensions, extension], self._revoked_certificates, ) @@ -1075,14 +1113,16 @@ def add_revoked_certificate( self._last_update, self._next_update, self._extensions, - self._revoked_certificates + [revoked_certificate], + [*self._revoked_certificates, revoked_certificate], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[_AllowedHashTypes], + algorithm: _AllowedHashTypes | None, backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> CertificateRevocationList: if self._issuer_name is None: raise ValueError("A CRL must have an issuer name") @@ -1093,15 +1133,23 @@ def sign( if self._next_update is None: raise ValueError("A CRL must have a next update time") - return rust_x509.create_x509_crl(self, private_key, algorithm) + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + return rust_x509.create_x509_crl( + self, private_key, algorithm, rsa_padding + ) class RevokedCertificateBuilder: def __init__( self, - serial_number: typing.Optional[int] = None, - revocation_date: typing.Optional[datetime.datetime] = None, - extensions: typing.List[Extension[ExtensionType]] = [], + serial_number: int | None = None, + revocation_date: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], ): self._serial_number = serial_number self._revocation_date = revocation_date @@ -1152,7 +1200,7 @@ def add_extension( return RevokedCertificateBuilder( self._serial_number, self._revocation_date, - self._extensions + [extension], + [*self._extensions, extension], ) def build(self, backend: typing.Any = None) -> RevokedCertificate: diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index ac99592f55a7..c61c1f4853fd 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -104,9 +104,7 @@ def public_bytes(self) -> bytes: Serializes the extension type to DER. """ raise NotImplementedError( - "public_bytes is not implemented for extension type {!r}".format( - self - ) + f"public_bytes is not implemented for extension type {self!r}" ) @@ -126,7 +124,7 @@ def get_extension_for_oid( raise ExtensionNotFound(f"No {oid} extension was found", oid) def get_extension_for_class( - self, extclass: typing.Type[ExtensionTypeVar] + self, extclass: type[ExtensionTypeVar] ) -> Extension[ExtensionTypeVar]: if extclass is UnrecognizedExtension: raise TypeError( @@ -183,9 +181,9 @@ class AuthorityKeyIdentifier(ExtensionType): def __init__( self, - key_identifier: typing.Optional[bytes], - authority_cert_issuer: typing.Optional[typing.Iterable[GeneralName]], - authority_cert_serial_number: typing.Optional[int], + key_identifier: bytes | None, + authority_cert_issuer: typing.Iterable[GeneralName] | None, + authority_cert_serial_number: int | None, ) -> None: if (authority_cert_issuer is None) != ( authority_cert_serial_number is None @@ -242,10 +240,10 @@ def from_issuer_subject_key_identifier( def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -269,17 +267,17 @@ def __hash__(self) -> int: ) @property - def key_identifier(self) -> typing.Optional[bytes]: + def key_identifier(self) -> bytes | None: return self._key_identifier @property def authority_cert_issuer( self, - ) -> typing.Optional[typing.List[GeneralName]]: + ) -> list[GeneralName] | None: return self._authority_cert_issuer @property - def authority_cert_serial_number(self) -> typing.Optional[int]: + def authority_cert_serial_number(self) -> int | None: return self._authority_cert_serial_number def public_bytes(self) -> bytes: @@ -431,7 +429,7 @@ def access_location(self) -> GeneralName: class BasicConstraints(ExtensionType): oid = ExtensionOID.BASIC_CONSTRAINTS - def __init__(self, ca: bool, path_length: typing.Optional[int]) -> None: + def __init__(self, ca: bool, path_length: int | None) -> None: if not isinstance(ca, bool): raise TypeError("ca must be a boolean value") @@ -453,7 +451,7 @@ def ca(self) -> bool: return self._ca @property - def path_length(self) -> typing.Optional[int]: + def path_length(self) -> int | None: return self._path_length def __repr__(self) -> str: @@ -580,10 +578,10 @@ def public_bytes(self) -> bytes: class DistributionPoint: def __init__( self, - full_name: typing.Optional[typing.Iterable[GeneralName]], - relative_name: typing.Optional[RelativeDistinguishedName], - reasons: typing.Optional[typing.FrozenSet[ReasonFlags]], - crl_issuer: typing.Optional[typing.Iterable[GeneralName]], + full_name: typing.Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, + reasons: frozenset[ReasonFlags] | None, + crl_issuer: typing.Iterable[GeneralName] | None, ) -> None: if full_name and relative_name: raise ValueError( @@ -656,35 +654,31 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: if self.full_name is not None: - fn: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( - self.full_name - ) + fn: tuple[GeneralName, ...] | None = tuple(self.full_name) else: fn = None if self.crl_issuer is not None: - crl_issuer: typing.Optional[ - typing.Tuple[GeneralName, ...] - ] = tuple(self.crl_issuer) + crl_issuer: tuple[GeneralName, ...] | None = tuple(self.crl_issuer) else: crl_issuer = None return hash((fn, self.relative_name, self.reasons, crl_issuer)) @property - def full_name(self) -> typing.Optional[typing.List[GeneralName]]: + def full_name(self) -> list[GeneralName] | None: return self._full_name @property - def relative_name(self) -> typing.Optional[RelativeDistinguishedName]: + def relative_name(self) -> RelativeDistinguishedName | None: return self._relative_name @property - def reasons(self) -> typing.Optional[typing.FrozenSet[ReasonFlags]]: + def reasons(self) -> frozenset[ReasonFlags] | None: return self._reasons @property - def crl_issuer(self) -> typing.Optional[typing.List[GeneralName]]: + def crl_issuer(self) -> list[GeneralName] | None: return self._crl_issuer @@ -741,8 +735,8 @@ class PolicyConstraints(ExtensionType): def __init__( self, - require_explicit_policy: typing.Optional[int], - inhibit_policy_mapping: typing.Optional[int], + require_explicit_policy: int | None, + inhibit_policy_mapping: int | None, ) -> None: if require_explicit_policy is not None and not isinstance( require_explicit_policy, int @@ -790,11 +784,11 @@ def __hash__(self) -> int: ) @property - def require_explicit_policy(self) -> typing.Optional[int]: + def require_explicit_policy(self) -> int | None: return self._require_explicit_policy @property - def inhibit_policy_mapping(self) -> typing.Optional[int]: + def inhibit_policy_mapping(self) -> int | None: return self._inhibit_policy_mapping def public_bytes(self) -> bytes: @@ -836,9 +830,7 @@ class PolicyInformation: def __init__( self, policy_identifier: ObjectIdentifier, - policy_qualifiers: typing.Optional[ - typing.Iterable[typing.Union[str, UserNotice]] - ], + policy_qualifiers: typing.Iterable[str | UserNotice] | None, ) -> None: if not isinstance(policy_identifier, ObjectIdentifier): raise TypeError("policy_identifier must be an ObjectIdentifier") @@ -874,9 +866,9 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: if self.policy_qualifiers is not None: - pq: typing.Optional[ - typing.Tuple[typing.Union[str, UserNotice], ...] - ] = tuple(self.policy_qualifiers) + pq: tuple[str | UserNotice, ...] | None = tuple( + self.policy_qualifiers + ) else: pq = None @@ -889,15 +881,15 @@ def policy_identifier(self) -> ObjectIdentifier: @property def policy_qualifiers( self, - ) -> typing.Optional[typing.List[typing.Union[str, UserNotice]]]: + ) -> list[str | UserNotice] | None: return self._policy_qualifiers class UserNotice: def __init__( self, - notice_reference: typing.Optional[NoticeReference], - explicit_text: typing.Optional[str], + notice_reference: NoticeReference | None, + explicit_text: str | None, ) -> None: if notice_reference and not isinstance( notice_reference, NoticeReference @@ -928,18 +920,18 @@ def __hash__(self) -> int: return hash((self.notice_reference, self.explicit_text)) @property - def notice_reference(self) -> typing.Optional[NoticeReference]: + def notice_reference(self) -> NoticeReference | None: return self._notice_reference @property - def explicit_text(self) -> typing.Optional[str]: + def explicit_text(self) -> str | None: return self._explicit_text class NoticeReference: def __init__( self, - organization: typing.Optional[str], + organization: str | None, notice_numbers: typing.Iterable[int], ) -> None: self._organization = organization @@ -968,11 +960,11 @@ def __hash__(self) -> int: return hash((self.organization, tuple(self.notice_numbers))) @property - def organization(self) -> typing.Optional[str]: + def organization(self) -> str | None: return self._organization @property - def notice_numbers(self) -> typing.List[int]: + def notice_numbers(self) -> list[int]: return self._notice_numbers @@ -1213,14 +1205,14 @@ def __repr__(self) -> str: decipher_only = False return ( - "" - ).format(self, encipher_only, decipher_only) + f"" + ) def __eq__(self, other: object) -> bool: if not isinstance(other, KeyUsage): @@ -1262,8 +1254,8 @@ class NameConstraints(ExtensionType): def __init__( self, - permitted_subtrees: typing.Optional[typing.Iterable[GeneralName]], - excluded_subtrees: typing.Optional[typing.Iterable[GeneralName]], + permitted_subtrees: typing.Iterable[GeneralName] | None, + excluded_subtrees: typing.Iterable[GeneralName] | None, ) -> None: if permitted_subtrees is not None: permitted_subtrees = list(permitted_subtrees) @@ -1339,22 +1331,18 @@ def _validate_dns_name(self, tree: typing.Iterable[GeneralName]) -> None: def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __hash__(self) -> int: if self.permitted_subtrees is not None: - ps: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( - self.permitted_subtrees - ) + ps: tuple[GeneralName, ...] | None = tuple(self.permitted_subtrees) else: ps = None if self.excluded_subtrees is not None: - es: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( - self.excluded_subtrees - ) + es: tuple[GeneralName, ...] | None = tuple(self.excluded_subtrees) else: es = None @@ -1363,13 +1351,13 @@ def __hash__(self) -> int: @property def permitted_subtrees( self, - ) -> typing.Optional[typing.List[GeneralName]]: + ) -> list[GeneralName] | None: return self._permitted_subtrees @property def excluded_subtrees( self, - ) -> typing.Optional[typing.List[GeneralName]]: + ) -> list[GeneralName] | None: return self._excluded_subtrees def public_bytes(self) -> bytes: @@ -1406,9 +1394,9 @@ def value(self) -> ExtensionTypeVar: def __repr__(self) -> str: return ( - "" - ).format(self) + f"" + ) def __eq__(self, other: object) -> bool: if not isinstance(other, Extension): @@ -1440,58 +1428,52 @@ def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: + def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): # Return the value of each GeneralName, except for OtherName instances # which we return directly because it has two important properties not # just one value. @@ -1524,58 +1506,52 @@ def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: + def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: @@ -1605,58 +1581,52 @@ def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: + def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: @@ -1686,58 +1656,52 @@ def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: + def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: @@ -1795,9 +1759,7 @@ def __init__(self, invalidity_date: datetime.datetime) -> None: self._invalidity_date = invalidity_date def __repr__(self) -> str: - return "".format( - self._invalidity_date - ) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, InvalidityDate): @@ -1841,9 +1803,7 @@ def __init__( ) def __repr__(self) -> str: - return "".format( - list(self) - ) + return f"" def __hash__(self) -> int: return hash(tuple(self._signed_certificate_timestamps)) @@ -1967,11 +1927,11 @@ class IssuingDistributionPoint(ExtensionType): def __init__( self, - full_name: typing.Optional[typing.Iterable[GeneralName]], - relative_name: typing.Optional[RelativeDistinguishedName], + full_name: typing.Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, only_contains_user_certs: bool, only_contains_ca_certs: bool, - only_some_reasons: typing.Optional[typing.FrozenSet[ReasonFlags]], + only_some_reasons: frozenset[ReasonFlags] | None, indirect_crl: bool, only_contains_attribute_certs: bool, ) -> None: @@ -2050,14 +2010,14 @@ def __init__( def __repr__(self) -> str: return ( - "".format(self) + f"{self.only_contains_attribute_certs})>" ) def __eq__(self, other: object) -> bool: @@ -2089,11 +2049,11 @@ def __hash__(self) -> int: ) @property - def full_name(self) -> typing.Optional[typing.List[GeneralName]]: + def full_name(self) -> list[GeneralName] | None: return self._full_name @property - def relative_name(self) -> typing.Optional[RelativeDistinguishedName]: + def relative_name(self) -> RelativeDistinguishedName | None: return self._relative_name @property @@ -2107,7 +2067,7 @@ def only_contains_ca_certs(self) -> bool: @property def only_some_reasons( self, - ) -> typing.Optional[typing.FrozenSet[ReasonFlags]]: + ) -> frozenset[ReasonFlags] | None: return self._only_some_reasons @property @@ -2128,8 +2088,8 @@ class MSCertificateTemplate(ExtensionType): def __init__( self, template_id: ObjectIdentifier, - major_version: typing.Optional[int], - minor_version: typing.Optional[int], + major_version: int | None, + minor_version: int | None, ) -> None: if not isinstance(template_id, ObjectIdentifier): raise TypeError("oid must be an ObjectIdentifier") @@ -2150,11 +2110,11 @@ def template_id(self) -> ObjectIdentifier: return self._template_id @property - def major_version(self) -> typing.Optional[int]: + def major_version(self) -> int | None: return self._major_version @property - def minor_version(self) -> typing.Optional[int]: + def minor_version(self) -> int | None: return self._minor_version def __repr__(self) -> str: @@ -2198,8 +2158,8 @@ def value(self) -> bytes: def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: diff --git a/src/cryptography/x509/general_name.py b/src/cryptography/x509/general_name.py index 79271afbf91e..672f28759cb0 100644 --- a/src/cryptography/x509/general_name.py +++ b/src/cryptography/x509/general_name.py @@ -269,9 +269,7 @@ def value(self) -> bytes: return self._value def __repr__(self) -> str: - return "".format( - self.type_id, self.value - ) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, OtherName): diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index ff98e8724af1..5e8ccfff5994 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -31,7 +31,7 @@ class _ASN1Type(utils.Enum): _ASN1_TYPE_TO_ENUM = {i.value: i for i in _ASN1Type} -_NAMEOID_DEFAULT_TYPE: typing.Dict[ObjectIdentifier, _ASN1Type] = { +_NAMEOID_DEFAULT_TYPE: dict[ObjectIdentifier, _ASN1Type] = { NameOID.COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString, @@ -60,7 +60,7 @@ class _ASN1Type(utils.Enum): _NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()} -def _escape_dn_value(val: typing.Union[str, bytes]) -> str: +def _escape_dn_value(val: str | bytes) -> str: """Escape special characters in RFC4514 Distinguished Name value.""" if not val: @@ -112,8 +112,8 @@ class NameAttribute: def __init__( self, oid: ObjectIdentifier, - value: typing.Union[str, bytes], - _type: typing.Optional[_ASN1Type] = None, + value: str | bytes, + _type: _ASN1Type | None = None, *, _validate: bool = True, ) -> None: @@ -132,10 +132,7 @@ def __init__( if not isinstance(value, str): raise TypeError("value argument must be a str") - if ( - oid == NameOID.COUNTRY_NAME - or oid == NameOID.JURISDICTION_COUNTRY_NAME - ): + if oid in (NameOID.COUNTRY_NAME, NameOID.JURISDICTION_COUNTRY_NAME): assert isinstance(value, str) c_len = len(value.encode("utf8")) if c_len != 2 and _validate is True: @@ -145,7 +142,7 @@ def __init__( elif c_len != 2: warnings.warn( "Country names should be two characters, but the " - "attribute is {} characters in length.".format(c_len), + f"attribute is {c_len} characters in length.", stacklevel=2, ) @@ -170,7 +167,7 @@ def oid(self) -> ObjectIdentifier: return self._oid @property - def value(self) -> typing.Union[str, bytes]: + def value(self) -> str | bytes: return self._value @property @@ -182,7 +179,7 @@ def rfc4514_attribute_name(self) -> str: return _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string) def rfc4514_string( - self, attr_name_overrides: typing.Optional[_OidNameMap] = None + self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. @@ -208,7 +205,7 @@ def __hash__(self) -> int: return hash((self.oid, self.value)) def __repr__(self) -> str: - return "".format(self) + return f"" class RelativeDistinguishedName: @@ -228,11 +225,11 @@ def __init__(self, attributes: typing.Iterable[NameAttribute]): def get_attributes_for_oid( self, oid: ObjectIdentifier - ) -> typing.List[NameAttribute]: + ) -> list[NameAttribute]: return [i for i in self if i.oid == oid] def rfc4514_string( - self, attr_name_overrides: typing.Optional[_OidNameMap] = None + self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. @@ -277,9 +274,7 @@ def __init__( def __init__( self, - attributes: typing.Iterable[ - typing.Union[NameAttribute, RelativeDistinguishedName] - ], + attributes: typing.Iterable[NameAttribute | RelativeDistinguishedName], ) -> None: attributes = list(attributes) if all(isinstance(x, NameAttribute) for x in attributes): @@ -301,12 +296,12 @@ def __init__( def from_rfc4514_string( cls, data: str, - attr_name_overrides: typing.Optional[_NameOidMap] = None, + attr_name_overrides: _NameOidMap | None = None, ) -> Name: return _RFC4514NameParser(data, attr_name_overrides or {}).parse() def rfc4514_string( - self, attr_name_overrides: typing.Optional[_OidNameMap] = None + self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. @@ -325,11 +320,11 @@ def rfc4514_string( def get_attributes_for_oid( self, oid: ObjectIdentifier - ) -> typing.List[NameAttribute]: + ) -> list[NameAttribute]: return [i for i in self if i.oid == oid] @property - def rdns(self) -> typing.List[RelativeDistinguishedName]: + def rdns(self) -> list[RelativeDistinguishedName]: return self._attributes def public_bytes(self, backend: typing.Any = None) -> bytes: @@ -348,8 +343,7 @@ def __hash__(self) -> int: def __iter__(self) -> typing.Iterator[NameAttribute]: for rdn in self._attributes: - for ava in rdn: - yield ava + yield from rdn def __len__(self) -> int: return sum(len(rdn) for rdn in self._attributes) @@ -395,7 +389,7 @@ def __init__(self, data: str, attr_name_overrides: _NameOidMap) -> None: def _has_data(self) -> bool: return self._idx < len(self._data) - def _peek(self) -> typing.Optional[str]: + def _peek(self) -> str | None: if self._has_data(): return self._data[self._idx] return None diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py index 7054795fcda8..9751ceaf9655 100644 --- a/src/cryptography/x509/ocsp.py +++ b/src/cryptography/x509/ocsp.py @@ -65,9 +65,9 @@ def __init__( algorithm: hashes.HashAlgorithm, cert_status: OCSPCertStatus, this_update: datetime.datetime, - next_update: typing.Optional[datetime.datetime], - revocation_time: typing.Optional[datetime.datetime], - revocation_reason: typing.Optional[x509.ReasonFlags], + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, ): if not isinstance(cert, x509.Certificate) or not isinstance( issuer, x509.Certificate @@ -180,7 +180,7 @@ def certificate_status(self) -> OCSPCertStatus: @property @abc.abstractmethod - def revocation_time(self) -> typing.Optional[datetime.datetime]: + def revocation_time(self) -> datetime.datetime | None: """ The date of when the certificate was revoked or None if not revoked. @@ -188,7 +188,7 @@ def revocation_time(self) -> typing.Optional[datetime.datetime]: @property @abc.abstractmethod - def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: + def revocation_reason(self) -> x509.ReasonFlags | None: """ The reason the certificate was revoked or None if not specified or not revoked. @@ -204,7 +204,7 @@ def this_update(self) -> datetime.datetime: @property @abc.abstractmethod - def next_update(self) -> typing.Optional[datetime.datetime]: + def next_update(self) -> datetime.datetime | None: """ The time when newer information will be available """ @@ -266,7 +266,7 @@ def signature_algorithm_oid(self) -> x509.ObjectIdentifier: @abc.abstractmethod def signature_hash_algorithm( self, - ) -> typing.Optional[hashes.HashAlgorithm]: + ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed """ @@ -287,7 +287,7 @@ def tbs_response_bytes(self) -> bytes: @property @abc.abstractmethod - def certificates(self) -> typing.List[x509.Certificate]: + def certificates(self) -> list[x509.Certificate]: """ A list of certificates used to help build a chain to verify the OCSP response. This situation occurs when the OCSP responder uses a delegate @@ -296,14 +296,14 @@ def certificates(self) -> typing.List[x509.Certificate]: @property @abc.abstractmethod - def responder_key_hash(self) -> typing.Optional[bytes]: + def responder_key_hash(self) -> bytes | None: """ The responder's key hash or None """ @property @abc.abstractmethod - def responder_name(self) -> typing.Optional[x509.Name]: + def responder_name(self) -> x509.Name | None: """ The responder's Name or None """ @@ -324,7 +324,7 @@ def certificate_status(self) -> OCSPCertStatus: @property @abc.abstractmethod - def revocation_time(self) -> typing.Optional[datetime.datetime]: + def revocation_time(self) -> datetime.datetime | None: """ The date of when the certificate was revoked or None if not revoked. @@ -332,7 +332,7 @@ def revocation_time(self) -> typing.Optional[datetime.datetime]: @property @abc.abstractmethod - def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: + def revocation_reason(self) -> x509.ReasonFlags | None: """ The reason the certificate was revoked or None if not specified or not revoked. @@ -348,7 +348,7 @@ def this_update(self) -> datetime.datetime: @property @abc.abstractmethod - def next_update(self) -> typing.Optional[datetime.datetime]: + def next_update(self) -> datetime.datetime | None: """ The time when newer information will be available """ @@ -405,15 +405,13 @@ def public_bytes(self, encoding: serialization.Encoding) -> bytes: class OCSPRequestBuilder: def __init__( self, - request: typing.Optional[ - typing.Tuple[ - x509.Certificate, x509.Certificate, hashes.HashAlgorithm - ] - ] = None, - request_hash: typing.Optional[ - typing.Tuple[bytes, bytes, int, hashes.HashAlgorithm] - ] = None, - extensions: typing.List[x509.Extension[x509.ExtensionType]] = [], + request: tuple[ + x509.Certificate, x509.Certificate, hashes.HashAlgorithm + ] + | None = None, + request_hash: tuple[bytes, bytes, int, hashes.HashAlgorithm] + | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], ) -> None: self._request = request self._request_hash = request_hash @@ -478,7 +476,7 @@ def add_extension( _reject_duplicate_extension(extension, self._extensions) return OCSPRequestBuilder( - self._request, self._request_hash, self._extensions + [extension] + self._request, self._request_hash, [*self._extensions, extension] ) def build(self) -> OCSPRequest: @@ -491,12 +489,11 @@ def build(self) -> OCSPRequest: class OCSPResponseBuilder: def __init__( self, - response: typing.Optional[_SingleResponse] = None, - responder_id: typing.Optional[ - typing.Tuple[x509.Certificate, OCSPResponderEncoding] - ] = None, - certs: typing.Optional[typing.List[x509.Certificate]] = None, - extensions: typing.List[x509.Extension[x509.ExtensionType]] = [], + response: _SingleResponse | None = None, + responder_id: tuple[x509.Certificate, OCSPResponderEncoding] + | None = None, + certs: list[x509.Certificate] | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], ): self._response = response self._responder_id = responder_id @@ -510,9 +507,9 @@ def add_response( algorithm: hashes.HashAlgorithm, cert_status: OCSPCertStatus, this_update: datetime.datetime, - next_update: typing.Optional[datetime.datetime], - revocation_time: typing.Optional[datetime.datetime], - revocation_reason: typing.Optional[x509.ReasonFlags], + 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.") @@ -583,13 +580,13 @@ def add_extension( self._response, self._responder_id, self._certs, - self._extensions + [extension], + [*self._extensions, extension], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[hashes.HashAlgorithm], + algorithm: hashes.HashAlgorithm | None, ) -> OCSPResponse: if self._response is None: raise ValueError("You must add a response before signing") @@ -614,9 +611,5 @@ def build_unsuccessful( return ocsp.create_ocsp_response(response_status, None, None, None) -def load_der_ocsp_request(data: bytes) -> OCSPRequest: - return ocsp.load_der_ocsp_request(data) - - -def load_der_ocsp_response(data: bytes) -> OCSPResponse: - return ocsp.load_der_ocsp_response(data) +load_der_ocsp_request = ocsp.load_der_ocsp_request +load_der_ocsp_response = ocsp.load_der_ocsp_response diff --git a/src/cryptography/x509/verification.py b/src/cryptography/x509/verification.py new file mode 100644 index 000000000000..ab1a37ae6b01 --- /dev/null +++ b/src/cryptography/x509/verification.py @@ -0,0 +1,24 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.x509.general_name import DNSName, IPAddress + +__all__ = [ + "Store", + "Subject", + "ServerVerifier", + "PolicyBuilder", + "VerificationError", +] + +Store = rust_x509.Store +Subject = typing.Union[DNSName, IPAddress] +ServerVerifier = rust_x509.ServerVerifier +PolicyBuilder = rust_x509.PolicyBuilder +VerificationError = rust_x509.VerificationError diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 47d972ff46ff..b2e0ac4aad38 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -2,36 +2,24 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "asn1" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c19b9324de5b815b6487e0f8098312791b09de0dbf3d5c2db1fe2d95bab973" +checksum = "ae3ecbce89a22627b5e8e6e11d69715617138290289e385cde773b1fe50befdb" dependencies = [ "asn1_derive", ] [[package]] name = "asn1_derive" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a045c3ccad89f244a86bd1e6cf1a7bf645296e7692698b056399b6efd4639407" +checksum = "861af988fac460ac69a09f41e6217a8fb9178797b76fcc9478444be6a59be19c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] [[package]] @@ -42,9 +30,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" @@ -52,11 +40,20 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -73,6 +70,17 @@ dependencies = [ "pyo3", ] +[[package]] +name = "cryptography-key-parsing" +version = "0.1.0" +dependencies = [ + "asn1", + "cfg-if", + "cryptography-x509", + "openssl", + "openssl-sys", +] + [[package]] name = "cryptography-openssl" version = "0.1.0" @@ -89,16 +97,19 @@ version = "0.1.0" dependencies = [ "asn1", "cc", + "cfg-if", "cryptography-cffi", + "cryptography-key-parsing", "cryptography-openssl", "cryptography-x509", + "cryptography-x509-verification", "foreign-types-shared", "once_cell", "openssl", "openssl-sys", - "ouroboros", "pem", "pyo3", + "self_cell", ] [[package]] @@ -108,6 +119,16 @@ dependencies = [ "asn1", ] +[[package]] +name = "cryptography-x509-verification" +version = "0.1.0" +dependencies = [ + "asn1", + "cryptography-x509", + "once_cell", + "pem", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -123,23 +144,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -147,26 +174,26 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.17.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.53" +version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df40a956736488b7b44fe79fe12d4f245bb5b3f5a1f6095e499760015be392" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ - "bitflags", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -183,14 +210,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] [[package]] name = "openssl-sys" -version = "0.9.88" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", @@ -198,29 +225,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ouroboros" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" -dependencies = [ - "aliasable", - "ouroboros_macro", -] - -[[package]] -name = "ouroboros_macro" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" -dependencies = [ - "Inflector", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -233,70 +237,46 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-targets", ] [[package]] name = "pem" -version = "1.1.1" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ "base64", ] [[package]] name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.18.3" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b1ac5b3731ba34fdaa9785f8d74d17448cd18f30cf19e0c7e7b1fdb5272109" +checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" dependencies = [ "cfg-if", "indoc", @@ -311,9 +291,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.18.3" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cb946f5ac61bb61a5014924910d936ebd2b23b705f7a4a3c40b05c720b079a3" +checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" dependencies = [ "once_cell", "target-lexicon", @@ -321,9 +301,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.18.3" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4d7c5337821916ea2a1d21d1092e8443cf34879e53a0ac653fbb98f44ff65c" +checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" dependencies = [ "libc", "pyo3-build-config", @@ -331,73 +311,69 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.18.3" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d39c55dab3fc5a4b25bbd1ac10a2da452c4aca13bb450f22818a002e29648d" +checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "pyo3-macros-backend" -version = "0.18.3" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97daff08a4c48320587b5224cc98d609e3c27b6d437315bd40b605c98eeb5918" +checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f" dependencies = [ + "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "smallvec" -version = "1.10.0" +name = "self_cell" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" [[package]] -name = "syn" -version = "1.0.109" +name = "smallvec" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "syn" -version = "2.0.18" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -406,21 +382,21 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.7" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "vcpkg" @@ -428,26 +404,11 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -460,42 +421,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 2ca1d79d6802..2322486d0406 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -5,23 +5,26 @@ authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV -rust-version = "1.56.0" +rust-version = "1.63.0" [dependencies] once_cell = "1" -pyo3 = { version = "0.18", features = ["abi3-py37"] } -asn1 = { version = "0.15.2", default-features = false } +cfg-if = "1" +pyo3 = { version = "0.20", features = ["abi3"] } +asn1 = { version = "0.15.5", default-features = false } cryptography-cffi = { path = "cryptography-cffi" } +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 = "1.1" -ouroboros = "0.15" -openssl = "0.10.53" -openssl-sys = "0.9.88" +pem = { version = "3", default-features = false } +openssl = "0.10.63" +openssl-sys = "0.9.99" foreign-types-shared = "0.1" +self_cell = "1" [build-dependencies] -cc = "1.0.72" +cc = "1.0.83" [features] extension-module = ["pyo3/extension-module"] @@ -35,4 +38,10 @@ crate-type = ["cdylib"] overflow-checks = true [workspace] -members = ["cryptography-cffi", "cryptography-openssl", "cryptography-x509"] +members = [ + "cryptography-cffi", + "cryptography-key-parsing", + "cryptography-openssl", + "cryptography-x509", + "cryptography-x509-verification", +] diff --git a/src/rust/build.rs b/src/rust/build.rs index 574560394d88..f247822e0dcd 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -6,15 +6,33 @@ use std::env; #[allow(clippy::unusual_byte_groupings)] fn main() { + if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { + let version = u64::from_str_radix(&version, 16).unwrap(); + + if version >= 0x3_00_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_300_OR_GREATER"); + } + if version >= 0x3_02_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_320_OR_GREATER"); + } + } + if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); - if version >= 0x3_07_00_00_0 { - println!("cargo:rustc-cfg=CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER"); + if version >= 0x3_08_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_LIBRESSL_380_OR_GREATER"); } } + if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); } + + 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 65051c2a4627..a025e58ceda7 100644 --- a/src/rust/cryptography-cffi/Cargo.toml +++ b/src/rust/cryptography-cffi/Cargo.toml @@ -5,11 +5,11 @@ authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV -rust-version = "1.56.0" +rust-version = "1.63.0" [dependencies] -pyo3 = { version = "0.18", features = ["abi3-py37"] } -openssl-sys = "0.9.88" +pyo3 = { version = "0.20", features = ["abi3"] } +openssl-sys = "0.9.99" [build-dependencies] -cc = "1.0.72" +cc = "1.0.83" diff --git a/src/rust/cryptography-cffi/build.rs b/src/rust/cryptography-cffi/build.rs index 4a40990b9da4..5f73714f3415 100644 --- a/src/rust/cryptography-cffi/build.rs +++ b/src/rust/cryptography-cffi/build.rs @@ -18,7 +18,7 @@ fn main() { // More details at https://github.com/alexcrichton/curl-rust/issues/279. if let Some(path) = macos_link_search_path() { println!("cargo:rustc-link-lib=clang_rt.osx"); - println!("cargo:rustc-link-search={}", path); + println!("cargo:rustc-link-search={path}"); } } @@ -27,6 +27,7 @@ fn main() { let python = env::var("PYO3_PYTHON").unwrap_or_else(|_| "python3".to_string()); println!("cargo:rerun-if-env-changed=PYO3_PYTHON"); println!("cargo:rerun-if-changed=../../_cffi_src/"); + println!("cargo:rerun-if-changed=../../cryptography/__about__.py"); let output = Command::new(&python) .env("OUT_DIR", &out_dir) .arg("../../_cffi_src/build_openssl.py") @@ -45,10 +46,15 @@ fn main() { "import platform; print(platform.python_implementation(), end='')", ) .unwrap(); - println!("cargo:rustc-cfg=python_implementation=\"{}\"", python_impl); - let python_include = run_python_script( + println!("cargo:rustc-cfg=python_implementation=\"{python_impl}\""); + let python_includes = run_python_script( &python, - "import sysconfig; print(sysconfig.get_path('include'), end='')", + "import os; \ + import setuptools.dist; \ + import setuptools.command.build_ext; \ + b = setuptools.command.build_ext.build_ext(setuptools.dist.Distribution()); \ + b.finalize_options(); \ + print(os.pathsep.join(b.include_dirs), end='')", ) .unwrap(); let openssl_include = @@ -58,12 +64,15 @@ fn main() { let mut build = cc::Build::new(); build .file(openssl_c) - .include(python_include) .include(openssl_include) .flag_if_supported("-Wconversion") .flag_if_supported("-Wno-error=sign-conversion") .flag_if_supported("-Wno-unused-parameter"); + for python_include in env::split_paths(&python_includes) { + build.include(python_include); + } + // Enable abi3 mode if we're not using PyPy. if python_impl != "PyPy" { // cp37 (Python 3.7 to help our grep when we some day drop 3.7 support) @@ -118,7 +127,7 @@ fn macos_link_search_path() -> Option { for line in stdout.lines() { if line.contains("libraries: =") { let path = line.split('=').nth(1)?; - return Some(format!("{}/lib/darwin", path)); + return Some(format!("{path}/lib/darwin")); } } diff --git a/src/rust/cryptography-cffi/src/lib.rs b/src/rust/cryptography-cffi/src/lib.rs index e263d53d8769..110341a1901e 100644 --- a/src/rust/cryptography-cffi/src/lib.rs +++ b/src/rust/cryptography-cffi/src/lib.rs @@ -2,6 +2,8 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] + #[cfg(not(python_implementation = "PyPy"))] use pyo3::FromPyPointer; @@ -22,6 +24,7 @@ pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyMod pyo3::types::PyModule::import(py, "_openssl")? }; #[cfg(not(python_implementation = "PyPy"))] + // SAFETY: `PyInit__openssl` returns an owned reference. let openssl_mod = unsafe { let ptr = PyInit__openssl(); pyo3::types::PyModule::from_owned_ptr(py, ptr) diff --git a/src/rust/cryptography-key-parsing/Cargo.toml b/src/rust/cryptography-key-parsing/Cargo.toml new file mode 100644 index 000000000000..3dd0b31fa1a6 --- /dev/null +++ b/src/rust/cryptography-key-parsing/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cryptography-key-parsing" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.63.0" + +[dependencies] +asn1 = { version = "0.15.5", default-features = false } +cfg-if = "1" +openssl = "0.10.63" +openssl-sys = "0.9.99" +cryptography-x509 = { path = "../cryptography-x509" } diff --git a/src/rust/cryptography-key-parsing/build.rs b/src/rust/cryptography-key-parsing/build.rs new file mode 100644 index 000000000000..cd318b35ff35 --- /dev/null +++ b/src/rust/cryptography-key-parsing/build.rs @@ -0,0 +1,15 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::env; + +fn main() { + if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); + } + + if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); + } +} diff --git a/src/rust/cryptography-key-parsing/src/lib.rs b/src/rust/cryptography-key-parsing/src/lib.rs new file mode 100644 index 000000000000..93c49181c1fe --- /dev/null +++ b/src/rust/cryptography-key-parsing/src/lib.rs @@ -0,0 +1,44 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +pub mod rsa; +pub mod spki; + +pub enum KeyParsingError { + InvalidKey, + ExplicitCurveUnsupported, + UnsupportedKeyType(asn1::ObjectIdentifier), + UnsupportedEllipticCurve(asn1::ObjectIdentifier), + Parse(asn1::ParseError), + OpenSSL(openssl::error::ErrorStack), +} + +impl From for KeyParsingError { + fn from(e: asn1::ParseError) -> KeyParsingError { + KeyParsingError::Parse(e) + } +} + +impl From for KeyParsingError { + fn from(e: openssl::error::ErrorStack) -> KeyParsingError { + KeyParsingError::OpenSSL(e) + } +} + +pub type KeyParsingResult = Result; + +#[cfg(test)] +mod tests { + use super::KeyParsingError; + + #[test] + fn test_key_parsing_error_from() { + let e = openssl::error::ErrorStack::get(); + + assert!(matches!( + KeyParsingError::from(e), + KeyParsingError::OpenSSL(_) + )); + } +} diff --git a/src/rust/cryptography-key-parsing/src/rsa.rs b/src/rust/cryptography-key-parsing/src/rsa.rs new file mode 100644 index 000000000000..066e7053cb52 --- /dev/null +++ b/src/rust/cryptography-key-parsing/src/rsa.rs @@ -0,0 +1,23 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::KeyParsingResult; + +#[derive(asn1::Asn1Read)] +struct Pksc1RsaPublicKey<'a> { + n: asn1::BigUint<'a>, + e: asn1::BigUint<'a>, +} + +pub fn parse_pkcs1_public_key( + data: &[u8], +) -> KeyParsingResult> { + let k = asn1::parse_single::(data)?; + + let n = openssl::bn::BigNum::from_slice(k.n.as_bytes())?; + let e = openssl::bn::BigNum::from_slice(k.e.as_bytes())?; + + let rsa = openssl::rsa::Rsa::from_public_components(n, e)?; + Ok(openssl::pkey::PKey::from_rsa(rsa)?) +} diff --git a/src/rust/cryptography-key-parsing/src/spki.rs b/src/rust/cryptography-key-parsing/src/spki.rs new file mode 100644 index 000000000000..e6e1133c490a --- /dev/null +++ b/src/rust/cryptography-key-parsing/src/spki.rs @@ -0,0 +1,142 @@ +// 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::{AlgorithmParameters, EcParameters, SubjectPublicKeyInfo}; + +use crate::{KeyParsingError, KeyParsingResult}; + +pub fn parse_public_key( + data: &[u8], +) -> KeyParsingResult> { + 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::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)))] + AlgorithmParameters::Ed448 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( + k.subject_public_key.as_bytes(), + openssl::pkey::Id::ED448, + )?), + 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)))] + AlgorithmParameters::X448 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( + k.subject_public_key.as_bytes(), + openssl::pkey::Id::X448, + )?), + 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()) + } + AlgorithmParameters::Dsa(dsa_params) => { + 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 pub_key_int = + asn1::parse_single::>(k.subject_public_key.as_bytes())?; + let pub_key = openssl::bn::BigNum::from_slice(pub_key_int.as_bytes())?; + + let dsa = openssl::dsa::Dsa::from_public_components(p, q, g, pub_key)?; + Ok(openssl::pkey::PKey::from_dsa(dsa)?) + } + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + AlgorithmParameters::Dh(dh_params) => { + let p = openssl::bn::BigNum::from_slice(dh_params.p.as_bytes())?; + let q = openssl::bn::BigNum::from_slice(dh_params.q.as_bytes())?; + let g = openssl::bn::BigNum::from_slice(dh_params.g.as_bytes())?; + let dh = openssl::dh::Dh::from_pqg(p, Some(q), g)?; + + let pub_key_int = + asn1::parse_single::>(k.subject_public_key.as_bytes())?; + let pub_key = openssl::bn::BigNum::from_slice(pub_key_int.as_bytes())?; + let dh = dh.set_public_key(pub_key)?; + + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_IS_LIBRESSL)] { + Ok(openssl::pkey::PKey::from_dh(dh)?) + } else { + Ok(openssl::pkey::PKey::from_dhx(dh)?) + } + } + } + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + AlgorithmParameters::DhKeyAgreement(dh_params) => { + 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 pub_key_int = + asn1::parse_single::>(k.subject_public_key.as_bytes())?; + let pub_key = openssl::bn::BigNum::from_slice(pub_key_int.as_bytes())?; + let dh = dh.set_public_key(pub_key)?; + + Ok(openssl::pkey::PKey::from_dh(dh)?) + } + _ => Err(KeyParsingError::UnsupportedKeyType( + k.algorithm.oid().clone(), + )), + } +} diff --git a/src/rust/cryptography-openssl/Cargo.toml b/src/rust/cryptography-openssl/Cargo.toml index 587a85909565..3a35c9fcaa2d 100644 --- a/src/rust/cryptography-openssl/Cargo.toml +++ b/src/rust/cryptography-openssl/Cargo.toml @@ -5,10 +5,10 @@ authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV -rust-version = "1.56.0" +rust-version = "1.63.0" [dependencies] -openssl = "0.10.53" -ffi = { package = "openssl-sys", version = "0.9.85" } +openssl = "0.10.63" +ffi = { package = "openssl-sys", version = "0.9.99" } foreign-types = "0.3" foreign-types-shared = "0.1" diff --git a/src/rust/cryptography-openssl/src/aead.rs b/src/rust/cryptography-openssl/src/aead.rs new file mode 100644 index 000000000000..000d5a9c65f9 --- /dev/null +++ b/src/rust/cryptography-openssl/src/aead.rs @@ -0,0 +1,91 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::{cvt, cvt_p, OpenSSLResult}; +use foreign_types_shared::{ForeignType, ForeignTypeRef}; + +pub enum AeadType { + ChaCha20Poly1305, +} + +foreign_types::foreign_type! { + type CType = ffi::EVP_AEAD_CTX; + fn drop = ffi::EVP_AEAD_CTX_free; + + pub struct AeadCtx; + pub struct AeadCtxRef; +} + +unsafe impl Sync for AeadCtx {} +unsafe impl Send for AeadCtx {} + +impl AeadCtx { + pub fn new(aead: AeadType, key: &[u8]) -> OpenSSLResult { + let aead = match aead { + AeadType::ChaCha20Poly1305 => unsafe { ffi::EVP_aead_chacha20_poly1305() }, + }; + + unsafe { + let ctx = cvt_p(ffi::EVP_AEAD_CTX_new( + aead, + key.as_ptr(), + key.len(), + ffi::EVP_AEAD_DEFAULT_TAG_LENGTH as usize, + ))?; + Ok(AeadCtx::from_ptr(ctx)) + } + } +} + +impl AeadCtxRef { + pub fn encrypt( + &self, + data: &[u8], + nonce: &[u8], + ad: &[u8], + out: &mut [u8], + ) -> OpenSSLResult<()> { + let mut out_len = out.len(); + unsafe { + cvt(ffi::EVP_AEAD_CTX_seal( + self.as_ptr(), + out.as_mut_ptr(), + &mut out_len, + out.len(), + nonce.as_ptr(), + nonce.len(), + data.as_ptr(), + data.len(), + ad.as_ptr(), + ad.len(), + ))?; + } + Ok(()) + } + + pub fn decrypt( + &self, + data: &[u8], + nonce: &[u8], + ad: &[u8], + out: &mut [u8], + ) -> OpenSSLResult<()> { + let mut out_len = out.len(); + unsafe { + cvt(ffi::EVP_AEAD_CTX_open( + self.as_ptr(), + out.as_mut_ptr(), + &mut out_len, + out.len(), + nonce.as_ptr(), + nonce.len(), + data.as_ptr(), + data.len(), + ad.as_ptr(), + ad.len(), + ))?; + } + Ok(()) + } +} diff --git a/src/rust/cryptography-openssl/src/cmac.rs b/src/rust/cryptography-openssl/src/cmac.rs new file mode 100644 index 000000000000..2f4d22653111 --- /dev/null +++ b/src/rust/cryptography-openssl/src/cmac.rs @@ -0,0 +1,73 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::ptr; + +use foreign_types_shared::{ForeignType, ForeignTypeRef}; + +use crate::hmac::DigestBytes; +use crate::{cvt, cvt_p, OpenSSLResult}; + +foreign_types::foreign_type! { + type CType = ffi::CMAC_CTX; + fn drop = ffi::CMAC_CTX_free; + + pub struct Cmac; + pub struct CmacRef; +} + +// SAFETY: It's safe to have `&` references from multiple threads. +unsafe impl Sync for Cmac {} +// SAFETY: It's safe to move the `Cmac` from one thread to another. +unsafe impl Send for Cmac {} + +impl Cmac { + pub fn new(key: &[u8], cipher: &openssl::cipher::CipherRef) -> OpenSSLResult { + // SAFETY: All FFI conditions are handled. + unsafe { + let ctx = Cmac::from_ptr(cvt_p(ffi::CMAC_CTX_new())?); + cvt(ffi::CMAC_Init( + ctx.as_ptr(), + key.as_ptr().cast(), + key.len(), + cipher.as_ptr(), + ptr::null_mut(), + ))?; + Ok(ctx) + } + } +} + +impl CmacRef { + pub fn update(&mut self, data: &[u8]) -> OpenSSLResult<()> { + // SAFETY: All FFI conditions are handled. + unsafe { + cvt(ffi::CMAC_Update( + self.as_ptr(), + data.as_ptr().cast(), + data.len(), + ))?; + } + Ok(()) + } + + pub fn finish(&mut self) -> OpenSSLResult { + let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; + let mut len = ffi::EVP_MAX_MD_SIZE as usize; + // SAFETY: All FFI conditions are handled. + unsafe { + cvt(ffi::CMAC_Final(self.as_ptr(), buf.as_mut_ptr(), &mut len))?; + } + Ok(DigestBytes { buf, len }) + } + + pub fn copy(&self) -> OpenSSLResult { + // SAFETY: All FFI conditions are handled. + unsafe { + let h = Cmac::from_ptr(cvt_p(ffi::CMAC_CTX_new())?); + cvt(ffi::CMAC_CTX_copy(h.as_ptr(), self.as_ptr()))?; + Ok(h) + } + } +} diff --git a/src/rust/cryptography-openssl/src/fips.rs b/src/rust/cryptography-openssl/src/fips.rs index 29c4c789d838..9cdbd3f34648 100644 --- a/src/rust/cryptography-openssl/src/fips.rs +++ b/src/rust/cryptography-openssl/src/fips.rs @@ -18,6 +18,7 @@ pub fn is_enabled() -> bool { CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) ))] + // SAFETY: No pre-conditions unsafe { ffi::EVP_default_properties_is_fips_enabled(ptr::null_mut()) == 1 } diff --git a/src/rust/cryptography-openssl/src/hmac.rs b/src/rust/cryptography-openssl/src/hmac.rs index b30de478688d..84b3a1e3b9b5 100644 --- a/src/rust/cryptography-openssl/src/hmac.rs +++ b/src/rust/cryptography-openssl/src/hmac.rs @@ -2,10 +2,12 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::{cvt, cvt_p, OpenSSLResult}; -use foreign_types_shared::{ForeignType, ForeignTypeRef}; use std::ptr; +use foreign_types_shared::{ForeignType, ForeignTypeRef}; + +use crate::{cvt, cvt_p, OpenSSLResult}; + foreign_types::foreign_type! { type CType = ffi::HMAC_CTX; fn drop = ffi::HMAC_CTX_free; @@ -14,11 +16,14 @@ foreign_types::foreign_type! { pub struct HmacRef; } +// SAFETY: It's safe to have `&` references from multiple threads. unsafe impl Sync for Hmac {} +// SAFETY: It's safe to move the `Hmac` from one thread to another. unsafe impl Send for Hmac {} impl Hmac { pub fn new(key: &[u8], md: openssl::hash::MessageDigest) -> OpenSSLResult { + // SAFETY: All FFI conditions are handled. unsafe { let h = Hmac::from_ptr(cvt_p(ffi::HMAC_CTX_new())?); cvt(ffi::HMAC_Init_ex( @@ -37,6 +42,7 @@ impl Hmac { impl HmacRef { pub fn update(&mut self, data: &[u8]) -> OpenSSLResult<()> { + // SAFETY: All FFI conditions are handled. unsafe { cvt(ffi::HMAC_Update(self.as_ptr(), data.as_ptr(), data.len()))?; } @@ -46,6 +52,7 @@ impl HmacRef { pub fn finish(&mut self) -> OpenSSLResult { let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; let mut len = ffi::EVP_MAX_MD_SIZE as std::os::raw::c_uint; + // SAFETY: All FFI conditions are handled. unsafe { cvt(ffi::HMAC_Final(self.as_ptr(), buf.as_mut_ptr(), &mut len))?; } @@ -56,6 +63,7 @@ impl HmacRef { } pub fn copy(&self) -> OpenSSLResult { + // SAFETY: All FFI conditions are handled. unsafe { let h = Hmac::from_ptr(cvt_p(ffi::HMAC_CTX_new())?); cvt(ffi::HMAC_CTX_copy(h.as_ptr(), self.as_ptr()))?; @@ -65,8 +73,8 @@ impl HmacRef { } pub struct DigestBytes { - buf: [u8; ffi::EVP_MAX_MD_SIZE as usize], - len: usize, + pub(crate) buf: [u8; ffi::EVP_MAX_MD_SIZE as usize], + pub(crate) len: usize, } impl std::ops::Deref for DigestBytes { diff --git a/src/rust/cryptography-openssl/src/lib.rs b/src/rust/cryptography-openssl/src/lib.rs index 0a2b48149e0f..d0fb6fff5c21 100644 --- a/src/rust/cryptography-openssl/src/lib.rs +++ b/src/rust/cryptography-openssl/src/lib.rs @@ -2,8 +2,15 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] + +#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] +pub mod aead; +pub mod cmac; pub mod fips; pub mod hmac; +#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] +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 new file mode 100644 index 000000000000..262062eedd3f --- /dev/null +++ b/src/rust/cryptography-openssl/src/poly1305.rs @@ -0,0 +1,45 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::mem::MaybeUninit; + +pub struct Poly1305State { + // The state data must be allocated in the heap so that its address does not change. This is + // because BoringSSL APIs that take a `poly1305_state*` ignore all the data before an aligned + // address. Since a stack-allocated struct would change address on every copy, BoringSSL would + // interpret each copy differently, causing unexpected behavior. + context: Box, +} + +impl Poly1305State { + pub fn new(key: &[u8]) -> Poly1305State { + assert_eq!(key.len(), 32); + let mut ctx: Box> = + Box::new(MaybeUninit::::uninit()); + + // After initializing the context, unwrap the Box> into + // a Box while keeping the same memory address. See the docstring of the + // Poly1305State struct above for the rationale. + let initialized_ctx: Box = unsafe { + ffi::CRYPTO_poly1305_init(ctx.as_mut().as_mut_ptr(), key.as_ptr()); + let raw_ctx_ptr = (*Box::into_raw(ctx)).as_mut_ptr(); + Box::from_raw(raw_ctx_ptr) + }; + + Poly1305State { + context: initialized_ctx, + } + } + + pub fn update(&mut self, data: &[u8]) -> () { + unsafe { + ffi::CRYPTO_poly1305_update(self.context.as_mut(), data.as_ptr(), data.len()); + }; + } + + pub fn finalize(&mut self, output: &mut [u8]) -> () { + assert_eq!(output.len(), 16); + unsafe { ffi::CRYPTO_poly1305_finish(self.context.as_mut(), output.as_mut_ptr()) }; + } +} diff --git a/src/rust/cryptography-x509-verification/Cargo.toml b/src/rust/cryptography-x509-verification/Cargo.toml new file mode 100644 index 000000000000..1ed759074167 --- /dev/null +++ b/src/rust/cryptography-x509-verification/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cryptography-x509-verification" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.63.0" + +[dependencies] +asn1 = { version = "0.15.5", default-features = false } +cryptography-x509 = { path = "../cryptography-x509" } +once_cell = "1" + +[dev-dependencies] +pem = { version = "3", default-features = false } diff --git a/src/rust/cryptography-x509-verification/src/certificate.rs b/src/rust/cryptography-x509-verification/src/certificate.rs new file mode 100644 index 000000000000..2260fd6d9604 --- /dev/null +++ b/src/rust/cryptography-x509-verification/src/certificate.rs @@ -0,0 +1,91 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +//! Validation-specific certificate functionality. + +use cryptography_x509::certificate::Certificate; + +pub(crate) fn cert_is_self_issued(cert: &Certificate<'_>) -> bool { + cert.issuer() == cert.subject() +} + +#[cfg(test)] +pub(crate) mod tests { + use super::cert_is_self_issued; + use crate::certificate::Certificate; + use crate::ops::tests::{cert, v1_cert_pem}; + use crate::ops::CryptoOps; + + #[test] + fn test_certificate_v1() { + let cert_pem = v1_cert_pem(); + let cert = cert(&cert_pem); + + assert!(!cert_is_self_issued(&cert)); + } + + fn ca_pem() -> pem::Pem { + // From vectors/cryptography_vectors/x509/custom/ca/ca.pem + pem::parse( + "-----BEGIN CERTIFICATE----- +MIIBUTCB96ADAgECAgIDCTAKBggqhkjOPQQDAjAnMQswCQYDVQQGEwJVUzEYMBYG +A1UEAwwPY3J5cHRvZ3JhcGh5IENBMB4XDTE3MDEwMTEyMDEwMFoXDTM4MTIzMTA4 +MzAwMFowJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBj/z7v5Obj13cPuwECLBnUGq0/N2CxS +JE4f4BBGZ7VfFblivTvPDG++Gve0oQ+0uctuhrNQ+WxRv8GC177F+QWjEzARMA8G +A1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhANES742XWm64tkGnz8Dn +pG6u2lHkZFQr3oaVvPcemvlbAiEA0WGGzmYx5C9UvfXIK7NEziT4pQtyESE0uRVK +Xw4nMqk= +-----END CERTIFICATE-----", + ) + .unwrap() + } + + #[test] + fn test_certificate_ca() { + let cert_pem = ca_pem(); + let cert = cert(&cert_pem); + + assert!(cert_is_self_issued(&cert)); + } + + pub(crate) struct PublicKeyErrorOps {} + impl CryptoOps for PublicKeyErrorOps { + type Key = (); + type Err = (); + type CertificateExtra = (); + + fn public_key(&self, _cert: &Certificate<'_>) -> Result { + // Simulate failing to retrieve a public key. + Err(()) + } + + fn verify_signed_by( + &self, + _cert: &Certificate<'_>, + _key: &Self::Key, + ) -> Result<(), Self::Err> { + Ok(()) + } + } + + #[test] + fn test_certificate_public_key_error() { + let cert_pem = ca_pem(); + let cert = cert(&cert_pem); + + assert!(cert_is_self_issued(&cert)); + } + + #[test] + fn test_certificate_public_key_error_ops() { + // Just to get coverage on the `PublicKeyErrorOps` helper. + let cert_pem = ca_pem(); + let cert = cert(&cert_pem); + let ops = PublicKeyErrorOps {}; + + assert!(ops.public_key(&cert).is_err()); + assert!(ops.verify_signed_by(&cert, &()).is_ok()); + } +} diff --git a/src/rust/cryptography-x509-verification/src/lib.rs b/src/rust/cryptography-x509-verification/src/lib.rs new file mode 100644 index 000000000000..6265f75c5502 --- /dev/null +++ b/src/rust/cryptography-x509-verification/src/lib.rs @@ -0,0 +1,350 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +#![forbid(unsafe_code)] +#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] + +pub mod certificate; +pub mod ops; +pub mod policy; +pub mod trust_store; +pub mod types; + +use std::vec; + +use cryptography_x509::extensions::{DuplicateExtensionsError, Extensions}; +use cryptography_x509::{ + extensions::{NameConstraints, SubjectAlternativeName}, + name::GeneralName, + 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::ApplyNameConstraintStatus::{Applied, Skipped}; + +#[derive(Debug, PartialEq, Eq)] +pub enum ValidationError { + CandidatesExhausted(Box), + Malformed(asn1::ParseError), + DuplicateExtension(DuplicateExtensionsError), + Other(String), +} + +impl From for ValidationError { + fn from(value: asn1::ParseError) -> Self { + Self::Malformed(value) + } +} + +impl From for ValidationError { + fn from(value: DuplicateExtensionsError) -> Self { + Self::DuplicateExtension(value) + } +} + +struct NameChain<'a, 'chain> { + child: Option<&'a NameChain<'a, 'chain>>, + sans: SubjectAlternativeName<'chain>, +} + +impl<'a, 'chain> NameChain<'a, 'chain> { + fn new( + child: Option<&'a NameChain<'a, 'chain>>, + extensions: &Extensions<'chain>, + self_issued_intermediate: bool, + ) -> Result { + let sans = match ( + self_issued_intermediate, + extensions.get_extension(&SUBJECT_ALTERNATIVE_NAME_OID), + ) { + (false, Some(sans)) => sans.value::>()?, + // TODO: there really ought to be a better way to express an empty + // `asn1::SequenceOf`. + _ => asn1::parse_single(b"\x30\x00")?, + }; + + Ok(Self { child, sans }) + } + + fn evaluate_single_constraint( + &self, + constraint: &GeneralName<'chain>, + san: &GeneralName<'chain>, + ) -> Result { + 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))), + (_, None) => Err(ValidationError::Other(format!( + "unsatisfiable DNS name constraint: malformed SAN {}", + name.0 + ))), + (None, _) => Err(ValidationError::Other(format!( + "malformed DNS name constraint: {}", + pattern.0 + ))), + } + } + (GeneralName::IPAddress(pattern), GeneralName::IPAddress(name)) => { + match ( + IPConstraint::from_bytes(pattern), + IPAddress::from_bytes(name), + ) { + (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), + (_, None) => Err(ValidationError::Other(format!( + "unsatisfiable IP name constraint: malformed SAN {:?}", + name, + ))), + (None, _) => Err(ValidationError::Other(format!( + "malformed IP name constraints: {:?}", + pattern + ))), + } + } + _ => Ok(Skipped), + } + } + + fn evaluate_constraints( + &self, + constraints: &NameConstraints<'chain>, + ) -> Result<(), ValidationError> { + if let Some(child) = self.child { + child.evaluate_constraints(constraints)?; + } + + for san in self.sans.clone() { + // If there are no applicable constraints, the SAN is considered valid so the default is true. + let mut permit = true; + if let Some(permitted_subtrees) = &constraints.permitted_subtrees { + for p in permitted_subtrees.unwrap_read().clone() { + let status = self.evaluate_single_constraint(&p.base, &san)?; + if status.is_applied() { + permit = status.is_match(); + if permit { + break; + } + } + } + } + + if !permit { + return Err(ValidationError::Other( + "no permitted name constraints matched SAN".into(), + )); + } + + if let Some(excluded_subtrees) = &constraints.excluded_subtrees { + for e in excluded_subtrees.unwrap_read().clone() { + let status = self.evaluate_single_constraint(&e.base, &san)?; + if status.is_match() { + return Err(ValidationError::Other( + "excluded name constraint matched SAN".into(), + )); + } + } + } + } + + Ok(()) + } +} + +pub type Chain<'c, B> = Vec>; + +pub fn verify<'chain, B: CryptoOps>( + leaf: &VerificationCertificate<'chain, B>, + intermediates: impl IntoIterator>, + policy: &Policy<'_, B>, + store: &Store<'chain, B>, +) -> Result, ValidationError> { + let builder = ChainBuilder::new(intermediates.into_iter().collect(), policy, store); + + builder.build_chain(leaf) +} + +struct ChainBuilder<'a, 'chain, B: CryptoOps> { + intermediates: Vec>, + policy: &'a Policy<'a, B>, + store: &'a Store<'chain, B>, +} + +// When applying a name constraint, we need to distinguish between a few different scenarios: +// * `Applied(true)`: The name constraint is the same type as the SAN and matches. +// * `Applied(false)`: The name constraint is the same type as the SAN and does not match. +// * `Skipped`: The name constraint is a different type to the SAN. +enum ApplyNameConstraintStatus { + Applied(bool), + Skipped, +} + +impl ApplyNameConstraintStatus { + fn is_applied(&self) -> bool { + matches!(self, Applied(_)) + } + + fn is_match(&self) -> bool { + matches!(self, Applied(true)) + } +} + +impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { + fn new( + intermediates: Vec>, + policy: &'a Policy<'a, B>, + store: &'a Store<'chain, B>, + ) -> Self { + Self { + intermediates, + policy, + store, + } + } + + fn potential_issuers( + &'a self, + cert: &'a VerificationCertificate<'chain, B>, + ) -> impl Iterator> + '_ { + // TODO: Optimizations: + // * Search by AKI and other identifiers? + self.store + .get_by_subject(&cert.certificate().tbs_cert.issuer) + .iter() + .chain(self.intermediates.iter().filter(|&candidate| { + candidate.certificate().subject() == cert.certificate().issuer() + })) + } + + fn build_chain_inner( + &self, + working_cert: &VerificationCertificate<'chain, B>, + current_depth: u8, + working_cert_extensions: &Extensions<'chain>, + name_chain: NameChain<'_, 'chain>, + ) -> Result, ValidationError> { + if let Some(nc) = working_cert_extensions.get_extension(&NAME_CONSTRAINTS_OID) { + name_chain.evaluate_constraints(&nc.value()?)?; + } + + // Look in the store's root set to see if the working cert is listed. + // If it is, we've reached the end. + if self.store.contains(working_cert) { + return Ok(vec![working_cert.clone()]); + } + + // Check that our current depth does not exceed our policy-configured + // max depth. We do this after the root set check, since the depth + // only measures the intermediate chain's length, not the root or leaf. + if current_depth > self.policy.max_chain_depth { + return Err(ValidationError::Other( + "chain construction exceeds max depth".into(), + )); + } + + // Otherwise, we collect a list of potential issuers for this cert, + // and continue with the first that verifies. + let mut last_err: Option = None; + for issuing_cert_candidate in self.potential_issuers(working_cert) { + // A candidate issuer is said to verify if it both + // signs for the working certificate and conforms to the + // policy. + let issuer_extensions = issuing_cert_candidate.certificate().extensions()?; + match self.policy.valid_issuer( + issuing_cert_candidate, + working_cert.certificate(), + current_depth, + &issuer_extensions, + ) { + Ok(_) => { + match self.build_chain_inner( + issuing_cert_candidate, + // NOTE(ww): According to RFC 5280, we should only + // increase the chain depth when the certificate is **not** + // self-issued. In practice however, implementations widely + // ignore this requirement, and unconditionally increment + // the depth with every chain member. We choose to do the same; + // see `pathlen::self-issued-certs-pathlen` from x509-limbo + // for the testcase we intentionally fail. + // + // Implementation note for someone looking to change this in the future: + // care should be taken to avoid infinite recursion with self-signed + // certificates in the intermediate set; changing this behavior will + // also require a "is not self-signed" check on intermediate candidates. + // + // See https://gist.github.com/woodruffw/776153088e0df3fc2f0675c5e835f7b8 + // for an example of this change. + current_depth.checked_add(1).ok_or_else(|| { + ValidationError::Other( + "current depth calculation overflowed".to_string(), + ) + })?, + &issuer_extensions, + NameChain::new( + Some(&name_chain), + &issuer_extensions, + // Per RFC 5280 4.2.1.10: Name constraints are not applied + // to subjects in self-issued certificates, *unless* the + // certificate is the "final" (i.e., leaf) certificate in the path. + // We accomplish this by only collecting the SANs when the issuing + // candidate (which is a non-leaf by definition) isn't self-issued. + cert_is_self_issued(issuing_cert_candidate.certificate()), + )?, + ) { + Ok(mut chain) => { + chain.push(working_cert.clone()); + return Ok(chain); + } + Err(e) => last_err = Some(e), + }; + } + Err(e) => last_err = Some(e), + }; + } + + // We only reach this if we fail to hit our base case above, or if + // a chain building step fails to find a next valid certificate. + Err(ValidationError::CandidatesExhausted(last_err.map_or_else( + || { + Box::new(ValidationError::Other( + "all candidates exhausted with no interior errors".to_string(), + )) + }, + |e| match e { + // Avoid spamming the user with nested `CandidatesExhausted` errors. + ValidationError::CandidatesExhausted(e) => e, + _ => Box::new(e), + }, + ))) + } + + fn build_chain( + &self, + leaf: &VerificationCertificate<'chain, B>, + ) -> Result, ValidationError> { + // Before anything else, check whether the given leaf cert + // is well-formed according to our policy (and its underlying + // certificate profile). + // + // The leaf must be an EE; a CA cert in the leaf position will be rejected. + let leaf_extensions = leaf.certificate().extensions()?; + + self.policy + .permits_ee(leaf.certificate(), &leaf_extensions)?; + + let mut chain = self.build_chain_inner( + leaf, + 0, + &leaf_extensions, + NameChain::new(None, &leaf_extensions, false)?, + )?; + // We build the chain in reverse order, fix it now. + chain.reverse(); + Ok(chain) + } +} diff --git a/src/rust/cryptography-x509-verification/src/ops.rs b/src/rust/cryptography-x509-verification/src/ops.rs new file mode 100644 index 000000000000..807bce5dff93 --- /dev/null +++ b/src/rust/cryptography-x509-verification/src/ops.rs @@ -0,0 +1,92 @@ +// 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::certificate::Certificate; + +pub struct VerificationCertificate<'a, B: CryptoOps> { + cert: Certificate<'a>, + public_key: once_cell::sync::OnceCell, + extra: B::CertificateExtra, +} + +impl<'a, B: CryptoOps> VerificationCertificate<'a, B> { + pub fn new(cert: Certificate<'a>, extra: B::CertificateExtra) -> Self { + VerificationCertificate { + cert, + extra, + public_key: once_cell::sync::OnceCell::new(), + } + } + + pub fn certificate(&self) -> &Certificate<'a> { + &self.cert + } + + pub fn public_key(&self, ops: &B) -> Result<&B::Key, B::Err> { + self.public_key + .get_or_try_init(|| ops.public_key(self.certificate())) + } + + pub fn extra(&self) -> &B::CertificateExtra { + &self.extra + } +} + +impl PartialEq for VerificationCertificate<'_, B> { + fn eq(&self, other: &Self) -> bool { + self.cert == other.cert + } +} +impl Eq for VerificationCertificate<'_, B> {} +impl Clone for VerificationCertificate<'_, B> { + fn clone(&self) -> Self { + VerificationCertificate::new(self.cert.clone(), self.extra.clone()) + } +} + +pub trait CryptoOps { + /// A public key type for this cryptographic backend. + type Key; + + /// An error type for this cryptographic backend. + type Err; + + /// Extra data that's passed around with the certificate. + type CertificateExtra: Clone; + + /// Extracts the public key from the given `Certificate` in + /// a `Key` format known by the cryptographic backend, or `None` + /// if the key is malformed. + fn public_key(&self, cert: &Certificate<'_>) -> Result; + + /// Verifies the signature on `Certificate` using the given + /// `Key`. + fn verify_signed_by(&self, cert: &Certificate<'_>, key: &Self::Key) -> Result<(), Self::Err>; +} + +#[cfg(test)] +pub(crate) mod tests { + use cryptography_x509::certificate::Certificate; + + pub(crate) fn v1_cert_pem() -> pem::Pem { + pem::parse( + " +-----BEGIN CERTIFICATE----- +MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV +BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz +MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM +RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF +AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO +/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE +Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ +zl9HYIMxATFyqSiD9jsx +-----END CERTIFICATE-----", + ) + .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 new file mode 100644 index 000000000000..7006ad5dd110 --- /dev/null +++ b/src/rust/cryptography-x509-verification/src/policy/extension.rs @@ -0,0 +1,756 @@ +// 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::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}; + +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, +} + +impl ExtensionPolicy { + pub(crate) fn permits( + &self, + policy: &Policy<'_, B>, + cert: &Certificate<'_>, + extensions: &Extensions<'_>, + ) -> Result<(), ValidationError> { + let mut authority_information_access_seen = false; + let mut authority_key_identifier_seen = false; + let mut subject_key_identifier_seen = false; + let mut key_usage_seen = false; + let mut subject_alternative_name_seen = false; + let mut basic_constraints_seen = false; + let mut name_constraints_seen = false; + let mut extended_key_usage_seen = false; + + // Iterate over each extension and run its policy. + for ext in extensions.iter() { + match ext.extn_id { + AUTHORITY_INFORMATION_ACCESS_OID => { + authority_information_access_seen = true; + self.authority_information_access + .permits(policy, cert, Some(&ext))?; + } + AUTHORITY_KEY_IDENTIFIER_OID => { + authority_key_identifier_seen = true; + self.authority_key_identifier + .permits(policy, cert, Some(&ext))?; + } + SUBJECT_KEY_IDENTIFIER_OID => { + subject_key_identifier_seen = true; + self.subject_key_identifier + .permits(policy, cert, Some(&ext))?; + } + KEY_USAGE_OID => { + key_usage_seen = true; + self.key_usage.permits(policy, cert, Some(&ext))?; + } + SUBJECT_ALTERNATIVE_NAME_OID => { + subject_alternative_name_seen = true; + self.subject_alternative_name + .permits(policy, cert, Some(&ext))?; + } + BASIC_CONSTRAINTS_OID => { + basic_constraints_seen = true; + self.basic_constraints.permits(policy, cert, Some(&ext))?; + } + NAME_CONSTRAINTS_OID => { + name_constraints_seen = true; + self.name_constraints.permits(policy, cert, Some(&ext))?; + } + EXTENDED_KEY_USAGE_OID => { + extended_key_usage_seen = true; + self.extended_key_usage.permits(policy, cert, Some(&ext))?; + } + _ if ext.critical => { + return Err(ValidationError::Other(format!( + "certificate contains unaccounted-for critical extensions: {}", + ext.extn_id + ))); + } + _ => {} + } + } + + // Now we check if there were any required extensions that aren't + // present + if !authority_information_access_seen { + self.authority_information_access + .permits(policy, cert, None)?; + } + if !authority_key_identifier_seen { + self.authority_key_identifier.permits(policy, cert, None)?; + } + if !subject_key_identifier_seen { + self.subject_key_identifier.permits(policy, cert, None)?; + } + if !key_usage_seen { + self.key_usage.permits(policy, cert, None)?; + } + if !subject_alternative_name_seen { + self.subject_alternative_name.permits(policy, cert, None)?; + } + if !basic_constraints_seen { + self.basic_constraints.permits(policy, cert, None)?; + } + if !name_constraints_seen { + self.name_constraints.permits(policy, cert, None)?; + } + if !extended_key_usage_seen { + self.extended_key_usage.permits(policy, cert, None)?; + } + + Ok(()) + } +} + +/// Represents different criticality states for an extension. +pub(crate) enum Criticality { + /// The extension MUST be marked as critical. + Critical, + /// The extension MAY be marked as critical. + Agnostic, + /// The extension MUST NOT be marked as critical. + NonCritical, +} + +impl Criticality { + pub(crate) fn permits(&self, critical: bool) -> bool { + match (self, critical) { + (Criticality::Critical, true) => true, + (Criticality::Critical, false) => false, + (Criticality::Agnostic, _) => true, + (Criticality::NonCritical, true) => false, + (Criticality::NonCritical, false) => true, + } + } +} + +type PresentExtensionValidatorCallback = + fn(&Policy<'_, B>, &Certificate<'_>, &Extension<'_>) -> Result<(), ValidationError>; + +type MaybeExtensionValidatorCallback = + fn(&Policy<'_, B>, &Certificate<'_>, Option<&Extension<'_>>) -> Result<(), ValidationError>; + +/// Represents different validation states for an extension. +pub(crate) enum ExtensionValidator { + /// The extension MUST NOT be present. + NotPresent, + /// The extension MUST be present. + Present { + /// The extension's criticality. + criticality: Criticality, + /// An optional validator over the extension's inner contents, with + /// the surrounding `Policy` as context. + validator: Option>, + }, + /// The extension MAY be present; the interior validator is + /// always called if supplied, including if the extension is not present. + MaybePresent { + criticality: Criticality, + validator: Option>, + }, +} + +impl ExtensionValidator { + pub(crate) fn not_present() -> Self { + Self::NotPresent + } + + pub(crate) fn present( + criticality: Criticality, + validator: Option>, + ) -> Self { + Self::Present { + criticality, + validator, + } + } + + pub(crate) fn maybe_present( + criticality: Criticality, + validator: Option>, + ) -> Self { + Self::MaybePresent { + criticality, + validator, + } + } + + pub(crate) fn permits( + &self, + policy: &Policy<'_, B>, + cert: &Certificate<'_>, + extension: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + match (self, extension) { + // Extension MUST NOT be present and isn't; OK. + (ExtensionValidator::NotPresent, None) => Ok(()), + // Extension MUST NOT be present but is; NOT OK. + (ExtensionValidator::NotPresent, Some(_)) => Err(ValidationError::Other( + "Certificate contains prohibited extension".to_string(), + )), + // Extension MUST be present but is not; NOT OK. + (ExtensionValidator::Present { .. }, None) => Err(ValidationError::Other( + "Certificate is missing required extension".to_string(), + )), + // Extension MUST be present and is; check it. + ( + ExtensionValidator::Present { + criticality, + validator, + }, + Some(extn), + ) => { + if !criticality.permits(extn.critical) { + return Err(ValidationError::Other( + "Certificate extension has incorrect criticality".to_string(), + )); + } + + // If a custom validator is supplied, apply it. + validator.map_or(Ok(()), |v| v(policy, cert, extn)) + } + // Extension MAY be present. + ( + ExtensionValidator::MaybePresent { + criticality, + validator, + }, + extn, + ) => { + // If the extension is present, apply our criticality check. + if extn.map_or(false, |extn| !criticality.permits(extn.critical)) { + return Err(ValidationError::Other( + "Certificate extension has incorrect criticality".to_string(), + )); + } + + // If a custom validator is supplied, apply it. + validator.map_or(Ok(()), |v| v(policy, cert, extn)) + } + } + } +} + +pub(crate) mod ee { + use cryptography_x509::{ + certificate::Certificate, + extensions::{ + BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage, SubjectAlternativeName, + }, + }; + + use crate::{ + ops::CryptoOps, + policy::{Policy, ValidationError}, + }; + + pub(crate) fn basic_constraints( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + if let Some(extn) = extn { + let basic_constraints: BasicConstraints = extn.value()?; + + if basic_constraints.ca { + return Err(ValidationError::Other( + "basicConstraints.cA must not be asserted in an EE certificate".to_string(), + )); + } + } + + Ok(()) + } + + pub(crate) fn subject_alternative_name( + policy: &Policy<'_, B>, + cert: &Certificate<'_>, + extn: &Extension<'_>, + ) -> Result<(), ValidationError> { + match (cert.subject().is_empty(), extn.critical) { + // If the subject is empty, the SAN MUST be critical. + (true, false) => { + return Err(ValidationError::Other( + "EE subjectAltName MUST be critical when subject is empty".to_string(), + )); + } + // If the subject is non-empty, the SAN MUST NOT be critical. + (false, true) => { + return Err(ValidationError::Other( + "EE subjectAltName MUST NOT be critical when subject is nonempty".to_string(), + )) + } + _ => (), + }; + + let san: SubjectAlternativeName<'_> = extn.value()?; + if !policy.subject.matches(&san) { + return Err(ValidationError::Other( + "leaf certificate has no matching subjectAltName".into(), + )); + } + + Ok(()) + } + + pub(crate) fn extended_key_usage( + policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + if let Some(extn) = extn { + let mut ekus: ExtendedKeyUsage<'_> = extn.value()?; + + // CABF requires EKUs in EE certs, but this is widely ignored + // by implementations (which treat a missing EKU as "any EKU"). + // On the other hand, if the EKU is present, it **must** be + // the one specified in the policy (e.g., `serverAuth`) and + // **must not** be the explicit `anyExtendedKeyUsage` EKU. + // See: CABF 7.1.2.7.10. + if ekus.any(|eku| eku == policy.extended_key_usage) { + Ok(()) + } else { + Err(ValidationError::Other("required EKU not found".to_string())) + } + } else { + Ok(()) + } + } + + pub(crate) fn key_usage( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + if let Some(extn) = extn { + let key_usage: KeyUsage<'_> = extn.value()?; + + if key_usage.key_cert_sign() { + return Err(ValidationError::Other( + "EE keyUsage must not assert keyCertSign".to_string(), + )); + } + } + + Ok(()) + } +} + +pub(crate) mod ca { + use cryptography_x509::{ + certificate::Certificate, + extensions::{ + AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage, + NameConstraints, + }, + oid::EKU_ANY_KEY_USAGE_OID, + }; + + use crate::{ + ops::CryptoOps, + policy::{Policy, ValidationError}, + }; + + pub(crate) fn authority_key_identifier( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + // CABF: AKI is required on all CA certificates *except* root CA certificates, + // where is it merely recommended. This is slightly different from RFC 5280, + // which requires AKI on all CA certificates *except* self-signed root CA certificates. + // + // This discrepancy poses a challenge: from a strict CABF perspective we should + // require the AKI unless we're on a root CA, but we lack the context to determine that + // here. We *could* infer that we're on a root by checking whether the CA is self-signed, + // but many root CAs still use RSA with SHA-1 (which is intentionally unsupported + // for signature verification). + // + // Consequently, the best we can currently do here is check whether the AKI conforms + // to the CABF mandated format, *if* it exists. This means that we will accept + // some chains that are not strictly CABF compliant (e.g. ones where intermediate + // CAs are missing AKIs), but this is a relatively minor discrepancy. + if let Some(extn) = extn { + let aki: AuthorityKeyIdentifier<'_> = extn.value()?; + // 7.1.2.11.1 Authority Key Identifier: + + // keyIdentifier MUST be present. + // TODO: Check that keyIdentifier matches subjectKeyIdentifier. + if aki.key_identifier.is_none() { + return Err(ValidationError::Other( + "authorityKeyIdentifier must contain keyIdentifier".to_string(), + )); + } + + // authorityCertIssuer and authorityCertSerialNumber MUST NOT be present. + if aki.authority_cert_issuer.is_some() { + return Err(ValidationError::Other( + "authorityKeyIdentifier must not contain authorityCertIssuer".to_string(), + )); + } + + if aki.authority_cert_serial_number.is_some() { + return Err(ValidationError::Other( + "authorityKeyIdentifier must not contain authorityCertSerialNumber".to_string(), + )); + } + } + + Ok(()) + } + + pub(crate) fn key_usage( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: &Extension<'_>, + ) -> Result<(), ValidationError> { + let key_usage: KeyUsage<'_> = extn.value()?; + + if !key_usage.key_cert_sign() { + return Err(ValidationError::Other( + "keyUsage.keyCertSign must be asserted in a CA certificate".to_string(), + )); + } + + Ok(()) + } + + pub(crate) fn basic_constraints( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: &Extension<'_>, + ) -> Result<(), ValidationError> { + let basic_constraints: BasicConstraints = extn.value()?; + + if !basic_constraints.ca { + return Err(ValidationError::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( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + if let Some(extn) = extn { + let name_constraints: NameConstraints<'_> = extn.value()?; + + let permitted_subtrees_empty = name_constraints + .permitted_subtrees + .as_ref() + .map_or(true, |pst| pst.unwrap_read().is_empty()); + let excluded_subtrees_empty = name_constraints + .excluded_subtrees + .as_ref() + .map_or(true, |est| est.unwrap_read().is_empty()); + + if permitted_subtrees_empty && excluded_subtrees_empty { + return Err(ValidationError::Other( + "nameConstraints must have non-empty permittedSubtrees or excludedSubtrees" + .to_string(), + )); + } + + // NOTE: Both RFC 5280 and CABF require each `GeneralSubtree` + // to have `minimum=0` and `maximum=NULL`, but experimentally + // not many validators check for this. + } + + Ok(()) + } + + pub(crate) fn extended_key_usage( + policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + if let Some(extn) = extn { + let mut ekus: ExtendedKeyUsage<'_> = extn.value()?; + + // NOTE: CABF explicitly forbids anyEKU in and most CA certs, + // but this is widely (universally?) ignored by other implementations. + if ekus.any(|eku| eku == policy.extended_key_usage || eku == EKU_ANY_KEY_USAGE_OID) { + Ok(()) + } else { + Err(ValidationError::Other("required EKU not found".to_string())) + } + } else { + Ok(()) + } + } +} + +pub(crate) mod common { + use cryptography_x509::{ + certificate::Certificate, + extensions::{Extension, SequenceOfAccessDescriptions}, + }; + + use crate::{ + ops::CryptoOps, + policy::{Policy, ValidationError}, + }; + + pub(crate) fn authority_information_access( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + extn: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + if let Some(extn) = extn { + // We don't currently do anything useful with these, but we + // do check that they're well-formed. + let _: SequenceOfAccessDescriptions<'_> = extn.value()?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + 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, ValidationError}; + use crate::types::DNSName; + + #[test] + fn test_criticality_variants() { + let criticality = Criticality::Critical; + assert!(criticality.permits(true)); + assert!(!criticality.permits(false)); + + let criticality = Criticality::Agnostic; + assert!(criticality.permits(true)); + assert!(criticality.permits(false)); + + let criticality = Criticality::NonCritical; + assert!(!criticality.permits(true)); + 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, + ext: &T, + ) -> Vec { + let ext_value = asn1::write_single(&ext).unwrap(); + let ext = Extension { + extn_id: oid, + critical, + extn_value: &ext_value, + }; + asn1::write_single(&ext).unwrap() + } + + fn present_extension_validator( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + _ext: &Extension<'_>, + ) -> Result<(), ValidationError> { + Ok(()) + } + + #[test] + fn test_extension_validator_present() { + // 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 ops = PublicKeyErrorOps {}; + let policy = Policy::new( + ops, + Subject::DNS(DNSName::new("example.com").unwrap()), + epoch(), + None, + ); + + // Test a policy that stipulates that a given extension MUST be present. + let extension_validator = + ExtensionValidator::present(Criticality::Critical, Some(present_extension_validator)); + + // Check the case where the extension is present. + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + 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)) + .is_ok()); + + // Check the case where the extension isn't present. + assert!(extension_validator.permits(&policy, &cert, None).is_err()); + } + + fn maybe_extension_validator( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + _ext: Option<&Extension<'_>>, + ) -> Result<(), ValidationError> { + Ok(()) + } + + #[test] + fn test_extension_validator_maybe() { + // 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 ops = PublicKeyErrorOps {}; + let policy = Policy::new( + ops, + Subject::DNS(DNSName::new("example.com").unwrap()), + epoch(), + None, + ); + + // Test a validator that stipulates that a given extension CAN be present. + let extension_validator = ExtensionValidator::maybe_present( + Criticality::Critical, + Some(maybe_extension_validator), + ); + + // Check the case where the extension is present. + let bc = BasicConstraints { + ca: false, + path_length: Some(3), + }; + 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)) + .is_ok()); + + // Check the case where the extension isn't present. + assert!(extension_validator.permits(&policy, &cert, None).is_ok()); + } + + #[test] + fn test_extension_validator_not_present() { + // 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 ops = PublicKeyErrorOps {}; + let policy = Policy::new( + ops, + Subject::DNS(DNSName::new("example.com").unwrap()), + epoch(), + None, + ); + + // Test a validator that stipulates that a given extension MUST NOT be present. + let extension_validator = ExtensionValidator::not_present(); + + // Check the case where the extension is present. + let bc = BasicConstraints { + ca: false, + path_length: Some(3), + }; + 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)) + .is_err()); + + // Check the case where the extension isn't present. + assert!(extension_validator.permits(&policy, &cert, None).is_ok()); + } + + #[test] + fn test_extension_validator_present_incorrect_criticality() { + // 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 ops = PublicKeyErrorOps {}; + let policy = Policy::new( + ops, + Subject::DNS(DNSName::new("example.com").unwrap()), + epoch(), + None, + ); + + // Test a present policy that stipulates that a given extension MUST be critical. + let extension_validator = + ExtensionValidator::present(Criticality::Critical, Some(present_extension_validator)); + + // Mark the extension as non-critical despite our policy stipulating that it must be critical. + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + 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)) + .is_err()); + } + + #[test] + fn test_extension_validator_maybe_present_incorrect_criticality() { + // 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 ops = PublicKeyErrorOps {}; + let policy = Policy::new( + ops, + Subject::DNS(DNSName::new("example.com").unwrap()), + epoch(), + None, + ); + + // Test a maybe present validator that stipulates that a given extension MUST be critical. + let extension_validator = ExtensionValidator::maybe_present( + Criticality::Critical, + Some(maybe_extension_validator), + ); + + // Mark the extension as non-critical despite our policy stipulating that it must be critical. + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + 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)) + .is_err()); + } +} diff --git a/src/rust/cryptography-x509-verification/src/policy/mod.rs b/src/rust/cryptography-x509-verification/src/policy/mod.rs new file mode 100644 index 000000000000..6d96e5feaef1 --- /dev/null +++ b/src/rust/cryptography-x509-verification/src/policy/mod.rs @@ -0,0 +1,749 @@ +// 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. + +mod extension; + +use std::collections::HashSet; +use std::ops::Range; +use std::sync::Arc; + +use asn1::ObjectIdentifier; +use cryptography_x509::certificate::Certificate; +use cryptography_x509::common::{ + AlgorithmIdentifier, AlgorithmParameters, EcParameters, RsaPssParameters, Time, + PSS_SHA256_HASH_ALG, PSS_SHA256_MASK_GEN_ALG, PSS_SHA384_HASH_ALG, PSS_SHA384_MASK_GEN_ALG, + PSS_SHA512_HASH_ALG, PSS_SHA512_MASK_GEN_ALG, +}; +use cryptography_x509::extensions::{BasicConstraints, Extensions, SubjectAlternativeName}; +use cryptography_x509::name::GeneralName; +use cryptography_x509::oid::{ + BASIC_CONSTRAINTS_OID, EC_SECP256R1, EC_SECP384R1, EC_SECP521R1, EKU_SERVER_AUTH_OID, +}; +use once_cell::sync::Lazy; + +use crate::ops::CryptoOps; +use crate::policy::extension::{ca, common, ee, Criticality, ExtensionPolicy, ExtensionValidator}; +use crate::types::{DNSName, DNSPattern, IPAddress}; +use crate::{ValidationError, VerificationCertificate}; + +// SubjectPublicKeyInfo AlgorithmIdentifier constants, as defined in CA/B 7.1.3.1. + +// RSA +static SPKI_RSA: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Rsa(Some(())), +}; + +// SECP256R1 +static SPKI_SECP256R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP256R1)), +}; + +// SECP384R1 +static SPKI_SECP384R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP384R1)), +}; + +// SECP521R1 +static SPKI_SECP521R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP521R1)), +}; + +/// Permitted algorithms, from CA/B Forum's Baseline Requirements, section 7.1.3.1 (page 96) +/// https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.0.pdf +pub static WEBPKI_PERMITTED_SPKI_ALGORITHMS: Lazy>>> = + Lazy::new(|| { + Arc::new(HashSet::from([ + SPKI_RSA.clone(), + SPKI_SECP256R1.clone(), + SPKI_SECP384R1.clone(), + SPKI_SECP521R1.clone(), + ])) + }); + +// Signature AlgorithmIdentifier constants, as defined in CA/B 7.1.3.2. + +// RSASSA‐PKCS1‐v1_5 with SHA‐256 +static 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 { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::RsaWithSha384(Some(())), +}; + +// RSASSA‐PKCS1‐v1_5 with SHA‐512 +static RSASSA_PKCS1V15_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::RsaWithSha512(Some(())), +}; + +// RSASSA‐PSS with SHA‐256, MGF‐1 with SHA‐256, and a salt length of 32 bytes +static RSASSA_PSS_SHA256: Lazy> = Lazy::new(|| AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::RsaPss(Some(Box::new(RsaPssParameters { + hash_algorithm: PSS_SHA256_HASH_ALG, + mask_gen_algorithm: PSS_SHA256_MASK_GEN_ALG, + salt_length: 32, + _trailer_field: 1, + }))), +}); + +// RSASSA‐PSS with SHA‐384, MGF‐1 with SHA‐384, and a salt length of 48 bytes +static RSASSA_PSS_SHA384: Lazy> = Lazy::new(|| AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::RsaPss(Some(Box::new(RsaPssParameters { + hash_algorithm: PSS_SHA384_HASH_ALG, + mask_gen_algorithm: PSS_SHA384_MASK_GEN_ALG, + salt_length: 48, + _trailer_field: 1, + }))), +}); + +// RSASSA‐PSS with SHA‐512, MGF‐1 with SHA‐512, and a salt length of 64 bytes +static RSASSA_PSS_SHA512: Lazy> = Lazy::new(|| AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::RsaPss(Some(Box::new(RsaPssParameters { + hash_algorithm: PSS_SHA512_HASH_ALG, + mask_gen_algorithm: PSS_SHA512_MASK_GEN_ALG, + salt_length: 64, + _trailer_field: 1, + }))), +}); + +// For P-256: the signature MUST use ECDSA with SHA‐256 +static 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 { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::EcDsaWithSha384(None), +}; + +// For P-521: the signature MUST use ECDSA with SHA‐512 +static ECDSA_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::EcDsaWithSha512(None), +}; + +/// Permitted algorithms, from CA/B Forum's Baseline Requirements, section 7.1.3.2 (pages 96-98) +/// https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.0.pdf +pub static WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS: Lazy>>> = + Lazy::new(|| { + Arc::new(HashSet::from([ + RSASSA_PKCS1V15_SHA256.clone(), + RSASSA_PKCS1V15_SHA384.clone(), + RSASSA_PKCS1V15_SHA512.clone(), + RSASSA_PSS_SHA256.clone(), + RSASSA_PSS_SHA384.clone(), + RSASSA_PSS_SHA512.clone(), + ECDSA_SHA256.clone(), + ECDSA_SHA384.clone(), + ECDSA_SHA512.clone(), + ])) + }); + +/// A default reasonable maximum chain depth. +/// +/// This depth was chosen to balance between common validation lengths +/// (chains in the Web PKI are ordinarily no longer than 2 or 3 intermediates +/// in the longest cases) and support for pathological cases. +/// +/// Relatively little prior art for selecting a default depth exists; +/// OpenSSL defaults to a limit of 100, which is far more permissive than +/// necessary. +const DEFAULT_MAX_CHAIN_DEPTH: u8 = 8; + +/// Represents a logical certificate "subject," i.e. a principal matching +/// one of the names listed in a certificate's `subjectAltNames` extension. +pub enum Subject<'a> { + DNS(DNSName<'a>), + IP(IPAddress), +} + +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)) + } + (GeneralName::IPAddress(addr), Self::IP(name)) => { + IPAddress::from_bytes(addr).map_or(false, |addr| addr == *name) + } + _ => false, + } + } + + /// Returns true if any of the names in the given `SubjectAlternativeName` + /// match this `Subject`. + pub fn matches(&self, san: &SubjectAlternativeName<'_>) -> bool { + san.clone().any(|gn| self.subject_alt_name_matches(&gn)) + } +} + +/// A `Policy` describes user-configurable aspects of X.509 path validation. +pub struct Policy<'a, B: CryptoOps> { + pub ops: B, + + /// A top-level constraint on the length of intermediate CA paths + /// constructed under this policy. + /// + /// Per RFC 5280, this limits the length of the non-self-issued intermediate + /// CA chain, without counting either the leaf or trust anchor. + pub max_chain_depth: u8, + + /// A subject (i.e. DNS name or other name format) that any EE certificates + /// validated by this policy must match. + pub subject: Subject<'a>, + + /// The validation time. All certificates validated by this policy must + /// be valid at this time. + pub validation_time: asn1::DateTime, + + /// An extended key usage that must appear in EEs validated by this policy. + pub extended_key_usage: ObjectIdentifier, + + /// The set of permitted public key algorithms, identified by their + /// algorithm identifiers. + pub permitted_public_key_algorithms: Arc>>, + + /// The set of permitted signature algorithms, identified by their + /// algorithm identifiers. + pub permitted_signature_algorithms: Arc>>, + + ca_extension_policy: ExtensionPolicy, + ee_extension_policy: ExtensionPolicy, +} + +impl<'a, B: CryptoOps> Policy<'a, B> { + /// Create a new policy with defaults for the certificate profile defined in + /// the CA/B Forum's Basic Requirements. + pub fn new( + ops: B, + subject: Subject<'a>, + time: asn1::DateTime, + max_chain_depth: Option, + ) -> Self { + Self { + ops, + max_chain_depth: max_chain_depth.unwrap_or(DEFAULT_MAX_CHAIN_DEPTH), + subject, + validation_time: time, + extended_key_usage: EKU_SERVER_AUTH_OID.clone(), + 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 + 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), + ), + }, + } + } + + fn permits_basic(&self, cert: &Certificate<'_>) -> Result<(), ValidationError> { + // CA/B 7.1.1: + // Certificates MUST be of type X.509 v3. + if cert.tbs_cert.version != 2 { + return Err(ValidationError::Other( + "certificate must be an X509v3 certificate".to_string(), + )); + } + + // 5280 4.1.1.2 / 4.1.2.3: signatureAlgorithm / TBS Certificate Signature + // The top-level signatureAlgorithm and TBSCert signature algorithm + // MUST match. + if cert.signature_alg != cert.tbs_cert.signature_alg { + return Err(ValidationError::Other( + "mismatch between signatureAlgorithm and SPKI algorithm".to_string(), + )); + } + + // 5280 4.1.2.2: Serial Number + // Per 5280: The serial number MUST be a positive integer. + // In practice, there are a few roots in common trust stores (like certifi) + // that have `serial == 0`, so we can't enforce this yet. + let serial_bytes = cert.tbs_cert.serial.as_bytes(); + if !(1..=21).contains(&serial_bytes.len()) { + // Conforming CAs MUST NOT use serial numbers longer than 20 octets. + // NOTE: In practice, this requires us to check for an encoding of + // 21 octets, since some CAs generate 20 bytes of randomness and + // then forget to check whether that number would be negative, resulting + // in a 21-byte encoding. + return Err(ValidationError::Other( + "certificate must have a serial between 1 and 20 octets".to_string(), + )); + } else if serial_bytes[0] & 0x80 == 0x80 { + // TODO: replace with `is_negative`: https://github.com/alex/rust-asn1/pull/425 + return Err(ValidationError::Other( + "certificate serial number cannot be negative".to_string(), + )); + } + + // 5280 4.1.2.4: Issuer + // The issuer MUST be a non-empty distinguished name. + if cert.issuer().is_empty() { + return Err(ValidationError::Other( + "certificate must have a non-empty Issuer".to_string(), + )); + } + + // 5280 4.1.2.5: Validity + // Validity dates before 2050 MUST be encoded as UTCTime; + // dates in or after 2050 MUST be encoded as GeneralizedTime. + let not_before = cert.tbs_cert.validity.not_before.as_datetime(); + let not_after = cert.tbs_cert.validity.not_after.as_datetime(); + permits_validity_date(&cert.tbs_cert.validity.not_before)?; + permits_validity_date(&cert.tbs_cert.validity.not_after)?; + if &self.validation_time < not_before || &self.validation_time > not_after { + return Err(ValidationError::Other( + "cert is not valid at validation time".to_string(), + )); + } + + Ok(()) + } + + /// Checks whether the given CA certificate is compatible with this policy. + pub(crate) fn permits_ca( + &self, + cert: &Certificate<'_>, + current_depth: u8, + extensions: &Extensions<'_>, + ) -> Result<(), ValidationError> { + self.permits_basic(cert)?; + + // 5280 4.1.2.6: Subject + // CA certificates MUST have a subject populated with a non-empty distinguished name. + // No check required here: `permits_basic` checks that the issuer is non-empty + // 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()?; + + if bc + .path_length + .map_or(false, |len| u64::from(current_depth) > len) + { + return Err(ValidationError::Other( + "path length constraint violated".to_string(), + ))?; + } + } + + self.ca_extension_policy.permits(self, cert, extensions)?; + + Ok(()) + } + + /// Checks whether the given EE certificate is compatible with this policy. + pub(crate) fn permits_ee( + &self, + cert: &Certificate<'_>, + extensions: &Extensions<'_>, + ) -> Result<(), ValidationError> { + self.permits_basic(cert)?; + + self.ee_extension_policy.permits(self, cert, extensions)?; + + Ok(()) + } + + /// Checks whether `issuer` is a valid issuing CA for `child` at a + /// path-building depth of `current_depth`. + /// + /// This checks that `issuer` is permitted under this policy and that + /// it was used to sign for `child`. + /// + /// As a precondition, the caller must have already checked that + /// `issuer.subject() == child.issuer()`. + /// + /// On success, this function returns the new path-building depth. This + /// may or may not be a higher number than the original depth, depending + /// on the kind of validation performed (e.g., whether the issuer was + /// self-issued). + pub(crate) fn valid_issuer( + &self, + issuer: &VerificationCertificate<'_, B>, + child: &Certificate<'_>, + current_depth: u8, + issuer_extensions: &Extensions<'_>, + ) -> Result<(), ValidationError> { + // The issuer needs to be a valid CA at the current depth. + self.permits_ca(issuer.certificate(), current_depth, issuer_extensions)?; + + // CA/B 7.1.3.1 SubjectPublicKeyInfo + if !self + .permitted_public_key_algorithms + .contains(&child.tbs_cert.spki.algorithm) + { + return Err(ValidationError::Other(format!( + "Forbidden public key algorithm: {:?}", + &child.tbs_cert.spki.algorithm + ))); + } + + // CA/B 7.1.3.2 Signature AlgorithmIdentifier + if !self + .permitted_signature_algorithms + .contains(&child.signature_alg) + { + return Err(ValidationError::Other(format!( + "Forbidden signature algorithm: {:?}", + &child.signature_alg + ))); + } + + let pk = issuer + .public_key(&self.ops) + .map_err(|_| ValidationError::Other("issuer has malformed public key".to_string()))?; + if self.ops.verify_signed_by(child, pk).is_err() { + return Err(ValidationError::Other( + "signature does not match".to_string(), + )); + } + + Ok(()) + } +} + +fn permits_validity_date(validity_date: &Time) -> Result<(), ValidationError> { + const GENERALIZED_DATE_INVALIDITY_RANGE: Range = 1950..2050; + + // NOTE: The inverse check on `asn1::UtcTime` is already done for us + // by the variant's constructor. + if let Time::GeneralizedTime(_) = validity_date { + if GENERALIZED_DATE_INVALIDITY_RANGE.contains(&validity_date.as_datetime().year()) { + return Err(ValidationError::Other( + "validity dates between 1950 and 2049 must be UtcTime".to_string(), + )); + } + } + + 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 super::{ + permits_validity_date, ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512, RSASSA_PKCS1V15_SHA256, + RSASSA_PKCS1V15_SHA384, RSASSA_PKCS1V15_SHA512, RSASSA_PSS_SHA256, RSASSA_PSS_SHA384, + RSASSA_PSS_SHA512, WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS, + }; + use crate::{ + policy::{ + Subject, SPKI_RSA, SPKI_SECP256R1, SPKI_SECP384R1, SPKI_SECP521R1, + WEBPKI_PERMITTED_SPKI_ALGORITHMS, + }, + types::{DNSName, IPAddress}, + }; + + #[test] + fn test_webpki_permitted_spki_algorithms_canonical_encodings() { + { + assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_RSA)); + let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00"; + assert_eq!(asn1::write_single(&SPKI_RSA).unwrap(), exp_encoding); + } + + { + assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_SECP256R1)); + let exp_encoding = b"0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07"; + assert_eq!(asn1::write_single(&SPKI_SECP256R1).unwrap(), exp_encoding); + } + + { + assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_SECP384R1)); + let exp_encoding = b"0\x10\x06\x07*\x86H\xce=\x02\x01\x06\x05+\x81\x04\x00\""; + assert_eq!(asn1::write_single(&SPKI_SECP384R1).unwrap(), exp_encoding); + } + + { + assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_SECP521R1)); + let exp_encoding = b"0\x10\x06\x07*\x86H\xce=\x02\x01\x06\x05+\x81\x04\x00#"; + assert_eq!(asn1::write_single(&SPKI_SECP521R1).unwrap(), exp_encoding); + } + } + + #[test] + fn test_webpki_permitted_signature_algorithms_canonical_encodings() { + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PKCS1V15_SHA256)); + let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00"; + assert_eq!( + asn1::write_single(&RSASSA_PKCS1V15_SHA256).unwrap(), + exp_encoding + ); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PKCS1V15_SHA384)); + let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0c\x05\x00"; + assert_eq!( + asn1::write_single(&RSASSA_PKCS1V15_SHA384).unwrap(), + exp_encoding + ); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PKCS1V15_SHA512)); + let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\r\x05\x00"; + assert_eq!( + asn1::write_single(&RSASSA_PKCS1V15_SHA512).unwrap(), + exp_encoding + ); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PSS_SHA256.deref())); + let exp_encoding = b"0A\x06\t*\x86H\x86\xf7\r\x01\x01\n04\xa0\x0f0\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\xa1\x1c0\x1a\x06\t*\x86H\x86\xf7\r\x01\x01\x080\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\xa2\x03\x02\x01 "; + assert_eq!( + asn1::write_single(&RSASSA_PSS_SHA256.deref()).unwrap(), + exp_encoding + ); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PSS_SHA384.deref())); + let exp_encoding = b"0A\x06\t*\x86H\x86\xf7\r\x01\x01\n04\xa0\x0f0\r\x06\t`\x86H\x01e\x03\x04\x02\x02\x05\x00\xa1\x1c0\x1a\x06\t*\x86H\x86\xf7\r\x01\x01\x080\r\x06\t`\x86H\x01e\x03\x04\x02\x02\x05\x00\xa2\x03\x02\x010"; + assert_eq!( + asn1::write_single(&RSASSA_PSS_SHA384.deref()).unwrap(), + exp_encoding + ); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PSS_SHA512.deref())); + let exp_encoding = b"0A\x06\t*\x86H\x86\xf7\r\x01\x01\n04\xa0\x0f0\r\x06\t`\x86H\x01e\x03\x04\x02\x03\x05\x00\xa1\x1c0\x1a\x06\t*\x86H\x86\xf7\r\x01\x01\x080\r\x06\t`\x86H\x01e\x03\x04\x02\x03\x05\x00\xa2\x03\x02\x01@"; + assert_eq!( + asn1::write_single(&RSASSA_PSS_SHA512.deref()).unwrap(), + exp_encoding + ); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&ECDSA_SHA256)); + let exp_encoding = b"0\n\x06\x08*\x86H\xce=\x04\x03\x02"; + assert_eq!(asn1::write_single(&ECDSA_SHA256).unwrap(), exp_encoding); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&ECDSA_SHA384)); + let exp_encoding = b"0\n\x06\x08*\x86H\xce=\x04\x03\x03"; + assert_eq!(asn1::write_single(&ECDSA_SHA384).unwrap(), exp_encoding); + } + + { + assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&ECDSA_SHA512)); + let exp_encoding = b"0\n\x06\x08*\x86H\xce=\x04\x03\x04"; + assert_eq!(asn1::write_single(&ECDSA_SHA512).unwrap(), exp_encoding); + } + } + + #[test] + fn test_subject_matches() { + let domain_sub = Subject::DNS(DNSName::new("test.cryptography.io").unwrap()); + let ip_sub = Subject::IP(IPAddress::from_str("127.0.0.1").unwrap()); + + // Single SAN, domain wildcard. + { + let domain_gn = GeneralName::DNSName(UnvalidatedIA5String("*.cryptography.io")); + let san_der = asn1::write_single(&SequenceOfWriter::new([domain_gn])).unwrap(); + let any_cryptography_io = + asn1::parse_single::>(&san_der).unwrap(); + + assert!(domain_sub.matches(&any_cryptography_io)); + assert!(!ip_sub.matches(&any_cryptography_io)); + } + + // Single SAN, IP address. + { + let ip_gn = GeneralName::IPAddress(&[127, 0, 0, 1]); + let san_der = asn1::write_single(&SequenceOfWriter::new([ip_gn])).unwrap(); + let localhost = asn1::parse_single::>(&san_der).unwrap(); + + assert!(ip_sub.matches(&localhost)); + assert!(!domain_sub.matches(&localhost)); + } + + // Multiple SANs, both domain wildcard and IP address. + { + let domain_gn = GeneralName::DNSName(UnvalidatedIA5String("*.cryptography.io")); + let ip_gn = GeneralName::IPAddress(&[127, 0, 0, 1]); + let san_der = asn1::write_single(&SequenceOfWriter::new([domain_gn, ip_gn])).unwrap(); + + let any_cryptography_io_or_localhost = + asn1::parse_single::>(&san_der).unwrap(); + + assert!(domain_sub.matches(&any_cryptography_io_or_localhost)); + assert!(ip_sub.matches(&any_cryptography_io_or_localhost)); + } + + // Single SAN, invalid domain pattern. + { + let domain_gn = GeneralName::DNSName(UnvalidatedIA5String("*es*.cryptography.io")); + let san_der = asn1::write_single(&SequenceOfWriter::new([domain_gn])).unwrap(); + let any_cryptography_io = + asn1::parse_single::>(&san_der).unwrap(); + + assert!(!domain_sub.matches(&any_cryptography_io)); + } + } + + #[test] + fn test_validity_date() { + { + // Pre-2050 date. + let utc_dt = DateTime::new(1980, 1, 1, 0, 0, 0).unwrap(); + let generalized_dt = utc_dt.clone(); + let utc_validity = Time::UtcTime(asn1::UtcTime::new(utc_dt).unwrap()); + let generalized_validity = + Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date(&utc_validity).is_ok()); + assert!(permits_validity_date(&generalized_validity).is_err()); + } + { + // 2049 date. + let utc_dt = DateTime::new(2049, 1, 1, 0, 0, 0).unwrap(); + let generalized_dt = utc_dt.clone(); + let utc_validity = Time::UtcTime(asn1::UtcTime::new(utc_dt).unwrap()); + let generalized_validity = + Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date(&utc_validity).is_ok()); + assert!(permits_validity_date(&generalized_validity).is_err()); + } + { + // 2050 date. + let utc_dt = DateTime::new(2050, 1, 1, 0, 0, 0).unwrap(); + let generalized_dt = utc_dt.clone(); + assert!(asn1::UtcTime::new(utc_dt).is_err()); + let generalized_validity = + Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date(&generalized_validity).is_ok()); + } + { + // 2051 date. + let utc_dt = DateTime::new(2051, 1, 1, 0, 0, 0).unwrap(); + let generalized_dt = utc_dt.clone(); + // The `asn1::UtcTime` constructor prevents this. + assert!(asn1::UtcTime::new(utc_dt).is_err()); + let generalized_validity = + Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date(&generalized_validity).is_ok()); + } + { + // Post-2050 date. + let utc_dt = DateTime::new(3050, 1, 1, 0, 0, 0).unwrap(); + let generalized_dt = utc_dt.clone(); + // The `asn1::UtcTime` constructor prevents this. + assert!(asn1::UtcTime::new(utc_dt).is_err()); + let generalized_validity = + Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date(&generalized_validity).is_ok()); + } + } +} diff --git a/src/rust/cryptography-x509-verification/src/trust_store.rs b/src/rust/cryptography-x509-verification/src/trust_store.rs new file mode 100644 index 000000000000..462b81965df4 --- /dev/null +++ b/src/rust/cryptography-x509-verification/src/trust_store.rs @@ -0,0 +1,59 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::collections::HashMap; + +use cryptography_x509::name::Name; + +use crate::CryptoOps; +use crate::VerificationCertificate; + +/// A `Store` represents the core state needed for X.509 path validation. +pub struct Store<'a, B: CryptoOps> { + by_subject: HashMap, Vec>>, +} + +impl<'a, B: CryptoOps> Store<'a, B> { + /// Create a new `Store` from the given iterable certificate source. + pub fn new(trusted: impl IntoIterator>) -> Self { + let mut by_subject: HashMap, Vec>> = HashMap::new(); + for cert in trusted { + by_subject + .entry(cert.certificate().tbs_cert.subject.clone()) + .or_default() + .push(cert.clone()); + } + Store { by_subject } + } + + /// Returns whether this store contains the given certificate. + pub fn contains(&self, cert: &VerificationCertificate<'a, B>) -> bool { + self.get_by_subject(&cert.certificate().tbs_cert.subject) + .contains(cert) + } + + pub fn get_by_subject(&self, subject: &Name<'a>) -> &[VerificationCertificate<'a, B>] { + self.by_subject + .get(subject) + .map(|v| v.as_slice()) + .unwrap_or_default() + } +} + +#[cfg(test)] +mod tests { + use super::Store; + use crate::certificate::tests::PublicKeyErrorOps; + use crate::ops::tests::{cert, v1_cert_pem}; + use crate::VerificationCertificate; + + #[test] + fn test_store() { + let cert_pem = v1_cert_pem(); + let cert = VerificationCertificate::new(cert(&cert_pem), ()); + let store = Store::<'_, PublicKeyErrorOps>::new([cert.clone()]); + + assert!(store.contains(&cert)); + } +} diff --git a/src/rust/cryptography-x509-verification/src/types.rs b/src/rust/cryptography-x509-verification/src/types.rs new file mode 100644 index 000000000000..f564715219cd --- /dev/null +++ b/src/rust/cryptography-x509-verification/src/types.rs @@ -0,0 +1,590 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::net::IpAddr; +use std::str::FromStr; + +/// A `DNSName` is an `asn1::IA5String` with additional invariant preservations +/// per [RFC 5280 4.2.1.6], which in turn uses the preferred name syntax defined +/// in [RFC 1034 3.5] and amended in [RFC 1123 2.1]. +/// +/// Non-ASCII domain names (i.e., internationalized names) must be pre-encoded; +/// comparisons are case-insensitive. +/// +/// [RFC 5280 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 +/// [RFC 1034 3.5]: https://datatracker.ietf.org/doc/html/rfc1034#section-3.5 +/// [RFC 1123 2.1]: https://datatracker.ietf.org/doc/html/rfc1123#section-2.1 +/// +/// ```rust +/// # use cryptography_x509_verification::types::DNSName; +/// assert_eq!(DNSName::new("foo.com").unwrap(), DNSName::new("FOO.com").unwrap()); +/// ``` +#[derive(Clone, Debug)] +pub struct DNSName<'a>(asn1::IA5String<'a>); + +impl<'a> DNSName<'a> { + pub fn new(value: &'a str) -> Option { + // Domains cannot be empty and must (practically) + // be less than 253 characters (255 in RFC 1034's octet encoding). + if value.is_empty() || value.len() > 253 { + None + } else { + for label in value.split('.') { + // Individual labels cannot be empty; cannot exceed 63 characters; + // cannot start or end with `-`. + // NOTE: RFC 1034's grammar prohibits consecutive hyphens, but these + // are used as part of the IDN prefix (e.g. `xn--`)'; we allow them here. + if label.is_empty() + || label.len() > 63 + || label.starts_with('-') + || label.ends_with('-') + { + return None; + } + + // Labels must only contain `a-zA-Z0-9-`. + if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { + return None; + } + } + asn1::IA5String::new(value).map(Self) + } + } + + pub fn as_str(&self) -> &'a str { + self.0.as_str() + } + + /// Return this `DNSName`'s parent domain, if it has one. + /// + /// ```rust + /// # use cryptography_x509_verification::types::DNSName; + /// let domain = DNSName::new("foo.example.com").unwrap(); + /// assert_eq!(domain.parent().unwrap().as_str(), "example.com"); + /// ``` + pub fn parent(&self) -> Option { + match self.as_str().split_once('.') { + Some((_, parent)) => Self::new(parent), + None => None, + } + } + + /// Returns this DNS name's labels, in reversed order + /// (from top-level domain to most-specific subdomain). + fn rlabels(&self) -> impl Iterator { + self.as_str().rsplit('.') + } +} + +impl PartialEq for DNSName<'_> { + fn eq(&self, other: &Self) -> bool { + // DNS names are always case-insensitive. + self.as_str().eq_ignore_ascii_case(other.as_str()) + } +} + +/// A `DNSPattern` represents a subset of the domain name wildcard matching +/// behavior defined in [RFC 6125 6.4.3]. In particular, all DNS patterns +/// must either be exact matches (post-normalization) *or* a single wildcard +/// matching a full label in the left-most label position. Partial label matching +/// (e.g. `f*o.example.com`) is not supported, nor is non-left-most matching +/// (e.g. `foo.*.example.com`). +/// +/// [RFC 6125 6.4.3]: https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3 +#[derive(Debug, PartialEq)] +pub enum DNSPattern<'a> { + Exact(DNSName<'a>), + Wildcard(DNSName<'a>), +} + +impl<'a> DNSPattern<'a> { + pub fn new(pat: &'a str) -> Option { + if let Some(pat) = pat.strip_prefix("*.") { + DNSName::new(pat).map(Self::Wildcard) + } else { + DNSName::new(pat).map(Self::Exact) + } + } + + pub fn matches(&self, name: &DNSName<'_>) -> bool { + match self { + Self::Exact(pat) => pat == name, + Self::Wildcard(pat) => match name.parent() { + Some(ref parent) => pat == parent, + // No parent means we have a single label; wildcards cannot match single labels. + None => false, + }, + } + } +} + +/// A `DNSConstraint` represents a DNS name constraint as defined in [RFC 5280 4.2.1.10]. +/// +/// [RFC 5280 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 +pub struct DNSConstraint<'a>(DNSName<'a>); + +impl<'a> DNSConstraint<'a> { + pub fn new(pattern: &'a str) -> Option { + DNSName::new(pattern).map(Self) + } + + /// Returns true if this `DNSConstraint` matches the given name. + /// + /// Constraint matching is defined by RFC 5280: any DNS name that can + /// be constructed by simply adding zero or more labels to the left-hand + /// side of the name satisfies the name constraint. + /// + /// ```rust + /// # use cryptography_x509_verification::types::{DNSConstraint, DNSName}; + /// let example_com = DNSName::new("example.com").unwrap(); + /// let badexample_com = DNSName::new("badexample.com").unwrap(); + /// let foo_example_com = DNSName::new("foo.example.com").unwrap(); + /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&example_com)); + /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&foo_example_com)); + /// assert!(!DNSConstraint::new(example_com.as_str()).unwrap().matches(&badexample_com)); + /// ``` + pub fn matches(&self, name: &DNSName<'_>) -> bool { + // NOTE: This may seem like an obtuse way to perform label matching, + // but it saves us a few allocations: doing a substring check instead + // would require us to clone each string and do case normalization. + // Note also that we check the length in advance: Rust's zip + // implementation terminates with the shorter iterator, so we need + // to first check that the candidate name is at least as long as + // the constraint it's matching against. + name.as_str().len() >= self.0.as_str().len() + && self + .0 + .rlabels() + .zip(name.rlabels()) + .all(|(a, o)| a.eq_ignore_ascii_case(o)) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct IPAddress(IpAddr); + +/// An `IPAddress` represents an IP address as defined in [RFC 5280 4.2.1.6]. +/// +/// [RFC 5280 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 +impl IPAddress { + #[allow(clippy::should_implement_trait)] + pub fn from_str(s: &str) -> Option { + IpAddr::from_str(s).ok().map(Self::from) + } + + /// Constructs an `IPAddress` from a slice. The provided data must be + /// 4 (IPv4) or 16 (IPv6) bytes in "network byte order", as specified by + /// [RFC 5280]. + /// + /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 + pub fn from_bytes(b: &[u8]) -> Option { + match b.len() { + 4 => { + let b: [u8; 4] = b.try_into().ok()?; + Some(IpAddr::from(b).into()) + } + 16 => { + let b: [u8; 16] = b.try_into().ok()?; + Some(IpAddr::from(b).into()) + } + _ => None, + } + } + + /// Parses the octets of the `IPAddress` as a mask. If it is well-formed, + /// i.e., has only one contiguous block of set bits starting from the most + /// significant bit, a prefix is returned. + pub fn as_prefix(&self) -> Option { + let (leading, total) = match self.0 { + IpAddr::V4(a) => { + let data = u32::from_be_bytes(a.octets()); + (data.leading_ones(), data.count_ones()) + } + IpAddr::V6(a) => { + let data = u128::from_be_bytes(a.octets()); + (data.leading_ones(), data.count_ones()) + } + }; + + if leading != total { + None + } else { + Some(leading as u8) + } + } + + /// Returns a new `IPAddress` with the first `prefix` bits of the `IPAddress`. + /// + /// ```rust + /// # use cryptography_x509_verification::types::IPAddress; + /// let ip = IPAddress::from_str("192.0.2.1").unwrap(); + /// assert_eq!(ip.mask(24), IPAddress::from_str("192.0.2.0").unwrap()); + /// ``` + pub fn mask(&self, prefix: u8) -> Self { + match self.0 { + IpAddr::V4(a) => { + let prefix = 32u8.saturating_sub(prefix).into(); + let masked = u32::from_be_bytes(a.octets()) + & u32::MAX + .checked_shr(prefix) + .unwrap_or(0) + .checked_shl(prefix) + .unwrap_or(0); + Self::from_bytes(&masked.to_be_bytes()).unwrap() + } + IpAddr::V6(a) => { + let prefix = 128u8.saturating_sub(prefix).into(); + let masked = u128::from_be_bytes(a.octets()) + & u128::MAX + .checked_shr(prefix) + .unwrap_or(0) + .checked_shl(prefix) + .unwrap_or(0); + Self::from_bytes(&masked.to_be_bytes()).unwrap() + } + } + } +} + +impl From for IPAddress { + fn from(addr: IpAddr) -> Self { + Self(addr) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct IPConstraint { + address: IPAddress, + prefix: u8, +} + +/// An `IPConstraint` represents a CIDR-style IP address range used in a name constraints +/// extension, as defined by [RFC 5280 4.2.1.10]. +/// +/// [RFC 5280 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 +impl IPConstraint { + /// Constructs an `IPConstraint` from a slice. The input slice must be 8 (IPv4) + /// or 32 (IPv6) bytes long and contain two IP addresses, the first being + /// a subnet and the second defining the subnet's mask. + /// + /// The subnet mask must contain only one contiguous run of set bits starting + /// from the most significant bit. For example, a valid IPv4 subnet mask would + /// be FF FF 00 00, whereas an invalid IPv4 subnet mask would be FF EF 00 00. + pub fn from_bytes(b: &[u8]) -> Option { + let slice_idx = match b.len() { + 8 => 4, + 32 => 16, + _ => return None, + }; + + let prefix = IPAddress::from_bytes(&b[slice_idx..])?.as_prefix()?; + Some(IPConstraint { + address: IPAddress::from_bytes(&b[..slice_idx])?.mask(prefix), + prefix, + }) + } + + /// Determines if the `addr` is within the `IPConstraint`. + /// + /// ```rust + /// # use cryptography_x509_verification::types::{IPAddress, IPConstraint}; + /// let range_bytes = b"\xc6\x33\x64\x00\xff\xff\xff\x00"; + /// let range = IPConstraint::from_bytes(range_bytes).unwrap(); + /// assert!(range.matches(&IPAddress::from_str("198.51.100.42").unwrap())); + /// ``` + pub fn matches(&self, addr: &IPAddress) -> bool { + self.address == addr.mask(self.prefix) + } +} + +#[cfg(test)] +mod tests { + use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint}; + + #[test] + fn test_dnsname_debug_trait() { + // Just to get coverage on the `Debug` derive. + assert_eq!( + "DNSName(IA5String(\"example.com\"))", + format!("{:?}", DNSName::new("example.com").unwrap()) + ); + } + + #[test] + fn test_dnsname_new() { + assert_eq!(DNSName::new(""), None); + assert_eq!(DNSName::new("."), None); + assert_eq!(DNSName::new(".."), None); + assert_eq!(DNSName::new(".a."), None); + assert_eq!(DNSName::new("a.a."), None); + assert_eq!(DNSName::new(".a"), None); + assert_eq!(DNSName::new("a."), None); + assert_eq!(DNSName::new("a.."), None); + assert_eq!(DNSName::new(" "), None); + assert_eq!(DNSName::new("\t"), None); + assert_eq!(DNSName::new(" whitespace "), None); + assert_eq!(DNSName::new("white. space"), None); + assert_eq!(DNSName::new("!badlabel!"), None); + assert_eq!(DNSName::new("bad!label"), None); + assert_eq!(DNSName::new("goodlabel.!badlabel!"), None); + assert_eq!(DNSName::new("-foo.bar.example.com"), None); + assert_eq!(DNSName::new("foo-.bar.example.com"), None); + assert_eq!(DNSName::new("foo.-bar.example.com"), None); + assert_eq!(DNSName::new("foo.bar-.example.com"), None); + assert_eq!(DNSName::new(&"a".repeat(64)), None); + assert_eq!(DNSName::new("⚠️"), None); + assert_eq!(DNSName::new(".foo.example"), None); + assert_eq!(DNSName::new(".example.com"), None); + + let long_valid_label = "a".repeat(63); + let long_name = std::iter::repeat(long_valid_label) + .take(5) + .collect::>() + .join("."); + assert_eq!(DNSName::new(&long_name), None); + + assert_eq!( + DNSName::new(&"a".repeat(63)).unwrap().as_str(), + "a".repeat(63) + ); + assert_eq!(DNSName::new("example.com").unwrap().as_str(), "example.com"); + assert_eq!( + DNSName::new("123.example.com").unwrap().as_str(), + "123.example.com" + ); + assert_eq!(DNSName::new("EXAMPLE.com").unwrap().as_str(), "EXAMPLE.com"); + assert_eq!(DNSName::new("EXAMPLE.COM").unwrap().as_str(), "EXAMPLE.COM"); + assert_eq!( + DNSName::new("xn--bcher-kva.example").unwrap().as_str(), + "xn--bcher-kva.example" + ); + } + + #[test] + fn test_dnsname_equality() { + assert_ne!( + DNSName::new("foo.example.com").unwrap(), + DNSName::new("example.com").unwrap() + ); + + // DNS name comparisons are case insensitive. + assert_eq!( + DNSName::new("EXAMPLE.COM").unwrap(), + DNSName::new("example.com").unwrap() + ); + assert_eq!( + DNSName::new("ExAmPLe.CoM").unwrap(), + DNSName::new("eXaMplE.cOm").unwrap() + ); + } + + #[test] + fn test_dnsname_parent() { + assert_eq!(DNSName::new("localhost").unwrap().parent(), None); + assert_eq!( + DNSName::new("example.com").unwrap().parent().unwrap(), + DNSName::new("com").unwrap() + ); + assert_eq!( + DNSName::new("foo.example.com").unwrap().parent().unwrap(), + DNSName::new("example.com").unwrap() + ); + } + + #[test] + fn test_dnspattern_new() { + assert_eq!(DNSPattern::new("*"), None); + assert_eq!(DNSPattern::new("*."), None); + assert_eq!(DNSPattern::new("f*o.example.com"), None); + assert_eq!(DNSPattern::new("*oo.example.com"), None); + assert_eq!(DNSPattern::new("fo*.example.com"), None); + assert_eq!(DNSPattern::new("foo.*.example.com"), None); + assert_eq!(DNSPattern::new("*.foo.*.example.com"), None); + + assert_eq!( + DNSPattern::new("example.com").unwrap(), + DNSPattern::Exact(DNSName::new("example.com").unwrap()) + ); + assert_eq!( + DNSPattern::new("*.example.com").unwrap(), + DNSPattern::Wildcard(DNSName::new("example.com").unwrap()) + ); + } + + #[test] + fn test_dnspattern_matches() { + let exactly_localhost = DNSPattern::new("localhost").unwrap(); + let any_localhost = DNSPattern::new("*.localhost").unwrap(); + let exactly_example_com = DNSPattern::new("example.com").unwrap(); + let any_example_com = DNSPattern::new("*.example.com").unwrap(); + + // Exact patterns match only the exact name. + assert!(exactly_localhost.matches(&DNSName::new("localhost").unwrap())); + assert!(exactly_localhost.matches(&DNSName::new("LOCALHOST").unwrap())); + assert!(exactly_example_com.matches(&DNSName::new("example.com").unwrap())); + assert!(exactly_example_com.matches(&DNSName::new("EXAMPLE.com").unwrap())); + assert!(!exactly_example_com.matches(&DNSName::new("foo.example.com").unwrap())); + + // Wildcard patterns match any subdomain, but not the parent or nested subdomains. + assert!(any_example_com.matches(&DNSName::new("foo.example.com").unwrap())); + assert!(any_example_com.matches(&DNSName::new("bar.example.com").unwrap())); + assert!(any_example_com.matches(&DNSName::new("BAZ.example.com").unwrap())); + assert!(!any_example_com.matches(&DNSName::new("example.com").unwrap())); + assert!(!any_example_com.matches(&DNSName::new("foo.bar.example.com").unwrap())); + assert!(!any_example_com.matches(&DNSName::new("foo.bar.baz.example.com").unwrap())); + assert!(!any_localhost.matches(&DNSName::new("localhost").unwrap())); + } + + #[test] + fn test_dnsconstraint_new() { + assert!(DNSConstraint::new("").is_none()); + assert!(DNSConstraint::new(".").is_none()); + assert!(DNSConstraint::new("*.").is_none()); + assert!(DNSConstraint::new("*").is_none()); + assert!(DNSConstraint::new(".example").is_none()); + assert!(DNSConstraint::new("*.example").is_none()); + assert!(DNSConstraint::new("*.example.com").is_none()); + + assert!(DNSConstraint::new("example").is_some()); + assert!(DNSConstraint::new("example.com").is_some()); + assert!(DNSConstraint::new("foo.example.com").is_some()); + } + + #[test] + fn test_dnsconstraint_matches() { + let example_com = DNSConstraint::new("example.com").unwrap(); + + // Exact domain and arbitrary subdomains match. + assert!(example_com.matches(&DNSName::new("example.com").unwrap())); + assert!(example_com.matches(&DNSName::new("foo.example.com").unwrap())); + assert!(example_com.matches(&DNSName::new("foo.bar.baz.quux.example.com").unwrap())); + + // Parent domains, distinct domains, and substring domains do not match. + assert!(!example_com.matches(&DNSName::new("com").unwrap())); + assert!(!example_com.matches(&DNSName::new("badexample.com").unwrap())); + assert!(!example_com.matches(&DNSName::new("wrong.com").unwrap())); + } + + #[test] + fn test_ipaddress_from_str() { + assert_ne!(IPAddress::from_str("192.168.1.1"), None) + } + + #[test] + fn test_ipaddress_from_bytes() { + let ipv4 = b"\xc0\x00\x02\x01"; + let ipv6 = b"\x20\x01\x0d\xb8\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x01"; + let bad = b"\xde\xad"; + + assert_eq!( + IPAddress::from_bytes(ipv4).unwrap(), + IPAddress::from_str("192.0.2.1").unwrap(), + ); + assert_eq!( + IPAddress::from_bytes(ipv6).unwrap(), + IPAddress::from_str("2001:db8::1").unwrap(), + ); + assert_eq!(IPAddress::from_bytes(bad), None); + } + + #[test] + fn test_ipaddress_as_prefix() { + let ipv4 = IPAddress::from_str("255.255.255.0").unwrap(); + let ipv6 = IPAddress::from_str("ffff:ffff:ffff:ffff::").unwrap(); + let ipv4_nonmask = IPAddress::from_str("192.0.2.1").unwrap(); + let ipv6_nonmask = IPAddress::from_str("2001:db8::1").unwrap(); + + assert_eq!(ipv4.as_prefix(), Some(24)); + assert_eq!(ipv6.as_prefix(), Some(64)); + assert_eq!(ipv4_nonmask.as_prefix(), None); + assert_eq!(ipv6_nonmask.as_prefix(), None); + } + + #[test] + fn test_ipaddress_mask() { + let ipv4 = IPAddress::from_str("192.0.2.252").unwrap(); + let ipv6 = IPAddress::from_str("2001:db8::f00:01ba").unwrap(); + + assert_eq!(ipv4.mask(0), IPAddress::from_str("0.0.0.0").unwrap()); + assert_eq!(ipv4.mask(64), ipv4); + assert_eq!(ipv4.mask(32), ipv4); + assert_eq!(ipv4.mask(24), IPAddress::from_str("192.0.2.0").unwrap()); + assert_eq!(ipv6.mask(0), IPAddress::from_str("::0").unwrap()); + assert_eq!(ipv6.mask(130), ipv6); + assert_eq!(ipv6.mask(128), ipv6); + assert_eq!(ipv6.mask(64), IPAddress::from_str("2001:db8::").unwrap()); + assert_eq!( + ipv6.mask(103), + IPAddress::from_str("2001:db8::e00:0").unwrap() + ); + } + + #[test] + fn test_ipconstraint_from_bytes() { + let ipv4_bad = b"\xc0\xa8\x01\x01\xff\xfe\xff\x00"; + let ipv4_bad_many_bits = b"\xc0\xa8\x01\x01\xff\xfc\xff\x00"; + let ipv4_bad_octet = b"\xc0\xa8\x01\x01\x00\xff\xff\xff"; + let ipv6_bad = b"\ + \x26\x01\x00\x00\x00\x00\x00\x01\ + \x00\x00\x00\x00\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x01\ + \x00\x00\x00\x00\x00\x00\x00\x00"; + let ipv6_good = b"\ + \x20\x01\x0d\xb8\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x01\ + \xf0\x00\x00\x00\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x00"; + let bad = b"\xff\xff\xff"; + + assert_eq!(IPConstraint::from_bytes(ipv4_bad), None); + assert_eq!(IPConstraint::from_bytes(ipv4_bad_many_bits), None); + assert_eq!(IPConstraint::from_bytes(ipv4_bad_octet), None); + assert_eq!(IPConstraint::from_bytes(ipv6_bad), None); + assert_ne!(IPConstraint::from_bytes(ipv6_good), None); + assert_eq!(IPConstraint::from_bytes(bad), None); + + // 192.168.1.1/16 + let ipv4_with_extra = b"\xc0\xa8\x01\x01\xff\xff\x00\x00"; + assert_ne!(IPConstraint::from_bytes(ipv4_with_extra), None); + + // 192.168.0.0/16 + let ipv4_masked = b"\xc0\xa8\x00\x00\xff\xff\x00\x00"; + assert_eq!( + IPConstraint::from_bytes(ipv4_with_extra), + IPConstraint::from_bytes(ipv4_masked) + ); + } + + #[test] + fn test_ipconstraint_matches() { + // 192.168.1.1/16 + let ipv4 = IPConstraint::from_bytes(b"\xc0\xa8\x01\x01\xff\xff\x00\x00").unwrap(); + let ipv4_32 = IPConstraint::from_bytes(b"\xc0\x00\x02\xde\xff\xff\xff\xff").unwrap(); + let ipv6 = IPConstraint::from_bytes( + b"\x26\x00\x0d\xb8\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x01\ + \xff\xff\xff\xff\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x00", + ) + .unwrap(); + let ipv6_128 = IPConstraint::from_bytes( + b"\x26\x00\x0d\xb8\x00\x00\x00\x00\ + \x00\x00\x00\x00\xff\x00\xde\xde\ + \xff\xff\xff\xff\xff\xff\xff\xff\ + \xff\xff\xff\xff\xff\xff\xff\xff", + ) + .unwrap(); + + assert!(ipv4.matches(&IPAddress::from_str("192.168.0.50").unwrap())); + assert!(!ipv4.matches(&IPAddress::from_str("192.160.0.50").unwrap())); + assert!(ipv4_32.matches(&IPAddress::from_str("192.0.2.222").unwrap())); + assert!(!ipv4_32.matches(&IPAddress::from_str("192.5.2.222").unwrap())); + assert!(!ipv4_32.matches(&IPAddress::from_str("192.0.2.1").unwrap())); + assert!(ipv6.matches(&IPAddress::from_str("2600:db8::abba").unwrap())); + assert!(ipv6_128.matches(&IPAddress::from_str("2600:db8::ff00:dede").unwrap())); + assert!(!ipv6_128.matches(&IPAddress::from_str("2600::ff00:dede").unwrap())); + assert!(!ipv6_128.matches(&IPAddress::from_str("2600:db8::ff00:0").unwrap())); + } +} diff --git a/src/rust/cryptography-x509/Cargo.toml b/src/rust/cryptography-x509/Cargo.toml index 017d51dd44a3..9a877fd13cb6 100644 --- a/src/rust/cryptography-x509/Cargo.toml +++ b/src/rust/cryptography-x509/Cargo.toml @@ -5,7 +5,7 @@ authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV -rust-version = "1.56.0" +rust-version = "1.63.0" [dependencies] -asn1 = { version = "0.15.2", default-features = false } +asn1 = { version = "0.15.5", default-features = false } diff --git a/src/rust/cryptography-x509/src/certificate.rs b/src/rust/cryptography-x509/src/certificate.rs index 2a5616e93ef9..6db6eade0766 100644 --- a/src/rust/cryptography-x509/src/certificate.rs +++ b/src/rust/cryptography-x509/src/certificate.rs @@ -4,17 +4,37 @@ use crate::common; use crate::extensions; +use crate::extensions::DuplicateExtensionsError; use crate::extensions::Extensions; use crate::name; +use crate::name::NameReadable; -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct Certificate<'a> { pub tbs_cert: TbsCertificate<'a>, pub signature_alg: common::AlgorithmIdentifier<'a>, pub signature: asn1::BitString<'a>, } -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +impl<'a> Certificate<'a> { + /// Returns the certificate's issuer. + pub fn issuer(&self) -> &NameReadable<'_> { + self.tbs_cert.issuer.unwrap_read() + } + + /// Returns the certificate's subject. + pub fn subject(&self) -> &NameReadable<'_> { + self.tbs_cert.subject.unwrap_read() + } + + /// Returns an iterable container over the certificate's extension, or + /// an error if the extension set contains a duplicate extension. + pub fn extensions(&self) -> Result, DuplicateExtensionsError> { + self.tbs_cert.extensions() + } +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct TbsCertificate<'a> { #[explicit(0)] #[default(0)] @@ -26,7 +46,7 @@ pub struct TbsCertificate<'a> { pub validity: Validity, pub subject: name::Name<'a>, - pub spki: common::SubjectPublicKeyInfo<'a>, + pub spki: common::WithTlv<'a, common::SubjectPublicKeyInfo<'a>>, #[implicit(1)] pub issuer_unique_id: Option>, #[implicit(2)] @@ -36,12 +56,12 @@ pub struct TbsCertificate<'a> { } impl<'a> TbsCertificate<'a> { - pub fn extensions(&'a self) -> Result, asn1::ObjectIdentifier> { + pub fn extensions(&self) -> Result, DuplicateExtensionsError> { Extensions::from_raw_extensions(self.raw_extensions.as_ref()) } } -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct Validity { pub not_before: common::Time, pub not_after: common::Time, diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs index 466d4b5bd179..77cebc30464e 100644 --- a/src/rust/cryptography-x509/src/common.rs +++ b/src/rust/cryptography-x509/src/common.rs @@ -2,9 +2,9 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::oid; use asn1::Asn1DefinedByWritable; -use std::marker::PhantomData; + +use crate::oid; #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone, Eq, Debug)] pub struct AlgorithmIdentifier<'a> { @@ -45,14 +45,30 @@ pub enum AlgorithmParameters<'a> { #[defined_by(oid::ED448_OID)] Ed448, + #[defined_by(oid::X25519_OID)] + X25519, + #[defined_by(oid::X448_OID)] + X448, + + // These encodings are only used in SPKI AlgorithmIdentifiers. + #[defined_by(oid::EC_OID)] + Ec(EcParameters<'a>), + + #[defined_by(oid::RSA_OID)] + Rsa(Option), + + // These ECDSA algorithms should have no parameters, + // but Java 11 (up to at least 11.0.19) encodes them + // with NULL parameters. The JDK team is looking to + // backport the fix as of June 2023. #[defined_by(oid::ECDSA_WITH_SHA224_OID)] - EcDsaWithSha224, + EcDsaWithSha224(Option), #[defined_by(oid::ECDSA_WITH_SHA256_OID)] - EcDsaWithSha256, + EcDsaWithSha256(Option), #[defined_by(oid::ECDSA_WITH_SHA384_OID)] - EcDsaWithSha384, + EcDsaWithSha384(Option), #[defined_by(oid::ECDSA_WITH_SHA512_OID)] - EcDsaWithSha512, + EcDsaWithSha512(Option), #[defined_by(oid::ECDSA_WITH_SHA3_224_OID)] EcDsaWithSha3_224, @@ -92,22 +108,30 @@ pub enum AlgorithmParameters<'a> { #[defined_by(oid::RSASSA_PSS_OID)] RsaPss(Option>>), + #[defined_by(oid::DSA_OID)] + Dsa(DssParams<'a>), + #[defined_by(oid::DSA_WITH_SHA224_OID)] - DsaWithSha224, + DsaWithSha224(Option), #[defined_by(oid::DSA_WITH_SHA256_OID)] - DsaWithSha256, + DsaWithSha256(Option), #[defined_by(oid::DSA_WITH_SHA384_OID)] - DsaWithSha384, + DsaWithSha384(Option), #[defined_by(oid::DSA_WITH_SHA512_OID)] - DsaWithSha512, + DsaWithSha512(Option), + + #[defined_by(oid::DH_OID)] + Dh(DHXParams<'a>), + #[defined_by(oid::DH_KEY_AGREEMENT_OID)] + DhKeyAgreement(BasicDHParams<'a>), #[default] Other(asn1::ObjectIdentifier, Option>), } -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct SubjectPublicKeyInfo<'a> { - _algorithm: AlgorithmIdentifier<'a>, + pub algorithm: AlgorithmIdentifier<'a>, pub subject_public_key: asn1::BitString<'a>, } @@ -153,7 +177,7 @@ impl<'a> asn1::Asn1Writable for RawTlv<'a> { } } -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub enum Time { UtcTime(asn1::UtcTime), GeneralizedTime(asn1::GeneralizedTime), @@ -168,31 +192,31 @@ impl Time { } } -#[derive(Hash, PartialEq, Clone)] -pub enum Asn1ReadableOrWritable<'a, T, U> { - Read(T, PhantomData<&'a ()>), - Write(U, PhantomData<&'a ()>), +#[derive(Hash, PartialEq, Eq, Clone)] +pub enum Asn1ReadableOrWritable { + Read(T), + Write(U), } -impl<'a, T, U> Asn1ReadableOrWritable<'a, T, U> { +impl Asn1ReadableOrWritable { pub fn new_read(v: T) -> Self { - Asn1ReadableOrWritable::Read(v, PhantomData) + Asn1ReadableOrWritable::Read(v) } pub fn new_write(v: U) -> Self { - Asn1ReadableOrWritable::Write(v, PhantomData) + Asn1ReadableOrWritable::Write(v) } pub fn unwrap_read(&self) -> &T { match self { - Asn1ReadableOrWritable::Read(v, _) => v, - Asn1ReadableOrWritable::Write(_, _) => panic!("unwrap_read called on a Write value"), + Asn1ReadableOrWritable::Read(v) => v, + Asn1ReadableOrWritable::Write(_) => panic!("unwrap_read called on a Write value"), } } } impl<'a, T: asn1::SimpleAsn1Readable<'a>, U> asn1::SimpleAsn1Readable<'a> - for Asn1ReadableOrWritable<'a, T, U> + for Asn1ReadableOrWritable { const TAG: asn1::Tag = T::TAG; fn parse_data(data: &'a [u8]) -> asn1::ParseResult { @@ -200,14 +224,14 @@ impl<'a, T: asn1::SimpleAsn1Readable<'a>, U> asn1::SimpleAsn1Readable<'a> } } -impl<'a, T: asn1::SimpleAsn1Writable, U: asn1::SimpleAsn1Writable> asn1::SimpleAsn1Writable - for Asn1ReadableOrWritable<'a, T, U> +impl asn1::SimpleAsn1Writable + for Asn1ReadableOrWritable { const TAG: asn1::Tag = U::TAG; fn write_data(&self, w: &mut asn1::WriteBuf) -> asn1::WriteResult { match self { - Asn1ReadableOrWritable::Read(v, _) => T::write_data(v, w), - Asn1ReadableOrWritable::Write(v, _) => U::write_data(v, w), + Asn1ReadableOrWritable::Read(v) => T::write_data(v, w), + Asn1ReadableOrWritable::Write(v) => U::write_data(v, w), } } } @@ -224,12 +248,60 @@ pub struct DHParams<'a> { pub g: asn1::BigUint<'a>, pub q: Option>, } + +// From PKCS#3 Section 9 +// DHParameter ::= SEQUENCE { +// prime INTEGER, -- p +// base INTEGER, -- g +// privateValueLength INTEGER OPTIONAL +// } +#[derive(asn1::Asn1Read, asn1::Asn1Write, Clone, PartialEq, Eq, Debug, Hash)] +pub struct BasicDHParams<'a> { + pub p: asn1::BigUint<'a>, + pub g: asn1::BigUint<'a>, + pub private_value_length: Option, +} + +// From https://www.rfc-editor.org/rfc/rfc3279#section-2.3.3 +// DomainParameters ::= SEQUENCE { +// p INTEGER, -- odd prime, p=jq +1 +// g INTEGER, -- generator, g +// q INTEGER, -- factor of p-1 +// j INTEGER OPTIONAL, -- subgroup factor +// validationParms ValidationParms OPTIONAL +// } +#[derive(asn1::Asn1Read, asn1::Asn1Write, Clone, PartialEq, Eq, Debug, Hash)] +pub struct DHXParams<'a> { + pub p: asn1::BigUint<'a>, + pub g: asn1::BigUint<'a>, + pub q: asn1::BigUint<'a>, + pub j: Option>, + // No support for this, so don't bother filling out the fields. + pub validation_params: Option>, +} + // RSA-PSS ASN.1 default hash algorithm pub const PSS_SHA1_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Sha1(Some(())), }; +// RSA-PSS ASN.1 hash algorithm definitions specified under the CA/B Forum BRs. +pub const PSS_SHA256_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Sha256(Some(())), +}; + +pub const PSS_SHA384_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Sha384(Some(())), +}; + +pub const PSS_SHA512_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Sha512(Some(())), +}; + // This is defined as an AlgorithmIdentifier in RFC 4055, // but the mask generation algorithm **must** contain an AlgorithmIdentifier // in its params, so we define it this way. @@ -245,6 +317,37 @@ pub const PSS_SHA1_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { params: PSS_SHA1_HASH_ALG, }; +// RSA-PSS ASN.1 mask gen algorithms defined under the CA/B Forum BRs. +pub const PSS_SHA256_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { + oid: oid::MGF1_OID, + params: PSS_SHA256_HASH_ALG, +}; + +pub const PSS_SHA384_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { + oid: oid::MGF1_OID, + params: PSS_SHA384_HASH_ALG, +}; + +pub const PSS_SHA512_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { + oid: oid::MGF1_OID, + params: PSS_SHA512_HASH_ALG, +}; + +// From RFC 5480 section 2.1.1: +// ECParameters ::= CHOICE { +// namedCurve OBJECT IDENTIFIER +// -- implicitCurve NULL +// -- specifiedCurve SpecifiedECDomain } +// +// Only the namedCurve form may appear in PKIX. Other forms may be found in +// other PKIs. +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] +pub enum EcParameters<'a> { + NamedCurve(asn1::ObjectIdentifier), + ImplicitCurve(asn1::Null), + SpecifiedCurve(asn1::Sequence<'a>), +} + // From RFC 4055 section 3.1: // RSASSA-PSS-params ::= SEQUENCE { // hashAlgorithm [0] HashAlgorithm DEFAULT @@ -269,6 +372,20 @@ pub struct RsaPssParameters<'a> { pub _trailer_field: u8, } +// https://datatracker.ietf.org/doc/html/rfc3279#section-2.3.2 +// +// Dss-Parms ::= SEQUENCE { +// p INTEGER, +// q INTEGER, +// g INTEGER +// } +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] +pub struct DssParams<'a> { + pub p: asn1::BigUint<'a>, + pub q: asn1::BigUint<'a>, + pub g: asn1::BigUint<'a>, +} + /// 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. @@ -297,11 +414,64 @@ impl<'a> asn1::SimpleAsn1Writable for UnvalidatedVisibleString<'a> { } } +#[derive(Clone)] +pub struct WithTlv<'a, T> { + tlv: asn1::Tlv<'a>, + value: T, +} + +impl<'a, T> WithTlv<'a, T> { + pub fn tlv(&self) -> &asn1::Tlv<'a> { + &self.tlv + } +} + +impl std::ops::Deref for WithTlv<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl<'a, T: asn1::Asn1Readable<'a>> asn1::Asn1Readable<'a> for WithTlv<'a, T> { + fn parse(p: &mut asn1::Parser<'a>) -> asn1::ParseResult { + let tlv = p.read_element::>()?; + Ok(Self { + tlv, + value: tlv.parse()?, + }) + } + + fn can_parse(t: asn1::Tag) -> bool { + T::can_parse(t) + } +} + +impl<'a, T: asn1::Asn1Writable> asn1::Asn1Writable for WithTlv<'a, T> { + fn write(&self, w: &mut asn1::Writer<'_>) -> asn1::WriteResult<()> { + self.value.write(w) + } +} + +impl PartialEq for WithTlv<'_, T> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} +impl Eq for WithTlv<'_, T> {} +impl std::hash::Hash for WithTlv<'_, T> { + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + #[cfg(test)] mod tests { - use super::{Asn1ReadableOrWritable, RawTlv, UnvalidatedVisibleString}; use asn1::Asn1Readable; + use super::{Asn1ReadableOrWritable, RawTlv, UnvalidatedVisibleString, WithTlv}; + #[test] #[should_panic] fn test_unvalidated_visible_string_write() { @@ -326,4 +496,12 @@ mod tests { let t = asn1::Tag::from_bytes(&[0]).unwrap().0; assert!(RawTlv::can_parse(t)); } + + #[test] + fn test_with_raw_tlv_can_parse() { + let t = asn1::Tag::from_bytes(&[0x30]).unwrap().0; + + assert!(WithTlv::>::can_parse(t)); + assert!(!WithTlv::::can_parse(t)); + } } diff --git a/src/rust/cryptography-x509/src/crl.rs b/src/rust/cryptography-x509/src/crl.rs index c81a3c4a95fd..acd4adb64eb0 100644 --- a/src/rust/cryptography-x509/src/crl.rs +++ b/src/rust/cryptography-x509/src/crl.rs @@ -2,16 +2,12 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::{ - common, - extensions::{self}, - name, -}; +use crate::{common, extensions, name}; pub type ReasonFlags<'a> = - Option, asn1::OwnedBitString>>; + Option, asn1::OwnedBitString>>; -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash)] pub struct CertificateRevocationList<'a> { pub tbs_cert_list: TBSCertList<'a>, pub signature_algorithm: common::AlgorithmIdentifier<'a>, @@ -20,13 +16,12 @@ pub struct CertificateRevocationList<'a> { pub type RevokedCertificates<'a> = Option< common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, RevokedCertificate<'a>>, asn1::SequenceOfWriter<'a, RevokedCertificate<'a>, Vec>>, >, >; -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash)] pub struct TBSCertList<'a> { pub version: Option, pub signature: common::AlgorithmIdentifier<'a>, @@ -38,7 +33,7 @@ pub struct TBSCertList<'a> { pub raw_crl_extensions: Option>, } -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub struct RevokedCertificate<'a> { pub user_certificate: asn1::BigUint<'a>, pub revocation_date: common::Time, diff --git a/src/rust/cryptography-x509/src/csr.rs b/src/rust/cryptography-x509/src/csr.rs index d2cf9b5e2739..790134bacce0 100644 --- a/src/rust/cryptography-x509/src/csr.rs +++ b/src/rust/cryptography-x509/src/csr.rs @@ -18,7 +18,7 @@ pub struct Csr<'a> { pub struct CertificationRequestInfo<'a> { pub version: u8, pub subject: name::Name<'a>, - pub spki: common::SubjectPublicKeyInfo<'a>, + pub spki: common::WithTlv<'a, common::SubjectPublicKeyInfo<'a>>, #[implicit(0, required)] pub attributes: Attributes<'a>, } @@ -54,7 +54,6 @@ pub fn check_attribute_length<'a>( } pub type Attributes<'a> = common::Asn1ReadableOrWritable< - 'a, asn1::SetOf<'a, Attribute<'a>>, asn1::SetOfWriter<'a, Attribute<'a>, Vec>>, >; @@ -63,7 +62,6 @@ pub type Attributes<'a> = common::Asn1ReadableOrWritable< pub struct Attribute<'a> { pub type_id: asn1::ObjectIdentifier, pub values: common::Asn1ReadableOrWritable< - 'a, asn1::SetOf<'a, asn1::Tlv<'a>>, asn1::SetOfWriter<'a, common::RawTlv<'a>, [common::RawTlv<'a>; 1]>, >, diff --git a/src/rust/cryptography-x509/src/extensions.rs b/src/rust/cryptography-x509/src/extensions.rs index 51c283af352c..bbd0f2377896 100644 --- a/src/rust/cryptography-x509/src/extensions.rs +++ b/src/rust/cryptography-x509/src/extensions.rs @@ -8,8 +8,10 @@ use crate::common; use crate::crl; use crate::name; +#[derive(Debug, PartialEq, Eq)] +pub struct DuplicateExtensionsError(pub asn1::ObjectIdentifier); + pub type RawExtensions<'a> = common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, Extension<'a>>, asn1::SequenceOfWriter<'a, Extension<'a>, Vec>>, >; @@ -27,14 +29,14 @@ impl<'a> Extensions<'a> { /// OID, if there are any duplicates. pub fn from_raw_extensions( raw: Option<&RawExtensions<'a>>, - ) -> Result { + ) -> Result { match raw { Some(raw_exts) => { let mut seen_oids = HashSet::new(); for ext in raw_exts.unwrap_read().clone() { if !seen_oids.insert(ext.extn_id.clone()) { - return Err(ext.extn_id); + return Err(DuplicateExtensionsError(ext.extn_id)); } } @@ -46,15 +48,21 @@ impl<'a> Extensions<'a> { /// Retrieves the extension identified by the given OID, /// or None if the extension is not present (or no extensions are present). - pub fn get_extension(&self, oid: &asn1::ObjectIdentifier) -> Option { - self.0 - .as_ref() - .and_then(|exts| exts.unwrap_read().clone().find(|ext| &ext.extn_id == oid)) + pub fn get_extension(&self, oid: &asn1::ObjectIdentifier) -> Option> { + self.iter().find(|ext| &ext.extn_id == oid) } /// Returns a reference to the underlying extensions. - pub fn as_raw(&self) -> &Option> { - &self.0 + pub fn as_raw(&self) -> Option<&RawExtensions<'a>> { + self.0.as_ref() + } + + /// Returns an iterator over the underlying extensions. + pub fn iter(&self) -> impl Iterator> { + self.as_raw() + .map(|raw| raw.unwrap_read().clone()) + .into_iter() + .flatten() } } @@ -66,6 +74,12 @@ pub struct Extension<'a> { pub extn_value: &'a [u8], } +impl<'a> Extension<'a> { + pub fn value>(&self) -> asn1::ParseResult { + asn1::parse_single(self.extn_value) + } +} + #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct PolicyConstraints { #[implicit(0)] @@ -81,14 +95,12 @@ pub struct AccessDescription<'a> { } pub type SequenceOfAccessDescriptions<'a> = common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, AccessDescription<'a>>, asn1::SequenceOfWriter<'a, AccessDescription<'a>, Vec>>, >; // Needed due to clippy type complexity warning. type SequenceOfPolicyQualifiers<'a> = common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, asn1::SequenceOfWriter<'a, PolicyQualifierInfo<'a>, Vec>>, >; @@ -121,7 +133,6 @@ pub struct UserNotice<'a> { pub struct NoticeReference<'a> { pub organization: DisplayText<'a>, pub notice_numbers: common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, asn1::BigUint<'a>>, asn1::SequenceOfWriter<'a, asn1::BigUint<'a>, Vec>>, >, @@ -140,7 +151,6 @@ pub enum DisplayText<'a> { // Needed due to clippy type complexity warning. pub type SequenceOfSubtrees<'a> = common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, GeneralSubtree<'a>>, asn1::SequenceOfWriter<'a, GeneralSubtree<'a>, Vec>>, >; @@ -193,7 +203,6 @@ pub enum DistributionPointName<'a> { #[implicit(1)] NameRelativeToCRLIssuer( common::Asn1ReadableOrWritable< - 'a, asn1::SetOf<'a, common::AttributeTypeValue<'a>>, asn1::SetOfWriter< 'a, @@ -221,35 +230,147 @@ pub struct BasicConstraints { pub path_length: Option, } +pub type SubjectAlternativeName<'a> = asn1::SequenceOf<'a, name::GeneralName<'a>>; +pub type IssuerAlternativeName<'a> = asn1::SequenceOf<'a, name::GeneralName<'a>>; +pub type ExtendedKeyUsage<'a> = asn1::SequenceOf<'a, asn1::ObjectIdentifier>; + +pub struct KeyUsage<'a>(asn1::BitString<'a>); + +impl<'a> asn1::SimpleAsn1Readable<'a> for KeyUsage<'a> { + const TAG: asn1::Tag = asn1::BitString::TAG; + + fn parse_data(data: &'a [u8]) -> asn1::ParseResult { + asn1::BitString::parse_data(data).map(Self) + } +} + +impl KeyUsage<'_> { + pub fn is_zeroed(&self) -> bool { + self.0.as_bytes().iter().all(|&b| b == 0) + } + + pub fn digital_signature(&self) -> bool { + self.0.has_bit_set(0) + } + + pub fn content_comitment(&self) -> bool { + self.0.has_bit_set(1) + } + + pub fn key_encipherment(&self) -> bool { + self.0.has_bit_set(2) + } + + pub fn data_encipherment(&self) -> bool { + self.0.has_bit_set(3) + } + + pub fn key_agreement(&self) -> bool { + self.0.has_bit_set(4) + } + + pub fn key_cert_sign(&self) -> bool { + self.0.has_bit_set(5) + } + + pub fn crl_sign(&self) -> bool { + self.0.has_bit_set(6) + } + + pub fn encipher_only(&self) -> bool { + self.0.has_bit_set(7) + } + + pub fn decipher_only(&self) -> bool { + self.0.has_bit_set(8) + } +} + #[cfg(test)] mod tests { - use asn1::SequenceOfWriter; - + use super::{BasicConstraints, Extension, Extensions, KeyUsage}; use crate::oid::{AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID}; - use super::{BasicConstraints, Extension, Extensions}; - #[test] fn test_get_extension() { - let extension_value = BasicConstraints { + let bc = BasicConstraints { ca: true, path_length: Some(3), }; let extension = Extension { extn_id: BASIC_CONSTRAINTS_OID, critical: true, - extn_value: &asn1::write_single(&extension_value).unwrap(), + extn_value: &asn1::write_single(&bc).unwrap(), }; - let extensions = SequenceOfWriter::new(vec![extension]); + let extensions = asn1::SequenceOfWriter::new(vec![extension]); let der = asn1::write_single(&extensions).unwrap(); + let raw = asn1::parse_single(&der).unwrap(); - let extensions: Extensions = - Extensions::from_raw_extensions(Some(&asn1::parse_single(&der).unwrap())).unwrap(); + let extensions = Extensions::from_raw_extensions(Some(&raw)).ok().unwrap(); assert!(&extensions.get_extension(&BASIC_CONSTRAINTS_OID).is_some()); assert!(&extensions .get_extension(&AUTHORITY_KEY_IDENTIFIER_OID) .is_none()); } + + #[test] + fn test_extensions_iter() { + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + let extension = Extension { + extn_id: BASIC_CONSTRAINTS_OID, + critical: true, + extn_value: &asn1::write_single(&bc).unwrap(), + }; + let extensions = asn1::SequenceOfWriter::new(vec![extension]); + + let der = asn1::write_single(&extensions).unwrap(); + let parsed = asn1::parse_single(&der).unwrap(); + + let extensions = Extensions::from_raw_extensions(Some(&parsed)).ok().unwrap(); + + let extension_list: Vec<_> = extensions.iter().collect(); + assert_eq!(extension_list.len(), 1); + } + + #[test] + fn test_extension_value() { + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + let extension = Extension { + extn_id: BASIC_CONSTRAINTS_OID, + critical: true, + extn_value: &asn1::write_single(&bc).unwrap(), + }; + + let extracted: BasicConstraints = extension.value().unwrap(); + assert_eq!(bc.ca, extracted.ca); + assert_eq!(bc.path_length, extracted.path_length); + } + + #[test] + fn test_keyusage() { + // let ku: KeyUsage = asn1::parse_single(data) + let ku_bits = [0b1111_1111u8, 0b1000_0000u8]; + let ku_bitstring = asn1::BitString::new(&ku_bits, 7).unwrap(); + let asn1 = asn1::write_single(&ku_bitstring).unwrap(); + + let ku: KeyUsage<'_> = asn1::parse_single(&asn1).unwrap(); + assert!(!ku.is_zeroed()); + assert!(ku.digital_signature()); + assert!(ku.content_comitment()); + assert!(ku.key_encipherment()); + assert!(ku.data_encipherment()); + assert!(ku.key_agreement()); + assert!(ku.key_cert_sign()); + assert!(ku.crl_sign()); + assert!(ku.encipher_only()); + assert!(ku.decipher_only()); + } } diff --git a/src/rust/cryptography-x509/src/lib.rs b/src/rust/cryptography-x509/src/lib.rs index 131c3fd156eb..c74424acfa34 100644 --- a/src/rust/cryptography-x509/src/lib.rs +++ b/src/rust/cryptography-x509/src/lib.rs @@ -3,8 +3,7 @@ // for complete details. #![forbid(unsafe_code)] -// These can be removed once our MSRV is >1.60 -#![allow(renamed_and_removed_lints, clippy::eval_order_dependence)] +#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] pub mod certificate; pub mod common; diff --git a/src/rust/cryptography-x509/src/name.rs b/src/rust/cryptography-x509/src/name.rs index f53e342cbf33..21b6cc8fca9a 100644 --- a/src/rust/cryptography-x509/src/name.rs +++ b/src/rust/cryptography-x509/src/name.rs @@ -4,9 +4,10 @@ use crate::common; +pub type NameReadable<'a> = asn1::SequenceOf<'a, asn1::SetOf<'a, common::AttributeTypeValue<'a>>>; + pub type Name<'a> = common::Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, asn1::SetOf<'a, common::AttributeTypeValue<'a>>>, + NameReadable<'a>, asn1::SequenceOfWriter< 'a, asn1::SetOfWriter<'a, common::AttributeTypeValue<'a>, Vec>>, @@ -41,7 +42,7 @@ impl<'a> asn1::SimpleAsn1Writable for UnvalidatedIA5String<'a> { } } -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash)] pub struct OtherName<'a> { pub type_id: asn1::ObjectIdentifier, #[explicit(0, required)] @@ -82,7 +83,6 @@ pub enum GeneralName<'a> { } pub(crate) type SequenceOfGeneralName<'a> = common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, GeneralName<'a>>, asn1::SequenceOfWriter<'a, GeneralName<'a>, Vec>>, >; diff --git a/src/rust/cryptography-x509/src/ocsp_req.rs b/src/rust/cryptography-x509/src/ocsp_req.rs index ba54d391f506..163c40fa38b0 100644 --- a/src/rust/cryptography-x509/src/ocsp_req.rs +++ b/src/rust/cryptography-x509/src/ocsp_req.rs @@ -2,11 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::{ - common, - extensions::{self}, - name, -}; +use crate::{common, extensions, name}; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct TBSRequest<'a> { @@ -16,7 +12,6 @@ pub struct TBSRequest<'a> { #[explicit(1)] pub requestor_name: Option>, pub request_list: common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, Request<'a>>, asn1::SequenceOfWriter<'a, Request<'a>>, >, diff --git a/src/rust/cryptography-x509/src/ocsp_resp.rs b/src/rust/cryptography-x509/src/ocsp_resp.rs index 21f01e2c7375..f40707ed2f75 100644 --- a/src/rust/cryptography-x509/src/ocsp_resp.rs +++ b/src/rust/cryptography-x509/src/ocsp_resp.rs @@ -2,11 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::{ - certificate, common, crl, - extensions::{self}, - name, ocsp_req, -}; +use crate::{certificate, common, crl, extensions, name, ocsp_req}; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct OCSPResponse<'a> { @@ -23,7 +19,6 @@ pub struct ResponseBytes<'a> { pub type OCSPCerts<'a> = Option< common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, certificate::Certificate<'a>>, asn1::SequenceOfWriter<'a, certificate::Certificate<'a>, Vec>>, >, @@ -46,7 +41,6 @@ pub struct ResponseData<'a> { pub responder_id: ResponderId<'a>, pub produced_at: asn1::GeneralizedTime, pub responses: common::Asn1ReadableOrWritable< - 'a, asn1::SequenceOf<'a, SingleResponse<'a>>, asn1::SequenceOfWriter<'a, SingleResponse<'a>, Vec>>, >, diff --git a/src/rust/cryptography-x509/src/oid.rs b/src/rust/cryptography-x509/src/oid.rs index ac80b9a31365..bf5d0ba29689 100644 --- a/src/rust/cryptography-x509/src/oid.rs +++ b/src/rust/cryptography-x509/src/oid.rs @@ -2,6 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +// X.509v3 extensions pub const EXTENSION_REQUEST: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 14); pub const MS_EXTENSION_REQUEST: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 311, 2, 1, 14); @@ -21,6 +22,7 @@ pub const CP_CPS_URI_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, pub const CP_USER_NOTICE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 2, 2); pub const NONCE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 2); pub const OCSP_NO_CHECK_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 5); +pub const SUBJECT_DIRECTORY_ATTRIBUTES_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 9); pub const SUBJECT_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 14); pub const KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 15); pub const SUBJECT_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 17); @@ -43,6 +45,36 @@ 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); +// Public key identifiers +pub const EC_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 2, 1); + +pub const EC_SECP192R1: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 3, 1, 1); +pub const EC_SECP224R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 33); +pub const EC_SECP256R1: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 3, 1, 7); +pub const EC_SECP384R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 34); +pub const EC_SECP521R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 35); + +pub const EC_SECP256K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 10); + +pub const EC_SECT233R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 27); +pub const EC_SECT283R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 17); +pub const EC_SECT409R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 37); +pub const EC_SECT571R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 39); + +pub const EC_SECT163R2: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 15); + +pub const EC_SECT163K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 1); +pub const EC_SECT233K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 26); +pub const EC_SECT283K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 16); +pub const EC_SECT409K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 36); +pub const EC_SECT571K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 38); + +pub const EC_BRAINPOOLP256R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 3, 3, 2, 8, 1, 1, 7); +pub const EC_BRAINPOOLP384R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 3, 3, 2, 8, 1, 1, 11); +pub const EC_BRAINPOOLP512R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 3, 3, 2, 8, 1, 1, 13); + +pub const RSA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 1); + // Signing methods pub const ECDSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 1); pub const ECDSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 2); @@ -72,11 +104,18 @@ pub const RSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier = pub const RSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 16); +pub const DSA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10040, 4, 1); pub const DSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 1); pub const DSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 2); pub const DSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 3); pub const DSA_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 4); +pub const DH_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10046, 2, 1); +pub const DH_KEY_AGREEMENT_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 3, 1); + +pub const X25519_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 110); +pub const X448_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 111); + pub const ED25519_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 112); pub const ED448_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 113); @@ -97,3 +136,14 @@ pub const SHA3_512_OID: asn1::ObjectIdentifier = pub const MGF1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 8); pub const RSASSA_PSS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 10); + +// Extended key usages +pub const EKU_SERVER_AUTH_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 1); +pub const EKU_CLIENT_AUTH_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 2); +pub const EKU_CODE_SIGNING_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 3); +pub const EKU_EMAIL_PROTECTION_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 4); +pub const EKU_TIME_STAMPING_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 8); +pub const EKU_OCSP_SIGNING_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 9); +pub const EKU_ANY_KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37, 0); +pub const EKU_CERTIFICATE_TRANSPARENCY_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 4); diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs index bf17a5952f29..641417545fce 100644 --- a/src/rust/src/asn1.rs +++ b/src/rust/src/asn1.rs @@ -2,15 +2,16 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::error::{CryptographyError, CryptographyResult}; use asn1::SimpleAsn1Readable; use cryptography_x509::certificate::Certificate; use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo, Time}; use cryptography_x509::name::Name; -use pyo3::basic::CompareOp; use pyo3::types::IntoPyDict; use pyo3::ToPyObject; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::types; + pub(crate) fn py_oid_to_oid(py_oid: &pyo3::PyAny) -> pyo3::PyResult { Ok(py_oid .downcast::>()? @@ -67,7 +68,7 @@ pub(crate) fn py_uint_to_big_endian_bytes<'p>( v: &'p pyo3::types::PyLong, ) -> pyo3::PyResult<&'p [u8]> { let zero = (0).to_object(py); - if v.rich_compare(zero, CompareOp::Lt)?.is_true()? { + if v.lt(zero)? { return Err(pyo3::exceptions::PyValueError::new_err( "Negative integers are not supported", )); @@ -91,26 +92,14 @@ pub(crate) fn encode_der_data<'p>( data: Vec, encoding: &'p pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let encoding_class = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "Encoding"))?; - - if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + if encoding.is(types::ENCODING_DER.get(py)?) { Ok(pyo3::types::PyBytes::new(py, &data)) - } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + } else if encoding.is(types::ENCODING_PEM.get(py)?) { Ok(pyo3::types::PyBytes::new( py, &pem::encode_config( - &pem::Pem { - tag: pem_tag, - contents: data, - }, - pem::EncodeConfig { - line_ending: pem::LineEnding::LF, - }, + &pem::Pem::new(pem_tag, data), + pem::EncodeConfig::new().set_line_ending(pem::LineEnding::LF), ) .into_bytes(), )) @@ -136,7 +125,7 @@ fn encode_dss_signature( Ok(pyo3::types::PyBytes::new(py, &result).to_object(py)) } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.asn1")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.asn1")] struct TestCertificate { #[pyo3(get)] not_before_tag: u8, @@ -148,7 +137,7 @@ struct TestCertificate { subject_value_tags: Vec, } -fn parse_name_value_tags(rdns: &mut Name<'_>) -> Vec { +fn parse_name_value_tags(rdns: &Name<'_>) -> Vec { let mut tags = vec![]; for rdn in rdns.unwrap_read().clone() { let mut attributes = rdn.collect::>(); @@ -168,13 +157,13 @@ fn time_tag(t: &Time) -> u8 { #[pyo3::prelude::pyfunction] fn test_parse_certificate(data: &[u8]) -> Result { - let mut cert = asn1::parse_single::>(data)?; + let cert = asn1::parse_single::>(data)?; Ok(TestCertificate { not_before_tag: time_tag(&cert.tbs_cert.validity.not_before), not_after_tag: time_tag(&cert.tbs_cert.validity.not_after), - issuer_value_tags: parse_name_value_tags(&mut cert.tbs_cert.issuer), - subject_value_tags: parse_name_value_tags(&mut cert.tbs_cert.subject), + issuer_value_tags: parse_name_value_tags(&cert.tbs_cert.issuer), + subject_value_tags: parse_name_value_tags(&cert.tbs_cert.subject), }) } diff --git a/src/rust/src/backend/aead.rs b/src/rust/src/backend/aead.rs new file mode 100644 index 000000000000..7c364dede81e --- /dev/null +++ b/src/rust/src/backend/aead.rs @@ -0,0 +1,851 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, types}; + +fn check_length(data: &[u8]) -> CryptographyResult<()> { + if data.len() > (i32::MAX as usize) { + // This is OverflowError to match what cffi would raise + return Err(CryptographyError::from( + pyo3::exceptions::PyOverflowError::new_err( + "Data or associated data too long. Max 2**31 - 1 bytes", + ), + )); + } + + Ok(()) +} + +enum Aad<'a> { + Single(CffiBuf<'a>), + List(&'a pyo3::types::PyList), +} + +struct EvpCipherAead { + base_encryption_ctx: openssl::cipher_ctx::CipherCtx, + base_decryption_ctx: openssl::cipher_ctx::CipherCtx, + tag_len: usize, + tag_first: bool, +} + +impl EvpCipherAead { + fn new( + cipher: &openssl::cipher::CipherRef, + key: &[u8], + tag_len: usize, + tag_first: bool, + ) -> CryptographyResult { + let mut base_encryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; + base_encryption_ctx.encrypt_init(Some(cipher), Some(key), None)?; + let mut base_decryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; + base_decryption_ctx.decrypt_init(Some(cipher), Some(key), None)?; + + Ok(EvpCipherAead { + base_encryption_ctx, + base_decryption_ctx, + tag_len, + tag_first, + }) + } + + fn process_aad( + ctx: &mut openssl::cipher_ctx::CipherCtx, + aad: Option>, + ) -> CryptographyResult<()> { + match aad { + Some(Aad::Single(ad)) => { + check_length(ad.as_bytes())?; + ctx.cipher_update(ad.as_bytes(), None)?; + } + Some(Aad::List(ads)) => { + for ad in ads.iter() { + let ad = ad.extract::>()?; + check_length(ad.as_bytes())?; + ctx.cipher_update(ad.as_bytes(), None)?; + } + } + None => {} + } + + Ok(()) + } + + fn process_data( + ctx: &mut openssl::cipher_ctx::CipherCtx, + data: &[u8], + out: &mut [u8], + ) -> CryptographyResult<()> { + let bs = ctx.block_size(); + + // For AEADs that operate as if they are streaming there's an easy + // path. For AEADs that are more like block ciphers (notably, OCB), + // this is a bit more complicated. + if bs == 1 { + let n = ctx.cipher_update(data, Some(out))?; + assert_eq!(n, data.len()); + + let mut final_block = [0]; + let n = ctx.cipher_final(&mut final_block)?; + assert_eq!(n, 0); + } else { + // Our algorithm here is: split the data into the full chunks, and + // the remaining partial chunk. Feed the full chunks into OpenSSL + // and let it write the results to `out`. Then feed the trailer + // in, allowing it to write the results to a buffer on the + // stack -- this never writes anything. Finally, finalize the AEAD + // and let it write the results to the stack buffer, then copy + // from the stack buffer over to `out`. The indirection via the + // stack buffer is required because OpenSSL uses it as scratch + // space, and `out` wouldn't be long enough. + let (initial, trailer) = data.split_at((data.len() / bs) * bs); + + let n = + // SAFETY: `initial.len()` is a precise multiple of the block + // size, which means the space required in the output is + // exactly `initial.len()`. + unsafe { ctx.cipher_update_unchecked(initial, Some(&mut out[..initial.len()]))? }; + assert_eq!(n, initial.len()); + + assert!(bs <= 16); + let mut buf = [0; 32]; + let n = ctx.cipher_update(trailer, Some(&mut buf))?; + assert_eq!(n, 0); + + let n = ctx.cipher_final(&mut buf)?; + assert_eq!(n, trailer.len()); + out[initial.len()..].copy_from_slice(&buf[..n]); + } + + Ok(()) + } + + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + plaintext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut ctx = openssl::cipher_ctx::CipherCtx::new()?; + ctx.copy(&self.base_encryption_ctx)?; + Self::encrypt_with_context(py, ctx, plaintext, aad, nonce, self.tag_len, self.tag_first) + } + + fn encrypt_with_context<'p>( + py: pyo3::Python<'p>, + mut ctx: openssl::cipher_ctx::CipherCtx, + plaintext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + tag_len: usize, + tag_first: bool, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + check_length(plaintext)?; + + if let Some(nonce) = nonce { + ctx.set_iv_length(nonce.len())?; + } + ctx.encrypt_init(None, None, nonce)?; + + Self::process_aad(&mut ctx, aad)?; + + Ok(pyo3::types::PyBytes::new_with( + py, + plaintext.len() + tag_len, + |b| { + let ciphertext; + let tag; + if tag_first { + (tag, ciphertext) = b.split_at_mut(tag_len); + } else { + (ciphertext, tag) = b.split_at_mut(plaintext.len()); + } + + Self::process_data(&mut ctx, plaintext, ciphertext)?; + + ctx.tag(tag).map_err(CryptographyError::from)?; + + Ok(()) + }, + )?) + } + + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + ciphertext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut ctx = openssl::cipher_ctx::CipherCtx::new()?; + ctx.copy(&self.base_decryption_ctx)?; + Self::decrypt_with_context( + py, + ctx, + ciphertext, + aad, + nonce, + self.tag_len, + self.tag_first, + ) + } + + fn decrypt_with_context<'p>( + py: pyo3::Python<'p>, + mut ctx: openssl::cipher_ctx::CipherCtx, + ciphertext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + tag_len: usize, + tag_first: bool, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + if ciphertext.len() < tag_len { + return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); + } + + if let Some(nonce) = nonce { + ctx.set_iv_length(nonce.len())?; + } + ctx.decrypt_init(None, None, nonce)?; + + let tag; + let ciphertext_data; + if tag_first { + // RFC 5297 defines the output as IV || C, where the tag we generate + // is the "IV" and C is the ciphertext. This is the opposite of our + // other AEADs, which are Ciphertext || Tag. + (tag, ciphertext_data) = ciphertext.split_at(tag_len); + } else { + (ciphertext_data, tag) = ciphertext.split_at(ciphertext.len() - tag_len); + } + ctx.set_tag(tag)?; + + Self::process_aad(&mut ctx, aad)?; + + Ok(pyo3::types::PyBytes::new_with( + py, + ciphertext_data.len(), + |b| { + Self::process_data(&mut ctx, ciphertext_data, b) + .map_err(|_| exceptions::InvalidTag::new_err(()))?; + + Ok(()) + }, + )?) + } +} + +#[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + CRYPTOGRAPHY_OPENSSL_320_OR_GREATER +)))] +struct LazyEvpCipherAead { + cipher: &'static openssl::cipher::CipherRef, + key: pyo3::Py, + + tag_len: usize, + tag_first: bool, +} + +#[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + CRYPTOGRAPHY_OPENSSL_320_OR_GREATER +)))] +impl LazyEvpCipherAead { + fn new( + cipher: &'static openssl::cipher::CipherRef, + key: pyo3::Py, + tag_len: usize, + tag_first: bool, + ) -> LazyEvpCipherAead { + LazyEvpCipherAead { + cipher, + key, + tag_len, + tag_first, + } + } + + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + plaintext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let key_buf = self.key.as_ref(py).extract::>()?; + + let mut encryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; + encryption_ctx.encrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; + EvpCipherAead::encrypt_with_context( + py, + encryption_ctx, + plaintext, + aad, + nonce, + self.tag_len, + self.tag_first, + ) + } + + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + ciphertext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let key_buf = self.key.as_ref(py).extract::>()?; + + let mut decryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; + decryption_ctx.decrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; + EvpCipherAead::decrypt_with_context( + py, + decryption_ctx, + ciphertext, + aad, + nonce, + self.tag_len, + self.tag_first, + ) + } +} + +#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] +struct EvpAead { + ctx: cryptography_openssl::aead::AeadCtx, + tag_len: usize, +} + +#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] +impl EvpAead { + fn new( + algorithm: cryptography_openssl::aead::AeadType, + key: &[u8], + tag_len: usize, + ) -> CryptographyResult { + Ok(EvpAead { + ctx: cryptography_openssl::aead::AeadCtx::new(algorithm, key)?, + tag_len, + }) + } + + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + plaintext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + check_length(plaintext)?; + + let ad = if let Some(Aad::Single(ad)) = &aad { + check_length(ad.as_bytes())?; + ad.as_bytes() + } else { + assert!(aad.is_none()); + b"" + }; + Ok(pyo3::types::PyBytes::new_with( + py, + plaintext.len() + self.tag_len, + |b| { + self.ctx + .encrypt(plaintext, nonce.unwrap_or(b""), ad, b) + .map_err(CryptographyError::from)?; + Ok(()) + }, + )?) + } + + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + ciphertext: &[u8], + aad: Option>, + nonce: Option<&[u8]>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + if ciphertext.len() < self.tag_len { + return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); + } + + let ad = if let Some(Aad::Single(ad)) = &aad { + check_length(ad.as_bytes())?; + ad.as_bytes() + } else { + assert!(aad.is_none()); + b"" + }; + + Ok(pyo3::types::PyBytes::new_with( + py, + ciphertext.len() - self.tag_len, + |b| { + self.ctx + .decrypt(ciphertext, nonce.unwrap_or(b""), ad, b) + .map_err(|_| exceptions::InvalidTag::new_err(()))?; + + Ok(()) + }, + )?) + } +} + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead")] +struct ChaCha20Poly1305 { + #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] + ctx: EvpAead, + #[cfg(any( + CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, + CRYPTOGRAPHY_IS_LIBRESSL, + all( + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + not(CRYPTOGRAPHY_IS_BORINGSSL) + ) + ))] + ctx: EvpCipherAead, + #[cfg(not(any( + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + CRYPTOGRAPHY_OPENSSL_320_OR_GREATER + )))] + ctx: LazyEvpCipherAead, +} + +#[pyo3::prelude::pymethods] +impl ChaCha20Poly1305 { + #[new] + fn new(py: pyo3::Python<'_>, key: pyo3::Py) -> CryptographyResult { + let key_buf = key.extract::>(py)?; + if key_buf.as_bytes().len() != 32 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("ChaCha20Poly1305 key must be 32 bytes."), + )); + } + + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] { + Ok(ChaCha20Poly1305 { + ctx: EvpAead::new( + cryptography_openssl::aead::AeadType::ChaCha20Poly1305, + key_buf.as_bytes(), + 16, + )?, + }) + } else if #[cfg(any( + CRYPTOGRAPHY_IS_LIBRESSL, + 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(), + key_buf.as_bytes(), + 16, + false, + )?, + }) + } 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(), + key, + 16, + false, + ) + }) + } + } + } + + #[staticmethod] + fn generate_key(py: pyo3::Python<'_>) -> CryptographyResult<&pyo3::PyAny> { + Ok(py + .import(pyo3::intern!(py, "os"))? + .call_method1(pyo3::intern!(py, "urandom"), (32,))?) + } + + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + nonce: CffiBuf<'_>, + data: CffiBuf<'_>, + associated_data: Option>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let nonce_bytes = nonce.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() != 12 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes"), + )); + } + + self.ctx + .encrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) + } + + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + nonce: CffiBuf<'_>, + data: CffiBuf<'_>, + associated_data: Option>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let nonce_bytes = nonce.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() != 12 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes"), + )); + } + + self.ctx + .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) + } +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.bindings._rust.openssl.aead", + name = "AESSIV" +)] +struct AesSiv { + ctx: EvpCipherAead, +} + +#[pyo3::prelude::pymethods] +impl AesSiv { + #[new] + fn new(key: CffiBuf<'_>) -> CryptographyResult { + let cipher_name = match key.as_bytes().len() { + 32 => "aes-128-siv", + 48 => "aes-192-siv", + 64 => "aes-256-siv", + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "AESSIV key must be 256, 384, or 512 bits.", + ), + )) + } + }; + + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] { + if cryptography_openssl::fips::is_enabled() { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "AES-SIV is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )); + } + + let cipher = openssl::cipher::Cipher::fetch(None, cipher_name, None)?; + Ok(AesSiv { + ctx: EvpCipherAead::new(&cipher, key.as_bytes(), 16, true)?, + }) + } else { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "AES-SIV is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )); + + } + } + } + + #[staticmethod] + fn generate_key(py: pyo3::Python<'_>, bit_length: usize) -> CryptographyResult<&pyo3::PyAny> { + if bit_length != 256 && bit_length != 384 && bit_length != 512 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("bit_length must be 256, 384, or 512"), + )); + } + + Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) + } + + #[pyo3(signature = (data, associated_data))] + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + associated_data: Option<&pyo3::types::PyList>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let data_bytes = data.as_bytes(); + let aad = associated_data.map(Aad::List); + + if data_bytes.is_empty() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("data must not be zero length"), + )); + }; + self.ctx.encrypt(py, data_bytes, aad, None) + } + + #[pyo3(signature = (data, associated_data))] + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + associated_data: Option<&pyo3::types::PyList>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let aad = associated_data.map(Aad::List); + self.ctx.decrypt(py, data.as_bytes(), aad, None) + } +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.bindings._rust.openssl.aead", + name = "AESOCB3" +)] +struct AesOcb3 { + ctx: EvpCipherAead, +} + +#[pyo3::prelude::pymethods] +impl AesOcb3 { + #[new] + fn new(key: CffiBuf<'_>) -> CryptographyResult { + cfg_if::cfg_if! { + if #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "AES-OCB3 is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )); + } else { + if cryptography_openssl::fips::is_enabled() { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "AES-OCB3 is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )); + } + + let cipher = match key.as_bytes().len() { + 16 => openssl::cipher::Cipher::aes_128_ocb(), + 24 => openssl::cipher::Cipher::aes_192_ocb(), + 32 => openssl::cipher::Cipher::aes_256_ocb(), + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "AESOCB3 key must be 128, 192, or 256 bits.", + ), + )) + } + }; + + Ok(AesOcb3 { + ctx: EvpCipherAead::new(cipher, key.as_bytes(), 16, false)?, + }) + } + } + } + + #[staticmethod] + fn generate_key(py: pyo3::Python<'_>, bit_length: usize) -> CryptographyResult<&pyo3::PyAny> { + if bit_length != 128 && bit_length != 192 && bit_length != 256 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("bit_length must be 128, 192, or 256"), + )); + } + + Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) + } + + #[pyo3(signature = (nonce, data, associated_data))] + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + nonce: CffiBuf<'_>, + data: CffiBuf<'_>, + associated_data: Option>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let nonce_bytes = nonce.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() < 12 || nonce_bytes.len() > 15 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be between 12 and 15 bytes"), + )); + } + + self.ctx + .encrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) + } + + #[pyo3(signature = (nonce, data, associated_data))] + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + nonce: CffiBuf<'_>, + data: CffiBuf<'_>, + associated_data: Option>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let nonce_bytes = nonce.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() < 12 || nonce_bytes.len() > 15 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be between 12 and 15 bytes"), + )); + } + + self.ctx + .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) + } +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.bindings._rust.openssl.aead", + name = "AESGCMSIV" +)] +struct AesGcmSiv { + ctx: EvpCipherAead, +} + +#[pyo3::prelude::pymethods] +impl AesGcmSiv { + #[new] + fn new(key: CffiBuf<'_>) -> CryptographyResult { + let cipher_name = match key.as_bytes().len() { + 16 => "aes-128-gcm-siv", + 24 => "aes-192-gcm-siv", + 32 => "aes-256-gcm-siv", + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "AES-GCM-SIV key must be 128, 192 or 256 bits.", + ), + )) + } + }; + + cfg_if::cfg_if! { + if #[cfg(not(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER))] { + let _ = cipher_name; + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "AES-GCM-SIV is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )) + } else { + if cryptography_openssl::fips::is_enabled() { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "AES-GCM-SIV is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )); + } + let cipher = openssl::cipher::Cipher::fetch(None, cipher_name, None)?; + Ok(AesGcmSiv { + ctx: EvpCipherAead::new(&cipher, key.as_bytes(), 16, false)?, + }) + } + } + } + + #[staticmethod] + fn generate_key(py: pyo3::Python<'_>, bit_length: usize) -> CryptographyResult<&pyo3::PyAny> { + if bit_length != 128 && bit_length != 192 && bit_length != 256 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("bit_length must be 128, 192, or 256"), + )); + } + + Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) + } + + #[pyo3(signature = (nonce, data, associated_data))] + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + nonce: CffiBuf<'_>, + data: CffiBuf<'_>, + associated_data: Option>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let nonce_bytes = nonce.as_bytes(); + let data_bytes = data.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if data_bytes.is_empty() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("data must not be zero length"), + )); + }; + if nonce_bytes.len() != 12 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes long"), + )); + } + self.ctx.encrypt(py, data_bytes, aad, Some(nonce_bytes)) + } + + #[pyo3(signature = (nonce, data, associated_data))] + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + nonce: CffiBuf<'_>, + data: CffiBuf<'_>, + associated_data: Option>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let nonce_bytes = nonce.as_bytes(); + let aad = associated_data.map(Aad::Single); + if nonce_bytes.len() != 12 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes long"), + )); + } + self.ctx + .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "aead")?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/cipher_registry.rs b/src/rust/src/backend/cipher_registry.rs new file mode 100644 index 000000000000..128f087ff498 --- /dev/null +++ b/src/rust/src/backend/cipher_registry.rs @@ -0,0 +1,179 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::collections::HashMap; + +use openssl::cipher::Cipher; + +use crate::error::CryptographyResult; +use crate::types; + +struct RegistryKey { + algorithm: pyo3::PyObject, + mode: pyo3::PyObject, + key_size: Option, + + algorithm_hash: isize, + mode_hash: isize, +} + +impl RegistryKey { + fn new( + py: pyo3::Python<'_>, + algorithm: pyo3::PyObject, + mode: pyo3::PyObject, + key_size: Option, + ) -> CryptographyResult { + Ok(Self { + algorithm: algorithm.clone_ref(py), + mode: mode.clone_ref(py), + key_size, + algorithm_hash: algorithm.as_ref(py).hash()?, + mode_hash: mode.as_ref(py).hash()?, + }) + } +} + +impl PartialEq for RegistryKey { + fn eq(&self, other: &RegistryKey) -> bool { + self.algorithm.is(&other.algorithm) + && self.mode.is(&other.mode) + && (self.key_size == other.key_size + || self.key_size.is_none() + || other.key_size.is_none()) + } +} + +impl Eq for RegistryKey {} + +impl std::hash::Hash for RegistryKey { + fn hash(&self, state: &mut H) { + self.algorithm_hash.hash(state); + self.mode_hash.hash(state); + } +} + +enum RegistryCipher { + Ref(&'static openssl::cipher::CipherRef), +} + +impl From<&'static openssl::cipher::CipherRef> for RegistryCipher { + fn from(c: &'static openssl::cipher::CipherRef) -> RegistryCipher { + RegistryCipher::Ref(c) + } +} + +struct RegistryBuilder<'p> { + py: pyo3::Python<'p>, + m: HashMap, +} + +impl<'p> RegistryBuilder<'p> { + fn new(py: pyo3::Python<'p>) -> Self { + RegistryBuilder { + py, + m: HashMap::new(), + } + } + + fn add( + &mut self, + algorithm: &pyo3::PyAny, + mode: &pyo3::PyAny, + key_size: Option, + cipher: impl Into, + ) -> CryptographyResult<()> { + self.m.insert( + RegistryKey::new(self.py, algorithm.into(), mode.into(), key_size)?, + cipher.into(), + ); + + Ok(()) + } + + fn build(self) -> HashMap { + self.m + } +} + +fn get_cipher_registry( + py: pyo3::Python<'_>, +) -> CryptographyResult<&HashMap> { + static REGISTRY: pyo3::sync::GILOnceCell> = + pyo3::sync::GILOnceCell::new(); + + REGISTRY.get_or_try_init(py, || { + let mut m = RegistryBuilder::new(py); + + let aes = types::AES.get(py)?; + let aes128 = types::AES128.get(py)?; + let aes256 = types::AES256.get(py)?; + let triple_des = types::TRIPLE_DES.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + let camellia = types::CAMELLIA.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] + let blowfish = types::BLOWFISH.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] + let cast5 = types::CAST5.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] + let idea = types::IDEA.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] + let sm4 = types::SM4.get(py)?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] + let seed = types::SEED.get(py)?; + + let cbc = types::CBC.get(py)?; + + m.add(aes, cbc, Some(128), Cipher::aes_128_cbc())?; + m.add(aes, cbc, Some(192), Cipher::aes_192_cbc())?; + m.add(aes, cbc, Some(256), Cipher::aes_256_cbc())?; + + m.add(aes128, cbc, Some(128), Cipher::aes_128_cbc())?; + m.add(aes256, cbc, Some(256), Cipher::aes_256_cbc())?; + + m.add(triple_des, cbc, Some(192), Cipher::des_ede3_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + m.add(camellia, cbc, Some(128), Cipher::camellia128_cbc())?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + m.add(camellia, cbc, Some(192), Cipher::camellia192_cbc())?; + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + m.add(camellia, cbc, Some(256), Cipher::camellia256_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] + m.add(sm4, cbc, Some(128), Cipher::sm4_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] + m.add(seed, cbc, Some(128), Cipher::seed_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] + m.add(blowfish, cbc, None, Cipher::bf_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] + m.add(cast5, cbc, None, Cipher::cast5_cbc())?; + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] + m.add(idea, cbc, Some(128), Cipher::idea_cbc())?; + + Ok(m.build()) + }) +} + +pub(crate) fn get_cipher<'a>( + py: pyo3::Python<'_>, + algorithm: &pyo3::PyAny, + mode_cls: &pyo3::PyAny, +) -> CryptographyResult> { + let registry = get_cipher_registry(py)?; + + let key_size = algorithm + .getattr(pyo3::intern!(py, "key_size"))? + .extract()?; + let key = RegistryKey::new(py, algorithm.get_type().into(), mode_cls.into(), key_size)?; + + match registry.get(&key) { + Some(RegistryCipher::Ref(c)) => Ok(Some(c)), + None => Ok(None), + } +} diff --git a/src/rust/src/backend/cmac.rs b/src/rust/src/backend/cmac.rs new file mode 100644 index 000000000000..acacbf02f6ad --- /dev/null +++ b/src/rust/src/backend/cmac.rs @@ -0,0 +1,106 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::backend::cipher_registry; +use crate::backend::hashes::already_finalized_error; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, types}; + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.cmac", + name = "CMAC" +)] +struct Cmac { + ctx: Option, +} + +impl Cmac { + fn get_ctx(&self) -> CryptographyResult<&cryptography_openssl::cmac::Cmac> { + if let Some(ctx) = self.ctx.as_ref() { + return Ok(ctx); + }; + Err(already_finalized_error()) + } + + fn get_mut_ctx(&mut self) -> CryptographyResult<&mut cryptography_openssl::cmac::Cmac> { + if let Some(ctx) = self.ctx.as_mut() { + return Ok(ctx); + } + Err(already_finalized_error()) + } +} + +#[pyo3::pymethods] +impl Cmac { + #[new] + fn new( + py: pyo3::Python<'_>, + algorithm: &pyo3::PyAny, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + if !algorithm.is_instance(types::BLOCK_CIPHER_ALGORITHM.get(py)?)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Expected instance of BlockCipherAlgorithm.", + ), + )); + } + + let cipher = + cipher_registry::get_cipher(py, algorithm, types::CBC.get(py)?)?.ok_or_else(|| { + exceptions::UnsupportedAlgorithm::new_err(( + "CMAC is not supported with this algorithm", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )) + })?; + + let key = algorithm + .getattr(pyo3::intern!(py, "key"))? + .extract::>()?; + let ctx = cryptography_openssl::cmac::Cmac::new(key.as_bytes(), cipher)?; + Ok(Cmac { ctx: Some(ctx) }) + } + + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + self.get_mut_ctx()?.update(data.as_bytes())?; + Ok(()) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let data = self.get_mut_ctx()?.finish()?; + self.ctx = None; + Ok(pyo3::types::PyBytes::new(py, &data)) + } + + fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { + let actual = self.finalize(py)?.as_bytes(); + if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err("Signature did not match digest."), + )); + } + + Ok(()) + } + + fn copy(&self) -> CryptographyResult { + Ok(Cmac { + ctx: Some(self.get_ctx()?.copy()?), + }) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "cmac")?; + + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/dh.rs b/src/rust/src/backend/dh.rs index 7f523c09e594..eb6cbdcdc9e4 100644 --- a/src/rust/src/backend/dh.rs +++ b/src/rust/src/backend/dh.rs @@ -2,37 +2,42 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use cryptography_x509::common; + use crate::asn1::encode_der_data; use crate::backend::utils; use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509; -use cryptography_x509::common; -use foreign_types_shared::ForeignTypeRef; +use crate::{types, x509}; const MIN_MODULUS_SIZE: u32 = 512; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] -struct DHPrivateKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] +pub(crate) struct DHPrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] -struct DHPublicKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] +pub(crate) struct DHPublicKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] struct DHParameters { dh: openssl::dh::Dh, } #[pyo3::prelude::pyfunction] -fn generate_parameters(generator: u32, key_size: u32) -> CryptographyResult { +fn generate_parameters( + generator: u32, + key_size: u32, + backend: Option<&pyo3::PyAny>, +) -> CryptographyResult { + let _ = backend; + if key_size < MIN_MODULUS_SIZE { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( - "DH key_size must be at least {} bits", - MIN_MODULUS_SIZE + "DH key_size must be at least {MIN_MODULUS_SIZE} bits" )), )); } @@ -47,24 +52,45 @@ fn generate_parameters(generator: u32, key_size: u32) -> CryptographyResult DHPrivateKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn private_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> DHPrivateKey { DHPrivateKey { pkey: pkey.to_owned(), } } -#[pyo3::prelude::pyfunction] -fn public_key_from_ptr(ptr: usize) -> DHPublicKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn public_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> DHPublicKey { DHPublicKey { pkey: pkey.to_owned(), } } +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +fn pkey_from_dh( + dh: openssl::dh::Dh, +) -> CryptographyResult> { + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_IS_LIBRESSL)] { + Ok(openssl::pkey::PKey::from_dh(dh)?) + } else { + if dh.prime_q().is_some() { + Ok(openssl::pkey::PKey::from_dhx(dh)?) + } else { + Ok(openssl::pkey::PKey::from_dh(dh)?) + } + } + } +} + #[pyo3::prelude::pyfunction] -fn from_der_parameters(data: &[u8]) -> CryptographyResult { +fn from_der_parameters( + data: &[u8], + backend: Option<&pyo3::PyAny>, +) -> CryptographyResult { + let _ = backend; let asn1_params = asn1::parse_single::>(data)?; let p = openssl::bn::BigNum::from_slice(asn1_params.p.as_bytes())?; @@ -80,79 +106,33 @@ fn from_der_parameters(data: &[u8]) -> CryptographyResult { } #[pyo3::prelude::pyfunction] -fn from_pem_parameters(data: &[u8]) -> CryptographyResult { +fn from_pem_parameters( + data: &[u8], + backend: Option<&pyo3::PyAny>, +) -> CryptographyResult { + let _ = backend; let parsed = x509::find_in_pem( data, - |p| p.tag == "DH PARAMETERS" || p.tag == "X9.42 DH PARAMETERS", + |p| p.tag() == "DH PARAMETERS" || p.tag() == "X9.42 DH PARAMETERS", "Valid PEM but no BEGIN DH PARAMETERS/END DH PARAMETERS delimiters. Are you sure this is a DH parameters?", )?; - from_der_parameters(&parsed.contents) + from_der_parameters(parsed.contents(), None) } fn dh_parameters_from_numbers( py: pyo3::Python<'_>, - numbers: &pyo3::PyAny, + numbers: &DHParameterNumbers, ) -> CryptographyResult> { - let p = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "p"))?)?; + let p = utils::py_int_to_bn(py, numbers.p.as_ref(py))?; let q = numbers - .getattr(pyo3::intern!(py, "q"))? - .extract::>()? - .map(|v| utils::py_int_to_bn(py, v)) + .q + .as_ref() + .map(|v| utils::py_int_to_bn(py, v.as_ref(py))) .transpose()?; - let g = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "g"))?)?; - - let dh = openssl::dh::Dh::from_pqg(p, q, g)?; - if !dh.check_key()? { - return Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err( - "DH private numbers did not pass safety checks.", - ), - )); - } - - Ok(dh) -} - -#[pyo3::prelude::pyfunction] -fn from_private_numbers( - py: pyo3::Python<'_>, - numbers: &pyo3::PyAny, -) -> CryptographyResult { - let public_numbers = numbers.getattr(pyo3::intern!(py, "public_numbers"))?; - let parameter_numbers = public_numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; - - let dh = dh_parameters_from_numbers(py, parameter_numbers)?; - - let pub_key = utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "y"))?)?; - let priv_key = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "x"))?)?; - - let pkey = openssl::pkey::PKey::from_dh(dh.set_key(pub_key, priv_key)?)?; - Ok(DHPrivateKey { pkey }) -} + let g = utils::py_int_to_bn(py, numbers.g.as_ref(py))?; -#[pyo3::prelude::pyfunction] -fn from_public_numbers( - py: pyo3::Python<'_>, - numbers: &pyo3::PyAny, -) -> CryptographyResult { - let parameter_numbers = numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; - let dh = dh_parameters_from_numbers(py, parameter_numbers)?; - - let pub_key = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "y"))?)?; - - let pkey = openssl::pkey::PKey::from_dh(dh.set_public_key(pub_key)?)?; - - Ok(DHPublicKey { pkey }) -} - -#[pyo3::prelude::pyfunction] -fn from_parameter_numbers( - py: pyo3::Python<'_>, - numbers: &pyo3::PyAny, -) -> CryptographyResult { - let dh = dh_parameters_from_numbers(py, numbers)?; - Ok(DHParameters { dh }) + Ok(openssl::dh::Dh::from_pqg(p, q, g)?) } fn clone_dh( @@ -174,11 +154,11 @@ impl DHPrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, - public_key: &DHPublicKey, + peer_public_key: &DHPublicKey, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; deriver - .set_peer(&public_key.pkey) + .set_peer(&peer_public_key.pkey) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; Ok(pyo3::types::PyBytes::new_with(py, deriver.len()?, |b| { @@ -195,7 +175,7 @@ impl DHPrivateKey { })?) } - fn private_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn private_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dh = self.pkey.dh().unwrap(); let py_p = utils::bn_to_py_int(py, dh.prime_p())?; @@ -208,30 +188,28 @@ impl DHPrivateKey { let py_pub_key = utils::bn_to_py_int(py, dh.public_key())?; let py_private_key = utils::bn_to_py_int(py, dh.private_key())?; - let dh_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dh" - ))?; - - let parameter_numbers = - dh_mod.call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?; - let public_numbers = dh_mod.call_method1( - pyo3::intern!(py, "DHPublicNumbers"), - (py_pub_key, parameter_numbers), - )?; - - Ok(dh_mod.call_method1( - pyo3::intern!(py, "DHPrivateNumbers"), - (py_private_key, public_numbers), - )?) + let parameter_numbers = DHParameterNumbers { + p: py_p.extract()?, + q: py_q.map(|q| q.extract()).transpose()?, + g: py_g.extract()?, + }; + let public_numbers = DHPublicNumbers { + y: py_pub_key.extract()?, + parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, + }; + + Ok(DHPrivateNumbers { + x: py_private_key.extract()?, + public_numbers: pyo3::Py::new(py, public_numbers)?, + }) } + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn public_key(&self) -> CryptographyResult { let orig_dh = self.pkey.dh().unwrap(); let dh = clone_dh(&orig_dh)?; - let pkey = - openssl::pkey::PKey::from_dh(dh.set_public_key(orig_dh.public_key().to_owned()?)?)?; + let pkey = pkey_from_dh(dh.set_public_key(orig_dh.public_key().to_owned()?)?)?; Ok(DHPublicKey { pkey }) } @@ -249,13 +227,7 @@ impl DHPrivateKey { format: &pyo3::PyAny, encryption_algorithm: &pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let private_format_class = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "PrivateFormat"))?; - if !format.is(private_format_class.getattr(pyo3::intern!(py, "PKCS8"))?) { + if !format.is(types::PRIVATE_FORMAT_PKCS8.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "DH private keys support only PKCS8 serialization", @@ -289,13 +261,7 @@ impl DHPublicKey { encoding: &pyo3::PyAny, format: &pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let public_format_class = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "PublicFormat"))?; - if !format.is(public_format_class.getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?) { + if !format.is(types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "DH public keys support only SubjectPublicKeyInfo serialization", @@ -312,7 +278,7 @@ impl DHPublicKey { }) } - fn public_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn public_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dh = self.pkey.dh().unwrap(); let py_p = utils::bn_to_py_int(py, dh.prime_p())?; @@ -324,43 +290,38 @@ impl DHPublicKey { let py_pub_key = utils::bn_to_py_int(py, dh.public_key())?; - let dh_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dh" - ))?; + let parameter_numbers = DHParameterNumbers { + p: py_p.extract()?, + q: py_q.map(|q| q.extract()).transpose()?, + g: py_g.extract()?, + }; - let parameter_numbers = - dh_mod.call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?; + Ok(DHPublicNumbers { + y: py_pub_key.extract()?, + parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, + }) + } - Ok(dh_mod.call_method1( - pyo3::intern!(py, "DHPublicNumbers"), - (py_pub_key, parameter_numbers), - )?) + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, DHPublicKey>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), - pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), - _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), - } + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf } } #[pyo3::prelude::pymethods] impl DHParameters { + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn generate_private_key(&self) -> CryptographyResult { let dh = clone_dh(&self.dh)?.generate_key()?; Ok(DHPrivateKey { - pkey: openssl::pkey::PKey::from_dh(dh)?, + pkey: pkey_from_dh(dh)?, }) } - fn parameter_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn parameter_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let py_p = utils::bn_to_py_int(py, self.dh.prime_p())?; let py_q = self .dh @@ -369,12 +330,11 @@ impl DHParameters { .transpose()?; let py_g = utils::bn_to_py_int(py, self.dh.generator())?; - Ok(py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dh" - ))? - .call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?) + Ok(DHParameterNumbers { + p: py_p.extract()?, + q: py_q.map(|q| q.extract()).transpose()?, + g: py_g.extract()?, + }) } fn parameter_bytes<'p>( @@ -383,13 +343,7 @@ impl DHParameters { encoding: &'p pyo3::PyAny, format: &pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let parameter_format_class = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "ParameterFormat"))?; - if !format.is(parameter_format_class.getattr(pyo3::intern!(py, "PKCS3"))?) { + if !format.is(types::PARAMETER_FORMAT_PKCS3.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Only PKCS3 serialization is supported"), )); @@ -417,22 +371,192 @@ impl DHParameters { } } +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] +struct DHPrivateNumbers { + #[pyo3(get)] + x: pyo3::Py, + #[pyo3(get)] + public_numbers: pyo3::Py, +} + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] +struct DHPublicNumbers { + #[pyo3(get)] + y: pyo3::Py, + #[pyo3(get)] + parameter_numbers: pyo3::Py, +} + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] +struct DHParameterNumbers { + #[pyo3(get)] + p: pyo3::Py, + #[pyo3(get)] + g: pyo3::Py, + #[pyo3(get)] + q: Option>, +} + +#[pyo3::prelude::pymethods] +impl DHPrivateNumbers { + #[new] + fn new( + x: pyo3::Py, + public_numbers: pyo3::Py, + ) -> DHPrivateNumbers { + DHPrivateNumbers { x, public_numbers } + } + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + fn private_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let dh = dh_parameters_from_numbers(py, self.public_numbers.get().parameter_numbers.get())?; + + let pub_key = utils::py_int_to_bn(py, self.public_numbers.get().y.as_ref(py))?; + let priv_key = utils::py_int_to_bn(py, self.x.as_ref(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 = pkey_from_dh(dh)?; + Ok(DHPrivateKey { pkey }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self.x.as_ref(py).eq(other.x.as_ref(py))? + && self + .public_numbers + .as_ref(py) + .eq(other.public_numbers.as_ref(py))?) + } +} + +#[pyo3::prelude::pymethods] +impl DHPublicNumbers { + #[new] + fn new( + y: pyo3::Py, + parameter_numbers: pyo3::Py, + ) -> DHPublicNumbers { + DHPublicNumbers { + y, + parameter_numbers, + } + } + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + fn public_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let dh = dh_parameters_from_numbers(py, self.parameter_numbers.get())?; + + let pub_key = utils::py_int_to_bn(py, self.y.as_ref(py))?; + + let pkey = pkey_from_dh(dh.set_public_key(pub_key)?)?; + + Ok(DHPublicKey { pkey }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self.y.as_ref(py).eq(other.y.as_ref(py))? + && self + .parameter_numbers + .as_ref(py) + .eq(other.parameter_numbers.as_ref(py))?) + } +} + +#[pyo3::prelude::pymethods] +impl DHParameterNumbers { + #[new] + fn new( + py: pyo3::Python<'_>, + p: pyo3::Py, + g: pyo3::Py, + q: Option>, + ) -> CryptographyResult { + if g.as_ref(py).lt(2)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("DH generator must be 2 or greater"), + )); + } + + if p.as_ref(py) + .call_method0("bit_length")? + .lt(MIN_MODULUS_SIZE)? + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "p (modulus) must be at least {MIN_MODULUS_SIZE}-bit" + )), + )); + } + + Ok(DHParameterNumbers { p, g, q }) + } + + fn parameters( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let dh = dh_parameters_from_numbers(py, self)?; + Ok(DHParameters { dh }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + let q_equal = match (self.q.as_ref(), other.q.as_ref()) { + (Some(self_q), Some(other_q)) => self_q.as_ref(py).eq(other_q.as_ref(py))?, + (None, None) => true, + _ => false, + }; + Ok(self.p.as_ref(py).eq(other.p.as_ref(py))? + && self.g.as_ref(py).eq(other.g.as_ref(py))? + && q_equal) + } +} + pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let m = pyo3::prelude::PyModule::new(py, "dh")?; m.add_function(pyo3::wrap_pyfunction!(generate_parameters, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_der_parameters, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_pem_parameters, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_private_numbers, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_public_numbers, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_parameter_numbers, m)?)?; m.add_class::()?; m.add_class::()?; m.add_class::()?; - - m.add("MIN_MODULUS_SIZE", MIN_MODULUS_SIZE)?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(m) } diff --git a/src/rust/src/backend/dsa.rs b/src/rust/src/backend/dsa.rs index 59a5a676d5d5..bf341ac71314 100644 --- a/src/rust/src/backend/dsa.rs +++ b/src/rust/src/backend/dsa.rs @@ -3,27 +3,30 @@ // for complete details. use crate::backend::utils; +use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -use foreign_types_shared::ForeignTypeRef; #[pyo3::prelude::pyclass( + frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAPrivateKey" )] -struct DsaPrivateKey { +pub(crate) struct DsaPrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::prelude::pyclass( + frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAPublicKey" )] -struct DsaPublicKey { +pub(crate) struct DsaPublicKey { pkey: openssl::pkey::PKey, } #[pyo3::prelude::pyclass( + frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAParameters" )] @@ -31,17 +34,17 @@ struct DsaParameters { dsa: openssl::dsa::Dsa, } -#[pyo3::prelude::pyfunction] -fn private_key_from_ptr(ptr: usize) -> DsaPrivateKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn private_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> DsaPrivateKey { DsaPrivateKey { pkey: pkey.to_owned(), } } -#[pyo3::prelude::pyfunction] -fn public_key_from_ptr(ptr: usize) -> DsaPublicKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn public_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> DsaPublicKey { DsaPublicKey { pkey: pkey.to_owned(), } @@ -53,58 +56,6 @@ fn generate_parameters(key_size: u32) -> CryptographyResult { Ok(DsaParameters { dsa }) } -#[pyo3::prelude::pyfunction] -fn from_private_numbers( - py: pyo3::Python<'_>, - numbers: &pyo3::PyAny, -) -> CryptographyResult { - let public_numbers = numbers.getattr(pyo3::intern!(py, "public_numbers"))?; - let parameter_numbers = public_numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; - - let dsa = openssl::dsa::Dsa::from_private_components( - utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "p"))?)?, - utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "q"))?)?, - utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "g"))?)?, - utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "x"))?)?, - utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "y"))?)?, - ) - .unwrap(); - let pkey = openssl::pkey::PKey::from_dsa(dsa)?; - Ok(DsaPrivateKey { pkey }) -} - -#[pyo3::prelude::pyfunction] -fn from_public_numbers( - py: pyo3::Python<'_>, - numbers: &pyo3::PyAny, -) -> CryptographyResult { - let parameter_numbers = numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; - - let dsa = openssl::dsa::Dsa::from_public_components( - utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "p"))?)?, - utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "q"))?)?, - utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "g"))?)?, - utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "y"))?)?, - ) - .unwrap(); - let pkey = openssl::pkey::PKey::from_dsa(dsa)?; - Ok(DsaPublicKey { pkey }) -} - -#[pyo3::prelude::pyfunction] -fn from_parameter_numbers( - py: pyo3::Python<'_>, - numbers: &pyo3::PyAny, -) -> CryptographyResult { - let dsa = openssl::dsa::Dsa::from_pqg( - utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "p"))?)?, - utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "q"))?)?, - utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "g"))?)?, - ) - .unwrap(); - Ok(DsaParameters { dsa }) -} - fn clone_dsa_params( d: &openssl::dsa::Dsa, ) -> Result, openssl::error::ErrorStack> { @@ -116,19 +67,10 @@ impl DsaPrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &pyo3::types::PyBytes, + data: CffiBuf<'_>, algorithm: &pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let (data, _): (&[u8], &pyo3::PyAny) = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.backends.openssl.utils" - ))? - .call_method1( - pyo3::intern!(py, "_calculate_digest_and_algorithm"), - (data, algorithm), - )? - .extract()?; + let (data, _) = utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; signer.sign_init()?; @@ -160,7 +102,7 @@ impl DsaPrivateKey { Ok(DsaParameters { dsa }) } - fn private_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn private_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dsa = self.pkey.dsa().unwrap(); let py_p = utils::bn_to_py_int(py, dsa.p())?; @@ -170,22 +112,19 @@ impl DsaPrivateKey { let py_pub_key = utils::bn_to_py_int(py, dsa.pub_key())?; let py_private_key = utils::bn_to_py_int(py, dsa.priv_key())?; - let dsa_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dsa" - ))?; - - let parameter_numbers = - dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?; - let public_numbers = dsa_mod.call_method1( - pyo3::intern!(py, "DSAPublicNumbers"), - (py_pub_key, parameter_numbers), - )?; - - Ok(dsa_mod.call_method1( - pyo3::intern!(py, "DSAPrivateNumbers"), - (py_private_key, public_numbers), - )?) + let parameter_numbers = DsaParameterNumbers { + p: py_p.extract()?, + q: py_q.extract()?, + g: py_g.extract()?, + }; + let public_numbers = DsaPublicNumbers { + y: py_pub_key.extract()?, + parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, + }; + Ok(DsaPrivateNumbers { + x: py_private_key.extract()?, + public_numbers: pyo3::Py::new(py, public_numbers)?, + }) } fn private_bytes<'p>( @@ -213,24 +152,15 @@ impl DsaPublicKey { fn verify( &self, py: pyo3::Python<'_>, - signature: &[u8], - data: &pyo3::types::PyBytes, + signature: CffiBuf<'_>, + data: CffiBuf<'_>, algorithm: &pyo3::PyAny, ) -> CryptographyResult<()> { - let (data, _): (&[u8], &pyo3::PyAny) = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.backends.openssl.utils" - ))? - .call_method1( - pyo3::intern!(py, "_calculate_digest_and_algorithm"), - (data, algorithm), - )? - .extract()?; + let (data, _) = utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; verifier.verify_init()?; - let valid = verifier.verify(data, signature).unwrap_or(false); + let valid = verifier.verify(data, signature.as_bytes()).unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), @@ -250,7 +180,7 @@ impl DsaPublicKey { Ok(DsaParameters { dsa }) } - fn public_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn public_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dsa = self.pkey.dsa().unwrap(); let py_p = utils::bn_to_py_int(py, dsa.p())?; @@ -259,17 +189,15 @@ impl DsaPublicKey { let py_pub_key = utils::bn_to_py_int(py, dsa.pub_key())?; - let dsa_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dsa" - ))?; - - let parameter_numbers = - dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?; - Ok(dsa_mod.call_method1( - pyo3::intern!(py, "DSAPublicNumbers"), - (py_pub_key, parameter_numbers), - )?) + let parameter_numbers = DsaParameterNumbers { + p: py_p.extract()?, + q: py_q.extract()?, + g: py_g.extract()?, + }; + Ok(DsaPublicNumbers { + y: py_pub_key.extract()?, + parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, + }) } fn public_bytes<'p>( @@ -281,16 +209,12 @@ impl DsaPublicKey { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, DsaPublicKey>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), - pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), - _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), - } + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) + } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf } } @@ -302,32 +226,287 @@ impl DsaParameters { Ok(DsaPrivateKey { pkey }) } - fn parameter_numbers<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn parameter_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let py_p = utils::bn_to_py_int(py, self.dsa.p())?; let py_q = utils::bn_to_py_int(py, self.dsa.q())?; let py_g = utils::bn_to_py_int(py, self.dsa.g())?; - let dsa_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dsa" - ))?; + Ok(DsaParameterNumbers { + p: py_p.extract()?, + q: py_q.extract()?, + g: py_g.extract()?, + }) + } +} + +fn check_dsa_parameters( + py: pyo3::Python<'_>, + parameters: &DsaParameterNumbers, +) -> CryptographyResult<()> { + if ![1024, 2048, 3072, 4096].contains( + ¶meters + .p + .as_ref(py) + .call_method0("bit_length")? + .extract::()?, + ) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "p must be exactly 1024, 2048, 3072, or 4096 bits long", + ), + )); + } + + if ![160, 224, 256].contains( + ¶meters + .q + .as_ref(py) + .call_method0("bit_length")? + .extract::()?, + ) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("q must be exactly 160, 224, or 256 bits long"), + )); + } + + if parameters.g.as_ref(py).le(1)? || parameters.g.as_ref(py).ge(parameters.p.as_ref(py))? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("g, p don't satisfy 1 < g < p."), + )); + } + + Ok(()) +} + +fn check_dsa_private_numbers( + py: pyo3::Python<'_>, + numbers: &DsaPrivateNumbers, +) -> CryptographyResult<()> { + let params = numbers.public_numbers.get().parameter_numbers.get(); + check_dsa_parameters(py, params)?; + + if numbers.x.as_ref(py).le(0)? || numbers.x.as_ref(py).ge(params.q.as_ref(py))? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("x must be > 0 and < q."), + )); + } + + if numbers + .public_numbers + .get() + .y + .as_ref(py) + .ne(params.g.as_ref(py).call_method1( + pyo3::intern!(py, "__pow__"), + (numbers.x.as_ref(py), params.p.as_ref(py)), + )?)? + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("y must be equal to (g ** x % p)."), + )); + } + + Ok(()) +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.primitives.asymmetric.dsa", + name = "DSAPrivateNumbers" +)] +struct DsaPrivateNumbers { + #[pyo3(get)] + x: pyo3::Py, + #[pyo3(get)] + public_numbers: pyo3::Py, +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.primitives.asymmetric.dsa", + name = "DSAPublicNumbers" +)] +struct DsaPublicNumbers { + #[pyo3(get)] + y: pyo3::Py, + #[pyo3(get)] + parameter_numbers: pyo3::Py, +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.primitives.asymmetric.dsa", + name = "DSAParameterNumbers" +)] +struct DsaParameterNumbers { + #[pyo3(get)] + p: pyo3::Py, + #[pyo3(get)] + q: pyo3::Py, + #[pyo3(get)] + g: pyo3::Py, +} + +#[pyo3::prelude::pymethods] +impl DsaPrivateNumbers { + #[new] + fn new( + x: pyo3::Py, + public_numbers: pyo3::Py, + ) -> DsaPrivateNumbers { + DsaPrivateNumbers { x, public_numbers } + } + + fn private_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let public_numbers = self.public_numbers.get(); + let parameter_numbers = public_numbers.parameter_numbers.get(); + + check_dsa_private_numbers(py, self)?; + + let dsa = openssl::dsa::Dsa::from_private_components( + utils::py_int_to_bn(py, parameter_numbers.p.as_ref(py))?, + utils::py_int_to_bn(py, parameter_numbers.q.as_ref(py))?, + utils::py_int_to_bn(py, parameter_numbers.g.as_ref(py))?, + utils::py_int_to_bn(py, self.x.as_ref(py))?, + utils::py_int_to_bn(py, public_numbers.y.as_ref(py))?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_dsa(dsa)?; + Ok(DsaPrivateKey { pkey }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self.x.as_ref(py).eq(other.x.as_ref(py))? + && self + .public_numbers + .as_ref(py) + .eq(other.public_numbers.as_ref(py))?) + } +} + +#[pyo3::prelude::pymethods] +impl DsaPublicNumbers { + #[new] + fn new( + y: pyo3::Py, + parameter_numbers: pyo3::Py, + ) -> DsaPublicNumbers { + DsaPublicNumbers { + y, + parameter_numbers, + } + } + + fn public_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let parameter_numbers = self.parameter_numbers.get(); + + check_dsa_parameters(py, parameter_numbers)?; + + let dsa = openssl::dsa::Dsa::from_public_components( + utils::py_int_to_bn(py, parameter_numbers.p.as_ref(py))?, + utils::py_int_to_bn(py, parameter_numbers.q.as_ref(py))?, + utils::py_int_to_bn(py, parameter_numbers.g.as_ref(py))?, + utils::py_int_to_bn(py, self.y.as_ref(py))?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_dsa(dsa)?; + Ok(DsaPublicKey { pkey }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self.y.as_ref(py).eq(other.y.as_ref(py))? + && self + .parameter_numbers + .as_ref(py) + .eq(other.parameter_numbers.as_ref(py))?) + } + + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let y = self.y.as_ref(py); + let parameter_numbers = self.parameter_numbers.as_ref(py).repr()?; + Ok(format!( + "" + )) + } +} + +#[pyo3::prelude::pymethods] +impl DsaParameterNumbers { + #[new] + fn new( + p: pyo3::Py, + q: pyo3::Py, + g: pyo3::Py, + ) -> DsaParameterNumbers { + DsaParameterNumbers { p, q, g } + } + + fn parameters( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + check_dsa_parameters(py, self)?; + + let dsa = openssl::dsa::Dsa::from_pqg( + utils::py_int_to_bn(py, self.p.as_ref(py))?, + utils::py_int_to_bn(py, self.q.as_ref(py))?, + utils::py_int_to_bn(py, self.g.as_ref(py))?, + ) + .unwrap(); + Ok(DsaParameters { dsa }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self.p.as_ref(py).eq(other.p.as_ref(py))? + && self.q.as_ref(py).eq(other.q.as_ref(py))? + && self.g.as_ref(py).eq(other.g.as_ref(py))?) + } - Ok(dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?) + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let p = self.p.as_ref(py); + let q = self.q.as_ref(py); + let g = self.g.as_ref(py); + Ok(format!("")) } } pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let m = pyo3::prelude::PyModule::new(py, "dsa")?; - m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; m.add_function(pyo3::wrap_pyfunction!(generate_parameters, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_private_numbers, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_public_numbers, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_parameter_numbers, m)?)?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(m) } diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs new file mode 100644 index 000000000000..6a224b49f155 --- /dev/null +++ b/src/rust/src/backend/ec.rs @@ -0,0 +1,662 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +use pyo3::ToPyObject; + +use crate::backend::utils; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, types}; + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] +pub(crate) struct ECPrivateKey { + pkey: openssl::pkey::PKey, + #[pyo3(get)] + curve: pyo3::Py, +} + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] +pub(crate) struct ECPublicKey { + pkey: openssl::pkey::PKey, + #[pyo3(get)] + curve: pyo3::Py, +} + +fn curve_from_py_curve( + py: pyo3::Python<'_>, + py_curve: &pyo3::PyAny, + allow_curve_class: bool, +) -> CryptographyResult { + if !py_curve.is_instance(types::ELLIPTIC_CURVE.get(py)?)? { + if allow_curve_class { + let warning_cls = types::DEPRECATED_IN_42.get(py)?; + let warning_msg = "Curve argument must be an instance of an EllipticCurve class. Did you pass a class by mistake? This will be an exception in a future version of cryptography."; + pyo3::PyErr::warn(py, warning_cls, warning_msg, 1)?; + } else { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err("curve must be an EllipticCurve instance"), + )); + } + } + + let curve_name = py_curve.getattr(pyo3::intern!(py, "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, + "secp384r1" => openssl::nid::Nid::SECP384R1, + "secp521r1" => openssl::nid::Nid::SECP521R1, + + "secp256k1" => openssl::nid::Nid::SECP256K1, + + "sect233r1" => openssl::nid::Nid::SECT233R1, + "sect283r1" => openssl::nid::Nid::SECT283R1, + "sect409r1" => openssl::nid::Nid::SECT409R1, + "sect571r1" => openssl::nid::Nid::SECT571R1, + + "sect163r2" => openssl::nid::Nid::SECT163R2, + + "sect163k1" => openssl::nid::Nid::SECT163K1, + "sect233k1" => openssl::nid::Nid::SECT233K1, + "sect283k1" => openssl::nid::Nid::SECT283K1, + "sect409k1" => openssl::nid::Nid::SECT409K1, + "sect571k1" => openssl::nid::Nid::SECT571K1, + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + "brainpoolP256r1" => openssl::nid::Nid::BRAINPOOL_P256R1, + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + "brainpoolP384r1" => openssl::nid::Nid::BRAINPOOL_P384R1, + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + "brainpoolP512r1" => openssl::nid::Nid::BRAINPOOL_P512R1, + + _ => { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!("Curve {curve_name} is not supported"), + exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, + )), + )); + } + }; + + Ok(openssl::ec::EcGroup::from_curve_name(nid)?) +} + +fn py_curve_from_curve<'p>( + py: pyo3::Python<'p>, + curve: &openssl::ec::EcGroupRef, +) -> CryptographyResult<&'p pyo3::PyAny> { + let name = curve + .curve_name() + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err( + "ECDSA keys with explicit parameters are unsupported at this time", + ) + })? + .short_name()?; + + 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", + ), + )); + } + + types::CURVE_TYPES + .get(py)? + .extract::<&pyo3::types::PyDict>()? + .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, + ))) + }) +} + +fn check_key_infinity( + ec: &openssl::ec::EcKeyRef, +) -> CryptographyResult<()> { + if ec.public_key().is_infinity(ec.group()) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Cannot load an EC public key where the point is at infinity", + ), + )); + } + Ok(()) +} + +#[pyo3::prelude::pyfunction] +fn curve_supported(py: pyo3::Python<'_>, py_curve: &pyo3::PyAny) -> bool { + curve_from_py_curve(py, py_curve, false).is_ok() +} + +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())?; + Ok(ECPrivateKey { + pkey: pkey.to_owned(), + curve: curve.into(), + }) +} + +pub(crate) fn public_key_from_pkey( + py: pyo3::Python<'_>, + pkey: &openssl::pkey::PKeyRef, +) -> CryptographyResult { + let ec = pkey.ec_key()?; + let curve = py_curve_from_curve(py, ec.group())?; + check_key_infinity(&ec)?; + Ok(ECPublicKey { + pkey: pkey.to_owned(), + curve: curve.into(), + }) +} +#[pyo3::prelude::pyfunction] +fn generate_private_key( + py: pyo3::Python<'_>, + curve: &pyo3::PyAny, + backend: Option<&pyo3::PyAny>, +) -> CryptographyResult { + let _ = backend; + + let ossl_curve = curve_from_py_curve(py, curve, true)?; + let key = openssl::ec::EcKey::generate(&ossl_curve)?; + + Ok(ECPrivateKey { + pkey: openssl::pkey::PKey::from_ec_key(key)?, + curve: py_curve_from_curve(py, &ossl_curve)?.into(), + }) +} + +#[pyo3::prelude::pyfunction] +fn derive_private_key( + py: pyo3::Python<'_>, + py_private_value: &pyo3::types::PyLong, + py_curve: &pyo3::PyAny, +) -> CryptographyResult { + let curve = curve_from_py_curve(py, py_curve, false)?; + let private_value = utils::py_int_to_bn(py, py_private_value)?; + + let mut point = openssl::ec::EcPoint::new(&curve)?; + let bn_ctx = openssl::bn::BigNumContext::new()?; + point.mul_generator(&curve, &private_value, &bn_ctx)?; + let ec = openssl::ec::EcKey::from_private_components(&curve, &private_value, &point) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid EC key"))?; + check_key_infinity(&ec)?; + let pkey = openssl::pkey::PKey::from_ec_key(ec)?; + + Ok(ECPrivateKey { + pkey, + curve: py_curve.into(), + }) +} + +#[pyo3::prelude::pyfunction] +fn from_public_bytes( + py: pyo3::Python<'_>, + py_curve: &pyo3::PyAny, + data: &[u8], +) -> CryptographyResult { + let curve = curve_from_py_curve(py, py_curve, false)?; + + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let point = openssl::ec::EcPoint::from_bytes(&curve, data, &mut bn_ctx) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid EC key."))?; + let ec = openssl::ec::EcKey::from_public_key(&curve, &point)?; + let pkey = openssl::pkey::PKey::from_ec_key(ec)?; + + Ok(ECPublicKey { + pkey, + curve: py_curve.into(), + }) +} + +#[pyo3::prelude::pymethods] +impl ECPrivateKey { + #[getter] + fn key_size<'p>(&'p self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + self.curve.as_ref(py).getattr(pyo3::intern!(py, "key_size")) + } + + fn exchange<'p>( + &self, + py: pyo3::Python<'p>, + algorithm: &pyo3::PyAny, + peer_public_key: &ECPublicKey, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + if !algorithm.is_instance(types::ECDH.get(py)?)? { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Unsupported EC exchange algorithm", + exceptions::Reasons::UNSUPPORTED_EXCHANGE_ALGORITHM, + )), + )); + } + + let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; + // If `set_peer_ex` is available, we don't valid the key. This is + // because we already validated it sufficiently when we created the + // ECPublicKey object. + #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] + deriver + .set_peer_ex(&peer_public_key.pkey, false) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; + + #[cfg(not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER))] + deriver + .set_peer(&peer_public_key.pkey) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; + + Ok(pyo3::types::PyBytes::new_with(py, deriver.len()?, |b| { + let n = deriver.derive(b).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Error computing shared key.") + })?; + assert_eq!(n, b.len()); + Ok(()) + })?) + } + + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + signature_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + if !signature_algorithm.is_instance(types::ECDSA.get(py)?)? { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Unsupported elliptic curve signature algorithm", + exceptions::Reasons::UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + )), + )); + } + + let (data, _) = utils::calculate_digest_and_algorithm( + py, + data.as_bytes(), + signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?, + )?; + + let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + signer.sign_init()?; + // TODO: This does an extra allocation and copy. This can't easily use + // `PyBytes::new_with` because the exact length of the signature isn't + // easily known a priori (if `r` or `s` has a leading 0, the signature + // will be a byte or two shorter than the maximum possible length). + let mut sig = vec![]; + signer.sign_to_vec(data, &mut sig)?; + Ok(pyo3::types::PyBytes::new(py, &sig)) + } + + fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let orig_ec = self.pkey.ec_key().unwrap(); + let ec = openssl::ec::EcKey::from_public_key(orig_ec.group(), orig_ec.public_key())?; + let pkey = openssl::pkey::PKey::from_ec_key(ec)?; + + Ok(ECPublicKey { + pkey, + curve: self.curve.clone_ref(py), + }) + } + + fn private_numbers( + &self, + py: pyo3::Python<'_>, + ) -> CryptographyResult { + let ec = self.pkey.ec_key().unwrap(); + + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let mut x = openssl::bn::BigNum::new()?; + let mut y = openssl::bn::BigNum::new()?; + ec.public_key() + .affine_coordinates(ec.group(), &mut x, &mut y, &mut bn_ctx)?; + let py_x = utils::bn_to_py_int(py, &x)?; + let py_y = utils::bn_to_py_int(py, &y)?; + + let py_private_key = utils::bn_to_py_int(py, ec.private_key())?; + + let public_numbers = EllipticCurvePublicNumbers { + x: py_x.extract()?, + y: py_y.extract()?, + curve: self.curve.clone_ref(py), + }; + + Ok(EllipticCurvePrivateNumbers { + private_value: py_private_key.extract()?, + public_numbers: pyo3::Py::new(py, public_numbers)?, + }) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + true, + false, + ) + } +} + +#[pyo3::prelude::pymethods] +impl ECPublicKey { + #[getter] + fn key_size<'p>(&'p self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + self.curve.as_ref(py).getattr(pyo3::intern!(py, "key_size")) + } + + fn verify( + &self, + py: pyo3::Python<'_>, + signature: CffiBuf<'_>, + data: CffiBuf<'_>, + signature_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<()> { + if !signature_algorithm.is_instance(types::ECDSA.get(py)?)? { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Unsupported elliptic curve signature algorithm", + exceptions::Reasons::UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + )), + )); + } + + let (data, _) = utils::calculate_digest_and_algorithm( + py, + data.as_bytes(), + signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?, + )?; + + let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + verifier.verify_init()?; + let valid = verifier.verify(data, signature.as_bytes()).unwrap_or(false); + if !valid { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err(()), + )); + } + + Ok(()) + } + + fn public_numbers( + &self, + py: pyo3::Python<'_>, + ) -> CryptographyResult { + let ec = self.pkey.ec_key().unwrap(); + + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let mut x = openssl::bn::BigNum::new()?; + let mut y = openssl::bn::BigNum::new()?; + ec.public_key() + .affine_coordinates(ec.group(), &mut x, &mut y, &mut bn_ctx)?; + let py_x = utils::bn_to_py_int(py, &x)?; + let py_y = utils::bn_to_py_int(py, &y)?; + + Ok(EllipticCurvePublicNumbers { + x: py_x.extract()?, + y: py_y.extract()?, + curve: self.curve.clone_ref(py), + }) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) + } + + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) + } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } +} + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] +struct EllipticCurvePrivateNumbers { + #[pyo3(get)] + private_value: pyo3::Py, + #[pyo3(get)] + public_numbers: pyo3::Py, +} + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] +struct EllipticCurvePublicNumbers { + #[pyo3(get)] + x: pyo3::Py, + #[pyo3(get)] + y: pyo3::Py, + #[pyo3(get)] + curve: pyo3::Py, +} + +fn public_key_from_numbers( + py: pyo3::Python<'_>, + numbers: &EllipticCurvePublicNumbers, + curve: &openssl::ec::EcGroupRef, +) -> CryptographyResult> { + let zero = (0).to_object(py); + if numbers.x.as_ref(py).lt(&zero)? || numbers.y.as_ref(py).lt(&zero)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Invalid EC key. Both x and y must be non-negative.", + ), + )); + } + + let x = utils::py_int_to_bn(py, numbers.x.as_ref(py))?; + let y = utils::py_int_to_bn(py, numbers.y.as_ref(py))?; + + let mut point = openssl::ec::EcPoint::new(curve)?; + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + point + .set_affine_coordinates_gfp(curve, &x, &y, &mut bn_ctx) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Invalid EC key. Point is not on the curve specified.", + ) + })?; + + Ok(openssl::ec::EcKey::from_public_key(curve, &point)?) +} + +#[pyo3::prelude::pymethods] +impl EllipticCurvePrivateNumbers { + #[new] + fn new( + private_value: pyo3::Py, + public_numbers: pyo3::Py, + ) -> EllipticCurvePrivateNumbers { + EllipticCurvePrivateNumbers { + private_value, + public_numbers, + } + } + + fn private_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let curve = curve_from_py_curve(py, self.public_numbers.get().curve.as_ref(py), false)?; + let public_key = public_key_from_numbers(py, self.public_numbers.get(), &curve)?; + let private_value = utils::py_int_to_bn(py, self.private_value.as_ref(py))?; + + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let mut expected_pub = openssl::ec::EcPoint::new(&curve)?; + expected_pub.mul_generator(&curve, &private_value, &bn_ctx)?; + if !expected_pub.eq(&curve, public_key.public_key(), &mut bn_ctx)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid EC key."), + )); + } + + let private_key = openssl::ec::EcKey::from_private_components( + &curve, + &private_value, + public_key.public_key(), + ) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid EC key."))?; + + let pkey = openssl::pkey::PKey::from_ec_key(private_key)?; + + Ok(ECPrivateKey { + pkey, + curve: self.public_numbers.get().curve.clone_ref(py), + }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self + .private_value + .as_ref(py) + .eq(other.private_value.as_ref(py))? + && self + .public_numbers + .as_ref(py) + .eq(other.public_numbers.as_ref(py))?) + } + + fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let mut hasher = DefaultHasher::new(); + self.private_value.as_ref(py).hash()?.hash(&mut hasher); + self.public_numbers.as_ref(py).hash()?.hash(&mut hasher); + Ok(hasher.finish()) + } +} + +#[pyo3::prelude::pymethods] +impl EllipticCurvePublicNumbers { + #[new] + fn new( + py: pyo3::Python<'_>, + x: pyo3::Py, + y: pyo3::Py, + curve: pyo3::Py, + ) -> CryptographyResult { + if !curve + .as_ref(py) + .is_instance(types::ELLIPTIC_CURVE.get(py)?)? + { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "curve must provide the EllipticCurve interface.", + ), + )); + } + + Ok(EllipticCurvePublicNumbers { x, y, curve }) + } + + fn public_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let curve = curve_from_py_curve(py, self.curve.as_ref(py), false)?; + let public_key = public_key_from_numbers(py, self, &curve)?; + + let pkey = openssl::pkey::PKey::from_ec_key(public_key)?; + + Ok(ECPublicKey { + pkey, + curve: self.curve.clone_ref(py), + }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self.x.as_ref(py).eq(other.x.as_ref(py))? + && self.y.as_ref(py).eq(other.y.as_ref(py))? + && self + .curve + .as_ref(py) + .getattr(pyo3::intern!(py, "name"))? + .eq(other.curve.as_ref(py).getattr(pyo3::intern!(py, "name"))?)? + && self + .curve + .as_ref(py) + .getattr(pyo3::intern!(py, "key_size"))? + .eq(other + .curve + .as_ref(py) + .getattr(pyo3::intern!(py, "key_size"))?)?) + } + + fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let mut hasher = DefaultHasher::new(); + self.x.as_ref(py).hash()?.hash(&mut hasher); + self.y.as_ref(py).hash()?.hash(&mut hasher); + self.curve + .as_ref(py) + .getattr(pyo3::intern!(py, "name"))? + .hash()? + .hash(&mut hasher); + self.curve + .as_ref(py) + .getattr(pyo3::intern!(py, "key_size"))? + .hash()? + .hash(&mut hasher); + Ok(hasher.finish()) + } + + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let x = self.x.as_ref(py); + let y = self.y.as_ref(py); + let curve_name = self.curve.as_ref(py).getattr(pyo3::intern!(py, "name"))?; + Ok(format!( + "" + )) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "ec")?; + m.add_function(pyo3::wrap_pyfunction!(curve_supported, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(generate_private_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(derive_private_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/ed25519.rs b/src/rust/src/backend/ed25519.rs index 7bee88104482..81ca3230088e 100644 --- a/src/rust/src/backend/ed25519.rs +++ b/src/rust/src/backend/ed25519.rs @@ -6,15 +6,14 @@ use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -use foreign_types_shared::ForeignTypeRef; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] -struct Ed25519PrivateKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +pub(crate) struct Ed25519PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] -struct Ed25519PublicKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +pub(crate) struct Ed25519PublicKey { pkey: openssl::pkey::PKey, } @@ -25,17 +24,17 @@ fn generate_key() -> CryptographyResult { }) } -#[pyo3::prelude::pyfunction] -fn private_key_from_ptr(ptr: usize) -> Ed25519PrivateKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn private_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> Ed25519PrivateKey { Ed25519PrivateKey { pkey: pkey.to_owned(), } } -#[pyo3::prelude::pyfunction] -fn public_key_from_ptr(ptr: usize) -> Ed25519PublicKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn public_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> Ed25519PublicKey { Ed25519PublicKey { pkey: pkey.to_owned(), } @@ -67,12 +66,12 @@ impl Ed25519PrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &[u8], + data: CffiBuf<'_>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { let n = signer - .sign_oneshot(b, data) + .sign_oneshot(b, data.as_bytes()) .map_err(CryptographyError::from)?; assert_eq!(n, b.len()); Ok(()) @@ -119,9 +118,10 @@ impl Ed25519PrivateKey { #[pyo3::prelude::pymethods] impl Ed25519PublicKey { - fn verify(&self, signature: &[u8], data: &[u8]) -> CryptographyResult<()> { + fn verify(&self, signature: CffiBuf<'_>, data: CffiBuf<'_>) -> CryptographyResult<()> { let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? - .verify_oneshot(signature, data)?; + .verify_oneshot(signature.as_bytes(), data.as_bytes()) + .unwrap_or(false); if !valid { return Err(CryptographyError::from( @@ -149,24 +149,18 @@ impl Ed25519PublicKey { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, Ed25519PublicKey>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), - pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), - _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), - } + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) + } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf } } pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let m = pyo3::prelude::PyModule::new(py, "ed25519")?; m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; diff --git a/src/rust/src/backend/ed448.rs b/src/rust/src/backend/ed448.rs index c0c621a321c3..15b679d5f993 100644 --- a/src/rust/src/backend/ed448.rs +++ b/src/rust/src/backend/ed448.rs @@ -6,15 +6,14 @@ use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -use foreign_types_shared::ForeignTypeRef; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed448")] -struct Ed448PrivateKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +pub(crate) struct Ed448PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed448")] -struct Ed448PublicKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +pub(crate) struct Ed448PublicKey { pkey: openssl::pkey::PKey, } @@ -25,17 +24,17 @@ fn generate_key() -> CryptographyResult { }) } -#[pyo3::prelude::pyfunction] -fn private_key_from_ptr(ptr: usize) -> Ed448PrivateKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn private_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> Ed448PrivateKey { Ed448PrivateKey { pkey: pkey.to_owned(), } } -#[pyo3::prelude::pyfunction] -fn public_key_from_ptr(ptr: usize) -> Ed448PublicKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn public_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> Ed448PublicKey { Ed448PublicKey { pkey: pkey.to_owned(), } @@ -65,12 +64,12 @@ impl Ed448PrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &[u8], + data: CffiBuf<'_>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { let n = signer - .sign_oneshot(b, data) + .sign_oneshot(b, data.as_bytes()) .map_err(CryptographyError::from)?; assert_eq!(n, b.len()); Ok(()) @@ -117,9 +116,9 @@ impl Ed448PrivateKey { #[pyo3::prelude::pymethods] impl Ed448PublicKey { - fn verify(&self, signature: &[u8], data: &[u8]) -> CryptographyResult<()> { + fn verify(&self, signature: CffiBuf<'_>, data: CffiBuf<'_>) -> CryptographyResult<()> { let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? - .verify_oneshot(signature, data)?; + .verify_oneshot(signature.as_bytes(), data.as_bytes())?; if !valid { return Err(CryptographyError::from( @@ -147,24 +146,18 @@ impl Ed448PublicKey { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, Ed448PublicKey>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), - pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), - _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), - } + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) + } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf } } pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let m = pyo3::prelude::PyModule::new(py, "ed448")?; m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; diff --git a/src/rust/src/backend/hashes.rs b/src/rust/src/backend/hashes.rs index d9157d6e8a18..ac5de597c354 100644 --- a/src/rust/src/backend/hashes.rs +++ b/src/rust/src/backend/hashes.rs @@ -2,13 +2,14 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use std::borrow::Cow; + use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; -use crate::exceptions; -use std::borrow::Cow; +use crate::{exceptions, types}; #[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")] -struct Hash { +pub(crate) struct Hash { #[pyo3(get)] algorithm: pyo3::Py, ctx: Option, @@ -40,10 +41,7 @@ pub(crate) fn message_digest_from_algorithm( py: pyo3::Python<'_>, algorithm: &pyo3::PyAny, ) -> CryptographyResult { - let hash_algorithm_class = py - .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? - .getattr(pyo3::intern!(py, "HashAlgorithm"))?; - if !algorithm.is_instance(hash_algorithm_class)? { + if !algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err("Expected instance of hashes.HashAlgorithm."), )); @@ -65,18 +63,25 @@ pub(crate) fn message_digest_from_algorithm( Some(md) => Ok(md), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( - format!("{} is not a supported hash on this backend", name), + format!("{name} is not a supported hash on this backend"), exceptions::Reasons::UNSUPPORTED_HASH, )), )), } } +impl Hash { + pub(crate) fn update_bytes(&mut self, data: &[u8]) -> CryptographyResult<()> { + self.get_mut_ctx()?.update(data)?; + Ok(()) + } +} + #[pyo3::pymethods] impl Hash { #[new] #[pyo3(signature = (algorithm, backend=None))] - fn new( + pub(crate) fn new( py: pyo3::Python<'_>, algorithm: &pyo3::PyAny, backend: Option<&pyo3::PyAny>, @@ -93,22 +98,18 @@ impl Hash { } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { - self.get_mut_ctx()?.update(data.as_bytes())?; - Ok(()) + self.update_bytes(data.as_bytes()) } - fn finalize<'p>( + pub(crate) fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] { - let xof_class = py - .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? - .getattr(pyo3::intern!(py, "ExtendableOutputFunction"))?; let algorithm = self.algorithm.clone_ref(py); let algorithm = algorithm.as_ref(py); - if algorithm.is_instance(xof_class)? { + if algorithm.is_instance(types::EXTENDABLE_OUTPUT_FUNCTION.get(py)?)? { let ctx = self.get_mut_ctx()?; let digest_size = algorithm .getattr(pyo3::intern!(py, "digest_size"))? diff --git a/src/rust/src/backend/hmac.rs b/src/rust/src/backend/hmac.rs index 13509b859024..d035a6156c3d 100644 --- a/src/rust/src/backend/hmac.rs +++ b/src/rust/src/backend/hmac.rs @@ -46,7 +46,12 @@ impl Hmac { let _ = backend; let md = message_digest_from_algorithm(py, algorithm)?; - let ctx = cryptography_openssl::hmac::Hmac::new(key.as_bytes(), md)?; + let ctx = cryptography_openssl::hmac::Hmac::new(key.as_bytes(), md).map_err(|_| { + exceptions::UnsupportedAlgorithm::new_err(( + "Digest is not supported for HMAC", + exceptions::Reasons::UNSUPPORTED_HASH, + )) + })?; Ok(Hmac { ctx: Some(ctx), diff --git a/src/rust/src/backend/kdf.rs b/src/rust/src/backend/kdf.rs index de527f4671da..35cf0eb266a3 100644 --- a/src/rust/src/backend/kdf.rs +++ b/src/rust/src/backend/kdf.rs @@ -42,8 +42,7 @@ fn derive_scrypt<'p>( // https://blog.filippo.io/the-scrypt-parameters/ let min_memory = 128 * n * r / (1024 * 1024); pyo3::exceptions::PyMemoryError::new_err(format!( - "Not enough memory to derive key. These parameters require {}MB of memory.", - min_memory + "Not enough memory to derive key. These parameters require {min_memory}MB of memory." )) }) })?) diff --git a/src/rust/src/backend/keys.rs b/src/rust/src/backend/keys.rs new file mode 100644 index 000000000000..ecdff5db6dcb --- /dev/null +++ b/src/rust/src/backend/keys.rs @@ -0,0 +1,262 @@ +// 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 foreign_types_shared::ForeignTypeRef; +use pyo3::IntoPy; + +use crate::backend::utils; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; + +#[pyo3::prelude::pyfunction] +#[pyo3(signature = (data, password, backend=None, *, unsafe_skip_rsa_key_validation=false))] +fn load_der_private_key( + py: pyo3::Python<'_>, + data: CffiBuf<'_>, + password: Option>, + backend: Option<&pyo3::PyAny>, + unsafe_skip_rsa_key_validation: bool, +) -> CryptographyResult { + let _ = backend; + if let Ok(pkey) = openssl::pkey::PKey::private_key_from_der(data.as_bytes()) { + if password.is_some() { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Password was given but private key is not encrypted.", + ), + )); + } + 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)?; + private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation) +} + +#[pyo3::prelude::pyfunction] +#[pyo3(signature = (data, password, backend=None, *, unsafe_skip_rsa_key_validation=false))] +fn load_pem_private_key( + py: pyo3::Python<'_>, + data: CffiBuf<'_>, + password: Option>, + backend: Option<&pyo3::PyAny>, + 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( + data.as_bytes(), + utils::password_callback(&mut status, password), + ); + let pkey = utils::handle_key_load_result(py, pkey, status, password)?; + private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr( + py: pyo3::Python<'_>, + ptr: usize, + unsafe_skip_rsa_key_validation: bool, +) -> CryptographyResult { + // SAFETY: Caller is responsible for passing a valid pointer. + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + private_key_from_pkey(py, pkey, unsafe_skip_rsa_key_validation) +} + +fn private_key_from_pkey( + py: pyo3::Python<'_>, + pkey: &openssl::pkey::PKeyRef, + unsafe_skip_rsa_key_validation: bool, +) -> CryptographyResult { + match pkey.id() { + openssl::pkey::Id::RSA => Ok(crate::backend::rsa::private_key_from_pkey( + pkey, + unsafe_skip_rsa_key_validation, + )? + .into_py(py)), + #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_380_OR_GREATER))] + 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_py(py), + ) + } + openssl::pkey::Id::EC => { + Ok(crate::backend::ec::private_key_from_pkey(py, pkey)?.into_py(py)) + } + openssl::pkey::Id::X25519 => { + Ok(crate::backend::x25519::private_key_from_pkey(pkey).into_py(py)) + } + + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + openssl::pkey::Id::X448 => { + Ok(crate::backend::x448::private_key_from_pkey(pkey).into_py(py)) + } + + openssl::pkey::Id::ED25519 => { + Ok(crate::backend::ed25519::private_key_from_pkey(pkey).into_py(py)) + } + + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + openssl::pkey::Id::ED448 => { + Ok(crate::backend::ed448::private_key_from_pkey(pkey).into_py(py)) + } + openssl::pkey::Id::DSA => Ok(crate::backend::dsa::private_key_from_pkey(pkey).into_py(py)), + openssl::pkey::Id::DH => Ok(crate::backend::dh::private_key_from_pkey(pkey).into_py(py)), + + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + openssl::pkey::Id::DHX => Ok(crate::backend::dh::private_key_from_pkey(pkey).into_py(py)), + _ => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err("Unsupported key type."), + )), + } +} + +#[pyo3::prelude::pyfunction] +fn load_der_public_key( + py: pyo3::Python<'_>, + data: CffiBuf<'_>, + backend: Option<&pyo3::PyAny>, +) -> CryptographyResult { + let _ = backend; + load_der_public_key_bytes(py, data.as_bytes()) +} + +pub(crate) fn load_der_public_key_bytes( + py: pyo3::Python<'_>, + data: &[u8], +) -> CryptographyResult { + match cryptography_key_parsing::spki::parse_public_key(data) { + Ok(pkey) => public_key_from_pkey(py, &pkey, pkey.id()), + // It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still need + // to check to see if it is a pure PKCS1 RSA public key (not embedded + // in a subjectPublicKeyInfo) + Err(e) => { + // Use the original error. + let pkey = + cryptography_key_parsing::rsa::parse_pkcs1_public_key(data).map_err(|_| e)?; + public_key_from_pkey(py, &pkey, pkey.id()) + } + } +} + +#[pyo3::prelude::pyfunction] +fn load_pem_public_key( + py: pyo3::Python<'_>, + data: CffiBuf<'_>, + backend: Option<&pyo3::PyAny>, +) -> CryptographyResult { + let _ = backend; + let p = pem::parse(data.as_bytes())?; + let pkey = match p.tag() { + "RSA PUBLIC KEY" => { + // We try to parse it as a PKCS1 first since that's the PEM delimiter, and if + // that fails we try to parse it as an SPKI. This is to match the permissiveness + // of OpenSSL, which doesn't care about the delimiter. + match cryptography_key_parsing::rsa::parse_pkcs1_public_key(p.contents()) { + Ok(pkey) => pkey, + Err(err) => { + let pkey = cryptography_key_parsing::spki::parse_public_key(p.contents()) + .map_err(|_| err)?; + if pkey.id() != openssl::pkey::Id::RSA { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Incorrect PEM delimiter for key type.", + ), + )); + } + pkey + } + } + } + "PUBLIC KEY" => cryptography_key_parsing::spki::parse_public_key(p.contents())?, + _ => return Err(CryptographyError::from(pem::PemError::MalformedFraming)), + }; + public_key_from_pkey(py, &pkey, pkey.id()) +} + +fn public_key_from_pkey( + py: pyo3::Python<'_>, + pkey: &openssl::pkey::PKeyRef, + id: openssl::pkey::Id, +) -> CryptographyResult { + // `id` is a separate argument so we can test this while passing something + // unsupported. + match id { + openssl::pkey::Id::RSA => Ok(crate::backend::rsa::public_key_from_pkey(pkey).into_py(py)), + openssl::pkey::Id::EC => { + Ok(crate::backend::ec::public_key_from_pkey(py, pkey)?.into_py(py)) + } + openssl::pkey::Id::X25519 => { + Ok(crate::backend::x25519::public_key_from_pkey(pkey).into_py(py)) + } + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + openssl::pkey::Id::X448 => Ok(crate::backend::x448::public_key_from_pkey(pkey).into_py(py)), + + openssl::pkey::Id::ED25519 => { + Ok(crate::backend::ed25519::public_key_from_pkey(pkey).into_py(py)) + } + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + openssl::pkey::Id::ED448 => { + Ok(crate::backend::ed448::public_key_from_pkey(pkey).into_py(py)) + } + + openssl::pkey::Id::DSA => Ok(crate::backend::dsa::public_key_from_pkey(pkey).into_py(py)), + openssl::pkey::Id::DH => Ok(crate::backend::dh::public_key_from_pkey(pkey).into_py(py)), + + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + openssl::pkey::Id::DHX => Ok(crate::backend::dh::public_key_from_pkey(pkey).into_py(py)), + + _ => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err("Unsupported key type."), + )), + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "keys")?; + + m.add_function(pyo3::wrap_pyfunction!(load_pem_private_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(load_der_private_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(load_der_public_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(load_pem_public_key, m)?)?; + + m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; + + Ok(m) +} + +#[cfg(test)] +mod tests { + use super::public_key_from_pkey; + + #[test] + fn test_public_key_from_pkey_unknown_key() { + pyo3::prepare_freethreaded_python(); + + pyo3::Python::with_gil(|py| { + let pkey = + openssl::pkey::PKey::public_key_from_raw_bytes(&[0; 32], openssl::pkey::Id::X25519) + .unwrap(); + // Pass a nonsense id for this key to test the unsupported + // algorithm path. + assert!(public_key_from_pkey(py, &pkey, openssl::pkey::Id::CMAC).is_err()); + }); + } +} diff --git a/src/rust/src/backend/mod.rs b/src/rust/src/backend/mod.rs index 765b0ab199f4..7e085d623b40 100644 --- a/src/rust/src/backend/mod.rs +++ b/src/rust/src/backend/mod.rs @@ -2,32 +2,38 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +pub(crate) mod aead; +pub(crate) mod cipher_registry; +pub(crate) mod cmac; pub(crate) mod dh; pub(crate) mod dsa; -#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] +pub(crate) mod ec; pub(crate) mod ed25519; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] pub(crate) mod ed448; pub(crate) mod hashes; pub(crate) mod hmac; pub(crate) mod kdf; +pub(crate) mod keys; pub(crate) mod poly1305; +pub(crate) mod rsa; pub(crate) mod utils; -#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] pub(crate) mod x25519; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] pub(crate) mod x448; pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_submodule(aead::create_module(module.py())?)?; + module.add_submodule(cmac::create_module(module.py())?)?; module.add_submodule(dh::create_module(module.py())?)?; module.add_submodule(dsa::create_module(module.py())?)?; + module.add_submodule(ec::create_module(module.py())?)?; + module.add_submodule(keys::create_module(module.py())?)?; - #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] module.add_submodule(ed25519::create_module(module.py())?)?; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] module.add_submodule(ed448::create_module(module.py())?)?; - #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] module.add_submodule(x25519::create_module(module.py())?)?; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] module.add_submodule(x448::create_module(module.py())?)?; @@ -37,6 +43,7 @@ pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult< module.add_submodule(hashes::create_module(module.py())?)?; module.add_submodule(hmac::create_module(module.py())?)?; module.add_submodule(kdf::create_module(module.py())?)?; + module.add_submodule(rsa::create_module(module.py())?)?; Ok(()) } diff --git a/src/rust/src/backend/poly1305.rs b/src/rust/src/backend/poly1305.rs index 17d279a4023f..66fc6239fa02 100644 --- a/src/rust/src/backend/poly1305.rs +++ b/src/rust/src/backend/poly1305.rs @@ -7,26 +7,48 @@ use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")] -struct Poly1305 { - signer: Option>, +#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] +struct Poly1305Boring { + context: cryptography_openssl::poly1305::Poly1305State, } -impl Poly1305 { - fn get_mut_signer(&mut self) -> CryptographyResult<&mut openssl::sign::Signer<'static>> { - if let Some(signer) = self.signer.as_mut() { - return Ok(signer); - }; - Err(already_finalized_error()) +#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] +impl Poly1305Boring { + fn new(key: CffiBuf<'_>) -> CryptographyResult { + if key.as_bytes().len() != 32 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long"), + )); + } + let ctx = cryptography_openssl::poly1305::Poly1305State::new(key.as_bytes()); + Ok(Poly1305Boring { context: ctx }) + } + + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + self.context.update(data.as_bytes()); + Ok(()) + } + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = pyo3::types::PyBytes::new_with(py, 16usize, |b| { + self.context.finalize(b.as_mut()); + Ok(()) + })?; + Ok(result) } } -#[pyo3::pymethods] -impl Poly1305 { - #[new] - fn new(key: CffiBuf<'_>) -> CryptographyResult { - #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] - { +#[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] +struct Poly1305Open { + signer: openssl::sign::Signer<'static>, +} + +#[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] +impl Poly1305Open { + fn new(key: CffiBuf<'_>) -> CryptographyResult { + if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "poly1305 is not supported by this version of OpenSSL.", @@ -35,33 +57,56 @@ impl Poly1305 { )); } - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] - { - if cryptography_openssl::fips::is_enabled() { - return Err(CryptographyError::from( - exceptions::UnsupportedAlgorithm::new_err(( - "poly1305 is not supported by this version of OpenSSL.", - exceptions::Reasons::UNSUPPORTED_MAC, - )), - )); - } - - let pkey = openssl::pkey::PKey::private_key_from_raw_bytes( - key.as_bytes(), - openssl::pkey::Id::POLY1305, - ) - .map_err(|_| { + let pkey = openssl::pkey::PKey::private_key_from_raw_bytes( + key.as_bytes(), + openssl::pkey::Id::POLY1305, + ) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long"))?; + + Ok(Poly1305Open { + signer: openssl::sign::Signer::new_without_digest(&pkey).map_err(|_| { pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long") - })?; - - Ok(Poly1305 { - signer: Some( - openssl::sign::Signer::new_without_digest(&pkey).map_err(|_| { - pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long") - })?, - ), - }) - } + })?, + }) + } + fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + let buf = data.as_bytes(); + self.signer.update(buf)?; + Ok(()) + } + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = pyo3::types::PyBytes::new_with(py, self.signer.len()?, |b| { + let n = self.signer.sign(b).unwrap(); + assert_eq!(n, b.len()); + Ok(()) + })?; + Ok(result) + } +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")] +struct Poly1305 { + #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] + inner: Option, + #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] + inner: Option, +} + +#[pyo3::pymethods] +impl Poly1305 { + #[new] + fn new(key: CffiBuf<'_>) -> CryptographyResult { + #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] + return Ok(Poly1305 { + inner: Some(Poly1305Boring::new(key)?), + }); + #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] + return Ok(Poly1305 { + inner: Some(Poly1305Open::new(key)?), + }); } #[staticmethod] @@ -88,22 +133,22 @@ impl Poly1305 { } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { - self.get_mut_signer()?.update(data.as_bytes())?; - Ok(()) + self.inner + .as_mut() + .map_or(Err(already_finalized_error()), |b| b.update(data)) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let signer = self.get_mut_signer()?; - let result = pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { - let n = signer.sign(b).unwrap(); - assert_eq!(n, b.len()); - Ok(()) - })?; - self.signer = None; - Ok(result) + let res = self + .inner + .as_mut() + .map_or(Err(already_finalized_error()), |b| b.finalize(py)); + self.inner = None; + + res } fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { diff --git a/src/rust/src/backend/rsa.rs b/src/rust/src/backend/rsa.rs new file mode 100644 index 000000000000..662f30aff084 --- /dev/null +++ b/src/rust/src/backend/rsa.rs @@ -0,0 +1,828 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +use crate::backend::{hashes, utils}; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, types}; + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.bindings._rust.openssl.rsa", + name = "RSAPrivateKey" +)] +pub(crate) struct RsaPrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.bindings._rust.openssl.rsa", + name = "RSAPublicKey" +)] +pub(crate) struct RsaPublicKey { + pkey: openssl::pkey::PKey, +} + +fn check_rsa_private_key( + rsa: &openssl::rsa::Rsa, +) -> CryptographyResult<()> { + if !rsa.check_key().unwrap_or(false) || rsa.p().unwrap().is_even() || rsa.q().unwrap().is_even() + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid private key"), + )); + } + Ok(()) +} + +pub(crate) fn private_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, + unsafe_skip_rsa_key_validation: bool, +) -> CryptographyResult { + if !unsafe_skip_rsa_key_validation { + check_rsa_private_key(&pkey.rsa().unwrap())?; + } + Ok(RsaPrivateKey { + pkey: pkey.to_owned(), + }) +} + +pub(crate) fn public_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> RsaPublicKey { + RsaPublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn generate_private_key(public_exponent: u32, key_size: u32) -> CryptographyResult { + let e = openssl::bn::BigNum::from_u32(public_exponent)?; + let rsa = openssl::rsa::Rsa::generate_with_e(key_size, &e)?; + let pkey = openssl::pkey::PKey::from_rsa(rsa)?; + Ok(RsaPrivateKey { pkey }) +} + +fn oaep_hash_supported(md: &openssl::hash::MessageDigest) -> bool { + (!cryptography_openssl::fips::is_enabled() && md == &openssl::hash::MessageDigest::sha1()) + || md == &openssl::hash::MessageDigest::sha224() + || md == &openssl::hash::MessageDigest::sha256() + || md == &openssl::hash::MessageDigest::sha384() + || md == &openssl::hash::MessageDigest::sha512() +} + +fn setup_encryption_ctx( + py: pyo3::Python<'_>, + ctx: &mut openssl::pkey_ctx::PkeyCtx, + padding: &pyo3::PyAny, +) -> CryptographyResult<()> { + if !padding.is_instance(types::ASYMMETRIC_PADDING.get(py)?)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Padding must be an instance of AsymmetricPadding.", + ), + )); + } + + let padding_enum = if padding.is_instance(types::PKCS1V15.get(py)?)? { + openssl::rsa::Padding::PKCS1 + } else if padding.is_instance(types::OAEP.get(py)?)? { + if !padding + .getattr(pyo3::intern!(py, "_mgf"))? + .is_instance(types::MGF1.get(py)?)? + { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Only MGF1 is supported.", + exceptions::Reasons::UNSUPPORTED_MGF, + )), + )); + } + + openssl::rsa::Padding::PKCS1_OAEP + } else { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!( + "{} is not supported by this backend.", + padding.getattr(pyo3::intern!(py, "name"))? + ), + exceptions::Reasons::UNSUPPORTED_PADDING, + )), + )); + }; + + ctx.set_rsa_padding(padding_enum)?; + + if padding_enum == openssl::rsa::Padding::PKCS1_OAEP { + let mgf1_md = hashes::message_digest_from_algorithm( + py, + padding + .getattr(pyo3::intern!(py, "_mgf"))? + .getattr(pyo3::intern!(py, "_algorithm"))?, + )?; + let oaep_md = hashes::message_digest_from_algorithm( + py, + padding.getattr(pyo3::intern!(py, "_algorithm"))?, + )?; + + if !oaep_hash_supported(&mgf1_md) || !oaep_hash_supported(&oaep_md) { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "This combination of padding and hash algorithm is not supported", + exceptions::Reasons::UNSUPPORTED_PADDING, + )), + )); + } + + ctx.set_rsa_mgf1_md(openssl::md::Md::from_nid(mgf1_md.type_()).unwrap())?; + ctx.set_rsa_oaep_md(openssl::md::Md::from_nid(oaep_md.type_()).unwrap())?; + + if let Some(label) = padding + .getattr(pyo3::intern!(py, "_label"))? + .extract::>()? + { + if !label.is_empty() { + ctx.set_rsa_oaep_label(label)?; + } + } + } + + Ok(()) +} + +fn setup_signature_ctx( + py: pyo3::Python<'_>, + ctx: &mut openssl::pkey_ctx::PkeyCtx, + padding: &pyo3::PyAny, + algorithm: &pyo3::PyAny, + key_size: usize, + is_signing: bool, +) -> CryptographyResult<()> { + if !padding.is_instance(types::ASYMMETRIC_PADDING.get(py)?)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Padding must be an instance of AsymmetricPadding.", + ), + )); + } + + let padding_enum = if padding.is_instance(types::PKCS1V15.get(py)?)? { + openssl::rsa::Padding::PKCS1 + } else if padding.is_instance(types::PSS.get(py)?)? { + if !padding + .getattr(pyo3::intern!(py, "_mgf"))? + .is_instance(types::MGF1.get(py)?)? + { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Only MGF1 is supported.", + exceptions::Reasons::UNSUPPORTED_MGF, + )), + )); + } + + // PSS padding requires a hash algorithm + if !algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Expected instance of hashes.HashAlgorithm.", + ), + )); + } + + if algorithm + .getattr(pyo3::intern!(py, "digest_size"))? + .extract::()? + + 2 + > key_size + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Digest too large for key size. Use a larger key or different digest.", + ), + )); + } + + openssl::rsa::Padding::PKCS1_PSS + } else { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!( + "{} is not supported by this backend.", + padding.getattr(pyo3::intern!(py, "name"))? + ), + exceptions::Reasons::UNSUPPORTED_PADDING, + )), + )); + }; + + if !algorithm.is_none() { + let md = hashes::message_digest_from_algorithm(py, algorithm)?; + ctx.set_signature_md(openssl::md::Md::from_nid(md.type_()).unwrap()) + .or_else(|_| { + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!( + "{} is not supported by this backend for RSA signing.", + algorithm.getattr(pyo3::intern!(py, "name"))? + ), + exceptions::Reasons::UNSUPPORTED_HASH, + )), + )) + })?; + } + ctx.set_rsa_padding(padding_enum).or_else(|_| { + Err(exceptions::UnsupportedAlgorithm::new_err(( + format!( + "{} is not supported for the RSA signature operation", + padding.getattr(pyo3::intern!(py, "name"))? + ), + exceptions::Reasons::UNSUPPORTED_PADDING, + ))) + })?; + + if padding_enum == openssl::rsa::Padding::PKCS1_PSS { + let salt = padding.getattr(pyo3::intern!(py, "_salt_length"))?; + if salt.is_instance(types::PADDING_MAX_LENGTH.get(py)?)? { + ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::MAXIMUM_LENGTH)?; + } else if salt.is_instance(types::PADDING_DIGEST_LENGTH.get(py)?)? { + ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; + } else if salt.is_instance(types::PADDING_AUTO.get(py)?)? { + if is_signing { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "PSS salt length can only be set to Auto when verifying", + ), + )); + } + } else { + ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::custom(salt.extract::()?))?; + }; + + let mgf1_md = hashes::message_digest_from_algorithm( + py, + padding + .getattr(pyo3::intern!(py, "_mgf"))? + .getattr(pyo3::intern!(py, "_algorithm"))?, + )?; + ctx.set_rsa_mgf1_md(openssl::md::Md::from_nid(mgf1_md.type_()).unwrap())?; + } + + Ok(()) +} + +#[pyo3::prelude::pymethods] +impl RsaPrivateKey { + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + padding: &pyo3::PyAny, + algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::PyAny> { + let (data, algorithm) = + utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; + + let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + ctx.sign_init().map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Unable to sign/verify with this key") + })?; + setup_signature_ctx(py, &mut ctx, padding, algorithm, self.pkey.size(), true)?; + + let length = ctx.sign(data, None)?; + Ok(pyo3::types::PyBytes::new_with(py, length, |b| { + let length = ctx.sign(data, Some(b)).map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Digest or salt length too long for key size. Use a larger key or shorter salt length if you are specifying a PSS salt", + ) + })?; + assert_eq!(length, b.len()); + Ok(()) + })?) + } + + fn decrypt<'p>( + &self, + py: pyo3::Python<'p>, + ciphertext: &[u8], + padding: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let key_size_bytes = + usize::try_from((self.pkey.rsa().unwrap().n().num_bits() + 7) / 8).unwrap(); + if key_size_bytes != ciphertext.len() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Ciphertext length must be equal to key size.", + ), + )); + } + + let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + ctx.decrypt_init()?; + + setup_encryption_ctx(py, &mut ctx, padding)?; + + // Everything from this line onwards is written with the goal of being + // as constant-time as is practical given the constraints of + // rust-openssl and our API. See Bleichenbacher's '98 attack on RSA, + // and its many many variants. As such, you should not attempt to + // change this (particularly to "clean it up") without understanding + // why it was written this way (see Chesterton's Fence), and without + // measuring to verify you have not introduced observable time + // differences. + // + // Once OpenSSL 3.2.0 is out, this can be simplified, as OpenSSL will + // have its own mitigations for Bleichenbacher's attack. + let length = ctx.decrypt(ciphertext, None).unwrap(); + let mut plaintext = vec![0; length]; + let result = ctx.decrypt(ciphertext, Some(&mut plaintext)); + + let py_result = + pyo3::types::PyBytes::new(py, &plaintext[..*result.as_ref().unwrap_or(&length)]); + if result.is_err() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Decryption failed"), + )); + } + Ok(py_result) + } + + #[getter] + fn key_size(&self) -> i32 { + self.pkey.rsa().unwrap().n().num_bits() + } + + fn public_key(&self) -> CryptographyResult { + let priv_rsa = self.pkey.rsa().unwrap(); + let rsa = openssl::rsa::Rsa::from_public_components( + priv_rsa.n().to_owned()?, + priv_rsa.e().to_owned()?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_rsa(rsa)?; + Ok(RsaPublicKey { pkey }) + } + + fn private_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let rsa = self.pkey.rsa().unwrap(); + + let py_p = utils::bn_to_py_int(py, rsa.p().unwrap())?; + let py_q = utils::bn_to_py_int(py, rsa.q().unwrap())?; + let py_d = utils::bn_to_py_int(py, rsa.d())?; + let py_dmp1 = utils::bn_to_py_int(py, rsa.dmp1().unwrap())?; + let py_dmq1 = utils::bn_to_py_int(py, rsa.dmq1().unwrap())?; + let py_iqmp = utils::bn_to_py_int(py, rsa.iqmp().unwrap())?; + let py_e = utils::bn_to_py_int(py, rsa.e())?; + let py_n = utils::bn_to_py_int(py, rsa.n())?; + + let public_numbers = RsaPublicNumbers { + e: py_e.extract()?, + n: py_n.extract()?, + }; + Ok(RsaPrivateNumbers { + p: py_p.extract()?, + q: py_q.extract()?, + d: py_d.extract()?, + dmp1: py_dmp1.extract()?, + dmq1: py_dmq1.extract()?, + iqmp: py_iqmp.extract()?, + public_numbers: pyo3::Py::new(py, public_numbers)?, + }) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + true, + false, + ) + } +} + +#[pyo3::prelude::pymethods] +impl RsaPublicKey { + fn verify( + &self, + py: pyo3::Python<'_>, + signature: CffiBuf<'_>, + data: CffiBuf<'_>, + padding: &pyo3::PyAny, + algorithm: &pyo3::PyAny, + ) -> CryptographyResult<()> { + let (data, algorithm) = + utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; + + let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + ctx.verify_init()?; + setup_signature_ctx(py, &mut ctx, padding, algorithm, self.pkey.size(), false)?; + + let valid = ctx.verify(data, signature.as_bytes()).unwrap_or(false); + if !valid { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err(()), + )); + } + + Ok(()) + } + + fn encrypt<'p>( + &self, + py: pyo3::Python<'p>, + plaintext: &[u8], + padding: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + ctx.encrypt_init()?; + + setup_encryption_ctx(py, &mut ctx, padding)?; + + let length = ctx.encrypt(plaintext, None)?; + Ok(pyo3::types::PyBytes::new_with(py, length, |b| { + let length = ctx + .encrypt(plaintext, Some(b)) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Encryption failed"))?; + assert_eq!(length, b.len()); + Ok(()) + })?) + } + + fn recover_data_from_signature<'p>( + &self, + py: pyo3::Python<'p>, + signature: &[u8], + padding: &pyo3::PyAny, + algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + if algorithm.is_instance(types::PREHASHED.get(py)?)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Prehashed is only supported in the sign and verify methods. It cannot be used with recover_data_from_signature.", + ), + )); + } + + let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + ctx.verify_recover_init()?; + setup_signature_ctx(py, &mut ctx, padding, algorithm, self.pkey.size(), false)?; + + let length = ctx.verify_recover(signature, None)?; + let mut buf = vec![0u8; length]; + let length = ctx + .verify_recover(signature, Some(&mut buf)) + .map_err(|_| exceptions::InvalidSignature::new_err(()))?; + + Ok(pyo3::types::PyBytes::new(py, &buf[..length])) + } + + #[getter] + fn key_size(&self) -> i32 { + self.pkey.rsa().unwrap().n().num_bits() + } + + fn public_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let rsa = self.pkey.rsa().unwrap(); + + let py_e = utils::bn_to_py_int(py, rsa.e())?; + let py_n = utils::bn_to_py_int(py, rsa.n())?; + + Ok(RsaPublicNumbers { + e: py_e.extract()?, + n: py_n.extract()?, + }) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) + } + + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) + } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf + } +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.primitives.asymmetric.rsa", + name = "RSAPrivateNumbers" +)] +struct RsaPrivateNumbers { + #[pyo3(get)] + p: pyo3::Py, + #[pyo3(get)] + q: pyo3::Py, + #[pyo3(get)] + d: pyo3::Py, + #[pyo3(get)] + dmp1: pyo3::Py, + #[pyo3(get)] + dmq1: pyo3::Py, + #[pyo3(get)] + iqmp: pyo3::Py, + #[pyo3(get)] + public_numbers: pyo3::Py, +} + +#[pyo3::prelude::pyclass( + frozen, + module = "cryptography.hazmat.primitives.asymmetric.rsa", + name = "RSAPublicNumbers" +)] +struct RsaPublicNumbers { + #[pyo3(get)] + e: pyo3::Py, + #[pyo3(get)] + n: pyo3::Py, +} + +#[allow(clippy::too_many_arguments)] +fn check_private_key_components( + p: &pyo3::types::PyLong, + q: &pyo3::types::PyLong, + private_exponent: &pyo3::types::PyLong, + dmp1: &pyo3::types::PyLong, + dmq1: &pyo3::types::PyLong, + iqmp: &pyo3::types::PyLong, + public_exponent: &pyo3::types::PyLong, + modulus: &pyo3::types::PyLong, +) -> CryptographyResult<()> { + if modulus.lt(3)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("modulus must be >= 3."), + )); + } + + if p.ge(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("p must be < modulus."), + )); + } + + if q.ge(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("q must be < modulus."), + )); + } + + if dmp1.ge(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("dmp1 must be < modulus."), + )); + } + + if dmq1.ge(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("dmq1 must be < modulus."), + )); + } + + if iqmp.ge(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("iqmp must be < modulus."), + )); + } + + if private_exponent.ge(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("private_exponent must be < modulus."), + )); + } + + if public_exponent.lt(3)? || public_exponent.ge(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("public_exponent must be >= 3 and < modulus."), + )); + } + + // No `bitand` method. + if public_exponent.call_method1("__and__", (1,))?.eq(0)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("public_exponent must be odd."), + )); + } + + if dmp1.call_method1("__and__", (1,))?.eq(0)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("dmp1 must be odd."), + )); + } + + if dmq1.call_method1("__and__", (1,))?.eq(0)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("dmq1 must be odd."), + )); + } + + if p.call_method1("__mul__", (q,))?.ne(modulus)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("p*q must equal modulus."), + )); + } + + Ok(()) +} + +#[pyo3::prelude::pymethods] +impl RsaPrivateNumbers { + #[new] + fn new( + p: pyo3::Py, + q: pyo3::Py, + d: pyo3::Py, + dmp1: pyo3::Py, + dmq1: pyo3::Py, + iqmp: pyo3::Py, + public_numbers: pyo3::Py, + ) -> RsaPrivateNumbers { + Self { + p, + q, + d, + dmp1, + dmq1, + iqmp, + public_numbers, + } + } + + #[pyo3(signature = (backend = None, *, unsafe_skip_rsa_key_validation = false))] + fn private_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + unsafe_skip_rsa_key_validation: bool, + ) -> CryptographyResult { + let _ = backend; + + check_private_key_components( + self.p.as_ref(py), + self.q.as_ref(py), + self.d.as_ref(py), + self.dmp1.as_ref(py), + self.dmq1.as_ref(py), + self.iqmp.as_ref(py), + self.public_numbers.get().e.as_ref(py), + self.public_numbers.get().n.as_ref(py), + )?; + let public_numbers = self.public_numbers.get(); + let rsa = openssl::rsa::Rsa::from_private_components( + utils::py_int_to_bn(py, public_numbers.n.as_ref(py))?, + utils::py_int_to_bn(py, public_numbers.e.as_ref(py))?, + utils::py_int_to_bn(py, self.d.as_ref(py))?, + utils::py_int_to_bn(py, self.p.as_ref(py))?, + utils::py_int_to_bn(py, self.q.as_ref(py))?, + utils::py_int_to_bn(py, self.dmp1.as_ref(py))?, + utils::py_int_to_bn(py, self.dmq1.as_ref(py))?, + utils::py_int_to_bn(py, self.iqmp.as_ref(py))?, + ) + .unwrap(); + if !unsafe_skip_rsa_key_validation { + check_rsa_private_key(&rsa)?; + } + let pkey = openssl::pkey::PKey::from_rsa(rsa)?; + Ok(RsaPrivateKey { pkey }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok(self.p.as_ref(py).eq(other.p.as_ref(py))? + && self.q.as_ref(py).eq(other.q.as_ref(py))? + && self.d.as_ref(py).eq(other.d.as_ref(py))? + && self.dmp1.as_ref(py).eq(other.dmp1.as_ref(py))? + && self.dmq1.as_ref(py).eq(other.dmq1.as_ref(py))? + && self.iqmp.as_ref(py).eq(other.iqmp.as_ref(py))? + && self + .public_numbers + .as_ref(py) + .eq(other.public_numbers.as_ref(py))?) + } + + fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let mut hasher = DefaultHasher::new(); + self.p.as_ref(py).hash()?.hash(&mut hasher); + self.q.as_ref(py).hash()?.hash(&mut hasher); + self.d.as_ref(py).hash()?.hash(&mut hasher); + self.dmp1.as_ref(py).hash()?.hash(&mut hasher); + self.dmq1.as_ref(py).hash()?.hash(&mut hasher); + self.iqmp.as_ref(py).hash()?.hash(&mut hasher); + self.public_numbers.as_ref(py).hash()?.hash(&mut hasher); + Ok(hasher.finish()) + } +} + +fn check_public_key_components( + e: &pyo3::types::PyLong, + n: &pyo3::types::PyLong, +) -> CryptographyResult<()> { + if n.lt(3)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("n must be >= 3."), + )); + } + + if e.lt(3)? || e.ge(n)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("e must be >= 3 and < n."), + )); + } + + // No `bitand` method. + if e.call_method1("__and__", (1,))?.eq(0)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("e must be odd."), + )); + } + + Ok(()) +} + +#[pyo3::prelude::pymethods] +impl RsaPublicNumbers { + #[new] + fn new(e: pyo3::Py, n: pyo3::Py) -> RsaPublicNumbers { + RsaPublicNumbers { e, n } + } + + fn public_key( + &self, + py: pyo3::Python<'_>, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + check_public_key_components(self.e.as_ref(py), self.n.as_ref(py))?; + + let rsa = openssl::rsa::Rsa::from_public_components( + utils::py_int_to_bn(py, self.n.as_ref(py))?, + utils::py_int_to_bn(py, self.e.as_ref(py))?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_rsa(rsa)?; + Ok(RsaPublicKey { pkey }) + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + Ok( + self.e.as_ref(py).eq(other.e.as_ref(py))? + && self.n.as_ref(py).eq(other.n.as_ref(py))?, + ) + } + + fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let mut hasher = DefaultHasher::new(); + self.e.as_ref(py).hash()?.hash(&mut hasher); + self.n.as_ref(py).hash()?.hash(&mut hasher); + Ok(hasher.finish()) + } + + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let e = self.e.as_ref(py); + let n = self.n.as_ref(py); + Ok(format!("")) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "rsa")?; + m.add_function(pyo3::wrap_pyfunction!(generate_private_key, m)?)?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/utils.rs b/src/rust/src/backend/utils.rs index dea36117182b..3373a565cf2c 100644 --- a/src/rust/src/backend/utils.rs +++ b/src/rust/src/backend/utils.rs @@ -2,7 +2,9 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use crate::backend::hashes::Hash; use crate::error::{CryptographyError, CryptographyResult}; +use crate::{error, types}; pub(crate) fn py_int_to_bn( py: pyo3::Python<'_>, @@ -48,41 +50,21 @@ pub(crate) fn pkey_private_bytes<'p>( openssh_allowed: bool, raw_allowed: bool, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let serialization_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))?; - let encoding_class: &pyo3::types::PyType = serialization_mod - .getattr(pyo3::intern!(py, "Encoding"))? - .extract()?; - let private_format_class: &pyo3::types::PyType = serialization_mod - .getattr(pyo3::intern!(py, "PrivateFormat"))? - .extract()?; - let key_serialization_encryption_class: &pyo3::types::PyType = serialization_mod - .getattr(pyo3::intern!(py, "KeySerializationEncryption"))? - .extract()?; - let no_encryption_class: &pyo3::types::PyType = serialization_mod - .getattr(pyo3::intern!(py, "NoEncryption"))? - .extract()?; - let best_available_encryption_class: &pyo3::types::PyType = serialization_mod - .getattr(pyo3::intern!(py, "BestAvailableEncryption"))? - .extract()?; - - if !encoding.is_instance(encoding_class)? { + if !encoding.is_instance(types::ENCODING.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "encoding must be an item from the Encoding enum", ), )); } - if !format.is_instance(private_format_class)? { + if !format.is_instance(types::PRIVATE_FORMAT.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "format must be an item from the PrivateFormat enum", ), )); } - if !encryption_algorithm.is_instance(key_serialization_encryption_class)? { + if !encryption_algorithm.is_instance(types::KEY_SERIALIZATION_ENCRYPTION.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Encryption algorithm must be a KeySerializationEncryption instance", @@ -90,14 +72,13 @@ pub(crate) fn pkey_private_bytes<'p>( )); } - #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] if raw_allowed - && (encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) - || format.is(private_format_class.getattr(pyo3::intern!(py, "Raw"))?)) + && (encoding.is(types::ENCODING_RAW.get(py)?) + || format.is(types::PRIVATE_FORMAT_RAW.get(py)?)) { - if !encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) - || !format.is(private_format_class.getattr(pyo3::intern!(py, "Raw"))?) - || !encryption_algorithm.is_instance(no_encryption_class)? + if !encoding.is(types::ENCODING_RAW.get(py)?) + || !format.is(types::PRIVATE_FORMAT_RAW.get(py)?) + || !encryption_algorithm.is_instance(types::NO_ENCRYPTION.get(py)?)? { return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "When using Raw both encoding and format must be Raw and encryption_algorithm must be NoEncryption()" @@ -107,9 +88,14 @@ pub(crate) fn pkey_private_bytes<'p>( return Ok(pyo3::types::PyBytes::new(py, &raw_bytes)); } - let password = if encryption_algorithm.is_instance(no_encryption_class)? { + let password = if encryption_algorithm.is_instance(types::NO_ENCRYPTION.get(py)?)? { b"" - } else if encryption_algorithm.is_instance(best_available_encryption_class)? { + } else if encryption_algorithm.is_instance(types::BEST_AVAILABLE_ENCRYPTION.get(py)?)? + || (encryption_algorithm.is_instance(types::ENCRYPTION_BUILDER.get(py)?)? + && encryption_algorithm + .getattr(pyo3::intern!(py, "_format"))? + .is(format)) + { encryption_algorithm .getattr(pyo3::intern!(py, "password"))? .extract::<&[u8]>()? @@ -127,8 +113,8 @@ pub(crate) fn pkey_private_bytes<'p>( )); } - if format.is(private_format_class.getattr(pyo3::intern!(py, "PKCS8"))?) { - if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + if format.is(types::PRIVATE_FORMAT_PKCS8.get(py)?) { + if encoding.is(types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { pkey.private_key_to_pem_pkcs8()? } else { @@ -138,7 +124,7 @@ pub(crate) fn pkey_private_bytes<'p>( )? }; return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); - } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + } else if encoding.is(types::ENCODING_DER.get(py)?) { let der_bytes = if password.is_empty() { pkey.private_key_to_pkcs8()? } else { @@ -154,9 +140,32 @@ pub(crate) fn pkey_private_bytes<'p>( )); } - if format.is(private_format_class.getattr(pyo3::intern!(py, "TraditionalOpenSSL"))?) { - if let Ok(dsa) = pkey.dsa() { - if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + if format.is(types::PRIVATE_FORMAT_TRADITIONAL_OPENSSL.get(py)?) { + if let Ok(rsa) = pkey.rsa() { + if encoding.is(types::ENCODING_PEM.get(py)?) { + let pem_bytes = if password.is_empty() { + rsa.private_key_to_pem()? + } else { + rsa.private_key_to_pem_passphrase( + openssl::symm::Cipher::aes_256_cbc(), + password, + )? + }; + return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); + } else if encoding.is(types::ENCODING_DER.get(py)?) { + if !password.is_empty() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Encryption is not supported for DER encoded traditional OpenSSL keys", + ), + )); + } + + let der_bytes = rsa.private_key_to_der()?; + return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); + } + } else if let Ok(dsa) = pkey.dsa() { + if encoding.is(types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { dsa.private_key_to_pem()? } else { @@ -166,7 +175,7 @@ pub(crate) fn pkey_private_bytes<'p>( )? }; return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); - } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + } else if encoding.is(types::ENCODING_DER.get(py)?) { if !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -178,21 +187,38 @@ pub(crate) fn pkey_private_bytes<'p>( let der_bytes = dsa.private_key_to_der()?; return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); } + } else if let Ok(ec) = pkey.ec_key() { + if encoding.is(types::ENCODING_PEM.get(py)?) { + let pem_bytes = if password.is_empty() { + ec.private_key_to_pem()? + } else { + ec.private_key_to_pem_passphrase( + openssl::symm::Cipher::aes_256_cbc(), + password, + )? + }; + return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); + } else if encoding.is(types::ENCODING_DER.get(py)?) { + if !password.is_empty() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Encryption is not supported for DER encoded traditional OpenSSL keys", + ), + )); + } + + let der_bytes = ec.private_key_to_der()?; + return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); + } } } // OpenSSH + PEM - if openssh_allowed && format.is(private_format_class.getattr(pyo3::intern!(py, "OpenSSH"))?) { - if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { - return Ok(py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization.ssh" - ))? - .call_method1( - pyo3::intern!(py, "_serialize_ssh_private_key"), - (key_obj, password, encryption_algorithm), - )? + if openssh_allowed && format.is(types::PRIVATE_FORMAT_OPENSSH.get(py)?) { + if encoding.is(types::ENCODING_PEM.get(py)?) { + return Ok(types::SERIALIZE_SSH_PRIVATE_KEY + .get(py)? + .call1((key_obj, password, encryption_algorithm))? .extract()?); } @@ -217,25 +243,14 @@ pub(crate) fn pkey_public_bytes<'p>( openssh_allowed: bool, raw_allowed: bool, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let serialization_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))?; - let encoding_class: &pyo3::types::PyType = serialization_mod - .getattr(pyo3::intern!(py, "Encoding"))? - .extract()?; - let public_format_class: &pyo3::types::PyType = serialization_mod - .getattr(pyo3::intern!(py, "PublicFormat"))? - .extract()?; - - if !encoding.is_instance(encoding_class)? { + if !encoding.is_instance(types::ENCODING.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "encoding must be an item from the Encoding enum", ), )); } - if !format.is_instance(public_format_class)? { + if !format.is_instance(types::PUBLIC_FORMAT.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "format must be an item from the PublicFormat enum", @@ -243,13 +258,12 @@ pub(crate) fn pkey_public_bytes<'p>( )); } - #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] if raw_allowed - && (encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) - || format.is(public_format_class.getattr(pyo3::intern!(py, "Raw"))?)) + && (encoding.is(types::ENCODING_RAW.get(py)?) + || format.is(types::PUBLIC_FORMAT_RAW.get(py)?)) { - if !encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) - || !format.is(public_format_class.getattr(pyo3::intern!(py, "Raw"))?) + if !encoding.is(types::ENCODING_RAW.get(py)?) + || !format.is(types::PUBLIC_FORMAT_RAW.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -262,11 +276,11 @@ pub(crate) fn pkey_public_bytes<'p>( } // SubjectPublicKeyInfo + PEM/DER - if format.is(public_format_class.getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?) { - if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + if format.is(types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) { + if encoding.is(types::ENCODING_PEM.get(py)?) { let pem_bytes = pkey.public_key_to_pem()?; return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); - } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + } else if encoding.is(types::ENCODING_DER.get(py)?) { let der_bytes = pkey.public_key_to_der()?; return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); } @@ -277,15 +291,50 @@ pub(crate) fn pkey_public_bytes<'p>( )); } + if let Ok(ec) = pkey.ec_key() { + if encoding.is(types::ENCODING_X962.get(py)?) { + let point_form = if format.is(types::PUBLIC_FORMAT_UNCOMPRESSED_POINT.get(py)?) { + openssl::ec::PointConversionForm::UNCOMPRESSED + } else if format.is(types::PUBLIC_FORMAT_COMPRESSED_POINT.get(py)?) { + openssl::ec::PointConversionForm::COMPRESSED + } else { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "X962 encoding must be used with CompressedPoint or UncompressedPoint format" + ) + )); + }; + let mut bn_ctx = openssl::bn::BigNumContext::new()?; + let data = ec + .public_key() + .to_bytes(ec.group(), point_form, &mut bn_ctx)?; + return Ok(pyo3::types::PyBytes::new(py, &data)); + } + } + + if let Ok(rsa) = pkey.rsa() { + if format.is(types::PUBLIC_FORMAT_PKCS1.get(py)?) { + if encoding.is(types::ENCODING_PEM.get(py)?) { + let pem_bytes = rsa.public_key_to_pem_pkcs1()?; + return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); + } else if encoding.is(types::ENCODING_DER.get(py)?) { + let der_bytes = rsa.public_key_to_der_pkcs1()?; + return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); + } + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "PKCS1 works only with PEM or DER encoding", + ), + )); + } + } + // OpenSSH + OpenSSH - if openssh_allowed && format.is(public_format_class.getattr(pyo3::intern!(py, "OpenSSH"))?) { - if encoding.is(encoding_class.getattr(pyo3::intern!(py, "OpenSSH"))?) { - return Ok(py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization.ssh" - ))? - .call_method1(pyo3::intern!(py, "serialize_ssh_public_key"), (key_obj,))? + if openssh_allowed && format.is(types::PUBLIC_FORMAT_OPENSSH.get(py)?) { + if encoding.is(types::ENCODING_OPENSSH.get(py)?) { + return Ok(types::SERIALIZE_SSH_PUBLIC_KEY + .get(py)? + .call1((key_obj,))? .extract()?); } @@ -300,3 +349,93 @@ pub(crate) fn pkey_public_bytes<'p>( pyo3::exceptions::PyValueError::new_err("format is invalid with this key"), )) } + +pub(crate) fn calculate_digest_and_algorithm<'p>( + py: pyo3::Python<'p>, + mut data: &'p [u8], + mut algorithm: &'p pyo3::PyAny, +) -> CryptographyResult<(&'p [u8], &'p pyo3::PyAny)> { + if algorithm.is_instance(types::PREHASHED.get(py)?)? { + algorithm = algorithm.getattr("_algorithm")?; + } else { + // Potential optimization: rather than allocate a PyBytes in + // `h.finalize()`, have a way to get the `DigestBytes` directly. + let mut h = Hash::new(py, algorithm, None)?; + h.update_bytes(data)?; + data = h.finalize(py)?.as_bytes(); + } + + if data.len() != algorithm.getattr("digest_size")?.extract()? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The provided data must be the same length as the hash algorithm's digest size.", + ), + )); + } + + 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( + types::BACKEND_HANDLE_KEY_LOADING_ERROR + .get(py)? + .call1((errors,)) + .unwrap_err(), + )) + } + } +} diff --git a/src/rust/src/backend/x25519.rs b/src/rust/src/backend/x25519.rs index f27c0594ab3c..b193e18b0483 100644 --- a/src/rust/src/backend/x25519.rs +++ b/src/rust/src/backend/x25519.rs @@ -5,15 +5,14 @@ use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::CryptographyResult; -use foreign_types_shared::ForeignTypeRef; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x25519")] -struct X25519PrivateKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +pub(crate) struct X25519PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x25519")] -struct X25519PublicKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +pub(crate) struct X25519PublicKey { pkey: openssl::pkey::PKey, } @@ -24,17 +23,17 @@ fn generate_key() -> CryptographyResult { }) } -#[pyo3::prelude::pyfunction] -fn private_key_from_ptr(ptr: usize) -> X25519PrivateKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn private_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> X25519PrivateKey { X25519PrivateKey { pkey: pkey.to_owned(), } } -#[pyo3::prelude::pyfunction] -fn public_key_from_ptr(ptr: usize) -> X25519PublicKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn public_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> X25519PublicKey { X25519PublicKey { pkey: pkey.to_owned(), } @@ -46,8 +45,7 @@ fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X25519) .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( - "An X25519 private key is 32 bytes long: {}", - e + "An X25519 private key is 32 bytes long: {e}" )) })?; Ok(X25519PrivateKey { pkey }) @@ -67,10 +65,10 @@ impl X25519PrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, - public_key: &X25519PublicKey, + peer_public_key: &X25519PublicKey, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; - deriver.set_peer(&public_key.pkey)?; + deriver.set_peer(&peer_public_key.pkey)?; Ok(pyo3::types::PyBytes::new_with(py, deriver.len()?, |b| { let n = deriver.derive(b).map_err(|_| { @@ -138,24 +136,18 @@ impl X25519PublicKey { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, X25519PublicKey>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), - pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), - _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), - } + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) + } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf } } pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let m = pyo3::prelude::PyModule::new(py, "x25519")?; m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; diff --git a/src/rust/src/backend/x448.rs b/src/rust/src/backend/x448.rs index 97e52ee6cc95..7a64002d943d 100644 --- a/src/rust/src/backend/x448.rs +++ b/src/rust/src/backend/x448.rs @@ -5,15 +5,14 @@ use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::CryptographyResult; -use foreign_types_shared::ForeignTypeRef; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x448")] -struct X448PrivateKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] +pub(crate) struct X448PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x448")] -struct X448PublicKey { +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] +pub(crate) struct X448PublicKey { pkey: openssl::pkey::PKey, } @@ -24,17 +23,17 @@ fn generate_key() -> CryptographyResult { }) } -#[pyo3::prelude::pyfunction] -fn private_key_from_ptr(ptr: usize) -> X448PrivateKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn private_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> X448PrivateKey { X448PrivateKey { pkey: pkey.to_owned(), } } -#[pyo3::prelude::pyfunction] -fn public_key_from_ptr(ptr: usize) -> X448PublicKey { - let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; +pub(crate) fn public_key_from_pkey( + pkey: &openssl::pkey::PKeyRef, +) -> X448PublicKey { X448PublicKey { pkey: pkey.to_owned(), } @@ -46,8 +45,7 @@ fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X448) .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( - "An X448 private key is 56 bytes long: {}", - e + "An X448 private key is 56 bytes long: {e}" )) })?; Ok(X448PrivateKey { pkey }) @@ -66,10 +64,10 @@ impl X448PrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, - public_key: &X448PublicKey, + peer_public_key: &X448PublicKey, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; - deriver.set_peer(&public_key.pkey)?; + deriver.set_peer(&peer_public_key.pkey)?; Ok(pyo3::types::PyBytes::new_with(py, deriver.len()?, |b| { let n = deriver.derive(b).map_err(|_| { @@ -137,24 +135,18 @@ impl X448PublicKey { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, X448PublicKey>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), - pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), - _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), - } + fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { + self.pkey.public_eq(&other.pkey) + } + + fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { + slf } } pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let m = pyo3::prelude::PyModule::new(py, "x448")?; m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; diff --git a/src/rust/src/buf.rs b/src/rust/src/buf.rs index b7afcf047da4..c1f2cc8253c7 100644 --- a/src/rust/src/buf.rs +++ b/src/rust/src/buf.rs @@ -2,7 +2,9 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use std::{ptr, slice}; +use std::slice; + +use crate::types; pub(crate) struct CffiBuf<'p> { _pyobj: &'p pyo3::PyAny, @@ -20,21 +22,15 @@ impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { fn extract(pyobj: &'a pyo3::PyAny) -> pyo3::PyResult { let py = pyobj.py(); - let (bufobj, ptrval): (&pyo3::PyAny, usize) = py - .import(pyo3::intern!(py, "cryptography.utils"))? - .call_method1(pyo3::intern!(py, "_extract_buffer_length"), (pyobj,))? + let (bufobj, ptrval): (&pyo3::PyAny, usize) = types::EXTRACT_BUFFER_LENGTH + .get(py)? + .call1((pyobj,))? .extract()?; let len = bufobj.len()?; - let ptr = if len == 0 { - ptr::NonNull::dangling().as_ptr() + let buf = if len == 0 { + &[] } else { - ptrval as *const u8 - }; - - Ok(CffiBuf { - _pyobj: pyobj, - _bufobj: bufobj, // SAFETY: _extract_buffer_length ensures that we have a valid ptr // and length (and we ensure we meet slice's requirements for // 0-length slices above), we're keeping pyobj alive which ensures @@ -43,7 +39,13 @@ impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { // https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ // for details. This is the same as our cffi status quo ante, so // we're doing an unsound thing and living with it. - buf: unsafe { slice::from_raw_parts(ptr, len) }, + unsafe { slice::from_raw_parts(ptrval as *const u8, len) } + }; + + Ok(CffiBuf { + _pyobj: pyobj, + _bufobj: bufobj, + buf, }) } } diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs index 6699520cb397..a4461d05a87a 100644 --- a/src/rust/src/error.rs +++ b/src/rust/src/error.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::{exceptions, OpenSSLError}; use pyo3::ToPyObject; +use crate::exceptions; + pub enum CryptographyError { Asn1Parse(asn1::ParseError), Asn1Write(asn1::WriteError), + KeyParsing(asn1::ParseError), Py(pyo3::PyErr), OpenSSL(openssl::error::ErrorStack), } @@ -45,34 +47,72 @@ impl From for CryptographyError { impl From for CryptographyError { fn from(e: pem::PemError) -> CryptographyError { CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!( - "Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. {:?}", - e + "Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. {e:?}" ))) } } +impl From for CryptographyError { + fn from(e: cryptography_key_parsing::KeyParsingError) -> CryptographyError { + match e { + cryptography_key_parsing::KeyParsingError::Parse(e) => CryptographyError::KeyParsing(e), + cryptography_key_parsing::KeyParsingError::OpenSSL(e) => CryptographyError::OpenSSL(e), + cryptography_key_parsing::KeyParsingError::InvalidKey => { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err("Invalid key")) + } + cryptography_key_parsing::KeyParsingError::ExplicitCurveUnsupported => { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err( + "ECDSA keys with explicit parameters are unsupported at this time", + )) + } + cryptography_key_parsing::KeyParsingError::UnsupportedKeyType(oid) => { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!( + "Unknown key type: {oid}" + ))) + } + cryptography_key_parsing::KeyParsingError::UnsupportedEllipticCurve(oid) => { + CryptographyError::Py(exceptions::UnsupportedAlgorithm::new_err(( + format!("Curve {oid} is not supported"), + exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, + ))) + } + } + } +} + +pub(crate) fn list_from_openssl_error( + py: pyo3::Python<'_>, + error_stack: openssl::error::ErrorStack, +) -> &pyo3::types::PyList { + let errors = pyo3::types::PyList::empty(py); + for e in error_stack.errors() { + errors + .append( + pyo3::PyCell::new(py, OpenSSLError { e: e.clone() }) + .expect("Failed to create OpenSSLError"), + ) + .expect("Failed to append to list"); + } + errors +} + impl From for pyo3::PyErr { fn from(e: CryptographyError) -> pyo3::PyErr { match e { CryptographyError::Asn1Parse(asn1_error) => pyo3::exceptions::PyValueError::new_err( - format!("error parsing asn1 value: {:?}", asn1_error), + format!("error parsing asn1 value: {asn1_error:?}"), ), CryptographyError::Asn1Write(asn1::WriteError::AllocationError) => { pyo3::exceptions::PyMemoryError::new_err( "failed to allocate memory while performing ASN.1 serialization", ) } + CryptographyError::KeyParsing(asn1_error) => pyo3::exceptions::PyValueError::new_err( + format!("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) => py_error, CryptographyError::OpenSSL(error_stack) => pyo3::Python::with_gil(|py| { - let errors = pyo3::types::PyList::empty(py); - for e in error_stack.errors() { - errors - .append( - pyo3::PyCell::new(py, OpenSSLError { e: e.clone() }) - .expect("Failed to create OpenSSLError"), - ) - .expect("Failed to append to list"); - } + let errors = list_from_openssl_error(py, error_stack); exceptions::InternalError::new_err(( format!( "Unknown OpenSSL error. This error is commonly encountered @@ -81,8 +121,7 @@ impl From for pyo3::PyErr { that uses OpenSSL try disabling it before reporting a bug. Otherwise please file an issue at https://github.com/pyca/cryptography/issues with - information on how to reproduce this. ({:?})", - errors + information on how to reproduce this. ({errors:?})" ), errors.to_object(py), )) @@ -96,6 +135,7 @@ impl CryptographyError { match self { CryptographyError::Py(e) => CryptographyError::Py(e), CryptographyError::Asn1Parse(e) => CryptographyError::Asn1Parse(e.add_location(loc)), + CryptographyError::KeyParsing(e) => CryptographyError::KeyParsing(e.add_location(loc)), CryptographyError::Asn1Write(e) => CryptographyError::Asn1Write(e), CryptographyError::OpenSSL(e) => CryptographyError::OpenSSL(e), } @@ -107,6 +147,57 @@ impl CryptographyError { // https://github.com/pyca/cryptography/pull/6173 pub(crate) type CryptographyResult = Result; +#[pyo3::prelude::pyfunction] +pub(crate) fn raise_openssl_error() -> crate::error::CryptographyResult<()> { + Err(openssl::error::ErrorStack::get().into()) +} + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl")] +pub(crate) struct OpenSSLError { + e: openssl::error::Error, +} + +#[pyo3::pymethods] +impl OpenSSLError { + #[getter] + fn lib(&self) -> i32 { + self.e.library_code() + } + + #[getter] + fn reason(&self) -> i32 { + self.e.reason_code() + } + + #[getter] + fn reason_text(&self) -> &[u8] { + self.e.reason().unwrap_or("").as_bytes() + } + + fn _lib_reason_match(&self, lib: i32, reason: i32) -> bool { + self.e.library_code() == lib && self.e.reason_code() == reason + } + + fn __repr__(&self) -> pyo3::PyResult { + Ok(format!( + "", + self.e.code(), + self.e.library_code(), + self.e.reason_code(), + self.e.reason().unwrap_or("") + )) + } +} + +#[pyo3::prelude::pyfunction] +pub(crate) fn capture_error_stack(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyList> { + let errs = pyo3::types::PyList::empty(py); + for e in openssl::error::ErrorStack::get().errors() { + errs.append(pyo3::PyCell::new(py, OpenSSLError { e: e.clone() })?)?; + } + Ok(errs) +} + #[cfg(test)] mod tests { use super::CryptographyError; @@ -126,6 +217,12 @@ mod tests { let e: CryptographyError = pyo3::PyDowncastError::new(py.None().as_ref(py), "abc").into(); assert!(matches!(e, CryptographyError::Py(_))); + + let e = cryptography_key_parsing::KeyParsingError::OpenSSL( + openssl::error::ErrorStack::get(), + ) + .into(); + assert!(matches!(e, CryptographyError::OpenSSL(_))); }) } @@ -140,5 +237,9 @@ mod tests { let openssl_error = openssl::error::ErrorStack::get(); CryptographyError::from(openssl_error).add_location(asn1::ParseLocation::Field("meh")); + + let asn1_parse_error = asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue); + CryptographyError::KeyParsing(asn1_parse_error) + .add_location(asn1::ParseLocation::Field("meh")); } } diff --git a/src/rust/src/exceptions.rs b/src/rust/src/exceptions.rs index ec1e18c7ff9c..c9456513993d 100644 --- a/src/rust/src/exceptions.rs +++ b/src/rust/src/exceptions.rs @@ -3,6 +3,7 @@ // for complete details. #[pyo3::prelude::pyclass( + frozen, module = "cryptography.hazmat.bindings._rust.exceptions", name = "_Reasons" )] @@ -25,6 +26,7 @@ pub(crate) enum Reasons { pyo3::import_exception!(cryptography.exceptions, AlreadyFinalized); pyo3::import_exception!(cryptography.exceptions, InternalError); pyo3::import_exception!(cryptography.exceptions, InvalidSignature); +pyo3::import_exception!(cryptography.exceptions, InvalidTag); pyo3::import_exception!(cryptography.exceptions, UnsupportedAlgorithm); pyo3::import_exception!(cryptography.x509, AttributeNotFound); pyo3::import_exception!(cryptography.x509, DuplicateExtension); diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 4d88e2813b50..9dd54f4b901d 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -2,7 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] mod asn1; mod backend; @@ -10,127 +10,16 @@ mod buf; mod error; mod exceptions; pub(crate) mod oid; +mod padding; mod pkcs7; -mod pool; +pub(crate) mod types; mod x509; -/// Returns the value of the input with the most-significant-bit copied to all -/// of the bits. -fn duplicate_msb_to_all(a: u8) -> u8 { - 0u8.wrapping_sub(a >> 7) -} - -/// This returns 0xFF if a < b else 0x00, but does so in a constant time -/// fashion. -fn constant_time_lt(a: u8, b: u8) -> u8 { - // Derived from: - // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1i/include/internal/constant_time.h#L120 - duplicate_msb_to_all(a ^ ((a ^ b) | (a.wrapping_sub(b) ^ b))) -} - -#[pyo3::prelude::pyfunction] -fn check_pkcs7_padding(data: &[u8]) -> bool { - let mut mismatch = 0; - let pad_size = *data.last().unwrap(); - let len: u8 = data.len().try_into().expect("data too long"); - for (i, b) in (0..len).zip(data.iter().rev()) { - let mask = constant_time_lt(i, pad_size); - mismatch |= mask & (pad_size ^ b); - } - - // Check to make sure the pad_size was within the valid range. - mismatch |= !constant_time_lt(0, pad_size); - mismatch |= constant_time_lt(len, pad_size); - - // Make sure any bits set are copied to the lowest bit - mismatch |= mismatch >> 4; - mismatch |= mismatch >> 2; - mismatch |= mismatch >> 1; - - // Now check the low bit to see if it's set - (mismatch & 1) == 0 -} - -#[pyo3::prelude::pyfunction] -fn check_ansix923_padding(data: &[u8]) -> bool { - let mut mismatch = 0; - let pad_size = *data.last().unwrap(); - let len: u8 = data.len().try_into().expect("data too long"); - // Skip the first one with the pad size - for (i, b) in (1..len).zip(data[..data.len() - 1].iter().rev()) { - let mask = constant_time_lt(i, pad_size); - mismatch |= mask & b; - } - - // Check to make sure the pad_size was within the valid range. - mismatch |= !constant_time_lt(0, pad_size); - mismatch |= constant_time_lt(len, pad_size); - - // Make sure any bits set are copied to the lowest bit - mismatch |= mismatch >> 4; - mismatch |= mismatch >> 2; - mismatch |= mismatch >> 1; - - // Now check the low bit to see if it's set - (mismatch & 1) == 0 -} - #[pyo3::prelude::pyfunction] fn openssl_version() -> i64 { openssl::version::number() } -#[pyo3::prelude::pyfunction] -fn raise_openssl_error() -> crate::error::CryptographyResult<()> { - Err(openssl::error::ErrorStack::get().into()) -} - -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl")] -struct OpenSSLError { - e: openssl::error::Error, -} - -#[pyo3::pymethods] -impl OpenSSLError { - #[getter] - fn lib(&self) -> i32 { - self.e.library_code() - } - - #[getter] - fn reason(&self) -> i32 { - self.e.reason_code() - } - - #[getter] - fn reason_text(&self) -> &[u8] { - self.e.reason().unwrap_or("").as_bytes() - } - - fn _lib_reason_match(&self, lib: i32, reason: i32) -> bool { - self.e.library_code() == lib && self.e.reason_code() == reason - } - - fn __repr__(&self) -> pyo3::PyResult { - Ok(format!( - "", - self.e.code(), - self.e.library_code(), - self.e.reason_code(), - self.e.reason().unwrap_or("") - )) - } -} - -#[pyo3::prelude::pyfunction] -fn capture_error_stack(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyList> { - let errs = pyo3::types::PyList::empty(py); - for e in openssl::error::ErrorStack::get().errors() { - errs.append(pyo3::PyCell::new(py, OpenSSLError { e: e.clone() })?)?; - } - Ok(errs) -} - #[pyo3::prelude::pyfunction] fn is_fips_enabled() -> bool { cryptography_openssl::fips::is_enabled() @@ -138,10 +27,9 @@ fn is_fips_enabled() -> bool { #[pyo3::prelude::pymodule] fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> { - m.add_function(pyo3::wrap_pyfunction!(check_pkcs7_padding, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(check_ansix923_padding, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(padding::check_pkcs7_padding, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(padding::check_ansix923_padding, m)?)?; m.add_class::()?; - m.add_class::()?; m.add_submodule(asn1::create_submodule(py)?)?; m.add_submodule(pkcs7::create_submodule(py)?)?; @@ -153,6 +41,7 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> crate::x509::crl::add_to_module(x509_mod)?; crate::x509::csr::add_to_module(x509_mod)?; crate::x509::sct::add_to_module(x509_mod)?; + crate::x509::verify::add_to_module(x509_mod)?; m.add_submodule(x509_mod)?; let ocsp_mod = pyo3::prelude::PyModule::new(py, "ocsp")?; @@ -164,27 +53,12 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> let openssl_mod = pyo3::prelude::PyModule::new(py, "openssl")?; openssl_mod.add_function(pyo3::wrap_pyfunction!(openssl_version, m)?)?; - openssl_mod.add_function(pyo3::wrap_pyfunction!(raise_openssl_error, m)?)?; - openssl_mod.add_function(pyo3::wrap_pyfunction!(capture_error_stack, m)?)?; + openssl_mod.add_function(pyo3::wrap_pyfunction!(error::raise_openssl_error, m)?)?; + openssl_mod.add_function(pyo3::wrap_pyfunction!(error::capture_error_stack, m)?)?; openssl_mod.add_function(pyo3::wrap_pyfunction!(is_fips_enabled, m)?)?; - openssl_mod.add_class::()?; + openssl_mod.add_class::()?; crate::backend::add_to_module(openssl_mod)?; m.add_submodule(openssl_mod)?; Ok(()) } - -#[cfg(test)] -mod tests { - use super::constant_time_lt; - - #[test] - fn test_constant_time_lt() { - for a in 0..=255 { - for b in 0..=255 { - let expected = if a < b { 0xff } else { 0 }; - assert_eq!(constant_time_lt(a, b), expected); - } - } - } -} diff --git a/src/rust/src/oid.rs b/src/rust/src/oid.rs index f6dae6122bbf..4bf764eee408 100644 --- a/src/rust/src/oid.rs +++ b/src/rust/src/oid.rs @@ -2,11 +2,13 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::error::CryptographyResult; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] +use crate::error::CryptographyResult; +use crate::types; + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust")] pub(crate) struct ObjectIdentifier { pub(crate) oid: asn1::ObjectIdentifier, } @@ -30,10 +32,9 @@ impl ObjectIdentifier { slf: pyo3::PyRef<'_, Self>, py: pyo3::Python<'p>, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let oid_names = py - .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? - .getattr(pyo3::intern!(py, "_OID_NAMES"))?; - oid_names.call_method1(pyo3::intern!(py, "get"), (slf, "Unknown OID")) + types::OID_NAMES + .get(py)? + .call_method1(pyo3::intern!(py, "get"), (slf, "Unknown OID")) } fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { @@ -54,18 +55,8 @@ impl ObjectIdentifier { )) } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, ObjectIdentifier>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.oid == other.oid), - pyo3::basic::CompareOp::Ne => Ok(self.oid != other.oid), - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "ObjectIdentifiers cannot be ordered", - )), - } + fn __eq__(&self, other: pyo3::PyRef<'_, ObjectIdentifier>) -> bool { + self.oid == other.oid } fn __hash__(&self) -> u64 { diff --git a/src/rust/src/padding.rs b/src/rust/src/padding.rs new file mode 100644 index 000000000000..523fe85a5718 --- /dev/null +++ b/src/rust/src/padding.rs @@ -0,0 +1,79 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +/// Returns the value of the input with the most-significant-bit copied to all +/// of the bits. +fn duplicate_msb_to_all(a: u8) -> u8 { + 0u8.wrapping_sub(a >> 7) +} + +/// This returns 0xFF if a < b else 0x00, but does so in a constant time +/// fashion. +fn constant_time_lt(a: u8, b: u8) -> u8 { + // Derived from: + // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1i/include/internal/constant_time.h#L120 + duplicate_msb_to_all(a ^ ((a ^ b) | (a.wrapping_sub(b) ^ b))) +} + +#[pyo3::prelude::pyfunction] +pub(crate) fn check_pkcs7_padding(data: &[u8]) -> bool { + let mut mismatch = 0; + let pad_size = *data.last().unwrap(); + let len: u8 = data.len().try_into().expect("data too long"); + for (i, b) in (0..len).zip(data.iter().rev()) { + let mask = constant_time_lt(i, pad_size); + mismatch |= mask & (pad_size ^ b); + } + + // Check to make sure the pad_size was within the valid range. + mismatch |= !constant_time_lt(0, pad_size); + mismatch |= constant_time_lt(len, pad_size); + + // Make sure any bits set are copied to the lowest bit + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + + // Now check the low bit to see if it's set + (mismatch & 1) == 0 +} + +#[pyo3::prelude::pyfunction] +pub(crate) fn check_ansix923_padding(data: &[u8]) -> bool { + let mut mismatch = 0; + let pad_size = *data.last().unwrap(); + let len: u8 = data.len().try_into().expect("data too long"); + // Skip the first one with the pad size + for (i, b) in (1..len).zip(data[..data.len() - 1].iter().rev()) { + let mask = constant_time_lt(i, pad_size); + mismatch |= mask & b; + } + + // Check to make sure the pad_size was within the valid range. + mismatch |= !constant_time_lt(0, pad_size); + mismatch |= constant_time_lt(len, pad_size); + + // Make sure any bits set are copied to the lowest bit + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + + // Now check the low bit to see if it's set + (mismatch & 1) == 0 +} + +#[cfg(test)] +mod tests { + use super::constant_time_lt; + + #[test] + fn test_constant_time_lt() { + for a in 0..=255 { + for b in 0..=255 { + let expected = if a < b { 0xff } else { 0 }; + assert_eq!(constant_time_lt(a, b), expected); + } + } + } +} diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs index d2c500a72de7..f307cf483ad7 100644 --- a/src/rust/src/pkcs7.rs +++ b/src/rust/src/pkcs7.rs @@ -2,17 +2,25 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::encode_der_data; -use crate::buf::CffiBuf; -use crate::error::CryptographyResult; -use crate::x509; -use cryptography_x509::csr::Attribute; -use cryptography_x509::{common, oid, pkcs7}; -use once_cell::sync::Lazy; use std::borrow::Cow; use std::collections::HashMap; use std::ops::Deref; +use cryptography_x509::csr::Attribute; +use cryptography_x509::{common, oid, pkcs7}; +use once_cell::sync::Lazy; +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +use openssl::pkcs7::Pkcs7; +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +use pyo3::IntoPy; + +use crate::asn1::encode_der_data; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +use crate::x509::certificate::load_der_x509_certificate; +use crate::{exceptions, types, x509}; + const PKCS7_CONTENT_TYPE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 3); const PKCS7_MESSAGE_DIGEST_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 4); const PKCS7_SIGNING_TIME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 5); @@ -46,7 +54,7 @@ fn serialize_certificates<'p>( let raw_certs = py_certs .iter() - .map(|c| c.raw.borrow_value_public()) + .map(|c| c.raw.borrow_dependent()) .collect::>(); let signed_data = pkcs7::SignedData { @@ -77,17 +85,10 @@ fn sign_and_serialize<'p>( encoding: &'p pyo3::PyAny, options: &'p pyo3::types::PyList, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let pkcs7_options = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization.pkcs7" - ))? - .getattr(pyo3::intern!(py, "PKCS7Options"))?; - let raw_data: CffiBuf<'p> = builder.getattr(pyo3::intern!(py, "_data"))?.extract()?; - let text_mode = options.contains(pkcs7_options.getattr(pyo3::intern!(py, "Text"))?)?; + let text_mode = options.contains(types::PKCS7_TEXT.get(py)?)?; let (data_with_header, data_without_header) = - if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "Binary"))?)? { + if options.contains(types::PKCS7_BINARY.get(py)?)? { ( Cow::Borrowed(raw_data.as_bytes()), Cow::Borrowed(raw_data.as_bytes()), @@ -112,6 +113,7 @@ fn sign_and_serialize<'p>( pyo3::PyRef<'p, x509::certificate::Certificate>, &pyo3::PyAny, &pyo3::PyAny, + &pyo3::PyAny, )> = builder.getattr(pyo3::intern!(py, "_signers"))?.extract()?; let py_certs: Vec> = builder @@ -122,11 +124,11 @@ fn sign_and_serialize<'p>( let mut digest_algs = vec![]; let mut certs = py_certs .iter() - .map(|p| p.raw.borrow_value_public()) + .map(|p| p.raw.borrow_dependent()) .collect::>(); - for (cert, py_private_key, py_hash_alg) in &py_signers { + for (cert, py_private_key, py_hash_alg, rsa_padding) in &py_signers { let (authenticated_attrs, signature) = if options - .contains(pkcs7_options.getattr(pyo3::intern!(py, "NoAttributes"))?)? + .contains(types::PKCS7_NO_ATTRIBUTES.get(py)?)? { ( None, @@ -134,7 +136,7 @@ fn sign_and_serialize<'p>( py, py_private_key, py_hash_alg, - py.None().into_ref(py), + rsa_padding, &data_with_header, )?, ) @@ -165,7 +167,7 @@ fn sign_and_serialize<'p>( ])), }); - if !options.contains(pkcs7_options.getattr(pyo3::intern!(py, "NoCapabilities"))?)? { + if !options.contains(types::PKCS7_NO_CAPABILITIES.get(py)?)? { authenticated_attrs.push(Attribute { type_id: PKCS7_SMIME_CAP_OID, values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ @@ -181,13 +183,7 @@ fn sign_and_serialize<'p>( Some(common::Asn1ReadableOrWritable::new_write( asn1::SetOfWriter::new(authenticated_attrs), )), - x509::sign::sign_data( - py, - py_private_key, - py_hash_alg, - py.None().into_ref(py), - &signed_data, - )?, + x509::sign::sign_data(py, py_private_key, py_hash_alg, rsa_padding, &signed_data)?, ) }; @@ -199,13 +195,13 @@ fn sign_and_serialize<'p>( if !digest_algs.contains(&digest_alg) { digest_algs.push(digest_alg.clone()); } - certs.push(cert.raw.borrow_value_public()); + certs.push(cert.raw.borrow_dependent()); signer_infos.push(pkcs7::SignerInfo { version: 1, issuer_and_serial_number: pkcs7::IssuerAndSerialNumber { - issuer: cert.raw.borrow_value_public().tbs_cert.issuer.clone(), - serial_number: cert.raw.borrow_value_public().tbs_cert.serial, + issuer: cert.raw.borrow_dependent().tbs_cert.issuer.clone(), + serial_number: cert.raw.borrow_dependent().tbs_cert.serial, }, digest_algorithm: digest_alg, authenticated_attributes: authenticated_attrs, @@ -213,7 +209,7 @@ fn sign_and_serialize<'p>( py, py_private_key, py_hash_alg, - py.None().into_ref(py), + rsa_padding, )?, encrypted_digest: signature, unauthenticated_attributes: None, @@ -221,13 +217,12 @@ fn sign_and_serialize<'p>( } let data_tlv_bytes; - let content = - if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "DetachedSignature"))?)? { - None - } else { - data_tlv_bytes = asn1::write_single(&data_with_header.deref())?; - Some(asn1::parse_single(&data_tlv_bytes).unwrap()) - }; + let content = if options.contains(types::PKCS7_DETACHED_SIGNATURE.get(py)?)? { + None + } else { + data_tlv_bytes = asn1::write_single(&data_with_header.deref())?; + Some(asn1::parse_single(&data_tlv_bytes).unwrap()) + }; let signed_data = pkcs7::SignedData { version: 1, @@ -236,7 +231,7 @@ fn sign_and_serialize<'p>( _content_type: asn1::DefinedByMarker::marker(), content: pkcs7::Content::Data(content.map(asn1::Explicit::new)), }, - certificates: if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "NoCerts"))?)? { + certificates: if options.contains(types::PKCS7_NO_CERTS.get(py)?)? { None } else { Some(asn1::SetOfWriter::new(&certs)) @@ -251,26 +246,14 @@ fn sign_and_serialize<'p>( }; let ci_bytes = asn1::write_single(&content_info)?; - let encoding_class = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "Encoding"))?; - - if encoding.is(encoding_class.getattr(pyo3::intern!(py, "SMIME"))?) { + if encoding.is(types::ENCODING_SMIME.get(py)?) { let mic_algs = digest_algs .iter() .map(|d| OIDS_TO_MIC_NAME[&d.oid()]) .collect::>() .join(","); - let smime_encode = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization.pkcs7" - ))? - .getattr(pyo3::intern!(py, "_smime_encode"))?; - Ok(smime_encode + Ok(types::SMIME_ENCODE + .get(py)? .call1((&*data_without_header, &*ci_bytes, mic_algs, text_mode))? .extract()?) } else { @@ -313,21 +296,107 @@ fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [ } } +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +fn load_pkcs7_certificates( + py: pyo3::Python<'_>, + pkcs7: Pkcs7, +) -> CryptographyResult<&pyo3::types::PyList> { + let nid = pkcs7.type_().map(|t| t.nid()); + if nid != Some(openssl::nid::Nid::PKCS7_SIGNED) { + 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), + exceptions::Reasons::UNSUPPORTED_SERIALIZATION, + )), + )); + } + + let signed_certificates = pkcs7.signed().and_then(|x| x.certificates()); + match signed_certificates { + None => Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The provided PKCS7 has no certificate data, but a cert loading method was called.", + ), + )), + Some(certificates) => { + let result = pyo3::types::PyList::empty(py); + for c in certificates { + let cert_der = pyo3::types::PyBytes::new(py, c.to_der()?.as_slice()).into_py(py); + let cert = load_der_x509_certificate(py, cert_der, None)?; + result.append(cert.into_py(py))?; + } + Ok(result) + } + } +} + +#[pyo3::prelude::pyfunction] +fn load_pem_pkcs7_certificates<'p>( + py: pyo3::Python<'p>, + data: &[u8], +) -> CryptographyResult<&'p pyo3::types::PyList> { + 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) + } else { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "PKCS#7 is not supported by this backend.", + exceptions::Reasons::UNSUPPORTED_SERIALIZATION, + )), + )); + } + } +} + +#[pyo3::prelude::pyfunction] +fn load_der_pkcs7_certificates<'p>( + py: pyo3::Python<'p>, + data: &[u8], +) -> CryptographyResult<&'p pyo3::types::PyList> { + cfg_if::cfg_if! { + if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { + 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) + } else { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "PKCS#7 is not supported by this backend.", + exceptions::Reasons::UNSUPPORTED_SERIALIZATION, + )), + )); + } + } +} + pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let submod = pyo3::prelude::PyModule::new(py, "pkcs7")?; submod.add_function(pyo3::wrap_pyfunction!(serialize_certificates, submod)?)?; submod.add_function(pyo3::wrap_pyfunction!(sign_and_serialize, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(load_pem_pkcs7_certificates, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(load_der_pkcs7_certificates, submod)?)?; Ok(submod) } #[cfg(test)] mod tests { - use super::smime_canonicalize; use std::borrow::Cow; use std::ops::Deref; + use super::smime_canonicalize; + #[test] fn test_smime_canonicalize() { for ( diff --git a/src/rust/src/pool.rs b/src/rust/src/pool.rs deleted file mode 100644 index b9e6e27cd4af..000000000000 --- a/src/rust/src/pool.rs +++ /dev/null @@ -1,76 +0,0 @@ -// This file is dual licensed under the terms of the Apache License, Version -// 2.0, and the BSD License. See the LICENSE file in the root of this repository -// for complete details. - -use std::cell::Cell; - -// An object pool that can contain a single object and will dynamically -// allocate new objects to fulfill requests if the pool'd object is already in -// use. -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] -pub(crate) struct FixedPool { - create_fn: pyo3::PyObject, - - value: Cell>, -} - -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] -struct PoolAcquisition { - pool: pyo3::Py, - - value: pyo3::PyObject, - fresh: bool, -} - -#[pyo3::pymethods] -impl FixedPool { - #[new] - fn new(py: pyo3::Python<'_>, create: pyo3::PyObject) -> pyo3::PyResult { - let value = create.call0(py)?; - - Ok(FixedPool { - create_fn: create, - - value: Cell::new(Some(value)), - }) - } - - fn acquire(slf: pyo3::Py, py: pyo3::Python<'_>) -> pyo3::PyResult { - let v = slf.as_ref(py).borrow().value.replace(None); - if let Some(value) = v { - Ok(PoolAcquisition { - pool: slf, - value, - fresh: false, - }) - } else { - let value = slf.as_ref(py).borrow().create_fn.call0(py)?; - Ok(PoolAcquisition { - pool: slf, - value, - fresh: true, - }) - } - } -} - -#[pyo3::pymethods] -impl PoolAcquisition { - fn __enter__(&self, py: pyo3::Python<'_>) -> pyo3::PyObject { - self.value.clone_ref(py) - } - - fn __exit__( - &self, - py: pyo3::Python<'_>, - _exc_type: &pyo3::PyAny, - _exc_value: &pyo3::PyAny, - _exc_tb: &pyo3::PyAny, - ) -> pyo3::PyResult<()> { - let pool = self.pool.as_ref(py).borrow(); - if !self.fresh { - pool.value.replace(Some(self.value.clone_ref(py))); - } - Ok(()) - } -} diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs new file mode 100644 index 000000000000..07cf417971b6 --- /dev/null +++ b/src/rust/src/types.rs @@ -0,0 +1,510 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +pub struct LazyPyImport { + module: &'static str, + names: &'static [&'static str], + value: pyo3::sync::GILOnceCell, +} + +impl LazyPyImport { + pub const fn new(module: &'static str, names: &'static [&'static str]) -> LazyPyImport { + LazyPyImport { + module, + names, + value: pyo3::sync::GILOnceCell::new(), + } + } + + pub fn get<'p>(&'p self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + self.value + .get_or_try_init(py, || { + let mut obj = py.import(self.module)?.as_ref(); + for name in self.names { + obj = obj.getattr(*name)?; + } + obj.extract() + }) + .map(|p| p.as_ref(py)) + } +} + +pub static DATETIME_DATETIME: LazyPyImport = LazyPyImport::new("datetime", &["datetime"]); +pub static DATETIME_TIMEZONE_UTC: LazyPyImport = + LazyPyImport::new("datetime", &["timezone", "utc"]); +pub static IPADDRESS_IPADDRESS: LazyPyImport = LazyPyImport::new("ipaddress", &["ip_address"]); +pub static IPADDRESS_IPNETWORK: LazyPyImport = LazyPyImport::new("ipaddress", &["ip_network"]); +pub static OS_URANDOM: LazyPyImport = LazyPyImport::new("os", &["urandom"]); + +pub static DEPRECATED_IN_36: LazyPyImport = + LazyPyImport::new("cryptography.utils", &["DeprecatedIn36"]); +pub static DEPRECATED_IN_41: LazyPyImport = + LazyPyImport::new("cryptography.utils", &["DeprecatedIn41"]); +pub static DEPRECATED_IN_42: LazyPyImport = + LazyPyImport::new("cryptography.utils", &["DeprecatedIn42"]); + +pub static ENCODING: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["Encoding"], +); +pub static ENCODING_DER: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["Encoding", "DER"], +); +pub static ENCODING_OPENSSH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["Encoding", "OpenSSH"], +); +pub static ENCODING_PEM: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["Encoding", "PEM"], +); +pub static ENCODING_RAW: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["Encoding", "Raw"], +); +pub static ENCODING_SMIME: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["Encoding", "SMIME"], +); +pub static ENCODING_X962: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["Encoding", "X962"], +); + +pub static PRIVATE_FORMAT: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PrivateFormat"], +); +pub static PRIVATE_FORMAT_OPENSSH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PrivateFormat", "OpenSSH"], +); +pub static PRIVATE_FORMAT_PKCS8: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PrivateFormat", "PKCS8"], +); +pub static PRIVATE_FORMAT_RAW: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PrivateFormat", "Raw"], +); +pub static PRIVATE_FORMAT_TRADITIONAL_OPENSSL: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PrivateFormat", "TraditionalOpenSSL"], +); + +pub static PUBLIC_FORMAT: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PublicFormat"], +); +pub static PUBLIC_FORMAT_COMPRESSED_POINT: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PublicFormat", "CompressedPoint"], +); +pub static PUBLIC_FORMAT_OPENSSH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PublicFormat", "OpenSSH"], +); +pub static PUBLIC_FORMAT_PKCS1: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PublicFormat", "PKCS1"], +); +pub static PUBLIC_FORMAT_RAW: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PublicFormat", "Raw"], +); +pub static PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PublicFormat", "SubjectPublicKeyInfo"], +); +pub static PUBLIC_FORMAT_UNCOMPRESSED_POINT: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PublicFormat", "UncompressedPoint"], +); + +pub static PARAMETER_FORMAT_PKCS3: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["ParameterFormat", "PKCS3"], +); + +pub static KEY_SERIALIZATION_ENCRYPTION: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["KeySerializationEncryption"], +); +pub static NO_ENCRYPTION: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["NoEncryption"], +); +pub static BEST_AVAILABLE_ENCRYPTION: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["BestAvailableEncryption"], +); +pub static ENCRYPTION_BUILDER: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["_KeySerializationEncryption"], +); + +pub static SERIALIZE_SSH_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.ssh", + &["_serialize_ssh_private_key"], +); +pub static SERIALIZE_SSH_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.ssh", + &["serialize_ssh_public_key"], +); + +pub static SIG_OIDS_TO_HASH: LazyPyImport = + LazyPyImport::new("cryptography.hazmat._oid", &["_SIG_OIDS_TO_HASH"]); +pub static OID_NAMES: LazyPyImport = LazyPyImport::new("cryptography.hazmat._oid", &["_OID_NAMES"]); + +pub static REASON_FLAGS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["ReasonFlags"]); +pub static ATTRIBUTE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attribute"]); +pub static ATTRIBUTES: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attributes"]); + +pub static CRL_NUMBER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLNumber"]); +pub static DELTA_CRL_INDICATOR: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["DeltaCRLIndicator"]); +pub static ISSUER_ALTERNATIVE_NAME: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["IssuerAlternativeName"]); +pub static AUTHORITY_INFORMATION_ACCESS: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["AuthorityInformationAccess"]); +pub static ISSUING_DISTRIBUTION_POINT: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["IssuingDistributionPoint"]); +pub static FRESHEST_CRL: LazyPyImport = LazyPyImport::new("cryptography.x509", &["FreshestCRL"]); +pub static CRL_REASON: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLReason"]); +pub static CERTIFICATE_ISSUER: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["CertificateIssuer"]); +pub static INVALIDITY_DATE: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["InvalidityDate"]); +pub static OCSP_NONCE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OCSPNonce"]); +pub static OCSP_ACCEPTABLE_RESPONSES: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["OCSPAcceptableResponses"]); +pub static SIGNED_CERTIFICATE_TIMESTAMPS: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["SignedCertificateTimestamps"]); +pub static PRECERT_POISON: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["PrecertPoison"]); +pub static PRECERTIFICATE_SIGNED_CERTIFICATE_TIMESTAMPS: LazyPyImport = LazyPyImport::new( + "cryptography.x509", + &["PrecertificateSignedCertificateTimestamps"], +); +pub static DISTRIBUTION_POINT: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["DistributionPoint"]); +pub static ACCESS_DESCRIPTION: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["AccessDescription"]); +pub static AUTHORITY_KEY_IDENTIFIER: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["AuthorityKeyIdentifier"]); +pub static UNRECOGNIZED_EXTENSION: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["UnrecognizedExtension"]); +pub static EXTENSION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Extension"]); +pub static EXTENSIONS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Extensions"]); +pub static NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Name"]); +pub static RELATIVE_DISTINGUISHED_NAME: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["RelativeDistinguishedName"]); +pub static NAME_ATTRIBUTE: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["NameAttribute"]); +pub static NAME_CONSTRAINTS: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["NameConstraints"]); +pub static MS_CERTIFICATE_TEMPLATE: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["MSCertificateTemplate"]); +pub static CRL_DISTRIBUTION_POINTS: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["CRLDistributionPoints"]); +pub static BASIC_CONSTRAINTS: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["BasicConstraints"]); +pub static INHIBIT_ANY_POLICY: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["InhibitAnyPolicy"]); +pub static OCSP_NO_CHECK: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OCSPNoCheck"]); +pub static POLICY_CONSTRAINTS: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["PolicyConstraints"]); +pub static CERTIFICATE_POLICIES: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["CertificatePolicies"]); +pub static SUBJECT_INFORMATION_ACCESS: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["SubjectInformationAccess"]); +pub static KEY_USAGE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["KeyUsage"]); +pub static EXTENDED_KEY_USAGE: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["ExtendedKeyUsage"]); +pub static SUBJECT_KEY_IDENTIFIER: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["SubjectKeyIdentifier"]); +pub static TLS_FEATURE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["TLSFeature"]); +pub static SUBJECT_ALTERNATIVE_NAME: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["SubjectAlternativeName"]); +pub static POLICY_INFORMATION: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["PolicyInformation"]); +pub static USER_NOTICE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["UserNotice"]); +pub static NOTICE_REFERENCE: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["NoticeReference"]); +pub static REGISTERED_ID: LazyPyImport = LazyPyImport::new("cryptography.x509", &["RegisteredID"]); +pub static DIRECTORY_NAME: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["DirectoryName"]); +pub static UNIFORM_RESOURCE_IDENTIFIER: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["UniformResourceIdentifier"]); +pub static DNS_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["DNSName"]); +pub static IP_ADDRESS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["IPAddress"]); +pub static RFC822_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["RFC822Name"]); +pub static OTHER_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OtherName"]); +pub static CERTIFICATE_VERSION_V1: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["Version", "v1"]); +pub static CERTIFICATE_VERSION_V3: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["Version", "v3"]); + +pub static CRL_REASON_FLAGS: LazyPyImport = + LazyPyImport::new("cryptography.x509.extensions", &["_CRLREASONFLAGS"]); +pub static REASON_BIT_MAPPING: LazyPyImport = + LazyPyImport::new("cryptography.x509.extensions", &["_REASON_BIT_MAPPING"]); +pub static TLS_FEATURE_TYPE_TO_ENUM: LazyPyImport = LazyPyImport::new( + "cryptography.x509.extensions", + &["_TLS_FEATURE_TYPE_TO_ENUM"], +); + +pub static OCSP_RESPONSE_STATUS: LazyPyImport = + LazyPyImport::new("cryptography.x509.ocsp", &["OCSPResponseStatus"]); +pub static OCSP_CERT_STATUS: LazyPyImport = + LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus"]); +pub static OCSP_CERT_STATUS_GOOD: LazyPyImport = + LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus", "GOOD"]); +pub static OCSP_CERT_STATUS_UNKNOWN: LazyPyImport = + LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus", "UNKNOWN"]); +pub static OCSP_RESPONDER_ENCODING_HASH: LazyPyImport = + LazyPyImport::new("cryptography.x509.ocsp", &["OCSPResponderEncoding", "HASH"]); + +pub static CERTIFICATE_TRANSPARENCY_VERSION_V1: LazyPyImport = LazyPyImport::new( + "cryptography.x509.certificate_transparency", + &["Version", "v1"], +); +pub static SIGNATURE_ALGORITHM: LazyPyImport = LazyPyImport::new( + "cryptography.x509.certificate_transparency", + &["SignatureAlgorithm"], +); +pub static LOG_ENTRY_TYPE_X509_CERTIFICATE: LazyPyImport = LazyPyImport::new( + "cryptography.x509.certificate_transparency", + &["LogEntryType", "X509_CERTIFICATE"], +); +pub static LOG_ENTRY_TYPE_PRE_CERTIFICATE: LazyPyImport = LazyPyImport::new( + "cryptography.x509.certificate_transparency", + &["LogEntryType", "PRE_CERTIFICATE"], +); + +pub static ASN1_TYPE_TO_ENUM: LazyPyImport = + LazyPyImport::new("cryptography.x509.name", &["_ASN1_TYPE_TO_ENUM"]); +pub static ASN1_TYPE_BIT_STRING: LazyPyImport = + LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "BitString"]); +pub static ASN1_TYPE_BMP_STRING: LazyPyImport = + LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "BMPString"]); +pub static ASN1_TYPE_UNIVERSAL_STRING: LazyPyImport = + LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "UniversalString"]); + +pub static PKCS7_BINARY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["PKCS7Options", "Binary"], +); +pub static PKCS7_TEXT: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["PKCS7Options", "Text"], +); +pub static PKCS7_NO_ATTRIBUTES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["PKCS7Options", "NoAttributes"], +); +pub static PKCS7_NO_CAPABILITIES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["PKCS7Options", "NoCapabilities"], +); +pub static PKCS7_NO_CERTS: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["PKCS7Options", "NoCerts"], +); +pub static PKCS7_DETACHED_SIGNATURE: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["PKCS7Options", "DetachedSignature"], +); + +pub static SMIME_ENCODE: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["_smime_encode"], +); + +pub static HASHES_MODULE: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.hashes", &[]); +pub static HASH_ALGORITHM: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["HashAlgorithm"]); +pub static EXTENDABLE_OUTPUT_FUNCTION: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.hashes", + &["ExtendableOutputFunction"], +); +pub static SHA1: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA1"]); + +pub static PREHASHED: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.utils", + &["Prehashed"], +); +pub static ASYMMETRIC_PADDING: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["AsymmetricPadding"], +); +pub static PADDING_AUTO: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["_Auto"], +); +pub static PADDING_MAX_LENGTH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["_MaxLength"], +); +pub static PADDING_DIGEST_LENGTH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["_DigestLength"], +); +pub static PKCS1V15: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["PKCS1v15"], +); +pub static PSS: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["PSS"], +); +pub static OAEP: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["OAEP"], +); +pub static MGF1: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["MGF1"], +); +pub static CALCULATE_MAX_PSS_SALT_LENGTH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.padding", + &["calculate_max_pss_salt_length"], +); + +pub static CRL_ENTRY_REASON_ENUM_TO_CODE: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.backends.openssl.decode_asn1", + &["_CRL_ENTRY_REASON_ENUM_TO_CODE"], +); +pub static BACKEND_HANDLE_KEY_LOADING_ERROR: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.backends.openssl.backend", + &["backend", "_handle_key_loading_error"], +); + +pub static RSA_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.rsa", + &["RSAPrivateKey"], +); +pub static RSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.rsa", + &["RSAPublicKey"], +); + +pub static ELLIPTIC_CURVE: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ec", + &["EllipticCurve"], +); +pub static ELLIPTIC_CURVE_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ec", + &["EllipticCurvePrivateKey"], +); +pub static ELLIPTIC_CURVE_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ec", + &["EllipticCurvePublicKey"], +); +pub static CURVE_TYPES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ec", + &["_CURVE_TYPES"], +); +pub static ECDSA: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.asymmetric.ec", &["ECDSA"]); +pub static ECDH: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.asymmetric.ec", &["ECDH"]); + +pub static ED25519_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ed25519", + &["Ed25519PrivateKey"], +); +pub static ED25519_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ed25519", + &["Ed25519PublicKey"], +); + +pub static ED448_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ed448", + &["Ed448PrivateKey"], +); +pub static ED448_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.ed448", + &["Ed448PublicKey"], +); + +pub static DSA_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.dsa", + &["DSAPrivateKey"], +); +pub static DSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.dsa", + &["DSAPublicKey"], +); + +pub static EXTRACT_BUFFER_LENGTH: LazyPyImport = + LazyPyImport::new("cryptography.utils", &["_extract_buffer_length"]); + +pub static BLOCK_CIPHER_ALGORITHM: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers", + &["BlockCipherAlgorithm"], +); + +pub static TRIPLE_DES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["TripleDES"], +); +pub static AES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["AES"], +); +pub static AES128: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["AES128"], +); +pub static AES256: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["AES256"], +); +pub static SM4: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["SM4"], +); +pub static SEED: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_SEEDInternal"], +); +pub static CAMELLIA: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["Camellia"], +); +pub static BLOWFISH: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_BlowfishInternal"], +); +pub static CAST5: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_CAST5Internal"], +); +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] +pub static IDEA: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.algorithms", + &["_IDEAInternal"], +); + +pub static CBC: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CBC"]); + +#[cfg(test)] +mod tests { + use super::LazyPyImport; + + #[test] + fn test_basic() { + pyo3::prepare_freethreaded_python(); + + let v = LazyPyImport::new("foo", &["bar"]); + pyo3::Python::with_gil(|py| { + assert!(v.get(py).is_err()); + }); + } +} diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs index 3446bbbbb604..552f4eda7d81 100644 --- a/src/rust/src/x509/certificate.rs +++ b/src/rust/src/x509/certificate.rs @@ -2,118 +2,86 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{ - big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, -}; -use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509::{extensions, sct, sign}; -use crate::{exceptions, x509}; -use cryptography_x509::common::Asn1ReadableOrWritable; -use cryptography_x509::extensions::Extension; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +use cryptography_x509::certificate::Certificate as RawCertificate; +use cryptography_x509::common::{AlgorithmParameters, Asn1ReadableOrWritable}; use cryptography_x509::extensions::{ AuthorityKeyIdentifier, BasicConstraints, DisplayText, DistributionPoint, - DistributionPointName, MSCertificateTemplate, NameConstraints, PolicyConstraints, - PolicyInformation, PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions, + DistributionPointName, DuplicateExtensionsError, IssuerAlternativeName, KeyUsage, + MSCertificateTemplate, NameConstraints, PolicyConstraints, PolicyInformation, + PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions, SequenceOfSubtrees, UserNotice, }; -use cryptography_x509::{common, name, oid}; +use cryptography_x509::extensions::{Extension, SubjectAlternativeName}; +use cryptography_x509::{common, oid}; +use cryptography_x509_verification::ops::CryptoOps; use pyo3::{IntoPy, ToPyObject}; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; -#[ouroboros::self_referencing] -pub(crate) struct OwnedCertificate { - data: pyo3::Py, - - #[borrows(data)] - #[covariant] - value: cryptography_x509::certificate::Certificate<'this>, -} +use crate::asn1::{ + big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, +}; +use crate::backend::{hashes, keys}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::verify::PyCryptoOps; +use crate::x509::{extensions, sct, sign}; +use crate::{exceptions, types, x509}; -impl OwnedCertificate { - // Re-expose ::new with `pub(crate)` visibility. - pub(crate) fn new_public( - data: pyo3::Py, - value_ref_builder: impl for<'this> FnOnce( - &'this pyo3::Py, - ) - -> cryptography_x509::certificate::Certificate<'this>, - ) -> OwnedCertificate { - OwnedCertificate::new(data, value_ref_builder) - } +self_cell::self_cell!( + pub(crate) struct OwnedCertificate { + owner: pyo3::Py, - pub(crate) fn borrow_value_public(&self) -> &cryptography_x509::certificate::Certificate<'_> { - self.borrow_value() + #[covariant] + dependent: RawCertificate, } -} +); -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Certificate { pub(crate) raw: OwnedCertificate, - pub(crate) cached_extensions: Option, + pub(crate) cached_extensions: pyo3::sync::GILOnceCell, } #[pyo3::prelude::pymethods] impl Certificate { fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); - self.raw.borrow_value().hash(&mut hasher); + self.raw.borrow_dependent().hash(&mut hasher); hasher.finish() } - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, Certificate>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.raw.borrow_value() == other.raw.borrow_value()), - pyo3::basic::CompareOp::Ne => Ok(self.raw.borrow_value() != other.raw.borrow_value()), - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "Certificates cannot be ordered", - )), - } + fn __eq__(&self, other: pyo3::PyRef<'_, Certificate>) -> bool { + self.raw.borrow_dependent() == other.raw.borrow_dependent() } fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let subject = self.subject(py)?; let subject_repr = subject.repr()?.extract::<&str>()?; - Ok(format!("", subject_repr)) + Ok(format!("")) } fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { slf } - fn public_key<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { - // This makes an unnecessary copy. It'd be nice to get rid of it. - let serialized = pyo3::types::PyBytes::new( + fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { + keys::load_der_public_key_bytes( py, - &asn1::write_single(&self.raw.borrow_value().tbs_cert.spki)?, - ); - Ok(py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "load_der_public_key"))? - .call1((serialized,))?) + self.raw.borrow_dependent().tbs_cert.spki.tlv().full_data(), + ) } fn fingerprint<'p>( &self, py: pyo3::Python<'p>, - algorithm: pyo3::PyObject, + algorithm: &pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::PyAny> { - let hasher = py - .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? - .getattr(pyo3::intern!(py, "Hash"))? - .call1((algorithm,))?; - // This makes an unnecessary copy. It'd be nice to get rid of it. - let serialized = - pyo3::types::PyBytes::new(py, &asn1::write_single(&self.raw.borrow_value())?); - hasher.call_method1(pyo3::intern!(py, "update"), (serialized,))?; - Ok(hasher.call_method0(pyo3::intern!(py, "finalize"))?) + let serialized = asn1::write_single(&self.raw.borrow_dependent())?; + + let mut h = hashes::Hash::new(py, algorithm, None)?; + h.update_bytes(&serialized)?; + Ok(h.finalize(py)?) } fn public_bytes<'p>( @@ -121,7 +89,7 @@ impl Certificate { py: pyo3::Python<'p>, encoding: &'p pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let result = asn1::write_single(self.raw.borrow_value())?; + let result = asn1::write_single(self.raw.borrow_dependent())?; encode_der_data(py, "CERTIFICATE".to_string(), result, encoding) } @@ -131,31 +99,27 @@ impl Certificate { &self, py: pyo3::Python<'p>, ) -> Result<&'p pyo3::PyAny, CryptographyError> { - let bytes = self.raw.borrow_value().tbs_cert.serial.as_bytes(); + let bytes = self.raw.borrow_dependent().tbs_cert.serial.as_bytes(); warn_if_negative_serial(py, bytes)?; Ok(big_byte_slice_to_py_int(py, bytes)?) } #[getter] fn version<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, CryptographyError> { - let version = &self.raw.borrow_value().tbs_cert.version; + let version = &self.raw.borrow_dependent().tbs_cert.version; cert_version(py, *version) } #[getter] fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - Ok( - x509::parse_name(py, &self.raw.borrow_value().tbs_cert.issuer) - .map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer")))?, - ) + Ok(x509::parse_name(py, self.raw.borrow_dependent().issuer()) + .map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer")))?) } #[getter] fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - Ok( - x509::parse_name(py, &self.raw.borrow_value().tbs_cert.subject) - .map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))?, - ) + Ok(x509::parse_name(py, self.raw.borrow_dependent().subject()) + .map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))?) } #[getter] @@ -163,7 +127,7 @@ impl Certificate { &self, py: pyo3::Python<'p>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let result = asn1::write_single(&self.raw.borrow_value().tbs_cert)?; + let result = asn1::write_single(&self.raw.borrow_dependent().tbs_cert)?; Ok(pyo3::types::PyBytes::new(py, &result)) } @@ -172,23 +136,17 @@ impl Certificate { &self, py: pyo3::Python<'p>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let val = self.raw.borrow_value(); + let val = self.raw.borrow_dependent(); let mut tbs_precert = val.tbs_cert.clone(); // Remove the SCT list extension - match val.tbs_cert.extensions() { + match val.extensions() { Ok(extensions) => { - let readable_extensions = match extensions.as_raw() { - Some(raw_exts) => raw_exts.unwrap_read().clone(), - None => { - return Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err( - "Could not find any extensions in TBS certificate", - ), - )) - } - }; - let ext_count = readable_extensions.len(); - let filtered_extensions: Vec> = readable_extensions + let ext_count = extensions + .as_raw() + .as_ref() + .map_or(0, |raw| raw.unwrap_read().len()); + let filtered_extensions: Vec> = extensions + .iter() .filter(|x| x.extn_id != oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID) .collect(); if filtered_extensions.len() == ext_count { @@ -206,10 +164,10 @@ impl Certificate { let result = asn1::write_single(&tbs_precert)?; Ok(pyo3::types::PyBytes::new(py, &result)) } - Err(oid) => { + Err(DuplicateExtensionsError(oid)) => { let oid_obj = oid_to_py_oid(py, &oid)?; Err(exceptions::DuplicateExtension::new_err(( - format!("Duplicate {} extension found", oid), + format!("Duplicate {} extension found", &oid), oid_obj.into_py(py), )) .into()) @@ -219,14 +177,21 @@ impl Certificate { #[getter] fn signature<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyBytes { - pyo3::types::PyBytes::new(py, self.raw.borrow_value().signature.as_bytes()) + pyo3::types::PyBytes::new(py, self.raw.borrow_dependent().signature.as_bytes()) } #[getter] fn not_valid_before<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let warning_cls = types::DEPRECATED_IN_42.get(py)?; + pyo3::PyErr::warn( + py, + warning_cls, + "Properties that return a naïve datetime object have been deprecated. Please switch to not_valid_before_utc.", + 1, + )?; let dt = &self .raw - .borrow_value() + .borrow_dependent() .tbs_cert .validity .not_before @@ -234,11 +199,30 @@ impl Certificate { x509::datetime_to_py(py, dt) } + #[getter] + fn not_valid_before_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let dt = &self + .raw + .borrow_dependent() + .tbs_cert + .validity + .not_before + .as_datetime(); + x509::datetime_to_py_utc(py, dt) + } + #[getter] fn not_valid_after<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let warning_cls = types::DEPRECATED_IN_42.get(py)?; + pyo3::PyErr::warn( + py, + warning_cls, + "Properties that return a naïve datetime object have been deprecated. Please switch to not_valid_after_utc.", + 1, + )?; let dt = &self .raw - .borrow_value() + .borrow_dependent() .tbs_cert .validity .not_after @@ -246,17 +230,29 @@ impl Certificate { x509::datetime_to_py(py, dt) } + #[getter] + fn not_valid_after_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let dt = &self + .raw + .borrow_dependent() + .tbs_cert + .validity + .not_after + .as_datetime(); + x509::datetime_to_py_utc(py, dt) + } + #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> Result<&'p pyo3::PyAny, CryptographyError> { - sign::identify_signature_hash_algorithm(py, &self.raw.borrow_value().signature_alg) + sign::identify_signature_hash_algorithm(py, &self.raw.borrow_dependent().signature_alg) } #[getter] fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - oid_to_py_oid(py, self.raw.borrow_value().signature_alg.oid()) + oid_to_py_oid(py, self.raw.borrow_dependent().signature_alg.oid()) } #[getter] @@ -264,81 +260,71 @@ impl Certificate { &'p self, py: pyo3::Python<'p>, ) -> CryptographyResult<&'p pyo3::PyAny> { - sign::identify_signature_algorithm_parameters(py, &self.raw.borrow_value().signature_alg) + sign::identify_signature_algorithm_parameters( + py, + &self.raw.borrow_dependent().signature_alg, + ) } #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { x509::parse_and_cache_extensions( py, - &mut self.cached_extensions, - &self.raw.borrow_value().tbs_cert.raw_extensions, - |oid, ext_data| match *oid { + &self.cached_extensions, + &self.raw.borrow_dependent().tbs_cert.raw_extensions, + |ext| match ext.extn_id { oid::PRECERT_POISON_OID => { - asn1::parse_single::<()>(ext_data)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "PrecertPoison"))? - .call0()?, - )) + ext.value::<()>()?; + Ok(Some(types::PRECERT_POISON.get(py)?.call0()?)) } oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID => { - let contents = asn1::parse_single::<&[u8]>(ext_data)?; + let contents = ext.value::<&[u8]>()?; let scts = sct::parse_scts(py, contents, sct::LogEntryType::PreCertificate)?; Ok(Some( - x509_module - .getattr(pyo3::intern!( - py, - "PrecertificateSignedCertificateTimestamps" - ))? + types::PRECERTIFICATE_SIGNED_CERTIFICATE_TIMESTAMPS + .get(py)? .call1((scts,))?, )) } - _ => parse_cert_ext(py, oid.clone(), ext_data), + _ => parse_cert_ext(py, ext), }, ) } fn verify_directly_issued_by( &self, - py: pyo3::Python<'_>, issuer: pyo3::PyRef<'_, Certificate>, ) -> CryptographyResult<()> { - if self.raw.borrow_value().tbs_cert.signature_alg != self.raw.borrow_value().signature_alg { + if self.raw.borrow_dependent().tbs_cert.signature_alg + != self.raw.borrow_dependent().signature_alg + { return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Inner and outer signature algorithms do not match. This is an invalid certificate." ))); }; - if self.raw.borrow_value().tbs_cert.issuer != issuer.raw.borrow_value().tbs_cert.subject { + if self.raw.borrow_dependent().tbs_cert.issuer + != issuer.raw.borrow_dependent().tbs_cert.subject + { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Issuer certificate subject does not match certificate issuer.", ), )); }; - sign::verify_signature_with_signature_algorithm( - py, - issuer.public_key(py)?, - &self.raw.borrow_value().signature_alg, - self.raw.borrow_value().signature.as_bytes(), - &asn1::write_single(&self.raw.borrow_value().tbs_cert)?, - ) + + let ops = PyCryptoOps {}; + let issuer_key = ops.public_key(issuer.raw.borrow_dependent())?; + ops.verify_signed_by(self.raw.borrow_dependent(), &issuer_key) } } fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, CryptographyError> { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; match version { - 0 => Ok(x509_module - .getattr(pyo3::intern!(py, "Version"))? - .get_item(pyo3::intern!(py, "v1"))?), - 2 => Ok(x509_module - .getattr(pyo3::intern!(py, "Version"))? - .get_item(pyo3::intern!(py, "v3"))?), + 0 => Ok(types::CERTIFICATE_VERSION_V1.get(py)?), + 2 => Ok(types::CERTIFICATE_VERSION_V3.get(py)?), _ => Err(CryptographyError::from( exceptions::InvalidVersion::new_err(( - format!("{} is not a valid X509 version", version), + format!("{version} is not a valid X509 version"), version, )), )), @@ -346,17 +332,24 @@ fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, Crypt } #[pyo3::prelude::pyfunction] -fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> CryptographyResult { +fn load_pem_x509_certificate( + py: pyo3::Python<'_>, + data: &[u8], + backend: Option<&pyo3::PyAny>, +) -> CryptographyResult { + let _ = backend; + // We support both PEM header strings that OpenSSL does // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L32-L33 let parsed = x509::find_in_pem( data, - |p| p.tag == "CERTIFICATE" || p.tag == "X509 CERTIFICATE", + |p| p.tag() == "CERTIFICATE" || p.tag() == "X509 CERTIFICATE", "Valid PEM but no BEGIN CERTIFICATE/END CERTIFICATE delimiters. Are you sure this is a certificate?", )?; load_der_x509_certificate( py, - pyo3::types::PyBytes::new(py, &parsed.contents).into_py(py), + pyo3::types::PyBytes::new(py, parsed.contents()).into_py(py), + None, ) } @@ -367,9 +360,13 @@ fn load_pem_x509_certificates( ) -> CryptographyResult> { let certs = pem::parse_many(data)? .iter() - .filter(|p| p.tag == "CERTIFICATE" || p.tag == "X509 CERTIFICATE") + .filter(|p| p.tag() == "CERTIFICATE" || p.tag() == "X509 CERTIFICATE") .map(|p| { - load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &p.contents).into_py(py)) + load_der_x509_certificate( + py, + pyo3::types::PyBytes::new(py, p.contents()).into_py(py), + None, + ) }) .collect::, _>>()?; @@ -381,38 +378,73 @@ fn load_pem_x509_certificates( } #[pyo3::prelude::pyfunction] -fn load_der_x509_certificate( +pub(crate) fn load_der_x509_certificate( py: pyo3::Python<'_>, data: pyo3::Py, + backend: Option<&pyo3::PyAny>, ) -> CryptographyResult { + let _ = backend; + let raw = OwnedCertificate::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; // Parse cert version immediately so we can raise error on parse if it is invalid. - cert_version(py, raw.borrow_value().tbs_cert.version)?; + 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_value().tbs_cert.serial.as_bytes())?; + warn_if_negative_serial(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. + warn_if_invalid_params(py, raw.borrow_dependent().signature_alg.params.clone())?; + warn_if_invalid_params( + py, + raw.borrow_dependent().tbs_cert.signature_alg.params.clone(), + )?; Ok(Certificate { raw, - cached_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), }) } fn warn_if_negative_serial(py: pyo3::Python<'_>, bytes: &'_ [u8]) -> pyo3::PyResult<()> { if bytes[0] & 0x80 != 0 { - let cryptography_warning = py - .import(pyo3::intern!(py, "cryptography.utils"))? - .getattr(pyo3::intern!(py, "DeprecatedIn36"))?; + let warning_cls = types::DEPRECATED_IN_36.get(py)?; pyo3::PyErr::warn( py, - cryptography_warning, - "Parsed a negative serial number, which is disallowed by RFC 5280.", + warning_cls, + "Parsed a negative serial number, which is disallowed by RFC 5280. Loading this certificate will cause an exception in the next release of cryptography.", 1, )?; } Ok(()) } +fn warn_if_invalid_params( + py: pyo3::Python<'_>, + params: AlgorithmParameters<'_>, +) -> pyo3::PyResult<()> { + match params { + AlgorithmParameters::EcDsaWithSha224(Some(..)) + | AlgorithmParameters::EcDsaWithSha256(Some(..)) + | AlgorithmParameters::EcDsaWithSha384(Some(..)) + | AlgorithmParameters::EcDsaWithSha512(Some(..)) + | AlgorithmParameters::DsaWithSha224(Some(..)) + | AlgorithmParameters::DsaWithSha256(Some(..)) + | AlgorithmParameters::DsaWithSha384(Some(..)) + | AlgorithmParameters::DsaWithSha512(Some(..)) => { + let warning_cls = types::DEPRECATED_IN_41.get(py)?; + pyo3::PyErr::warn( + py, + warning_cls, + "The parsed certificate contains a NULL parameter value in its signature algorithm parameters. This is invalid and will be rejected in a future version of cryptography. If this certificate was created via Java, please upgrade to JDK21+ or the latest JDK11/17 once a fix is issued. If this certificate was created in some other fashion please report the issue to the cryptography issue tracker. See https://github.com/pyca/cryptography/issues/8996 and https://github.com/pyca/cryptography/issues/9253 for more details.", + 2, + )?; + } + _ => {} + } + Ok(()) +} + fn parse_display_text( py: pyo3::Python<'_>, text: DisplayText<'_>, @@ -422,12 +454,10 @@ fn parse_display_text( DisplayText::Utf8String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)), DisplayText::VisibleString(o) => { if asn1::VisibleString::new(o.as_str()).is_none() { - let cryptography_warning = py - .import(pyo3::intern!(py, "cryptography.utils"))? - .getattr(pyo3::intern!(py, "DeprecatedIn41"))?; + let warning_cls = types::DEPRECATED_IN_41.get(py)?; pyo3::PyErr::warn( py, - cryptography_warning, + warning_cls, "Invalid ASN.1 (UTF-8 characters in a VisibleString) in the explicit text and/or notice reference of the certificate policies extension. In a future version of cryptography, an exception will be raised.", 1, )?; @@ -451,7 +481,6 @@ fn parse_user_notice( py: pyo3::Python<'_>, un: UserNotice<'_>, ) -> Result { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let et = match un.explicit_text { Some(data) => parse_display_text(py, data)?, None => py.None(), @@ -463,15 +492,14 @@ fn parse_user_notice( for num in data.notice_numbers.unwrap_read().clone() { numbers.append(big_byte_slice_to_py_int(py, num.as_bytes())?.to_object(py))?; } - x509_module - .call_method1(pyo3::intern!(py, "NoticeReference"), (org, numbers))? + types::NOTICE_REFERENCE + .get(py)? + .call1((org, numbers))? .to_object(py) } None => py.None(), }; - Ok(x509_module - .call_method1(pyo3::intern!(py, "UserNotice"), (nr, et))? - .to_object(py)) + Ok(types::USER_NOTICE.get(py)?.call1((nr, et))?.to_object(py)) } fn parse_policy_qualifiers<'a>( @@ -508,9 +536,11 @@ fn parse_policy_qualifiers<'a>( Ok(py_pq.to_object(py)) } -fn parse_cp(py: pyo3::Python<'_>, ext_data: &[u8]) -> Result { - let cp = asn1::parse_single::>>(ext_data)?; - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; +fn parse_cp( + py: pyo3::Python<'_>, + ext: &Extension<'_>, +) -> Result { + let cp = ext.value::>>()?; let certificate_policies = pyo3::types::PyList::empty(py); for policyinfo in cp { let pi_oid = oid_to_py_oid(py, &policyinfo.policy_identifier)?.to_object(py); @@ -520,8 +550,9 @@ fn parse_cp(py: pyo3::Python<'_>, ext_data: &[u8]) -> Result py.None(), }; - let pi = x509_module - .call_method1(pyo3::intern!(py, "PolicyInformation"), (pi_oid, py_pqis))? + let pi = types::POLICY_INFORMATION + .get(py)? + .call1((pi_oid, py_pqis))? .to_object(py); certificate_policies.append(pi)?; } @@ -568,18 +599,17 @@ fn parse_distribution_point( Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, None => py.None(), }; - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; - Ok(x509_module - .getattr(pyo3::intern!(py, "DistributionPoint"))? + Ok(types::DISTRIBUTION_POINT + .get(py)? .call1((full_name, relative_name, reasons, crl_issuer))? .to_object(py)) } pub(crate) fn parse_distribution_points( py: pyo3::Python<'_>, - data: &[u8], + ext: &Extension<'_>, ) -> Result { - let dps = asn1::parse_single::>>(data)?; + let dps = ext.value::>>()?; let py_dps = pyo3::types::PyList::empty(py); for dp in dps { let py_dp = parse_distribution_point(py, dp)?; @@ -592,9 +622,8 @@ pub(crate) fn parse_distribution_point_reasons( py: pyo3::Python<'_>, reasons: Option<&asn1::BitString<'_>>, ) -> Result { - let reason_bit_mapping = py - .import(pyo3::intern!(py, "cryptography.x509.extensions"))? - .getattr(pyo3::intern!(py, "_REASON_BIT_MAPPING"))?; + let reason_bit_mapping = types::REASON_BIT_MAPPING.get(py)?; + Ok(match reasons { Some(bs) => { let mut vec = Vec::new(); @@ -613,16 +642,14 @@ pub(crate) fn encode_distribution_point_reasons( py: pyo3::Python<'_>, py_reasons: &pyo3::PyAny, ) -> pyo3::PyResult { - let reason_flag_mapping = py - .import(pyo3::intern!(py, "cryptography.x509.extensions"))? - .getattr(pyo3::intern!(py, "_CRLREASONFLAGS"))?; + let reason_flag_mapping = types::CRL_REASON_FLAGS.get(py)?; let mut bits = vec![0, 0]; for py_reason in py_reasons.iter()? { let bit = reason_flag_mapping .get_item(py_reason?)? .extract::()?; - set_bit(&mut bits, bit, true) + set_bit(&mut bits, bit, true); } if bits[1] == 0 { bits.truncate(1); @@ -633,10 +660,9 @@ pub(crate) fn encode_distribution_point_reasons( pub(crate) fn parse_authority_key_identifier<'p>( py: pyo3::Python<'p>, - ext_data: &[u8], + ext: &Extension<'_>, ) -> Result<&'p pyo3::PyAny, CryptographyError> { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; - let aki = asn1::parse_single::>(ext_data)?; + let aki = ext.value::>()?; let serial = match aki.authority_cert_serial_number { Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.to_object(py), None => py.None(), @@ -645,23 +671,22 @@ pub(crate) fn parse_authority_key_identifier<'p>( Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, None => py.None(), }; - Ok(x509_module - .getattr(pyo3::intern!(py, "AuthorityKeyIdentifier"))? + Ok(types::AUTHORITY_KEY_IDENTIFIER + .get(py)? .call1((aki.key_identifier, issuer, serial))?) } pub(crate) fn parse_access_descriptions( py: pyo3::Python<'_>, - ext_data: &[u8], + ext: &Extension<'_>, ) -> Result { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let ads = pyo3::types::PyList::empty(py); - let parsed = asn1::parse_single::>(ext_data)?; + let parsed = ext.value::>()?; for access in parsed.unwrap_read().clone() { let py_oid = oid_to_py_oid(py, &access.access_method)?.to_object(py); let gn = x509::parse_general_name(py, access.access_location)?; - let ad = x509_module - .getattr(pyo3::intern!(py, "AccessDescription"))? + let ad = types::ACCESS_DESCRIPTION + .get(py)? .call1((py_oid, gn))? .to_object(py); ads.append(ad)?; @@ -671,170 +696,115 @@ pub(crate) fn parse_access_descriptions( pub fn parse_cert_ext<'p>( py: pyo3::Python<'p>, - oid: asn1::ObjectIdentifier, - ext_data: &[u8], + ext: &Extension<'_>, ) -> CryptographyResult> { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; - match oid { + match ext.extn_id { oid::SUBJECT_ALTERNATIVE_NAME_OID => { - let gn_seq = - asn1::parse_single::>>(ext_data)?; + let gn_seq = ext.value::>()?; let sans = x509::parse_general_names(py, &gn_seq)?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "SubjectAlternativeName"))? - .call1((sans,))?, + types::SUBJECT_ALTERNATIVE_NAME.get(py)?.call1((sans,))?, )) } oid::ISSUER_ALTERNATIVE_NAME_OID => { - let gn_seq = - asn1::parse_single::>>(ext_data)?; + let gn_seq = ext.value::>()?; let ians = x509::parse_general_names(py, &gn_seq)?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "IssuerAlternativeName"))? - .call1((ians,))?, + types::ISSUER_ALTERNATIVE_NAME.get(py)?.call1((ians,))?, )) } oid::TLS_FEATURE_OID => { - let tls_feature_type_to_enum = py - .import(pyo3::intern!(py, "cryptography.x509.extensions"))? - .getattr(pyo3::intern!(py, "_TLS_FEATURE_TYPE_TO_ENUM"))?; + let tls_feature_type_to_enum = types::TLS_FEATURE_TYPE_TO_ENUM.get(py)?; let features = pyo3::types::PyList::empty(py); - for feature in asn1::parse_single::>(ext_data)? { + for feature in ext.value::>()? { let py_feature = tls_feature_type_to_enum.get_item(feature.to_object(py))?; features.append(py_feature)?; } - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "TLSFeature"))? - .call1((features,))?, - )) + Ok(Some(types::TLS_FEATURE.get(py)?.call1((features,))?)) } oid::SUBJECT_KEY_IDENTIFIER_OID => { - let identifier = asn1::parse_single::<&[u8]>(ext_data)?; + let identifier = ext.value::<&[u8]>()?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "SubjectKeyIdentifier"))? + types::SUBJECT_KEY_IDENTIFIER + .get(py)? .call1((identifier,))?, )) } oid::EXTENDED_KEY_USAGE_OID => { let ekus = pyo3::types::PyList::empty(py); - for oid in asn1::parse_single::>(ext_data)? - { + for oid in ext.value::>()? { let oid_obj = oid_to_py_oid(py, &oid)?; ekus.append(oid_obj)?; } - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "ExtendedKeyUsage"))? - .call1((ekus,))?, - )) + Ok(Some(types::EXTENDED_KEY_USAGE.get(py)?.call1((ekus,))?)) } oid::KEY_USAGE_OID => { - let kus = asn1::parse_single::>(ext_data)?; - let digital_signature = kus.has_bit_set(0); - let content_comitment = kus.has_bit_set(1); - let key_encipherment = kus.has_bit_set(2); - let data_encipherment = kus.has_bit_set(3); - let key_agreement = kus.has_bit_set(4); - let key_cert_sign = kus.has_bit_set(5); - let crl_sign = kus.has_bit_set(6); - let encipher_only = kus.has_bit_set(7); - let decipher_only = kus.has_bit_set(8); - Ok(Some( - x509_module.getattr(pyo3::intern!(py, "KeyUsage"))?.call1(( - digital_signature, - content_comitment, - key_encipherment, - data_encipherment, - key_agreement, - key_cert_sign, - crl_sign, - encipher_only, - decipher_only, - ))?, - )) + let kus = ext.value::>()?; + + Ok(Some(types::KEY_USAGE.get(py)?.call1(( + kus.digital_signature(), + kus.content_comitment(), + kus.key_encipherment(), + kus.data_encipherment(), + kus.key_agreement(), + kus.key_cert_sign(), + kus.crl_sign(), + kus.encipher_only(), + kus.decipher_only(), + ))?)) } oid::AUTHORITY_INFORMATION_ACCESS_OID => { - let ads = parse_access_descriptions(py, ext_data)?; + let ads = parse_access_descriptions(py, ext)?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))? - .call1((ads,))?, + types::AUTHORITY_INFORMATION_ACCESS.get(py)?.call1((ads,))?, )) } oid::SUBJECT_INFORMATION_ACCESS_OID => { - let ads = parse_access_descriptions(py, ext_data)?; + let ads = parse_access_descriptions(py, ext)?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "SubjectInformationAccess"))? - .call1((ads,))?, + types::SUBJECT_INFORMATION_ACCESS.get(py)?.call1((ads,))?, )) } oid::CERTIFICATE_POLICIES_OID => { - let cp = parse_cp(py, ext_data)?; - Ok(Some(x509_module.call_method1( - pyo3::intern!(py, "CertificatePolicies"), - (cp,), - )?)) + let cp = parse_cp(py, ext)?; + Ok(Some(types::CERTIFICATE_POLICIES.get(py)?.call1((cp,))?)) } oid::POLICY_CONSTRAINTS_OID => { - let pc = asn1::parse_single::(ext_data)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "PolicyConstraints"))? - .call1((pc.require_explicit_policy, pc.inhibit_policy_mapping))?, - )) + let pc = ext.value::()?; + Ok(Some(types::POLICY_CONSTRAINTS.get(py)?.call1(( + pc.require_explicit_policy, + pc.inhibit_policy_mapping, + ))?)) } oid::OCSP_NO_CHECK_OID => { - asn1::parse_single::<()>(ext_data)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "OCSPNoCheck"))? - .call0()?, - )) + ext.value::<()>()?; + Ok(Some(types::OCSP_NO_CHECK.get(py)?.call0()?)) } oid::INHIBIT_ANY_POLICY_OID => { - let bignum = asn1::parse_single::>(ext_data)?; + let bignum = ext.value::>()?; let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "InhibitAnyPolicy"))? - .call1((pynum,))?, - )) + Ok(Some(types::INHIBIT_ANY_POLICY.get(py)?.call1((pynum,))?)) } oid::BASIC_CONSTRAINTS_OID => { - let bc = asn1::parse_single::(ext_data)?; + let bc = ext.value::()?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "BasicConstraints"))? + types::BASIC_CONSTRAINTS + .get(py)? .call1((bc.ca, bc.path_length))?, )) } - oid::AUTHORITY_KEY_IDENTIFIER_OID => { - Ok(Some(parse_authority_key_identifier(py, ext_data)?)) - } + oid::AUTHORITY_KEY_IDENTIFIER_OID => Ok(Some(parse_authority_key_identifier(py, ext)?)), oid::CRL_DISTRIBUTION_POINTS_OID => { - let dp = parse_distribution_points(py, ext_data)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "CRLDistributionPoints"))? - .call1((dp,))?, - )) + let dp = parse_distribution_points(py, ext)?; + Ok(Some(types::CRL_DISTRIBUTION_POINTS.get(py)?.call1((dp,))?)) } oid::FRESHEST_CRL_OID => { - let dp = parse_distribution_points(py, ext_data)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "FreshestCRL"))? - .call1((dp,))?, - )) + let dp = parse_distribution_points(py, ext)?; + Ok(Some(types::FRESHEST_CRL.get(py)?.call1((dp,))?)) } oid::NAME_CONSTRAINTS_OID => { - let nc = asn1::parse_single::>(ext_data)?; + let nc = ext.value::>()?; let permitted_subtrees = match nc.permitted_subtrees { Some(data) => parse_general_subtrees(py, data)?, None => py.None(), @@ -844,19 +814,19 @@ pub fn parse_cert_ext<'p>( None => py.None(), }; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "NameConstraints"))? + types::NAME_CONSTRAINTS + .get(py)? .call1((permitted_subtrees, excluded_subtrees))?, )) } oid::MS_CERTIFICATE_TEMPLATE => { - let ms_cert_tpl = asn1::parse_single::(ext_data)?; + let ms_cert_tpl = ext.value::()?; let py_oid = oid_to_py_oid(py, &ms_cert_tpl.template_id)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "MSCertificateTemplate"))? - .call1((py_oid, ms_cert_tpl.major_version, ms_cert_tpl.minor_version))?, - )) + Ok(Some(types::MS_CERTIFICATE_TEMPLATE.get(py)?.call1(( + py_oid, + ms_cert_tpl.major_version, + ms_cert_tpl.minor_version, + ))?)) } _ => Ok(None), } @@ -890,23 +860,12 @@ fn create_x509_certificate( ) -> CryptographyResult { let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?; - let serialization_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))?; - let der_encoding = serialization_mod - .getattr(pyo3::intern!(py, "Encoding"))? - .getattr(pyo3::intern!(py, "DER"))?; - let spki_format = serialization_mod - .getattr(pyo3::intern!(py, "PublicFormat"))? - .getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?; + let der = types::ENCODING_DER.get(py)?; + let spki = types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?; let spki_bytes = builder .getattr(pyo3::intern!(py, "_public_key"))? - .call_method1( - pyo3::intern!(py, "public_bytes"), - (der_encoding, spki_format), - )? + .call_method1(pyo3::intern!(py, "public_bytes"), (der, spki))? .extract::<&[u8]>()?; let py_serial = builder @@ -949,7 +908,7 @@ fn create_x509_certificate( signature_alg: sigalg, signature: asn1::BitString::new(signature, 0).unwrap(), })?; - load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) + load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &data).into_py(py), None) } pub(crate) fn set_bit(vals: &mut [u8], n: usize, set: bool) { diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index 8ceb518846d1..a941f50b928c 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -2,15 +2,18 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{oid_to_py_oid, py_oid_to_oid}; -use crate::error::{CryptographyError, CryptographyResult}; -use crate::{exceptions, x509}; use cryptography_x509::common::{Asn1ReadableOrWritable, AttributeTypeValue, RawTlv}; -use cryptography_x509::extensions::{AccessDescription, Extension, Extensions, RawExtensions}; -use cryptography_x509::name::{GeneralName, Name, OtherName, UnvalidatedIA5String}; +use cryptography_x509::extensions::{ + AccessDescription, DuplicateExtensionsError, Extension, Extensions, RawExtensions, +}; +use cryptography_x509::name::{GeneralName, Name, NameReadable, OtherName, UnvalidatedIA5String}; use pyo3::types::IntoPyDict; use pyo3::{IntoPy, ToPyObject}; +use crate::asn1::{oid_to_py_oid, py_oid_to_oid}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, types, x509}; + /// Parse all sections in a PEM file and return the first matching section. /// If no matching sections are found, return an error. pub(crate) fn find_in_pem( @@ -51,18 +54,14 @@ pub(crate) fn encode_name_entry<'p>( py: pyo3::Python<'p>, py_name_entry: &'p pyo3::PyAny, ) -> CryptographyResult> { - let asn1_type = py - .import(pyo3::intern!(py, "cryptography.x509.name"))? - .getattr(pyo3::intern!(py, "_ASN1Type"))?; - let attr_type = py_name_entry.getattr(pyo3::intern!(py, "_type"))?; let tag = attr_type .getattr(pyo3::intern!(py, "value"))? .extract::()?; - let value: &[u8] = if !attr_type.is(asn1_type.getattr(pyo3::intern!(py, "BitString"))?) { - let encoding = if attr_type.is(asn1_type.getattr(pyo3::intern!(py, "BMPString"))?) { + let value: &[u8] = if !attr_type.is(types::ASN1_TYPE_BIT_STRING.get(py)?) { + let encoding = if attr_type.is(types::ASN1_TYPE_BMP_STRING.get(py)?) { "utf_16_be" - } else if attr_type.is(asn1_type.getattr(pyo3::intern!(py, "UniversalString"))?) { + } else if attr_type.is(types::ASN1_TYPE_UNIVERSAL_STRING.get(py)?) { "utf_32_be" } else { "utf8" @@ -101,7 +100,7 @@ pub(crate) fn encode_general_names<'a>( let mut gns = vec![]; for el in py_gns.iter()? { let gn = encode_general_name(py, el?)?; - gns.push(gn) + gns.push(gn); } Ok(gns) } @@ -110,40 +109,39 @@ pub(crate) fn encode_general_name<'a>( py: pyo3::Python<'a>, gn: &'a pyo3::PyAny, ) -> Result, CryptographyError> { - let gn_module = py.import(pyo3::intern!(py, "cryptography.x509.general_name"))?; let gn_type = gn.get_type().as_ref(); let gn_value = gn.getattr(pyo3::intern!(py, "value"))?; - if gn_type.is(gn_module.getattr(pyo3::intern!(py, "DNSName"))?) { + + if gn_type.is(types::DNS_NAME.get(py)?) { Ok(GeneralName::DNSName(UnvalidatedIA5String( gn_value.extract::<&str>()?, ))) - } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RFC822Name"))?) { + } else if gn_type.is(types::RFC822_NAME.get(py)?) { Ok(GeneralName::RFC822Name(UnvalidatedIA5String( gn_value.extract::<&str>()?, ))) - } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "DirectoryName"))?) { + } else if gn_type.is(types::DIRECTORY_NAME.get(py)?) { let name = encode_name(py, gn_value)?; Ok(GeneralName::DirectoryName(name)) - } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "OtherName"))?) { + } else if gn_type.is(types::OTHER_NAME.get(py)?) { Ok(GeneralName::OtherName(OtherName { type_id: py_oid_to_oid(gn.getattr(pyo3::intern!(py, "type_id"))?)?, value: asn1::parse_single(gn_value.extract::<&[u8]>()?).map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( - "OtherName value must be valid DER: {:?}", - e + "OtherName value must be valid DER: {e:?}" )) })?, })) - } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "UniformResourceIdentifier"))?) { + } else if gn_type.is(types::UNIFORM_RESOURCE_IDENTIFIER.get(py)?) { Ok(GeneralName::UniformResourceIdentifier( UnvalidatedIA5String(gn_value.extract::<&str>()?), )) - } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "IPAddress"))?) { + } else if gn_type.is(types::IP_ADDRESS.get(py)?) { Ok(GeneralName::IPAddress( gn.call_method0(pyo3::intern!(py, "_packed"))? .extract::<&[u8]>()?, )) - } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RegisteredID"))?) { + } else if gn_type.is(types::REGISTERED_ID.get(py)?) { let oid = py_oid_to_oid(gn_value)?; Ok(GeneralName::RegisteredID(oid)) } else { @@ -173,26 +171,21 @@ pub(crate) fn encode_access_descriptions<'a>( pub(crate) fn parse_name<'p>( py: pyo3::Python<'p>, - name: &Name<'_>, + name: &NameReadable<'_>, ) -> Result<&'p pyo3::PyAny, CryptographyError> { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let py_rdns = pyo3::types::PyList::empty(py); - for rdn in name.unwrap_read().clone() { + for rdn in name.clone() { let py_rdn = parse_rdn(py, &rdn)?; py_rdns.append(py_rdn)?; } - Ok(x509_module.call_method1(pyo3::intern!(py, "Name"), (py_rdns,))?) + Ok(types::NAME.get(py)?.call1((py_rdns,))?) } fn parse_name_attribute( py: pyo3::Python<'_>, attribute: AttributeTypeValue<'_>, ) -> Result { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let oid = oid_to_py_oid(py, &attribute.type_id)?.to_object(py); - let tag_enum = py - .import(pyo3::intern!(py, "cryptography.x509.name"))? - .getattr(pyo3::intern!(py, "_ASN1_TYPE_TO_ENUM"))?; let tag_val = attribute .value .tag() @@ -203,7 +196,7 @@ fn parse_name_attribute( )) })? .to_object(py); - let py_tag = tag_enum.get_item(tag_val)?; + let py_tag = types::ASN1_TYPE_TO_ENUM.get(py)?.get_item(tag_val)?; let py_data = match attribute.value.tag().as_u8() { // BitString tag value Some(3) => pyo3::types::PyBytes::new(py, attribute.value.data()), @@ -224,12 +217,9 @@ fn parse_name_attribute( } }; let kwargs = [("_validate", false)].into_py_dict(py); - Ok(x509_module - .call_method( - pyo3::intern!(py, "NameAttribute"), - (oid, py_data, py_tag), - Some(kwargs), - )? + Ok(types::NAME_ATTRIBUTE + .get(py)? + .call((oid, py_data, py_tag), Some(kwargs))? .to_object(py)) } @@ -237,14 +227,14 @@ pub(crate) fn parse_rdn<'a>( py: pyo3::Python<'_>, rdn: &asn1::SetOf<'a, AttributeTypeValue<'a>>, ) -> Result { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let py_attrs = pyo3::types::PyList::empty(py); for attribute in rdn.clone() { let na = parse_name_attribute(py, attribute)?; py_attrs.append(na)?; } - Ok(x509_module - .call_method1(pyo3::intern!(py, "RelativeDistinguishedName"), (py_attrs,))? + Ok(types::RELATIVE_DISTINGUISHED_NAME + .get(py)? + .call1((py_attrs,))? .to_object(py)) } @@ -252,44 +242,37 @@ pub(crate) fn parse_general_name( py: pyo3::Python<'_>, gn: GeneralName<'_>, ) -> Result { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let py_gn = match gn { GeneralName::OtherName(data) => { let oid = oid_to_py_oid(py, &data.type_id)?.to_object(py); - x509_module - .call_method1( - pyo3::intern!(py, "OtherName"), - (oid, data.value.full_data()), - )? + types::OTHER_NAME + .get(py)? + .call1((oid, data.value.full_data()))? .to_object(py) } - GeneralName::RFC822Name(data) => x509_module - .getattr(pyo3::intern!(py, "RFC822Name"))? + GeneralName::RFC822Name(data) => types::RFC822_NAME + .get(py)? .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), - GeneralName::DNSName(data) => x509_module - .getattr(pyo3::intern!(py, "DNSName"))? + GeneralName::DNSName(data) => types::DNS_NAME + .get(py)? .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::DirectoryName(data) => { - let py_name = parse_name(py, &data)?; - x509_module - .call_method1(pyo3::intern!(py, "DirectoryName"), (py_name,))? + let py_name = parse_name(py, data.unwrap_read())?; + types::DIRECTORY_NAME + .get(py)? + .call1((py_name,))? .to_object(py) } - GeneralName::UniformResourceIdentifier(data) => x509_module - .getattr(pyo3::intern!(py, "UniformResourceIdentifier"))? + GeneralName::UniformResourceIdentifier(data) => types::UNIFORM_RESOURCE_IDENTIFIER + .get(py)? .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::IPAddress(data) => { - let ip_module = py.import(pyo3::intern!(py, "ipaddress"))?; if data.len() == 4 || data.len() == 16 { - let addr = ip_module - .call_method1(pyo3::intern!(py, "ip_address"), (data,))? - .to_object(py); - x509_module - .call_method1(pyo3::intern!(py, "IPAddress"), (addr,))? - .to_object(py) + let addr = types::IPADDRESS_IPADDRESS.get(py)?.call1((data,))?; + types::IP_ADDRESS.get(py)?.call1((addr,))?.to_object(py) } else { // if it's not an IPv4 or IPv6 we assume it's an IPNetwork and // verify length in this function. @@ -298,9 +281,7 @@ pub(crate) fn parse_general_name( } GeneralName::RegisteredID(data) => { let oid = oid_to_py_oid(py, &data)?.to_object(py); - x509_module - .call_method1(pyo3::intern!(py, "RegisteredID"), (oid,))? - .to_object(py) + types::REGISTERED_ID.get(py)?.call1((oid,))?.to_object(py) } _ => { return Err(CryptographyError::from( @@ -329,8 +310,6 @@ fn create_ip_network( py: pyo3::Python<'_>, data: &[u8], ) -> Result { - let ip_module = py.import(pyo3::intern!(py, "ipaddress"))?; - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let prefix = match data.len() { 8 => { let num = u32::from_be_bytes(data[4..].try_into().unwrap()); @@ -344,28 +323,21 @@ fn create_ip_network( format!("Invalid IPNetwork, must be 8 bytes for IPv4 and 32 bytes for IPv6. Found length: {}", data.len()), ))), }; - let base = ip_module.call_method1( - "ip_address", - (pyo3::types::PyBytes::new(py, &data[..data.len() / 2]),), - )?; + let base = types::IPADDRESS_IPADDRESS + .get(py)? + .call1((pyo3::types::PyBytes::new(py, &data[..data.len() / 2]),))?; let net = format!( "{}/{}", base.getattr(pyo3::intern!(py, "exploded"))? .extract::<&str>()?, prefix? ); - let addr = ip_module - .call_method1(pyo3::intern!(py, "ip_network"), (net,))? - .to_object(py); - Ok(x509_module - .call_method1(pyo3::intern!(py, "IPAddress"), (addr,))? - .to_object(py)) + let addr = types::IPADDRESS_IPNETWORK.get(py)?.call1((net,))?; + Ok(types::IP_ADDRESS.get(py)?.call1((addr,))?.to_object(py)) } fn ipv4_netmask(num: u32) -> Result { - // we invert and check leading zeros because leading_ones wasn't stabilized - // until 1.46.0. When we raise our MSRV we should change this - if (!num).leading_zeros() + num.trailing_zeros() != 32 { + if num.leading_ones() + num.trailing_zeros() != 32 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid netmask"), )); @@ -374,9 +346,7 @@ fn ipv4_netmask(num: u32) -> Result { } fn ipv6_netmask(num: u128) -> Result { - // we invert and check leading zeros because leading_ones wasn't stabilized - // until 1.46.0. When we raise our MSRV we should change this - if (!num).leading_zeros() + num.trailing_zeros() != 128 { + if num.leading_ones() + num.trailing_zeros() != 128 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid netmask"), )); @@ -386,53 +356,45 @@ fn ipv6_netmask(num: u128) -> Result { pub(crate) fn parse_and_cache_extensions< 'p, - F: Fn(&asn1::ObjectIdentifier, &[u8]) -> Result, CryptographyError>, + F: Fn(&Extension<'_>) -> Result, CryptographyError>, >( py: pyo3::Python<'p>, - cached_extensions: &mut Option, + cached_extensions: &pyo3::sync::GILOnceCell, raw_extensions: &Option>, parse_ext: F, ) -> pyo3::PyResult { - if let Some(cached) = cached_extensions { - return Ok(cached.clone_ref(py)); - } - - let extensions = match Extensions::from_raw_extensions(raw_extensions.as_ref()) { - Ok(extensions) => extensions, - Err(oid) => { - let oid_obj = oid_to_py_oid(py, &oid)?; - return Err(exceptions::DuplicateExtension::new_err(( - format!("Duplicate {} extension found", oid), - oid_obj.into_py(py), - ))); - } - }; - - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; - let exts = pyo3::types::PyList::empty(py); - if let Some(extensions) = extensions.as_raw() { - for raw_ext in extensions.unwrap_read().clone() { - let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?; - - let extn_value = match parse_ext(&raw_ext.extn_id, raw_ext.extn_value)? { - Some(e) => e, - None => x509_module.call_method1( - pyo3::intern!(py, "UnrecognizedExtension"), - (oid_obj, raw_ext.extn_value), - )?, + cached_extensions + .get_or_try_init(py, || { + let extensions = match Extensions::from_raw_extensions(raw_extensions.as_ref()) { + Ok(extensions) => extensions, + Err(DuplicateExtensionsError(oid)) => { + let oid_obj = oid_to_py_oid(py, &oid)?; + return Err(exceptions::DuplicateExtension::new_err(( + format!("Duplicate {} extension found", &oid), + oid_obj.into_py(py), + ))); + } }; - let ext_obj = x509_module.call_method1( - pyo3::intern!(py, "Extension"), - (oid_obj, raw_ext.critical, extn_value), - )?; - exts.append(ext_obj)?; - } - } - let extensions = x509_module - .call_method1(pyo3::intern!(py, "Extensions"), (exts,))? - .to_object(py); - *cached_extensions = Some(extensions.clone_ref(py)); - Ok(extensions) + + let exts = pyo3::types::PyList::empty(py); + for raw_ext in extensions.iter() { + let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?; + + let extn_value = match parse_ext(&raw_ext)? { + Some(e) => e, + None => types::UNRECOGNIZED_EXTENSION + .get(py)? + .call1((oid_obj, raw_ext.extn_value))?, + }; + let ext_obj = + types::EXTENSION + .get(py)? + .call1((oid_obj, raw_ext.critical, extn_value))?; + exts.append(ext_obj)?; + } + Ok(types::EXTENSIONS.get(py)?.call1((exts,))?.to_object(py)) + }) + .map(|p| p.clone_ref(py)) } pub(crate) fn encode_extensions< @@ -447,18 +409,13 @@ pub(crate) fn encode_extensions< py_exts: &'p pyo3::PyAny, encode_ext: F, ) -> pyo3::PyResult>> { - let unrecognized_extension_type: &pyo3::types::PyType = py - .import(pyo3::intern!(py, "cryptography.x509"))? - .getattr(pyo3::intern!(py, "UnrecognizedExtension"))? - .extract()?; - let mut exts = vec![]; for py_ext in py_exts.iter()? { let py_ext = py_ext?; let oid = py_oid_to_oid(py_ext.getattr(pyo3::intern!(py, "oid"))?)?; let ext_val = py_ext.getattr(pyo3::intern!(py, "value"))?; - if ext_val.is_instance(unrecognized_extension_type)? { + if ext_val.is_instance(types::UNRECOGNIZED_EXTENSION.get(py)?)? { exts.push(Extension { extn_id: oid, critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, @@ -476,12 +433,11 @@ pub(crate) fn encode_extensions< extn_id: oid, critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, extn_value: py_data.as_bytes(), - }) + }); } None => { return Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( - "Extension not supported: {}", - oid + "Extension not supported: {oid}" ))) } } @@ -508,8 +464,7 @@ fn encode_extension_value<'p>( } Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( - "Extension not supported: {}", - oid + "Extension not supported: {oid}" ))) } @@ -517,40 +472,65 @@ pub(crate) fn datetime_to_py<'p>( py: pyo3::Python<'p>, dt: &asn1::DateTime, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let datetime_module = py.import(pyo3::intern!(py, "datetime"))?; - datetime_module - .getattr(pyo3::intern!(py, "datetime"))? - .call1(( - dt.year(), - dt.month(), - dt.day(), - dt.hour(), - dt.minute(), - dt.second(), - )) + types::DATETIME_DATETIME.get(py)?.call1(( + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second(), + )) +} + +pub(crate) fn datetime_to_py_utc<'p>( + py: pyo3::Python<'p>, + dt: &asn1::DateTime, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + let timezone = types::DATETIME_TIMEZONE_UTC.get(py)?; + types::DATETIME_DATETIME.get(py)?.call1(( + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second(), + 0, + timezone, + )) } pub(crate) fn py_to_datetime( py: pyo3::Python<'_>, val: &pyo3::PyAny, ) -> pyo3::PyResult { + // We treat naive datetimes as UTC times, while aware datetimes get + // normalized to UTC before conversion. + let val_utc = if val.getattr(pyo3::intern!(py, "tzinfo"))?.is_none() { + val + } else { + let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; + val.call_method1(pyo3::intern!(py, "astimezone"), (utc,))? + }; + Ok(asn1::DateTime::new( - val.getattr(pyo3::intern!(py, "year"))?.extract()?, - val.getattr(pyo3::intern!(py, "month"))?.extract()?, - val.getattr(pyo3::intern!(py, "day"))?.extract()?, - val.getattr(pyo3::intern!(py, "hour"))?.extract()?, - val.getattr(pyo3::intern!(py, "minute"))?.extract()?, - val.getattr(pyo3::intern!(py, "second"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "year"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "month"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "day"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "hour"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "minute"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "second"))?.extract()?, ) .unwrap()) } pub(crate) fn datetime_now(py: pyo3::Python<'_>) -> pyo3::PyResult { + let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; + py_to_datetime( py, - py.import(pyo3::intern!(py, "datetime"))? - .getattr(pyo3::intern!(py, "datetime"))? - .call_method0(pyo3::intern!(py, "utcnow"))?, + types::DATETIME_DATETIME + .get(py)? + .call_method1(pyo3::intern!(py, "now"), (utc,))?, ) } diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs index 92301503563f..f4d6feebc820 100644 --- a/src/rust/src/x509/crl.rs +++ b/src/rust/src/x509/crl.rs @@ -2,30 +2,44 @@ // 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, IssuerAlternativeName}; +use cryptography_x509::{ + common, + crl::{ + self, CertificateRevocationList as RawCertificateRevocationList, + RevokedCertificate as RawRevokedCertificate, + }, + name, oid, +}; +use pyo3::{IntoPy, ToPyObject}; + use crate::asn1::{ big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, }; +use crate::backend::hashes::Hash; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, extensions, sign}; -use crate::{exceptions, x509}; -use cryptography_x509::{common, crl, name, oid}; -use pyo3::{IntoPy, ToPyObject}; -use std::sync::Arc; +use crate::{exceptions, types, x509}; #[pyo3::prelude::pyfunction] fn load_der_x509_crl( py: pyo3::Python<'_>, data: pyo3::Py, + backend: Option<&pyo3::PyAny>, ) -> Result { + let _ = backend; + let owned = OwnedCertificateRevocationList::try_new(data, |data| { asn1::parse_single(data.as_bytes(py)) })?; - let version = owned.borrow_value().tbs_cert_list.version.unwrap_or(1); + let version = owned.borrow_dependent().tbs_cert_list.version.unwrap_or(1); if version != 1 { return Err(CryptographyError::from( exceptions::InvalidVersion::new_err(( - format!("{} is not a valid CRL version", version), + format!("{version} is not a valid CRL version"), version, )), )); @@ -33,8 +47,8 @@ fn load_der_x509_crl( Ok(CertificateRevocationList { owned: Arc::new(owned), - revoked_certs: pyo3::once_cell::GILOnceCell::new(), - cached_extensions: None, + revoked_certs: pyo3::sync::GILOnceCell::new(), + cached_extensions: pyo3::sync::GILOnceCell::new(), }) } @@ -42,49 +56,53 @@ fn load_der_x509_crl( fn load_pem_x509_crl( py: pyo3::Python<'_>, data: &[u8], + backend: Option<&pyo3::PyAny>, ) -> Result { + let _ = backend; + let block = x509::find_in_pem( data, - |p| p.tag == "X509 CRL", + |p| p.tag() == "X509 CRL", "Valid PEM but no BEGIN X509 CRL/END X509 delimiters. Are you sure this is a CRL?", )?; load_der_x509_crl( py, - pyo3::types::PyBytes::new(py, &block.contents).into_py(py), + pyo3::types::PyBytes::new(py, block.contents()).into_py(py), + None, ) } -#[ouroboros::self_referencing] -struct OwnedCertificateRevocationList { - data: pyo3::Py, - #[borrows(data)] - #[covariant] - value: crl::CertificateRevocationList<'this>, -} +self_cell::self_cell!( + struct OwnedCertificateRevocationList { + owner: pyo3::Py, + #[covariant] + dependent: RawCertificateRevocationList, + } +); -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] struct CertificateRevocationList { owned: Arc, - revoked_certs: pyo3::once_cell::GILOnceCell>, - cached_extensions: Option, + revoked_certs: pyo3::sync::GILOnceCell>, + cached_extensions: pyo3::sync::GILOnceCell, } impl CertificateRevocationList { fn public_bytes_der(&self) -> CryptographyResult> { - Ok(asn1::write_single(self.owned.borrow_value())?) + Ok(asn1::write_single(&self.owned.borrow_dependent())?) } fn revoked_cert(&self, py: pyo3::Python<'_>, idx: usize) -> RevokedCertificate { RevokedCertificate { owned: self.revoked_certs.get(py).unwrap()[idx].clone(), - cached_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), } } fn len(&self) -> usize { self.owned - .borrow_value() + .borrow_dependent() .tbs_cert_list .revoked_certificates .as_ref() @@ -94,33 +112,22 @@ impl CertificateRevocationList { #[pyo3::prelude::pymethods] impl CertificateRevocationList { - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, CertificateRevocationList>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => { - Ok(self.owned.borrow_value() == other.owned.borrow_value()) - } - pyo3::basic::CompareOp::Ne => { - Ok(self.owned.borrow_value() != other.owned.borrow_value()) - } - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "CRLs cannot be ordered", - )), - } + fn __eq__(&self, other: pyo3::PyRef<'_, CertificateRevocationList>) -> bool { + self.owned.borrow_dependent() == other.owned.borrow_dependent() } fn __len__(&self) -> usize { self.len() } + // Silenced due to false-positives + // https://github.com/rust-lang/rust-clippy/issues/12135 + #[allow(clippy::useless_asref)] fn __iter__(&self) -> CRLIterator { CRLIterator { contents: OwnedCRLIteratorData::try_new(Arc::clone(&self.owned), |v| { Ok::<_, ()>( - v.borrow_value() + v.borrow_dependent() .tbs_cert_list .revoked_certificates .as_ref() @@ -145,7 +152,7 @@ impl CertificateRevocationList { revoked_certs }); - if idx.is_instance_of::()? { + if idx.is_instance_of::() { let indices = idx .downcast::()? .indices(self.len().try_into().unwrap())?; @@ -170,21 +177,18 @@ impl CertificateRevocationList { fn fingerprint<'p>( &self, py: pyo3::Python<'p>, - algorithm: pyo3::PyObject, + algorithm: &pyo3::PyAny, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let hashes_mod = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; - let h = hashes_mod - .getattr(pyo3::intern!(py, "Hash"))? - .call1((algorithm,))?; - let data = self.public_bytes_der()?; - h.call_method1(pyo3::intern!(py, "update"), (data.as_slice(),))?; - h.call_method0(pyo3::intern!(py, "finalize")) + + let mut h = Hash::new(py, algorithm, None)?; + h.update_bytes(&data)?; + Ok(h.finalize(py)?) } #[getter] fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - oid_to_py_oid(py, self.owned.borrow_value().signature_algorithm.oid()) + oid_to_py_oid(py, self.owned.borrow_dependent().signature_algorithm.oid()) } #[getter] @@ -193,22 +197,29 @@ impl CertificateRevocationList { py: pyo3::Python<'p>, ) -> pyo3::PyResult<&'p pyo3::PyAny> { let oid = self.signature_algorithm_oid(py)?; - let oid_module = py.import(pyo3::intern!(py, "cryptography.hazmat._oid"))?; - match oid_module - .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))? - .get_item(oid) - { + match types::SIG_OIDS_TO_HASH.get(py)?.get_item(oid) { Ok(v) => Ok(v), Err(_) => Err(exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", - self.owned.borrow_value().signature_algorithm.oid(), + self.owned.borrow_dependent().signature_algorithm.oid() ))), } } + #[getter] + fn signature_algorithm_parameters<'p>( + &'p self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::PyAny> { + sign::identify_signature_algorithm_parameters( + py, + &self.owned.borrow_dependent().signature_algorithm, + ) + } + #[getter] fn signature(&self) -> &[u8] { - self.owned.borrow_value().signature_value.as_bytes() + self.owned.borrow_dependent().signature_value.as_bytes() } #[getter] @@ -216,7 +227,7 @@ impl CertificateRevocationList { &self, py: pyo3::Python<'p>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let b = asn1::write_single(&self.owned.borrow_value().tbs_cert_list)?; + let b = asn1::write_single(&self.owned.borrow_dependent().tbs_cert_list)?; Ok(pyo3::types::PyBytes::new(py, &b)) } @@ -225,7 +236,7 @@ impl CertificateRevocationList { py: pyo3::Python<'p>, encoding: &'p pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let result = asn1::write_single(self.owned.borrow_value())?; + let result = asn1::write_single(&self.owned.borrow_dependent())?; encode_der_data(py, "X509 CRL".to_string(), result, encoding) } @@ -234,24 +245,62 @@ impl CertificateRevocationList { fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { Ok(x509::parse_name( py, - &self.owned.borrow_value().tbs_cert_list.issuer, + self.owned + .borrow_dependent() + .tbs_cert_list + .issuer + .unwrap_read(), )?) } #[getter] fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - match &self.owned.borrow_value().tbs_cert_list.next_update { + let warning_cls = types::DEPRECATED_IN_42.get(py)?; + pyo3::PyErr::warn( + py, + warning_cls, + "Properties that return a naïve datetime object have been deprecated. Please switch to next_update_utc.", + 1, + )?; + match &self.owned.borrow_dependent().tbs_cert_list.next_update { Some(t) => x509::datetime_to_py(py, t.as_datetime()), None => Ok(py.None().into_ref(py)), } } + #[getter] + fn next_update_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + match &self.owned.borrow_dependent().tbs_cert_list.next_update { + Some(t) => x509::datetime_to_py_utc(py, t.as_datetime()), + None => Ok(py.None().into_ref(py)), + } + } + #[getter] fn last_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + let warning_cls = types::DEPRECATED_IN_42.get(py)?; + pyo3::PyErr::warn( + py, + warning_cls, + "Properties that return a naïve datetime object have been deprecated. Please switch to last_update_utc.", + 1, + )?; x509::datetime_to_py( py, self.owned - .borrow_value() + .borrow_dependent() + .tbs_cert_list + .this_update + .as_datetime(), + ) + } + + #[getter] + fn last_update_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + x509::datetime_to_py_utc( + py, + self.owned + .borrow_dependent() .tbs_cert_list .this_update .as_datetime(), @@ -259,57 +308,42 @@ impl CertificateRevocationList { } #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let tbs_cert_list = &self.owned.borrow_value().tbs_cert_list; + fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let tbs_cert_list = &self.owned.borrow_dependent().tbs_cert_list; - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, - &mut self.cached_extensions, + &self.cached_extensions, &tbs_cert_list.raw_crl_extensions, - |oid, ext_data| match *oid { + |ext| match ext.extn_id { oid::CRL_NUMBER_OID => { - let bignum = asn1::parse_single::>(ext_data)?; + let bignum = ext.value::>()?; let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "CRLNumber"))? - .call1((pynum,))?, - )) + Ok(Some(types::CRL_NUMBER.get(py)?.call1((pynum,))?)) } oid::DELTA_CRL_INDICATOR_OID => { - let bignum = asn1::parse_single::>(ext_data)?; + let bignum = ext.value::>()?; let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "DeltaCRLIndicator"))? - .call1((pynum,))?, - )) + Ok(Some(types::DELTA_CRL_INDICATOR.get(py)?.call1((pynum,))?)) } oid::ISSUER_ALTERNATIVE_NAME_OID => { - let gn_seq = asn1::parse_single::>>( - ext_data, - )?; + let gn_seq = ext.value::>()?; let ians = x509::parse_general_names(py, &gn_seq)?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "IssuerAlternativeName"))? - .call1((ians,))?, + types::ISSUER_ALTERNATIVE_NAME.get(py)?.call1((ians,))?, )) } oid::AUTHORITY_INFORMATION_ACCESS_OID => { - let ads = certificate::parse_access_descriptions(py, ext_data)?; + let ads = certificate::parse_access_descriptions(py, ext)?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))? - .call1((ads,))?, + types::AUTHORITY_INFORMATION_ACCESS.get(py)?.call1((ads,))?, )) } - oid::AUTHORITY_KEY_IDENTIFIER_OID => Ok(Some( - certificate::parse_authority_key_identifier(py, ext_data)?, - )), + oid::AUTHORITY_KEY_IDENTIFIER_OID => { + Ok(Some(certificate::parse_authority_key_identifier(py, ext)?)) + } oid::ISSUING_DISTRIBUTION_POINT_OID => { - let idp = asn1::parse_single::>(ext_data)?; + let idp = ext.value::>()?; let (full_name, relative_name) = match idp.distribution_point { Some(data) => certificate::parse_distribution_point_name(py, data)?, None => (py.None(), py.None()), @@ -322,27 +356,19 @@ impl CertificateRevocationList { } else { py.None() }; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "IssuingDistributionPoint"))? - .call1(( - full_name, - relative_name, - idp.only_contains_user_certs, - idp.only_contains_ca_certs, - py_reasons, - idp.indirect_crl, - idp.only_contains_attribute_certs, - ))?, - )) + Ok(Some(types::ISSUING_DISTRIBUTION_POINT.get(py)?.call1(( + full_name, + relative_name, + idp.only_contains_user_certs, + idp.only_contains_ca_certs, + py_reasons, + idp.indirect_crl, + idp.only_contains_attribute_certs, + ))?)) } oid::FRESHEST_CRL_OID => { - let dp = certificate::parse_distribution_points(py, ext_data)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "FreshestCRL"))? - .call1((dp,))?, - )) + let dp = certificate::parse_distribution_points(py, ext)?; + Ok(Some(types::FRESHEST_CRL.get(py)?.call1((dp,))?)) } _ => Ok(None), }, @@ -350,13 +376,13 @@ impl CertificateRevocationList { } fn get_revoked_certificate_by_serial_number( - &mut self, + &self, py: pyo3::Python<'_>, serial: &pyo3::types::PyLong, ) -> pyo3::PyResult> { let serial_bytes = py_uint_to_big_endian_bytes(py, serial)?; let owned = OwnedRevokedCertificate::try_new(Arc::clone(&self.owned), |v| { - let certs = match &v.borrow_value().tbs_cert_list.revoked_certificates { + let certs = match &v.borrow_dependent().tbs_cert_list.revoked_certificates { Some(certs) => certs.unwrap_read().clone(), None => return Err(()), }; @@ -372,7 +398,7 @@ impl CertificateRevocationList { match owned { Ok(o) => Ok(Some(RevokedCertificate { owned: o, - cached_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), })), Err(()) => Ok(None), } @@ -383,8 +409,8 @@ impl CertificateRevocationList { py: pyo3::Python<'p>, public_key: &'p pyo3::PyAny, ) -> CryptographyResult { - if slf.owned.borrow_value().tbs_cert_list.signature - != slf.owned.borrow_value().signature_algorithm + if slf.owned.borrow_dependent().tbs_cert_list.signature + != slf.owned.borrow_dependent().signature_algorithm { return Ok(false); }; @@ -396,21 +422,23 @@ impl CertificateRevocationList { Ok(sign::verify_signature_with_signature_algorithm( py, public_key, - &slf.owned.borrow_value().signature_algorithm, - slf.owned.borrow_value().signature_value.as_bytes(), - &asn1::write_single(&slf.owned.borrow_value().tbs_cert_list)?, + &slf.owned.borrow_dependent().signature_algorithm, + slf.owned.borrow_dependent().signature_value.as_bytes(), + &asn1::write_single(&slf.owned.borrow_dependent().tbs_cert_list)?, ) .is_ok()) } } -#[ouroboros::self_referencing] -struct OwnedCRLIteratorData { - data: Arc, - #[borrows(data)] - #[covariant] - value: Option>>, -} +type RawCRLIterator<'a> = Option>>; +self_cell::self_cell!( + struct OwnedCRLIteratorData { + owner: Arc, + + #[covariant] + dependent: RawCRLIterator, + } +); #[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] struct CRLIterator { @@ -426,15 +454,22 @@ fn try_map_arc_data_mut_crl_iterator( &mut Option>>, ) -> Result, E>, ) -> Result { - OwnedRevokedCertificate::try_new(Arc::clone(it.borrow_data()), |inner_it| { - it.with_value_mut(|value| f(inner_it, unsafe { std::mem::transmute(value) })) + OwnedRevokedCertificate::try_new(Arc::clone(it.borrow_owner()), |inner_it| { + // SAFETY: This is safe because `Arc::clone` ensures the data is + // alive, but Rust doesn't understand the lifetime relationship it + // produces. Open-coded implementation of the API discussed in + // https://github.com/joshua-maros/ouroboros/issues/38 + it.with_dependent_mut(|_, value| f(inner_it, unsafe { std::mem::transmute(value) })) }) } #[pyo3::prelude::pymethods] impl CRLIterator { fn __len__(&self) -> usize { - self.contents.borrow_value().clone().map_or(0, |v| v.len()) + self.contents + .borrow_dependent() + .clone() + .map_or(0, |v| v.len()) } fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { @@ -452,56 +487,77 @@ impl CRLIterator { .ok()?; Some(RevokedCertificate { owned: revoked, - cached_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), }) } } -#[ouroboros::self_referencing] -struct OwnedRevokedCertificate { - data: Arc, - #[borrows(data)] - #[covariant] - value: crl::RevokedCertificate<'this>, -} +self_cell::self_cell!( + struct OwnedRevokedCertificate { + owner: Arc, + #[covariant] + dependent: RawRevokedCertificate, + } +); impl Clone for OwnedRevokedCertificate { fn clone(&self) -> OwnedRevokedCertificate { - // This is safe because `Arc::clone` ensures the data is alive, but - // Rust doesn't understand the lifetime relationship it produces. - // Open-coded implementation of the API discussed in + // SAFETY: This is safe because `Arc::clone` ensures the data is + // alive, but Rust doesn't understand the lifetime relationship it + // produces. Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 - OwnedRevokedCertificate::new(Arc::clone(self.borrow_data()), |_| unsafe { - std::mem::transmute(self.borrow_value().clone()) + OwnedRevokedCertificate::new(Arc::clone(self.borrow_owner()), |_| unsafe { + std::mem::transmute(self.borrow_dependent().clone()) }) } } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] struct RevokedCertificate { owned: OwnedRevokedCertificate, - cached_extensions: Option, + cached_extensions: pyo3::sync::GILOnceCell, } #[pyo3::prelude::pymethods] impl RevokedCertificate { #[getter] fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - big_byte_slice_to_py_int(py, self.owned.borrow_value().user_certificate.as_bytes()) + big_byte_slice_to_py_int( + py, + self.owned.borrow_dependent().user_certificate.as_bytes(), + ) } #[getter] fn revocation_date<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - x509::datetime_to_py(py, self.owned.borrow_value().revocation_date.as_datetime()) + let warning_cls = types::DEPRECATED_IN_42.get(py)?; + pyo3::PyErr::warn( + py, + warning_cls, + "Properties that return a naïve datetime object have been deprecated. Please switch to revocation_date_utc.", + 1, + )?; + x509::datetime_to_py( + py, + self.owned.borrow_dependent().revocation_date.as_datetime(), + ) + } + + #[getter] + fn revocation_date_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + x509::datetime_to_py_utc( + py, + self.owned.borrow_dependent().revocation_date.as_datetime(), + ) } #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { x509::parse_and_cache_extensions( py, - &mut self.cached_extensions, - &self.owned.borrow_value().raw_crl_entry_extensions, - |oid, ext_data| parse_crl_entry_ext(py, oid.clone(), ext_data), + &self.cached_extensions, + &self.owned.borrow_dependent().raw_crl_entry_extensions, + |ext| parse_crl_entry_ext(py, ext), ) } } @@ -510,7 +566,6 @@ pub(crate) fn parse_crl_reason_flags<'p>( py: pyo3::Python<'p>, reason: &crl::CRLReason, ) -> CryptographyResult<&'p pyo3::PyAny> { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let flag_name = match reason.value() { 0 => "unspecified", 1 => "key_compromise", @@ -525,49 +580,32 @@ pub(crate) fn parse_crl_reason_flags<'p>( value => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( - "Unsupported reason code: {}", - value + "Unsupported reason code: {value}" )), )) } }; - Ok(x509_module - .getattr(pyo3::intern!(py, "ReasonFlags"))? - .getattr(flag_name)?) + Ok(types::REASON_FLAGS.get(py)?.getattr(flag_name)?) } pub fn parse_crl_entry_ext<'p>( py: pyo3::Python<'p>, - oid: asn1::ObjectIdentifier, - data: &[u8], + ext: &Extension<'_>, ) -> CryptographyResult> { - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; - match oid { + match ext.extn_id { oid::CRL_REASON_OID => { - let flags = parse_crl_reason_flags(py, &asn1::parse_single::(data)?)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "CRLReason"))? - .call1((flags,))?, - )) + let flags = parse_crl_reason_flags(py, &ext.value::()?)?; + Ok(Some(types::CRL_REASON.get(py)?.call1((flags,))?)) } oid::CERTIFICATE_ISSUER_OID => { - let gn_seq = asn1::parse_single::>>(data)?; + let gn_seq = ext.value::>>()?; let gns = x509::parse_general_names(py, &gn_seq)?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "CertificateIssuer"))? - .call1((gns,))?, - )) + Ok(Some(types::CERTIFICATE_ISSUER.get(py)?.call1((gns,))?)) } oid::INVALIDITY_DATE_OID => { - let time = asn1::parse_single::(data)?; + let time = ext.value::()?; let py_dt = x509::datetime_to_py(py, time.as_datetime())?; - Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "InvalidityDate"))? - .call1((py_dt,))?, - )) + Ok(Some(types::INVALIDITY_DATE.get(py)?.call1((py_dt,))?)) } _ => Ok(None), } @@ -579,13 +617,10 @@ fn create_x509_crl( builder: &pyo3::PyAny, private_key: &pyo3::PyAny, hash_algorithm: &pyo3::PyAny, + rsa_padding: &pyo3::PyAny, ) -> CryptographyResult { - let sigalg = x509::sign::compute_signature_algorithm( - py, - private_key, - hash_algorithm, - py.None().into_ref(py), - )?; + let sigalg = + x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?; let mut revoked_certs = vec![]; for py_revoked_cert in builder .getattr(pyo3::intern!(py, "_revoked_certificates"))? @@ -595,7 +630,8 @@ fn create_x509_crl( let serial_number = py_revoked_cert .getattr(pyo3::intern!(py, "serial_number"))? .extract()?; - let py_revocation_date = py_revoked_cert.getattr(pyo3::intern!(py, "revocation_date"))?; + let py_revocation_date = + py_revoked_cert.getattr(pyo3::intern!(py, "revocation_date_utc"))?; revoked_certs.push(crl::RevokedCertificate { user_certificate: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, serial_number)?) .unwrap(), @@ -632,19 +668,14 @@ fn create_x509_crl( }; let tbs_bytes = asn1::write_single(&tbs_cert_list)?; - let signature = x509::sign::sign_data( - py, - private_key, - hash_algorithm, - py.None().into_ref(py), - &tbs_bytes, - )?; + let signature = + x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?; let data = asn1::write_single(&crl::CertificateRevocationList { tbs_cert_list, signature_algorithm: sigalg, signature_value: asn1::BitString::new(signature, 0).unwrap(), })?; - load_der_x509_crl(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) + load_der_x509_crl(py, pyo3::types::PyBytes::new(py, &data).into_py(py), None) } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs index 2e7797f49baa..c49f6e04421a 100644 --- a/src/rust/src/x509/csr.rs +++ b/src/rust/src/x509/csr.rs @@ -2,78 +2,63 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{encode_der_data, oid_to_py_oid, py_oid_to_oid}; -use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509::{certificate, sign}; -use crate::{exceptions, x509}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + use asn1::SimpleAsn1Readable; use cryptography_x509::csr::{check_attribute_length, Attribute, CertificationRequestInfo, Csr}; use cryptography_x509::{common, oid}; use pyo3::IntoPy; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; -#[ouroboros::self_referencing] -struct OwnedCsr { - data: pyo3::Py, - #[borrows(data)] - #[covariant] - value: Csr<'this>, -} +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, sign}; +use crate::{exceptions, types, x509}; + +self_cell::self_cell!( + struct OwnedCsr { + owner: pyo3::Py, -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] + #[covariant] + dependent: Csr, + } +); + +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] struct CertificateSigningRequest { raw: OwnedCsr, - cached_extensions: Option, + cached_extensions: pyo3::sync::GILOnceCell, } #[pyo3::prelude::pymethods] impl CertificateSigningRequest { fn __hash__(&self, py: pyo3::Python<'_>) -> u64 { let mut hasher = DefaultHasher::new(); - self.raw.borrow_data().as_bytes(py).hash(&mut hasher); + self.raw.borrow_owner().as_bytes(py).hash(&mut hasher); hasher.finish() } - fn __richcmp__( + fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, CertificateSigningRequest>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => { - Ok(self.raw.borrow_data().as_bytes(py) == other.raw.borrow_data().as_bytes(py)) - } - pyo3::basic::CompareOp::Ne => { - Ok(self.raw.borrow_data().as_bytes(py) != other.raw.borrow_data().as_bytes(py)) - } - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "CSRs cannot be ordered", - )), - } + ) -> bool { + self.raw.borrow_owner().as_bytes(py) == other.raw.borrow_owner().as_bytes(py) } - fn public_key<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { - // This makes an unnecessary copy. It'd be nice to get rid of it. - let serialized = pyo3::types::PyBytes::new( + fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { + keys::load_der_public_key_bytes( py, - &asn1::write_single(&self.raw.borrow_value().csr_info.spki)?, - ); - Ok(py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "load_der_public_key"))? - .call1((serialized,))?) + self.raw.borrow_dependent().csr_info.spki.tlv().full_data(), + ) } #[getter] fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { Ok(x509::parse_name( py, - &self.raw.borrow_value().csr_info.subject, + self.raw.borrow_dependent().csr_info.subject.unwrap_read(), )?) } @@ -82,13 +67,13 @@ impl CertificateSigningRequest { &self, py: pyo3::Python<'p>, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let result = asn1::write_single(&self.raw.borrow_value().csr_info)?; + let result = asn1::write_single(&self.raw.borrow_dependent().csr_info)?; Ok(pyo3::types::PyBytes::new(py, &result)) } #[getter] fn signature<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyBytes { - pyo3::types::PyBytes::new(py, self.raw.borrow_value().signature.as_bytes()) + pyo3::types::PyBytes::new(py, self.raw.borrow_dependent().signature.as_bytes()) } #[getter] @@ -96,24 +81,23 @@ impl CertificateSigningRequest { &self, py: pyo3::Python<'p>, ) -> Result<&'p pyo3::PyAny, CryptographyError> { - let sig_oids_to_hash = py - .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? - .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?; - let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?); - match hash_alg { - Ok(data) => Ok(data), - Err(_) => Err(CryptographyError::from( - exceptions::UnsupportedAlgorithm::new_err(format!( - "Signature algorithm OID: {} not recognized", - self.raw.borrow_value().signature_alg.oid() - )), - )), - } + sign::identify_signature_hash_algorithm(py, &self.raw.borrow_dependent().signature_alg) } #[getter] fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - oid_to_py_oid(py, self.raw.borrow_value().signature_alg.oid()) + oid_to_py_oid(py, self.raw.borrow_dependent().signature_alg.oid()) + } + + #[getter] + fn signature_algorithm_parameters<'p>( + &'p self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::PyAny> { + sign::identify_signature_algorithm_parameters( + py, + &self.raw.borrow_dependent().signature_alg, + ) } fn public_bytes<'p>( @@ -121,7 +105,7 @@ impl CertificateSigningRequest { py: pyo3::Python<'p>, encoding: &'p pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let result = asn1::write_single(self.raw.borrow_value())?; + let result = asn1::write_single(self.raw.borrow_dependent())?; encode_der_data(py, "CERTIFICATE REQUEST".to_string(), result, encoding) } @@ -131,19 +115,14 @@ impl CertificateSigningRequest { py: pyo3::Python<'p>, oid: &pyo3::PyAny, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let cryptography_warning = py - .import(pyo3::intern!(py, "cryptography.utils"))? - .getattr(pyo3::intern!(py, "DeprecatedIn36"))?; - pyo3::PyErr::warn( - py, - cryptography_warning, - "CertificateSigningRequest.get_attribute_for_oid has been deprecated. Please switch to request.attributes.get_attribute_for_oid.", - 1, - )?; + let warning_cls = types::DEPRECATED_IN_36.get(py)?; + let warning_msg = "CertificateSigningRequest.get_attribute_for_oid has been deprecated. Please switch to request.attributes.get_attribute_for_oid."; + pyo3::PyErr::warn(py, warning_cls, warning_msg, 1)?; + let rust_oid = py_oid_to_oid(oid)?; for attribute in self .raw - .borrow_value() + .borrow_dependent() .csr_info .attributes .unwrap_read() @@ -171,17 +150,17 @@ impl CertificateSigningRequest { } } Err(exceptions::AttributeNotFound::new_err(( - format!("No {} attribute was found", oid), + format!("No {oid} attribute was found"), oid.into_py(py), ))) } #[getter] - fn attributes<'p>(&mut self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn attributes<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let pyattrs = pyo3::types::PyList::empty(py); for attribute in self .raw - .borrow_value() + .borrow_dependent() .csr_info .attributes .unwrap_read() @@ -200,20 +179,17 @@ impl CertificateSigningRequest { "Long-form tags are not supported in CSR attribute values", )) })?; - let pyattr = py - .import(pyo3::intern!(py, "cryptography.x509"))? - .call_method1(pyo3::intern!(py, "Attribute"), (oid, serialized, tag))?; + let pyattr = types::ATTRIBUTE.get(py)?.call1((oid, serialized, tag))?; pyattrs.append(pyattr)?; } - py.import(pyo3::intern!(py, "cryptography.x509"))? - .call_method1(pyo3::intern!(py, "Attributes"), (pyattrs,)) + types::ATTRIBUTES.get(py)?.call1((pyattrs,)) } #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let raw_exts = self .raw - .borrow_value() + .borrow_dependent() .csr_info .get_extension_attribute() .map_err(|_| { @@ -222,12 +198,9 @@ impl CertificateSigningRequest { ) })?; - x509::parse_and_cache_extensions( - py, - &mut self.cached_extensions, - &raw_exts, - |oid, ext_data| certificate::parse_cert_ext(py, oid.clone(), ext_data), - ) + x509::parse_and_cache_extensions(py, &self.cached_extensions, &raw_exts, |ext| { + certificate::parse_cert_ext(py, ext) + }) } #[getter] @@ -238,10 +211,10 @@ impl CertificateSigningRequest { let public_key = slf.public_key(py)?; Ok(sign::verify_signature_with_signature_algorithm( py, - public_key, - &slf.raw.borrow_value().signature_alg, - slf.raw.borrow_value().signature.as_bytes(), - &asn1::write_single(&slf.raw.borrow_value().csr_info)?, + public_key.as_ref(py), + &slf.raw.borrow_dependent().signature_alg, + slf.raw.borrow_dependent().signature.as_bytes(), + &asn1::write_single(&slf.raw.borrow_dependent().csr_info)?, ) .is_ok()) } @@ -251,17 +224,21 @@ impl CertificateSigningRequest { fn load_pem_x509_csr( py: pyo3::Python<'_>, data: &[u8], + backend: Option<&pyo3::PyAny>, ) -> CryptographyResult { + let _ = backend; + // We support both PEM header strings that OpenSSL does // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L35-L36 let parsed = x509::find_in_pem( data, - |p| p.tag == "CERTIFICATE REQUEST" || p.tag == "NEW CERTIFICATE REQUEST", + |p| p.tag() == "CERTIFICATE REQUEST" || p.tag() == "NEW CERTIFICATE REQUEST", "Valid PEM but no BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters. Are you sure this is a CSR?", )?; load_der_x509_csr( py, - pyo3::types::PyBytes::new(py, &parsed.contents).into_py(py), + pyo3::types::PyBytes::new(py, parsed.contents()).into_py(py), + None, ) } @@ -269,14 +246,17 @@ fn load_pem_x509_csr( fn load_der_x509_csr( py: pyo3::Python<'_>, data: pyo3::Py, + backend: Option<&pyo3::PyAny>, ) -> CryptographyResult { + let _ = backend; + let raw = OwnedCsr::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; - let version = raw.borrow_value().csr_info.version; + let version = raw.borrow_dependent().csr_info.version; if version != 0 { return Err(CryptographyError::from( exceptions::InvalidVersion::new_err(( - format!("{} is not a valid CSR version", version), + format!("{version} is not a valid CSR version"), version, )), )); @@ -284,7 +264,7 @@ fn load_der_x509_csr( Ok(CertificateSigningRequest { raw, - cached_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), }) } @@ -294,30 +274,16 @@ fn create_x509_csr( builder: &pyo3::PyAny, private_key: &pyo3::PyAny, hash_algorithm: &pyo3::PyAny, + rsa_padding: &pyo3::PyAny, ) -> CryptographyResult { - let sigalg = x509::sign::compute_signature_algorithm( - py, - private_key, - hash_algorithm, - py.None().into_ref(py), - )?; - let serialization_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))?; - let der_encoding = serialization_mod - .getattr(pyo3::intern!(py, "Encoding"))? - .getattr(pyo3::intern!(py, "DER"))?; - let spki_format = serialization_mod - .getattr(pyo3::intern!(py, "PublicFormat"))? - .getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?; + let sigalg = + x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?; + let der = types::ENCODING_DER.get(py)?; + let spki = types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?; let spki_bytes = private_key .call_method0(pyo3::intern!(py, "public_key"))? - .call_method1( - pyo3::intern!(py, "public_bytes"), - (der_encoding, spki_format), - )? + .call_method1(pyo3::intern!(py, "public_bytes"), (der, spki))? .extract::<&[u8]>()?; let mut attrs = vec![]; @@ -333,7 +299,7 @@ fn create_x509_csr( values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ asn1::parse_single(&ext_bytes)?, ])), - }) + }); } for py_attr in builder.getattr(pyo3::intern!(py, "_attributes"))?.iter()? { @@ -357,7 +323,7 @@ fn create_x509_csr( values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ common::RawTlv::new(tag, value), ])), - }) + }); } let py_subject_name = builder.getattr(pyo3::intern!(py, "_subject_name"))?; @@ -370,19 +336,14 @@ fn create_x509_csr( }; let tbs_bytes = asn1::write_single(&csr_info)?; - let signature = x509::sign::sign_data( - py, - private_key, - hash_algorithm, - py.None().into_ref(py), - &tbs_bytes, - )?; + let signature = + x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?; let data = asn1::write_single(&Csr { csr_info, signature_alg: sigalg, signature: asn1::BitString::new(signature, 0).unwrap(), })?; - load_der_x509_csr(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) + load_der_x509_csr(py, pyo3::types::PyBytes::new(py, &data).into_py(py), None) } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { diff --git a/src/rust/src/x509/extensions.rs b/src/rust/src/x509/extensions.rs index 98d1bd63b910..03fd1da9ff07 100644 --- a/src/rust/src/x509/extensions.rs +++ b/src/rust/src/x509/extensions.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 cryptography_x509::{common, crl, extensions, oid}; + use crate::asn1::{py_oid_to_oid, py_uint_to_big_endian_bytes}; use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509; use crate::x509::{certificate, sct}; -use cryptography_x509::{common, crl, extensions, oid}; +use crate::{types, x509}; fn encode_general_subtrees<'a>( py: pyo3::Python<'a>, @@ -211,7 +212,7 @@ fn encode_certificate_policies( let mut qualifiers = vec![]; for py_qualifier in py_policy_qualifiers.iter()? { let py_qualifier = py_qualifier?; - let qualifier = if py_qualifier.is_instance_of::()? { + let qualifier = if py_qualifier.is_instance_of::() { let cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { Some(s) => s, None => { @@ -462,13 +463,8 @@ pub(crate) fn encode_extension( Ok(Some(der)) } &oid::CRL_REASON_OID => { - let value = ext - .py() - .import(pyo3::intern!( - py, - "cryptography.hazmat.backends.openssl.decode_asn1" - ))? - .getattr(pyo3::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))? + let value = types::CRL_ENTRY_REASON_ENUM_TO_CODE + .get(ext.py())? .get_item(ext.getattr(pyo3::intern!(py, "reason"))?)? .extract::()?; Ok(Some(asn1::write_single(&asn1::Enumerated::new(value))?)) diff --git a/src/rust/src/x509/mod.rs b/src/rust/src/x509/mod.rs index c43bf9023e71..a1503ea98592 100644 --- a/src/rust/src/x509/mod.rs +++ b/src/rust/src/x509/mod.rs @@ -12,8 +12,9 @@ pub(crate) mod ocsp_req; pub(crate) mod ocsp_resp; pub(crate) mod sct; pub(crate) mod sign; +pub(crate) mod verify; pub(crate) use common::{ - datetime_to_py, find_in_pem, parse_and_cache_extensions, parse_general_name, - parse_general_names, parse_name, parse_rdn, py_to_datetime, + datetime_to_py, datetime_to_py_utc, find_in_pem, parse_and_cache_extensions, + parse_general_name, parse_general_names, parse_name, parse_rdn, py_to_datetime, }; diff --git a/src/rust/src/x509/ocsp.rs b/src/rust/src/x509/ocsp.rs index 05ea096078bb..b86753110606 100644 --- a/src/rust/src/x509/ocsp.rs +++ b/src/rust/src/x509/ocsp.rs @@ -2,22 +2,30 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::error::CryptographyResult; -use crate::x509; -use crate::x509::certificate::Certificate; +use std::collections::HashMap; + use cryptography_x509::common; use cryptography_x509::ocsp_req::CertID; use once_cell::sync::Lazy; -use std::collections::HashMap; + +use crate::backend::hashes::Hash; +use crate::error::CryptographyResult; +use crate::x509; +use crate::x509::certificate::Certificate; pub(crate) static ALGORITHM_PARAMETERS_TO_HASH: Lazy< HashMap, &str>, > = Lazy::new(|| { let mut h = HashMap::new(); + h.insert(common::AlgorithmParameters::Sha1(None), "SHA1"); h.insert(common::AlgorithmParameters::Sha1(Some(())), "SHA1"); + h.insert(common::AlgorithmParameters::Sha224(None), "SHA224"); h.insert(common::AlgorithmParameters::Sha224(Some(())), "SHA224"); + h.insert(common::AlgorithmParameters::Sha256(None), "SHA256"); h.insert(common::AlgorithmParameters::Sha256(Some(())), "SHA256"); + h.insert(common::AlgorithmParameters::Sha384(None), "SHA384"); h.insert(common::AlgorithmParameters::Sha384(Some(())), "SHA384"); + h.insert(common::AlgorithmParameters::Sha512(None), "SHA512"); h.insert(common::AlgorithmParameters::Sha512(Some(())), "SHA512"); h }); @@ -70,14 +78,14 @@ pub(crate) fn certid_new<'p>( issuer: &'p Certificate, hash_algorithm: &'p pyo3::PyAny, ) -> CryptographyResult> { - let issuer_der = asn1::write_single(&cert.raw.borrow_value_public().tbs_cert.issuer)?; + let issuer_der = asn1::write_single(&cert.raw.borrow_dependent().tbs_cert.issuer)?; let issuer_name_hash = hash_data(py, hash_algorithm, &issuer_der)?; let issuer_key_hash = hash_data( py, hash_algorithm, issuer .raw - .borrow_value_public() + .borrow_dependent() .tbs_cert .spki .subject_public_key @@ -91,7 +99,7 @@ pub(crate) fn certid_new<'p>( .clone(), issuer_name_hash, issuer_key_hash, - serial_number: cert.raw.borrow_value_public().tbs_cert.serial, + serial_number: cert.raw.borrow_dependent().tbs_cert.serial, }) } @@ -118,10 +126,7 @@ pub(crate) fn hash_data<'p>( py_hash_alg: &'p pyo3::PyAny, data: &[u8], ) -> pyo3::PyResult<&'p [u8]> { - let hash = py - .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? - .getattr(pyo3::intern!(py, "Hash"))? - .call1((py_hash_alg,))?; - hash.call_method1(pyo3::intern!(py, "update"), (data,))?; - hash.call_method0(pyo3::intern!(py, "finalize"))?.extract() + let mut h = Hash::new(py, py_hash_alg, None)?; + h.update_bytes(data)?; + Ok(h.finalize(py)?.as_bytes()) } diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs index 1571524edfeb..baa2dd00dfb4 100644 --- a/src/rust/src/x509/ocsp_req.rs +++ b/src/rust/src/x509/ocsp_req.rs @@ -2,20 +2,25 @@ // 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 pyo3::IntoPy; + use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{extensions, ocsp}; -use crate::{exceptions, x509}; -use cryptography_x509::{common, ocsp_req, oid}; -use pyo3::IntoPy; +use crate::{exceptions, types, x509}; -#[ouroboros::self_referencing] -struct OwnedOCSPRequest { - data: pyo3::Py, - #[borrows(data)] - #[covariant] - value: ocsp_req::OCSPRequest<'this>, -} +self_cell::self_cell!( + struct OwnedOCSPRequest { + owner: pyo3::Py, + #[covariant] + dependent: RawOCSPRequest, + } +); #[pyo3::prelude::pyfunction] fn load_der_ocsp_request( @@ -25,7 +30,7 @@ fn load_der_ocsp_request( let raw = OwnedOCSPRequest::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; if raw - .borrow_value() + .borrow_dependent() .tbs_request .request_list .unwrap_read() @@ -41,21 +46,21 @@ fn load_der_ocsp_request( Ok(OCSPRequest { raw, - cached_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), }) } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPRequest { raw: OwnedOCSPRequest, - cached_extensions: Option, + cached_extensions: pyo3::sync::GILOnceCell, } impl OCSPRequest { fn cert_id(&self) -> ocsp_req::CertID<'_> { self.raw - .borrow_value() + .borrow_dependent() .tbs_request .request_list .unwrap_read() @@ -85,9 +90,8 @@ impl OCSPRequest { ) -> Result<&'p pyo3::PyAny, CryptographyError> { let cert_id = self.cert_id(); - let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&cert_id.hash_algorithm.params) { - Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?), + Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", @@ -107,16 +111,15 @@ impl OCSPRequest { } #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let tbs_request = &self.raw.borrow_value().tbs_request; + fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let tbs_request = &self.raw.borrow_dependent().tbs_request; - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, - &mut self.cached_extensions, + &self.cached_extensions, &tbs_request.raw_request_extensions, - |oid, value| { - match *oid { + |ext| { + match ext.extn_id { oid::NONCE_OID => { // This is a disaster. RFC 2560 says that the contents of the nonce is // just the raw extension value. This is nonsense, since they're always @@ -124,24 +127,21 @@ impl OCSPRequest { // nonce is an OCTET STRING, and so you should unwrap the TLV to get // the nonce. So we try parsing as a TLV and fall back to just using // the raw value. - let nonce = asn1::parse_single::<&[u8]>(value).unwrap_or(value); - Ok(Some( - x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?, - )) + let nonce = ext.value::<&[u8]>().unwrap_or(ext.extn_value); + Ok(Some(types::OCSP_NONCE.get(py)?.call1((nonce,))?)) } oid::ACCEPTABLE_RESPONSES_OID => { - let oids = asn1::parse_single::< - asn1::SequenceOf<'_, asn1::ObjectIdentifier>, - >(value)?; + let oids = ext.value::>()?; let py_oids = pyo3::types::PyList::empty(py); for oid in oids { py_oids.append(oid_to_py_oid(py, &oid)?)?; } - Ok(Some(x509_module.call_method1( - pyo3::intern!(py, "OCSPAcceptableResponses"), - (py_oids,), - )?)) + Ok(Some( + types::OCSP_ACCEPTABLE_RESPONSES + .get(py)? + .call1((py_oids,))?, + )) } _ => Ok(None), } @@ -154,20 +154,13 @@ impl OCSPRequest { py: pyo3::Python<'p>, encoding: &pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let der = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "Encoding"))? - .getattr(pyo3::intern!(py, "DER"))?; - if !encoding.is(der) { + if !encoding.is(types::ENCODING_DER.get(py)?) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) .into()); } - let result = asn1::write_single(self.raw.borrow_value())?; + let result = asn1::write_single(self.raw.borrow_dependent())?; Ok(pyo3::types::PyBytes::new(py, &result)) } } diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs index 721e0313a613..e5f8b479576a 100644 --- a/src/rust/src/x509/ocsp_resp.rs +++ b/src/rust/src/x509/ocsp_resp.rs @@ -2,14 +2,20 @@ // 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::ocsp_resp::SingleResponse; +use cryptography_x509::{ + common, + ocsp_resp::{self, OCSPResponse as RawOCSPResponse, SingleResponse as RawSingleResponse}, + oid, +}; +use pyo3::IntoPy; + use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, crl, extensions, ocsp, py_to_datetime, sct}; -use crate::{exceptions, x509}; -use cryptography_x509::ocsp_resp::SingleResponse; -use cryptography_x509::{common, ocsp_resp, oid}; -use pyo3::IntoPy; -use std::sync::Arc; +use crate::{exceptions, types, x509}; const BASIC_RESPONSE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 1); @@ -20,7 +26,7 @@ fn load_der_ocsp_response( ) -> Result { let raw = OwnedOCSPResponse::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; - let response = raw.borrow_value(); + let response = raw.borrow_dependent(); match response.response_status.value() { SUCCESSFUL_RESPONSE => match response.response_bytes { Some(ref bytes) => { @@ -53,30 +59,30 @@ fn load_der_ocsp_response( }; Ok(OCSPResponse { raw: Arc::new(raw), - cached_extensions: None, - cached_single_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), + cached_single_extensions: pyo3::sync::GILOnceCell::new(), }) } -#[ouroboros::self_referencing] -struct OwnedOCSPResponse { - data: pyo3::Py, - #[borrows(data)] - #[covariant] - value: ocsp_resp::OCSPResponse<'this>, -} +self_cell::self_cell!( + struct OwnedOCSPResponse { + owner: pyo3::Py, + #[covariant] + dependent: RawOCSPResponse, + } +); -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPResponse { raw: Arc, - cached_extensions: Option, - cached_single_extensions: Option, + cached_extensions: pyo3::sync::GILOnceCell, + cached_single_extensions: pyo3::sync::GILOnceCell, } impl OCSPResponse { fn requires_successful_response(&self) -> pyo3::PyResult<&ocsp_resp::BasicOCSPResponse<'_>> { - match self.raw.borrow_value().response_bytes.as_ref() { + match self.raw.borrow_dependent().response_bytes.as_ref() { Some(b) => Ok(b.response.get()), None => Err(pyo3::exceptions::PyValueError::new_err( "OCSP response status is not successful so the property has no value", @@ -101,7 +107,7 @@ impl OCSPResponse { Ok(OCSPResponseIterator { contents: OwnedOCSPResponseIteratorData::try_new(Arc::clone(&self.raw), |v| { Ok::<_, ()>( - v.borrow_value() + v.borrow_dependent() .response_bytes .as_ref() .unwrap() @@ -119,7 +125,7 @@ impl OCSPResponse { #[getter] fn response_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let status = self.raw.borrow_value().response_status.value(); + let status = self.raw.borrow_dependent().response_status.value(); let attr = if status == SUCCESSFUL_RESPONSE { "SUCCESSFUL" } else if status == MALFORMED_REQUEST_RESPOSNE { @@ -134,16 +140,16 @@ impl OCSPResponse { assert_eq!(status, UNAUTHORIZED_RESPONSE); "UNAUTHORIZED" }; - py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))? - .getattr(pyo3::intern!(py, "OCSPResponseStatus"))? - .getattr(attr) + types::OCSP_RESPONSE_STATUS.get(py)?.getattr(attr) } #[getter] fn responder_name<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; match resp.tbs_response_data.responder_id { - ocsp_resp::ResponderId::ByName(ref name) => Ok(x509::parse_name(py, name)?), + ocsp_resp::ResponderId::ByName(ref name) => { + Ok(x509::parse_name(py, name.unwrap_read())?) + } ocsp_resp::ResponderId::ByKey(_) => Ok(py.None().into_ref(py)), } } @@ -176,10 +182,9 @@ impl OCSPResponse { &self, py: pyo3::Python<'p>, ) -> Result<&'p pyo3::PyAny, CryptographyError> { - let sig_oids_to_hash = py - .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? - .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?; - let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?); + let hash_alg = types::SIG_OIDS_TO_HASH + .get(py)? + .get_item(self.signature_algorithm_oid(py)?); match hash_alg { Ok(data) => Ok(data), Err(_) => { @@ -240,7 +245,7 @@ impl OCSPResponse { py, x509::certificate::Certificate { raw: raw_cert, - cached_extensions: None, + cached_extensions: pyo3::sync::GILOnceCell::new(), }, )?)?; } @@ -314,12 +319,12 @@ impl OCSPResponse { } #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { self.requires_successful_response()?; let response_data = &self .raw - .borrow_value() + .borrow_dependent() .response_bytes .as_ref() .unwrap() @@ -327,13 +332,12 @@ impl OCSPResponse { .get() .tbs_response_data; - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, - &mut self.cached_extensions, + &self.cached_extensions, &response_data.raw_response_extensions, - |oid, ext_data| { - match oid { + |ext| { + match &ext.extn_id { &oid::NONCE_OID => { // This is a disaster. RFC 2560 says that the contents of the nonce is // just the raw extension value. This is nonsense, since they're always @@ -341,10 +345,8 @@ impl OCSPResponse { // nonce is an OCTET STRING, and so you should unwrap the TLV to get // the nonce. So we try parsing as a TLV and fall back to just using // the raw value. - let nonce = asn1::parse_single::<&[u8]>(ext_data).unwrap_or(ext_data); - Ok(Some( - x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?, - )) + let nonce = ext.value::<&[u8]>().unwrap_or(ext.extn_value); + Ok(Some(types::OCSP_NONCE.get(py)?.call1((nonce,))?)) } _ => Ok(None), } @@ -353,11 +355,11 @@ impl OCSPResponse { } #[getter] - fn single_extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { + fn single_extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { self.requires_successful_response()?; let single_resp = single_response( self.raw - .borrow_value() + .borrow_dependent() .response_bytes .as_ref() .unwrap() @@ -365,22 +367,21 @@ impl OCSPResponse { .get(), )?; - let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, - &mut self.cached_single_extensions, + &self.cached_single_extensions, &single_resp.raw_single_extensions, - |oid, ext_data| match oid { + |ext| match &ext.extn_id { &oid::SIGNED_CERTIFICATE_TIMESTAMPS_OID => { - let contents = asn1::parse_single::<&[u8]>(ext_data)?; + let contents = ext.value::<&[u8]>()?; let scts = sct::parse_scts(py, contents, sct::LogEntryType::Certificate)?; Ok(Some( - x509_module - .getattr(pyo3::intern!(py, "SignedCertificateTimestamps"))? + types::SIGNED_CERTIFICATE_TIMESTAMPS + .get(py)? .call1((scts,))?, )) } - _ => crl::parse_crl_entry_ext(py, oid.clone(), ext_data), + _ => crl::parse_crl_entry_ext(py, ext), }, ) } @@ -390,20 +391,13 @@ impl OCSPResponse { py: pyo3::Python<'p>, encoding: &pyo3::PyAny, ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let der = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.serialization" - ))? - .getattr(pyo3::intern!(py, "Encoding"))? - .getattr(pyo3::intern!(py, "DER"))?; - if !encoding.is(der) { + if !encoding.is(types::ENCODING_DER.get(py)?) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) .into()); } - let result = asn1::write_single(self.raw.borrow_value())?; + let result = asn1::write_single(self.raw.borrow_dependent())?; Ok(pyo3::types::PyBytes::new(py, &result)) } } @@ -418,11 +412,13 @@ fn map_arc_data_ocsp_response( &ocsp_resp::OCSPResponse<'this>, ) -> cryptography_x509::certificate::Certificate<'this>, ) -> certificate::OwnedCertificate { - certificate::OwnedCertificate::new_public(it.borrow_data().clone_ref(py), |inner_it| { - it.with(|value| { - f(inner_it.as_bytes(py), unsafe { - std::mem::transmute(value.value) - }) + certificate::OwnedCertificate::new(it.borrow_owner().clone_ref(py), |inner_it| { + it.with_dependent(|_, value| { + // SAFETY: This is safe because `Arc::clone` ensures the data is + // alive, but Rust doesn't understand the lifetime relationship it + // produces. Open-coded implementation of the API discussed in + // https://github.com/joshua-maros/ouroboros/issues/38 + f(inner_it.as_bytes(py), unsafe { std::mem::transmute(value) }) }) }) } @@ -433,8 +429,12 @@ fn try_map_arc_data_mut_ocsp_response_iterator( &mut asn1::SequenceOf<'this, ocsp_resp::SingleResponse<'this>>, ) -> Result, E>, ) -> Result { - OwnedSingleResponse::try_new(Arc::clone(it.borrow_data()), |inner_it| { - it.with_value_mut(|value| f(inner_it, unsafe { std::mem::transmute(value) })) + OwnedSingleResponse::try_new(Arc::clone(it.borrow_owner()), |inner_it| { + // SAFETY: This is safe because `Arc::clone` ensures the data is + // alive, but Rust doesn't understand the lifetime relationship it + // produces. Open-coded implementation of the API discussed in + // https://github.com/joshua-maros/ouroboros/issues/38 + it.with_dependent_mut(|_, value| f(inner_it, unsafe { std::mem::transmute(value) })) }) } @@ -447,8 +447,7 @@ fn single_response<'a>( if num_responses != 1 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( - "OCSP response contains {} SINGLERESP structures. Use .response_iter to iterate through them", - num_responses + "OCSP response contains {num_responses} SINGLERESP structures. Use .response_iter to iterate through them" )) )); } @@ -472,18 +471,15 @@ fn singleresp_py_certificate_status<'p>( ocsp_resp::CertStatus::Revoked(_) => pyo3::intern!(py, "REVOKED"), ocsp_resp::CertStatus::Unknown(_) => pyo3::intern!(py, "UNKNOWN"), }; - py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))? - .getattr(pyo3::intern!(py, "OCSPCertStatus"))? - .getattr(attr) + types::OCSP_CERT_STATUS.get(py)?.getattr(attr) } fn singleresp_py_hash_algorithm<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> Result<&'p pyo3::PyAny, CryptographyError> { - let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&resp.cert_id.hash_algorithm.params) { - Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?), + Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", @@ -556,8 +552,6 @@ fn create_ocsp_response( let borrowed_cert; let py_certs: Option>>; let response_bytes = if response_status == SUCCESSFUL_RESPONSE { - let ocsp_mod = py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))?; - let py_single_resp = builder.getattr(pyo3::intern!(py, "_response"))?; py_cert = py_single_resp .getattr(pyo3::intern!(py, "_cert"))? @@ -574,27 +568,17 @@ fn create_ocsp_response( .extract()?; let py_cert_status = py_single_resp.getattr(pyo3::intern!(py, "_cert_status"))?; - let cert_status = if py_cert_status.is(ocsp_mod - .getattr(pyo3::intern!(py, "OCSPCertStatus"))? - .getattr(pyo3::intern!(py, "GOOD"))?) - { + let cert_status = if py_cert_status.is(types::OCSP_CERT_STATUS_GOOD.get(py)?) { ocsp_resp::CertStatus::Good(()) - } else if py_cert_status.is(ocsp_mod - .getattr(pyo3::intern!(py, "OCSPCertStatus"))? - .getattr(pyo3::intern!(py, "UNKNOWN"))?) - { + } else if py_cert_status.is(types::OCSP_CERT_STATUS_UNKNOWN.get(py)?) { ocsp_resp::CertStatus::Unknown(()) } else { let revocation_reason = if !py_single_resp .getattr(pyo3::intern!(py, "_revocation_reason"))? .is_none() { - let value = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.backends.openssl.decode_asn1" - ))? - .getattr(pyo3::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))? + let value = types::CRL_ENTRY_REASON_ENUM_TO_CODE + .get(py)? .get_item(py_single_resp.getattr(pyo3::intern!(py, "_revocation_reason"))?)? .extract::()?; Some(asn1::Enumerated::new(value)) @@ -635,20 +619,14 @@ fn create_ocsp_response( }]; borrowed_cert = responder_cert.borrow(); - let responder_id = if responder_encoding.is(ocsp_mod - .getattr(pyo3::intern!(py, "OCSPResponderEncoding"))? - .getattr(pyo3::intern!(py, "HASH"))?) - { - let sha1 = py - .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? - .getattr(pyo3::intern!(py, "SHA1"))? - .call0()?; + let responder_id = if responder_encoding.is(types::OCSP_RESPONDER_ENCODING_HASH.get(py)?) { + let sha1 = types::SHA1.get(py)?.call0()?; ocsp_resp::ResponderId::ByKey(ocsp::hash_data( py, sha1, borrowed_cert .raw - .borrow_value_public() + .borrow_dependent() .tbs_cert .spki .subject_public_key @@ -658,7 +636,7 @@ fn create_ocsp_response( ocsp_resp::ResponderId::ByName( borrowed_cert .raw - .borrow_value_public() + .borrow_dependent() .tbs_cert .subject .clone(), @@ -710,7 +688,7 @@ fn create_ocsp_response( common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( py_certs .iter() - .map(|c| c.raw.borrow_value_public().clone()) + .map(|c| c.raw.borrow_dependent().clone()) .collect(), )) }); @@ -744,13 +722,15 @@ pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult< Ok(()) } -#[ouroboros::self_referencing] -struct OwnedOCSPResponseIteratorData { - data: Arc, - #[borrows(data)] - #[covariant] - value: asn1::SequenceOf<'this, SingleResponse<'this>>, -} +type RawOCSPResponseIterator<'a> = asn1::SequenceOf<'a, SingleResponse<'a>>; + +self_cell::self_cell!( + struct OwnedOCSPResponseIteratorData { + owner: Arc, + #[covariant] + dependent: RawOCSPResponseIterator, + } +); #[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPResponseIterator { @@ -776,22 +756,22 @@ impl OCSPResponseIterator { } } -#[ouroboros::self_referencing] -struct OwnedSingleResponse { - data: Arc, - #[borrows(data)] - #[covariant] - value: ocsp_resp::SingleResponse<'this>, -} +self_cell::self_cell!( + struct OwnedSingleResponse { + owner: Arc, + #[covariant] + dependent: RawSingleResponse, + } +); -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPSingleResponse { raw: OwnedSingleResponse, } impl OCSPSingleResponse { fn single_response(&self) -> &SingleResponse<'_> { - self.raw.borrow_value() + self.raw.borrow_dependent() } } diff --git a/src/rust/src/x509/sct.rs b/src/rust/src/x509/sct.rs index a13785bf3fb1..b7cce3ff4036 100644 --- a/src/rust/src/x509/sct.rs +++ b/src/rust/src/x509/sct.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::CryptographyError; -use pyo3::types::IntoPyDict; -use pyo3::ToPyObject; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use pyo3::ToPyObject; + +use crate::error::CryptographyError; +use crate::types; + struct TLSReader<'a> { data: &'a [u8], } @@ -71,8 +73,7 @@ impl TryFrom for HashAlgorithm { 6 => HashAlgorithm::Sha512, _ => { return Err(pyo3::exceptions::PyValueError::new_err(format!( - "Invalid/unsupported hash algorithm for SCT: {}", - value + "Invalid/unsupported hash algorithm for SCT: {value}" ))) } }) @@ -119,15 +120,14 @@ impl TryFrom for SignatureAlgorithm { 3 => SignatureAlgorithm::Ecdsa, _ => { return Err(pyo3::exceptions::PyValueError::new_err(format!( - "Invalid/unsupported signature algorithm for SCT: {}", - value + "Invalid/unsupported signature algorithm for SCT: {value}" ))) } }) } } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Sct { log_id: [u8; 32], timestamp: u64, @@ -142,18 +142,8 @@ pub(crate) struct Sct { #[pyo3::prelude::pymethods] impl Sct { - fn __richcmp__( - &self, - other: pyo3::PyRef<'_, Sct>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.sct_data == other.sct_data), - pyo3::basic::CompareOp::Ne => Ok(self.sct_data != other.sct_data), - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "SCTs cannot be ordered", - )), - } + fn __eq__(&self, other: pyo3::PyRef<'_, Sct>) -> bool { + self.sct_data == other.sct_data } fn __hash__(&self) -> u64 { @@ -164,12 +154,7 @@ impl Sct { #[getter] fn version<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - py.import(pyo3::intern!( - py, - "cryptography.x509.certificate_transparency" - ))? - .getattr(pyo3::intern!(py, "Version"))? - .getattr(pyo3::intern!(py, "v1")) + types::CERTIFICATE_TRANSPARENCY_VERSION_V1.get(py) } #[getter] @@ -179,34 +164,27 @@ impl Sct { #[getter] fn timestamp<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let datetime_class = py - .import(pyo3::intern!(py, "datetime"))? - .getattr(pyo3::intern!(py, "datetime"))?; - datetime_class + let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; + + let kwargs = pyo3::types::PyDict::new(py); + kwargs.set_item("microsecond", self.timestamp % 1000 * 1000)?; + kwargs.set_item("tzinfo", None::>)?; + + types::DATETIME_DATETIME + .get(py)? .call_method1( - pyo3::intern!(py, "utcfromtimestamp"), - (self.timestamp / 1000,), + pyo3::intern!(py, "fromtimestamp"), + (self.timestamp / 1000, utc), )? - .call_method( - "replace", - (), - Some(vec![("microsecond", self.timestamp % 1000 * 1000)].into_py_dict(py)), - ) + .call_method("replace", (), Some(kwargs)) } #[getter] fn entry_type<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let et_class = py - .import(pyo3::intern!( - py, - "cryptography.x509.certificate_transparency" - ))? - .getattr(pyo3::intern!(py, "LogEntryType"))?; - let attr_name = match self.entry_type { - LogEntryType::Certificate => "X509_CERTIFICATE", - LogEntryType::PreCertificate => "PRE_CERTIFICATE", - }; - et_class.getattr(attr_name) + Ok(match self.entry_type { + LogEntryType::Certificate => types::LOG_ENTRY_TYPE_X509_CERTIFICATE.get(py)?, + LogEntryType::PreCertificate => types::LOG_ENTRY_TYPE_PRE_CERTIFICATE.get(py)?, + }) } #[getter] @@ -214,19 +192,16 @@ impl Sct { &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let hashes_mod = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; - hashes_mod.call_method0(self.hash_algorithm.to_attr()) + types::HASHES_MODULE + .get(py)? + .call_method0(self.hash_algorithm.to_attr()) } #[getter] fn signature_algorithm<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let sa_class = py - .import(pyo3::intern!( - py, - "cryptography.x509.certificate_transparency" - ))? - .getattr(pyo3::intern!(py, "SignatureAlgorithm"))?; - sa_class.getattr(self.signature_algorithm.to_attr()) + types::SIGNATURE_ALGORITHM + .get(py)? + .getattr(self.signature_algorithm.to_attr()) } #[getter] diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs index b3a799b8cb01..4d9637d1f2de 100644 --- a/src/rust/src/x509/sign.rs +++ b/src/rust/src/x509/sign.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::asn1::oid_to_py_oid; -use crate::error::{CryptographyError, CryptographyResult}; -use crate::exceptions; +use std::collections::HashMap; + use cryptography_x509::{common, oid}; use once_cell::sync::Lazy; -use std::collections::HashMap; + +use crate::asn1::oid_to_py_oid; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, types}; // This is similar to a hashmap in ocsp.rs but contains more hash algorithms // that aren't allowable in OCSP @@ -47,51 +49,15 @@ enum HashType { } fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::PyResult { - let rsa_private_key: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.rsa" - ))? - .getattr(pyo3::intern!(py, "RSAPrivateKey"))? - .extract()?; - let dsa_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dsa" - ))? - .getattr(pyo3::intern!(py, "DSAPrivateKey"))? - .extract()?; - let ec_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ec" - ))? - .getattr(pyo3::intern!(py, "EllipticCurvePrivateKey"))? - .extract()?; - let ed25519_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ed25519" - ))? - .getattr(pyo3::intern!(py, "Ed25519PrivateKey"))? - .extract()?; - let ed448_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ed448" - ))? - .getattr(pyo3::intern!(py, "Ed448PrivateKey"))? - .extract()?; - - if private_key.is_instance(rsa_private_key)? { + if private_key.is_instance(types::RSA_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Rsa) - } else if private_key.is_instance(dsa_key_type)? { + } else if private_key.is_instance(types::DSA_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Dsa) - } else if private_key.is_instance(ec_key_type)? { + } else if private_key.is_instance(types::ELLIPTIC_CURVE_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ec) - } else if private_key.is_instance(ed25519_key_type)? { + } else if private_key.is_instance(types::ED25519_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ed25519) - } else if private_key.is_instance(ed448_key_type)? { + } else if private_key.is_instance(types::ED448_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ed448) } else { Err(pyo3::exceptions::PyTypeError::new_err( @@ -108,11 +74,7 @@ fn identify_hash_type( return Ok(HashType::None); } - let hash_algorithm_type: &pyo3::types::PyType = py - .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? - .getattr(pyo3::intern!(py, "HashAlgorithm"))? - .extract()?; - if !hash_algorithm.is_instance(hash_algorithm_type)? { + if !hash_algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? { return Err(pyo3::exceptions::PyTypeError::new_err( "Algorithm must be a registered hash algorithm.", )); @@ -131,8 +93,7 @@ fn identify_hash_type( "sha3-384" => Ok(HashType::Sha3_384), "sha3-512" => Ok(HashType::Sha3_512), name => Err(exceptions::UnsupportedAlgorithm::new_err(format!( - "Hash algorithm {:?} not supported for signatures", - name + "Hash algorithm {name:?} not supported for signatures" ))), } } @@ -143,23 +104,17 @@ fn compute_pss_salt_length<'p>( hash_algorithm: &'p pyo3::PyAny, rsa_padding: &'p pyo3::PyAny, ) -> pyo3::PyResult { - let padding_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.padding" - ))?; - let maxlen = padding_mod.getattr(pyo3::intern!(py, "_MaxLength"))?; - let digestlen = padding_mod.getattr(pyo3::intern!(py, "_DigestLength"))?; let py_saltlen = rsa_padding.getattr(pyo3::intern!(py, "_salt_length"))?; - if py_saltlen.is_instance(maxlen)? { - padding_mod - .getattr(pyo3::intern!(py, "calculate_max_pss_salt_length"))? + if py_saltlen.is_instance(types::PADDING_MAX_LENGTH.get(py)?)? { + types::CALCULATE_MAX_PSS_SALT_LENGTH + .get(py)? .call1((private_key, hash_algorithm))? .extract::() - } else if py_saltlen.is_instance(digestlen)? { + } else if py_saltlen.is_instance(types::PADDING_DIGEST_LENGTH.get(py)?)? { hash_algorithm .getattr(pyo3::intern!(py, "digest_size"))? .extract::() - } else if py_saltlen.is_instance(py.get_type::())? { + } else if py_saltlen.is_instance_of::() { py_saltlen.extract::() } else { Err(pyo3::exceptions::PyTypeError::new_err( @@ -177,16 +132,9 @@ pub(crate) fn compute_signature_algorithm<'p>( let key_type = identify_key_type(py, private_key)?; let hash_type = identify_hash_type(py, hash_algorithm)?; - let pss_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.padding" - ))? - .getattr(pyo3::intern!(py, "PSS"))? - .extract()?; // If this is RSA-PSS we need to compute the signature algorithm from the // parameters provided in rsa_padding. - if !rsa_padding.is_none() && rsa_padding.is_instance(pss_type)? { + if !rsa_padding.is_none() && rsa_padding.is_instance(types::PSS.get(py)?)? { let hash_alg_params = identify_alg_params_for_hash_type(hash_type)?; let hash_algorithm_id = common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), @@ -234,19 +182,19 @@ pub(crate) fn compute_signature_algorithm<'p>( (KeyType::Ec, HashType::Sha224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::EcDsaWithSha224, + params: common::AlgorithmParameters::EcDsaWithSha224(None), }), (KeyType::Ec, HashType::Sha256) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::EcDsaWithSha256, + params: common::AlgorithmParameters::EcDsaWithSha256(None), }), (KeyType::Ec, HashType::Sha384) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::EcDsaWithSha384, + params: common::AlgorithmParameters::EcDsaWithSha384(None), }), (KeyType::Ec, HashType::Sha512) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::EcDsaWithSha512, + params: common::AlgorithmParameters::EcDsaWithSha512(None), }), (KeyType::Ec, HashType::Sha3_224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), @@ -300,19 +248,19 @@ pub(crate) fn compute_signature_algorithm<'p>( (KeyType::Dsa, HashType::Sha224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::DsaWithSha224, + params: common::AlgorithmParameters::DsaWithSha224(None), }), (KeyType::Dsa, HashType::Sha256) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::DsaWithSha256, + params: common::AlgorithmParameters::DsaWithSha256(None), }), (KeyType::Dsa, HashType::Sha384) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::DsaWithSha384, + params: common::AlgorithmParameters::DsaWithSha384(None), }), (KeyType::Dsa, HashType::Sha512) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), - params: common::AlgorithmParameters::DsaWithSha512, + params: common::AlgorithmParameters::DsaWithSha512(None), }), ( KeyType::Dsa, @@ -340,25 +288,13 @@ pub(crate) fn sign_data<'p>( private_key.call_method1(pyo3::intern!(py, "sign"), (data,))? } KeyType::Ec => { - let ec_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ec" - ))?; - let ecdsa = ec_mod - .getattr(pyo3::intern!(py, "ECDSA"))? - .call1((hash_algorithm,))?; + let ecdsa = types::ECDSA.get(py)?.call1((hash_algorithm,))?; private_key.call_method1(pyo3::intern!(py, "sign"), (data, ecdsa))? } KeyType::Rsa => { let mut padding = rsa_padding; if padding.is_none() { - let padding_mod = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.padding" - ))?; - padding = padding_mod - .getattr(pyo3::intern!(py, "PKCS1v15"))? - .call0()?; + padding = types::PKCS1V15.get(py)?.call0()?; } private_key.call_method1(pyo3::intern!(py, "sign"), (data, padding, hash_algorithm))? } @@ -417,51 +353,15 @@ pub(crate) fn identify_public_key_type( py: pyo3::Python<'_>, public_key: &pyo3::PyAny, ) -> pyo3::PyResult { - let rsa_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.rsa" - ))? - .getattr(pyo3::intern!(py, "RSAPublicKey"))? - .extract()?; - let dsa_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.dsa" - ))? - .getattr(pyo3::intern!(py, "DSAPublicKey"))? - .extract()?; - let ec_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ec" - ))? - .getattr(pyo3::intern!(py, "EllipticCurvePublicKey"))? - .extract()?; - let ed25519_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ed25519" - ))? - .getattr(pyo3::intern!(py, "Ed25519PublicKey"))? - .extract()?; - let ed448_key_type: &pyo3::types::PyType = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ed448" - ))? - .getattr(pyo3::intern!(py, "Ed448PublicKey"))? - .extract()?; - - if public_key.is_instance(rsa_key_type)? { + if public_key.is_instance(types::RSA_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Rsa) - } else if public_key.is_instance(dsa_key_type)? { + } else if public_key.is_instance(types::DSA_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Dsa) - } else if public_key.is_instance(ec_key_type)? { + } else if public_key.is_instance(types::ELLIPTIC_CURVE_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ec) - } else if public_key.is_instance(ed25519_key_type)? { + } else if public_key.is_instance(types::ED25519_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ed25519) - } else if public_key.is_instance(ed448_key_type)? { + } else if public_key.is_instance(types::ED448_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ed448) } else { Err(pyo3::exceptions::PyTypeError::new_err( @@ -483,20 +383,20 @@ fn identify_key_type_for_algorithm_params( | common::AlgorithmParameters::RsaWithSha3_384(..) | common::AlgorithmParameters::RsaWithSha3_512(..) | common::AlgorithmParameters::RsaPss(..) => Ok(KeyType::Rsa), - common::AlgorithmParameters::EcDsaWithSha224 - | common::AlgorithmParameters::EcDsaWithSha256 - | common::AlgorithmParameters::EcDsaWithSha384 - | common::AlgorithmParameters::EcDsaWithSha512 + common::AlgorithmParameters::EcDsaWithSha224(..) + | common::AlgorithmParameters::EcDsaWithSha256(..) + | common::AlgorithmParameters::EcDsaWithSha384(..) + | common::AlgorithmParameters::EcDsaWithSha512(..) | common::AlgorithmParameters::EcDsaWithSha3_224 | common::AlgorithmParameters::EcDsaWithSha3_256 | common::AlgorithmParameters::EcDsaWithSha3_384 | common::AlgorithmParameters::EcDsaWithSha3_512 => Ok(KeyType::Ec), common::AlgorithmParameters::Ed25519 => Ok(KeyType::Ed25519), common::AlgorithmParameters::Ed448 => Ok(KeyType::Ed448), - common::AlgorithmParameters::DsaWithSha224 - | common::AlgorithmParameters::DsaWithSha256 - | common::AlgorithmParameters::DsaWithSha384 - | common::AlgorithmParameters::DsaWithSha512 => Ok(KeyType::Dsa), + common::AlgorithmParameters::DsaWithSha224(..) + | common::AlgorithmParameters::DsaWithSha256(..) + | common::AlgorithmParameters::DsaWithSha384(..) + | common::AlgorithmParameters::DsaWithSha512(..) => Ok(KeyType::Dsa), _ => Err(pyo3::exceptions::PyValueError::new_err( "Unsupported signature algorithm", )), @@ -525,9 +425,8 @@ fn hash_oid_py_hash( py: pyo3::Python<'_>, oid: asn1::ObjectIdentifier, ) -> CryptographyResult<&pyo3::PyAny> { - let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; match HASH_OIDS_TO_HASH.get(&oid) { - Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?), + Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", @@ -541,9 +440,7 @@ pub(crate) fn identify_signature_hash_algorithm<'p>( py: pyo3::Python<'p>, signature_algorithm: &common::AlgorithmIdentifier<'_>, ) -> CryptographyResult<&'p pyo3::PyAny> { - let sig_oids_to_hash = py - .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? - .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?; + let sig_oids_to_hash = types::SIG_OIDS_TO_HASH.get(py)?; match &signature_algorithm.params { common::AlgorithmParameters::RsaPss(opt_pss) => { let pss = opt_pss.as_ref().ok_or_else(|| { @@ -586,16 +483,8 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>( } let py_mask_gen_hash_alg = hash_oid_py_hash(py, pss.mask_gen_algorithm.params.oid().clone())?; - let padding = py.import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.padding" - ))?; - let py_mgf = padding - .getattr(pyo3::intern!(py, "MGF1"))? - .call1((py_mask_gen_hash_alg,))?; - Ok(padding - .getattr(pyo3::intern!(py, "PSS"))? - .call1((py_mgf, pss.salt_length))?) + let py_mgf = types::MGF1.get(py)?.call1((py_mask_gen_hash_alg,))?; + Ok(types::PSS.get(py)?.call1((py_mgf, pss.salt_length))?) } common::AlgorithmParameters::RsaWithSha1(_) | common::AlgorithmParameters::RsaWithSha1Alt(_) @@ -607,19 +496,12 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>( | common::AlgorithmParameters::RsaWithSha3_256(_) | common::AlgorithmParameters::RsaWithSha3_384(_) | common::AlgorithmParameters::RsaWithSha3_512(_) => { - let pkcs = py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.padding" - ))? - .getattr(pyo3::intern!(py, "PKCS1v15"))? - .call0()?; - Ok(pkcs) + Ok(types::PKCS1V15.get(py)?.call0()?) } - common::AlgorithmParameters::EcDsaWithSha224 - | common::AlgorithmParameters::EcDsaWithSha256 - | common::AlgorithmParameters::EcDsaWithSha384 - | common::AlgorithmParameters::EcDsaWithSha512 + common::AlgorithmParameters::EcDsaWithSha224(_) + | common::AlgorithmParameters::EcDsaWithSha256(_) + | common::AlgorithmParameters::EcDsaWithSha384(_) + | common::AlgorithmParameters::EcDsaWithSha512(_) | common::AlgorithmParameters::EcDsaWithSha3_224 | common::AlgorithmParameters::EcDsaWithSha3_256 | common::AlgorithmParameters::EcDsaWithSha3_384 @@ -627,13 +509,7 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>( let signature_hash_algorithm = identify_signature_hash_algorithm(py, signature_algorithm)?; - Ok(py - .import(pyo3::intern!( - py, - "cryptography.hazmat.primitives.asymmetric.ec" - ))? - .getattr(pyo3::intern!(py, "ECDSA"))? - .call1((signature_hash_algorithm,))?) + Ok(types::ECDSA.get(py)?.call1((signature_hash_algorithm,))?) } _ => Ok(py.None().into_ref(py)), } @@ -641,11 +517,12 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>( #[cfg(test)] mod tests { + use cryptography_x509::{common, oid}; + use super::{ identify_alg_params_for_hash_type, identify_key_type_for_algorithm_params, HashType, KeyType, }; - use cryptography_x509::{common, oid}; #[test] fn test_identify_key_type_for_algorithm_params() { @@ -682,20 +559,44 @@ mod tests { &common::AlgorithmParameters::RsaWithSha3_512(Some(())), KeyType::Rsa, ), - (&common::AlgorithmParameters::EcDsaWithSha224, KeyType::Ec), - (&common::AlgorithmParameters::EcDsaWithSha256, KeyType::Ec), - (&common::AlgorithmParameters::EcDsaWithSha384, KeyType::Ec), - (&common::AlgorithmParameters::EcDsaWithSha512, KeyType::Ec), + ( + &common::AlgorithmParameters::EcDsaWithSha224(None), + KeyType::Ec, + ), + ( + &common::AlgorithmParameters::EcDsaWithSha256(None), + KeyType::Ec, + ), + ( + &common::AlgorithmParameters::EcDsaWithSha384(None), + KeyType::Ec, + ), + ( + &common::AlgorithmParameters::EcDsaWithSha512(None), + KeyType::Ec, + ), (&common::AlgorithmParameters::EcDsaWithSha3_224, KeyType::Ec), (&common::AlgorithmParameters::EcDsaWithSha3_256, KeyType::Ec), (&common::AlgorithmParameters::EcDsaWithSha3_384, KeyType::Ec), (&common::AlgorithmParameters::EcDsaWithSha3_512, KeyType::Ec), (&common::AlgorithmParameters::Ed25519, KeyType::Ed25519), (&common::AlgorithmParameters::Ed448, KeyType::Ed448), - (&common::AlgorithmParameters::DsaWithSha224, KeyType::Dsa), - (&common::AlgorithmParameters::DsaWithSha256, KeyType::Dsa), - (&common::AlgorithmParameters::DsaWithSha384, KeyType::Dsa), - (&common::AlgorithmParameters::DsaWithSha512, KeyType::Dsa), + ( + &common::AlgorithmParameters::DsaWithSha224(None), + KeyType::Dsa, + ), + ( + &common::AlgorithmParameters::DsaWithSha256(None), + KeyType::Dsa, + ), + ( + &common::AlgorithmParameters::DsaWithSha384(None), + KeyType::Dsa, + ), + ( + &common::AlgorithmParameters::DsaWithSha512(None), + KeyType::Dsa, + ), ] { assert_eq!( identify_key_type_for_algorithm_params(params).unwrap(), diff --git a/src/rust/src/x509/verify.rs b/src/rust/src/x509/verify.rs new file mode 100644 index 000000000000..8cd9cfdf964b --- /dev/null +++ b/src/rust/src/x509/verify.rs @@ -0,0 +1,345 @@ +// 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::certificate::Certificate; +use cryptography_x509_verification::{ + ops::{CryptoOps, VerificationCertificate}, + policy::{Policy, Subject}, + trust_store::Store, + types::{DNSName, IPAddress}, +}; + +use crate::backend::keys; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::types; +use crate::x509::certificate::Certificate as PyCertificate; +use crate::x509::common::{datetime_now, datetime_to_py, py_to_datetime}; +use crate::x509::sign; + +pub(crate) struct PyCryptoOps {} + +impl CryptoOps for PyCryptoOps { + type Key = pyo3::Py; + type Err = CryptographyError; + type CertificateExtra = pyo3::Py; + + fn public_key(&self, cert: &Certificate<'_>) -> Result { + pyo3::Python::with_gil(|py| -> Result { + keys::load_der_public_key_bytes(py, cert.tbs_cert.spki.tlv().full_data()) + }) + } + + fn verify_signed_by(&self, cert: &Certificate<'_>, key: &Self::Key) -> Result<(), Self::Err> { + pyo3::Python::with_gil(|py| -> CryptographyResult<()> { + sign::verify_signature_with_signature_algorithm( + py, + key.as_ref(py), + &cert.signature_alg, + cert.signature.as_bytes(), + &asn1::write_single(&cert.tbs_cert)?, + ) + }) + } +} + +pyo3::create_exception!( + cryptography.hazmat.bindings._rust.x509, + VerificationError, + pyo3::exceptions::PyException +); + +#[pyo3::pyclass(frozen, module = "cryptography.x509.verification")] +struct PolicyBuilder { + time: Option, + store: Option>, + max_chain_depth: Option, +} + +#[pyo3::pymethods] +impl PolicyBuilder { + #[new] + fn new() -> PolicyBuilder { + PolicyBuilder { + time: None, + store: None, + max_chain_depth: None, + } + } + + fn time( + &self, + py: pyo3::Python<'_>, + new_time: &pyo3::PyAny, + ) -> CryptographyResult { + if self.time.is_some() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The validation time may only be set once.", + ), + )); + } + Ok(PolicyBuilder { + time: Some(py_to_datetime(py, new_time)?), + store: self.store.as_ref().map(|s| s.clone_ref(py)), + max_chain_depth: self.max_chain_depth, + }) + } + + fn store(&self, new_store: pyo3::Py) -> CryptographyResult { + if self.store.is_some() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("The trust store may only be set once."), + )); + } + Ok(PolicyBuilder { + time: self.time.clone(), + store: Some(new_store), + max_chain_depth: self.max_chain_depth, + }) + } + + fn max_chain_depth( + &self, + py: pyo3::Python<'_>, + new_max_chain_depth: u8, + ) -> CryptographyResult { + if self.max_chain_depth.is_some() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The maximum chain depth may only be set once.", + ), + )); + } + Ok(PolicyBuilder { + time: self.time.clone(), + store: self.store.as_ref().map(|s| s.clone_ref(py)), + max_chain_depth: Some(new_max_chain_depth), + }) + } + + fn build_server_verifier( + &self, + py: pyo3::Python<'_>, + subject: pyo3::PyObject, + ) -> CryptographyResult { + let store = match self.store.as_ref() { + Some(s) => s.clone_ref(py), + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "A server verifier must have a trust store.", + ), + )); + } + }; + + let time = match self.time.as_ref() { + Some(t) => t.clone(), + None => datetime_now(py)?, + }; + let subject_owner = build_subject_owner(py, &subject)?; + + let policy = OwnedPolicy::try_new(subject_owner, |subject_owner| { + let subject = build_subject(py, subject_owner)?; + Ok::, pyo3::PyErr>(PyCryptoPolicy(Policy::new( + PyCryptoOps {}, + subject, + time, + self.max_chain_depth, + ))) + })?; + + Ok(PyServerVerifier { + py_subject: subject, + policy, + store, + }) + } +} + +struct PyCryptoPolicy<'a>(Policy<'a, PyCryptoOps>); + +/// This enum exists solely to provide heterogeneously typed ownership for `OwnedPolicy`. +enum SubjectOwner { + // TODO: Switch this to `Py` once Pyo3's `to_str()` preserves a + // lifetime relationship between an a `PyString` and its borrowed `&str` + // reference in all limited API builds. PyO3 can't currently do that in + // older limited API builds because it needs `PyUnicode_AsUTF8AndSize` to do + // so, which was only stabilized with 3.10. + DNSName(String), + IPAddress(pyo3::Py), +} + +self_cell::self_cell!( + struct OwnedPolicy { + owner: SubjectOwner, + + #[covariant] + dependent: PyCryptoPolicy, + } +); + +#[pyo3::pyclass( + frozen, + name = "ServerVerifier", + module = "cryptography.hazmat.bindings._rust.x509" +)] +struct PyServerVerifier { + #[pyo3(get, name = "subject")] + py_subject: pyo3::Py, + policy: OwnedPolicy, + #[pyo3(get)] + store: pyo3::Py, +} + +impl PyServerVerifier { + fn as_policy(&self) -> &Policy<'_, PyCryptoOps> { + &self.policy.borrow_dependent().0 + } +} + +#[pyo3::pymethods] +impl PyServerVerifier { + #[getter] + fn validation_time<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + datetime_to_py(py, &self.as_policy().validation_time) + } + + #[getter] + fn max_chain_depth(&self) -> u8 { + self.as_policy().max_chain_depth + } + + fn verify<'p>( + &self, + py: pyo3::Python<'p>, + leaf: pyo3::Py, + intermediates: Vec>, + ) -> CryptographyResult<&'p pyo3::types::PyList> { + let policy = self.as_policy(); + let store = self.store.get(); + + let chain = cryptography_x509_verification::verify( + &VerificationCertificate::new( + leaf.get().raw.borrow_dependent().clone(), + leaf.clone_ref(py), + ), + intermediates.iter().map(|i| { + VerificationCertificate::new( + i.get().raw.borrow_dependent().clone(), + i.clone_ref(py), + ) + }), + policy, + store.raw.borrow_dependent(), + ) + .map_err(|e| VerificationError::new_err(format!("validation failed: {e:?}")))?; + + let result = pyo3::types::PyList::empty(py); + for c in chain { + result.append(c.extra())?; + } + Ok(result) + } +} + +fn build_subject_owner( + py: pyo3::Python<'_>, + subject: &pyo3::Py, +) -> pyo3::PyResult { + let subject = subject.as_ref(py); + + if subject.is_instance(types::DNS_NAME.get(py)?)? { + let value = subject + .getattr(pyo3::intern!(py, "value"))? + .downcast::()?; + + Ok(SubjectOwner::DNSName(value.to_str()?.to_owned())) + } else if subject.is_instance(types::IP_ADDRESS.get(py)?)? { + let value = subject + .getattr(pyo3::intern!(py, "_packed"))? + .call0()? + .downcast::()?; + + Ok(SubjectOwner::IPAddress(value.into())) + } else { + Err(pyo3::exceptions::PyTypeError::new_err( + "unsupported subject type", + )) + } +} + +fn build_subject<'a>( + py: pyo3::Python<'_>, + subject: &'a SubjectOwner, +) -> pyo3::PyResult> { + match subject { + SubjectOwner::DNSName(dns_name) => { + let dns_name = DNSName::new(dns_name) + .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("invalid domain name"))?; + + Ok(Subject::DNS(dns_name)) + } + SubjectOwner::IPAddress(ip_addr) => { + let ip_addr = IPAddress::from_bytes(ip_addr.as_bytes(py)) + .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("invalid IP address"))?; + + Ok(Subject::IP(ip_addr)) + } + } +} + +type PyCryptoOpsStore<'a> = Store<'a, PyCryptoOps>; + +self_cell::self_cell!( + struct RawPyStore { + owner: Vec>, + + #[covariant] + dependent: PyCryptoOpsStore, + } +); + +#[pyo3::pyclass( + frozen, + name = "Store", + module = "cryptography.hazmat.bindings._rust.x509" +)] +struct PyStore { + raw: RawPyStore, +} + +#[pyo3::pymethods] +impl PyStore { + #[new] + fn new(py: pyo3::Python<'_>, certs: Vec>) -> pyo3::PyResult { + if certs.is_empty() { + return Err(pyo3::exceptions::PyValueError::new_err( + "can't create an empty store", + )); + } + Ok(Self { + raw: RawPyStore::new(certs, |v| { + Store::new(v.iter().map(|t| { + VerificationCertificate::new( + t.get().raw.borrow_dependent().clone(), + t.clone_ref(py), + ) + })) + }), + }) + } +} + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add( + "VerificationError", + module.py().get_type::(), + )?; + + Ok(()) +} diff --git a/tests/bench/test_aead.py b/tests/bench/test_aead.py index f93c4e8892eb..7a309682f90d 100644 --- a/tests/bench/test_aead.py +++ b/tests/bench/test_aead.py @@ -4,6 +4,7 @@ import pytest +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, @@ -12,7 +13,13 @@ ChaCha20Poly1305, ) -from ..hazmat.primitives.test_aead import _aead_supported + +def _aead_supported(cls): + try: + cls(b"0" * 32) + return True + except UnsupportedAlgorithm: + return False @pytest.mark.skipif( diff --git a/tests/bench/test_x509.py b/tests/bench/test_x509.py index 87a60af0f597..abfbbf92a199 100644 --- a/tests/bench/test_x509.py +++ b/tests/bench/test_x509.py @@ -2,14 +2,18 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import datetime +import json import os +import certifi + from cryptography import x509 from ..utils import load_vectors_from_file -def test_object_identier_constructor(benchmark): +def test_object_identifier_constructor(benchmark): benchmark(x509.ObjectIdentifier, "1.3.6.1.4.1.11129.2.4.5") @@ -40,3 +44,38 @@ def test_load_pem_certificate(benchmark): ) benchmark(x509.load_pem_x509_certificate, cert_bytes) + + +def test_verify_docs_python_org(benchmark, pytestconfig): + limbo_root = pytestconfig.getoption("--x509-limbo-root", skip=True) + with open(os.path.join(limbo_root, "limbo.json"), "rb") as f: + [testcase] = [ + tc + for tc in json.load(f)["testcases"] + if tc["id"] == "online::docs.python.org" + ] + + with open(certifi.where(), "rb") as f: + store = x509.verification.Store( + x509.load_pem_x509_certificates(f.read()) + ) + + leaf = x509.load_pem_x509_certificate( + testcase["peer_certificate"].encode() + ) + intermediates = [ + x509.load_pem_x509_certificate(c.encode()) + for c in testcase["untrusted_intermediates"] + ] + time = datetime.datetime.fromisoformat(testcase["validation_time"]) + + def bench(): + verifier = ( + x509.verification.PolicyBuilder() + .store(store) + .time(time) + .build_server_verifier(x509.DNSName("docs.python.org")) + ) + verifier.verify(leaf, intermediates) + + benchmark(bench) diff --git a/tests/conftest.py b/tests/conftest.py index 0e128a16513e..d1f11abbb3c7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,6 +27,7 @@ def pytest_report_header(config): def pytest_addoption(parser): parser.addoption("--wycheproof-root", default=None) + parser.addoption("--x509-limbo-root", default=None) parser.addoption("--enable-fips", default=False) @@ -36,7 +37,7 @@ def pytest_runtest_setup(item): pytest.skip(marker.kwargs["reason"]) -@pytest.fixture() +@pytest.fixture(autouse=True) def backend(request): check_backend_support(openssl_backend, request) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index c8fa1efa21f5..a289c5ba7415 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -11,7 +11,7 @@ from cryptography.exceptions import InternalError, _Reasons from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends.openssl.backend import backend -from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher @@ -20,12 +20,11 @@ from ...doubles import ( DummyAsymmetricPadding, - DummyBlockCipherAlgorithm, DummyCipherAlgorithm, DummyHashAlgorithm, DummyMode, ) -from ...hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 +from ...hazmat.primitives.test_rsa import rsa_key_2048 from ...utils import ( load_vectors_from_file, raises_unsupported_algorithm, @@ -33,21 +32,7 @@ # Make ruff happy since we're importing fixtures that pytest patches in as # func args -__all__ = ["rsa_key_512", "rsa_key_2048"] - - -def skip_if_libre_ssl(openssl_version): - if "LibreSSL" in openssl_version: - pytest.skip("LibreSSL hard-codes RAND_bytes to use arc4random.") - - -class TestLibreSkip: - def test_skip_no(self): - assert skip_if_libre_ssl("OpenSSL 1.0.2h 3 May 2016") is None - - def test_skip_yes(self): - with pytest.raises(pytest.skip.Exception): - skip_if_libre_ssl("LibreSSL 2.1.6") +__all__ = ["rsa_key_2048"] class DummyMGF(padding.MGF): @@ -151,19 +136,6 @@ def test_unknown_error_in_cipher_finalize(self): with pytest.raises(InternalError): enc.finalize() - def test_int_to_bn(self): - value = (2**4242) - 4242 - bn = backend._int_to_bn(value) - assert bn != backend._ffi.NULL - bn = backend._ffi.gc(bn, backend._lib.BN_clear_free) - - assert bn - assert backend._bn_to_int(bn) == value - - def test_bn_to_int(self): - bn = backend._int_to_bn(0) - assert backend._bn_to_int(bn) == 0 - class TestOpenSSLRSA: def test_generate_rsa_parameters_supported(self): @@ -172,24 +144,6 @@ def test_generate_rsa_parameters_supported(self): assert backend.generate_rsa_parameters_supported(3, 1024) is True assert backend.generate_rsa_parameters_supported(3, 511) is False - def test_generate_bad_public_exponent(self): - with pytest.raises(ValueError): - backend.generate_rsa_private_key(public_exponent=1, key_size=2048) - - with pytest.raises(ValueError): - backend.generate_rsa_private_key(public_exponent=4, key_size=2048) - - def test_cant_generate_insecure_tiny_key(self): - with pytest.raises(ValueError): - backend.generate_rsa_private_key( - public_exponent=65537, key_size=511 - ) - - with pytest.raises(ValueError): - backend.generate_rsa_private_key( - public_exponent=65537, key_size=256 - ) - def test_rsa_padding_unsupported_pss_mgf1_hash(self): assert ( backend.rsa_padding_supported( @@ -271,10 +225,10 @@ def test_rsa_padding_unsupported_mgf(self): is False ) - def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_512): + def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_2048): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - rsa_key_512.decrypt( - b"0" * 64, + rsa_key_2048.decrypt( + b"0" * 256, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.MD5()), algorithm=hashes.MD5(), @@ -283,53 +237,20 @@ def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_512): ) -class TestOpenSSLCMAC: - def test_unsupported_cipher(self): - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - backend.create_cmac_ctx(DummyBlockCipherAlgorithm(b"bad")) - - class TestOpenSSLSerializationWithOpenSSL: - def test_pem_password_cb(self): - userdata = backend._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") - pw = b"abcdefg" - password = backend._ffi.new("char []", pw) - userdata.password = password - userdata.length = len(pw) - buflen = 10 - buf = backend._ffi.new("char []", buflen) - res = backend._lib.Cryptography_pem_password_cb( - buf, buflen, 0, userdata - ) - assert res == len(pw) - assert userdata.called == 1 - assert backend._ffi.buffer(buf, len(pw))[:] == pw - assert userdata.maxsize == buflen - assert userdata.error == 0 - - def test_pem_password_cb_no_password(self): - userdata = backend._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") - buflen = 10 - buf = backend._ffi.new("char []", buflen) - res = backend._lib.Cryptography_pem_password_cb( - buf, buflen, 0, userdata - ) - assert res == 0 - assert userdata.error == -1 - def test_unsupported_evp_pkey_type(self): - key = backend._create_evp_pkey_gc() + key = backend._lib.EVP_PKEY_new() + key = backend._ffi.gc(key, backend._lib.EVP_PKEY_free) with raises_unsupported_algorithm(None): - backend._evp_pkey_to_private_key( - key, unsafe_skip_rsa_key_validation=False + rust_openssl.keys.private_key_from_ptr( + int(backend._ffi.cast("uintptr_t", key)), + unsafe_skip_rsa_key_validation=False, ) - with raises_unsupported_algorithm(None): - backend._evp_pkey_to_public_key(key) def test_very_long_pem_serialization_password(self): - password = b"x" * 1024 + password = b"x" * 1025 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Passwords longer than"): load_vectors_from_file( os.path.join( "asymmetric", @@ -337,7 +258,7 @@ def test_very_long_pem_serialization_password(self): "key1.pem", ), lambda pemfile: ( - backend.load_pem_private_key( + serialization.load_pem_private_key( pemfile.read().encode(), password, unsafe_skip_rsa_key_validation=False, @@ -346,12 +267,6 @@ def test_very_long_pem_serialization_password(self): ) -class TestOpenSSLEllipticCurve: - def test_sn_to_elliptic_curve_not_supported(self): - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): - _sn_to_elliptic_curve(backend, "fake") - - class TestRSAPEMSerialization: def test_password_length_limit(self, rsa_key_2048): password = b"x" * 1024 @@ -393,23 +308,3 @@ def test_private_load_dhx_unsupported( ) with pytest.raises(ValueError): loader_func(key_bytes, None, backend) - - @pytest.mark.parametrize( - ("key_path", "loader_func"), - [ - ( - os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), - serialization.load_pem_public_key, - ), - ( - os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"), - serialization.load_der_public_key, - ), - ], - ) - def test_public_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, backend) diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py index 05e8f9480356..371a7c990188 100644 --- a/tests/hazmat/backends/test_openssl_memleak.py +++ b/tests/hazmat/backends/test_openssl_memleak.py @@ -172,11 +172,7 @@ def assert_no_memory_leaks(s, argv=[]): env.pop("COV_CORE_DATAFILE", None) env.pop("COV_CORE_SOURCE", None) - argv = [ - sys.executable, - "-c", - f"{s}\n\n{MEMORY_LEAK_SCRIPT}", - ] + argv + argv = [sys.executable, "-c", f"{s}\n\n{MEMORY_LEAK_SCRIPT}", *argv] # Shell out to a fresh Python process because OpenSSL does not allow you to # install new memory hooks after the first malloc/free occurs. proc = subprocess.Popen( diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index c061c9bf11b0..64c3cfdec05c 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -72,7 +72,7 @@ def test_openssl_assert_error_on_stack(self): -1, ) with pytest.raises(InternalError) as exc_info: - _openssl_assert(b.lib, False) + _openssl_assert(False) error = exc_info.value.err_code[0] assert error.lib == b.lib.ERR_LIB_EVP diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py index c6811a496b24..a4624cefc555 100644 --- a/tests/hazmat/primitives/test_aead.py +++ b/tests/hazmat/primitives/test_aead.py @@ -4,7 +4,9 @@ import binascii +import mmap import os +import sys import pytest @@ -12,6 +14,7 @@ from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, + AESGCMSIV, AESOCB3, AESSIV, ChaCha20Poly1305, @@ -26,11 +29,6 @@ from .utils import _load_all_params -class FakeData(bytes): - def __len__(self): - return 2**31 - - def _aead_supported(cls): try: cls(b"0" * 32) @@ -39,6 +37,10 @@ def _aead_supported(cls): return False +def large_mmap(): + return mmap.mmap(-1, 2**32, prot=mmap.PROT_READ) + + @pytest.mark.skipif( _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL without ChaCha20Poly1305 support", @@ -53,16 +55,21 @@ def test_chacha20poly1305_unsupported_on_older_openssl(backend): reason="Does not support ChaCha20Poly1305", ) class TestChaCha20Poly1305: + @pytest.mark.skipif( + sys.platform not in {"linux", "darwin"}, reason="mmap required" + ) def test_data_too_large(self): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) nonce = b"0" * 12 + large_data = large_mmap() + with pytest.raises(OverflowError): - chacha.encrypt(nonce, FakeData(), b"") + chacha.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): - chacha.encrypt(nonce, b"", FakeData()) + chacha.encrypt(nonce, b"", large_data) def test_generate_key(self): key = ChaCha20Poly1305.generate_key() @@ -189,16 +196,21 @@ def test_buffer_protocol(self, backend): reason="Does not support AESCCM", ) class TestAESCCM: + @pytest.mark.skipif( + sys.platform not in {"linux", "darwin"}, reason="mmap required" + ) def test_data_too_large(self): key = AESCCM.generate_key(128) aesccm = AESCCM(key) nonce = b"0" * 12 + large_data = large_mmap() + with pytest.raises(OverflowError): - aesccm.encrypt(nonce, FakeData(), b"") + aesccm.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): - aesccm.encrypt(nonce, b"", FakeData()) + aesccm.encrypt(nonce, b"", large_data) def test_default_tag_length(self, backend): key = AESCCM.generate_key(128) @@ -362,16 +374,27 @@ def _load_gcm_vectors(): class TestAESGCM: + @pytest.mark.skipif( + sys.platform not in {"linux", "darwin"}, reason="mmap required" + ) def test_data_too_large(self): key = AESGCM.generate_key(128) aesgcm = AESGCM(key) nonce = b"0" * 12 + large_data = large_mmap() + with pytest.raises(OverflowError): - aesgcm.encrypt(nonce, FakeData(), b"") + aesgcm.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): - aesgcm.encrypt(nonce, b"", FakeData()) + aesgcm.encrypt(nonce, b"", large_data) + + def test_decrypt_data_too_short(self): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + with pytest.raises(InvalidTag): + aesgcm.decrypt(b"0" * 12, b"0", None) def test_vectors(self, backend, subtests): vectors = _load_gcm_vectors() @@ -496,16 +519,21 @@ def test_aesocb3_unsupported_on_older_openssl(backend): reason="Does not support AESOCB3", ) class TestAESOCB3: + @pytest.mark.skipif( + sys.platform not in {"linux", "darwin"}, reason="mmap required" + ) def test_data_too_large(self): key = AESOCB3.generate_key(128) aesocb3 = AESOCB3(key) nonce = b"0" * 12 + large_data = large_mmap() + with pytest.raises(OverflowError): - aesocb3.encrypt(nonce, FakeData(), b"") + aesocb3.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): - aesocb3.encrypt(nonce, b"", FakeData()) + aesocb3.encrypt(nonce, b"", large_data) def test_vectors(self, backend, subtests): vectors = [] @@ -558,6 +586,38 @@ def test_vectors_invalid(self, backend, subtests): with pytest.raises(InvalidTag): aesocb3.decrypt(nonce, ct, b"nonsense") + @pytest.mark.parametrize( + ("key_len", "expected"), + [ + (128, b"g\xe9D\xd22V\xc5\xe0\xb6\xc6\x1f\xa2/\xdf\x1e\xa2"), + (192, b"\xf6s\xf2\xc3\xe7\x17J\xae{\xae\x98l\xa9\xf2\x9e\x17"), + (256, b"\xd9\x0e\xb8\xe9\xc9w\xc8\x8by\xddy=\x7f\xfa\x16\x1c"), + ], + ) + def test_rfc7253(self, backend, key_len, expected): + # This is derived from page 18 of RFC 7253, with a tag length of + # 128 bits. + + k = AESOCB3(b"\x00" * ((key_len - 8) // 8) + b"\x80") + + c = b"" + + for i in range(0, 128): + s = b"\x00" * i + n = (3 * i + 1).to_bytes(12, "big") + c += k.encrypt(n, s, s) + n = (3 * i + 2).to_bytes(12, "big") + c += k.encrypt(n, s, b"") + n = (3 * i + 3).to_bytes(12, "big") + c += k.encrypt(n, b"", s) + + assert len(c) == 22400 + + n = (385).to_bytes(12, "big") + output = k.encrypt(n, b"", c) + + assert output == expected + @pytest.mark.parametrize( ("nonce", "data", "associated_data"), [ @@ -583,6 +643,11 @@ def test_invalid_nonce_length(self, backend): with pytest.raises(ValueError): aesocb3.encrypt(b"\x00" * 16, b"hi", None) + with pytest.raises(ValueError): + aesocb3.decrypt(b"\x00" * 11, b"hi", None) + with pytest.raises(ValueError): + aesocb3.decrypt(b"\x00" * 16, b"hi", None) + def test_bad_key(self, backend): with pytest.raises(TypeError): AESOCB3(object()) # type:ignore[arg-type] @@ -629,15 +694,23 @@ def test_buffer_protocol(self, backend): reason="Does not support AESSIV", ) class TestAESSIV: + @pytest.mark.skipif( + sys.platform not in {"linux", "darwin"}, reason="mmap required" + ) def test_data_too_large(self): key = AESSIV.generate_key(256) aessiv = AESSIV(key) + large_data = large_mmap() + + with pytest.raises(OverflowError): + aessiv.encrypt(large_data, None) + with pytest.raises(OverflowError): - aessiv.encrypt(FakeData(), None) + aessiv.encrypt(b"irrelevant", [large_data]) with pytest.raises(OverflowError): - aessiv.encrypt(b"irrelevant", [FakeData()]) + aessiv.decrypt(b"very very irrelevant", [large_data]) def test_no_empty_encryption(self): key = AESSIV.generate_key(256) @@ -646,7 +719,7 @@ def test_no_empty_encryption(self): with pytest.raises(ValueError): aessiv.encrypt(b"", None) - with pytest.raises(ValueError): + with pytest.raises(InvalidTag): aessiv.decrypt(b"", None) def test_vectors(self, backend, subtests): @@ -660,10 +733,11 @@ def test_vectors(self, backend, subtests): aad1 = vector.get("aad", None) aad2 = vector.get("aad2", None) aad3 = vector.get("aad3", None) - aad = [] - for a in [aad1, aad2, aad3]: - if a is not None: - aad.append(binascii.unhexlify(a)) + aad = [ + binascii.unhexlify(a) + for a in (aad1, aad2, aad3) + if a is not None + ] ct = binascii.unhexlify(vector["ciphertext"]) tag = binascii.unhexlify(vector["tag"]) pt = binascii.unhexlify(vector.get("plaintext", b"")) @@ -685,10 +759,11 @@ def test_vectors_invalid(self, backend, subtests): aad1 = vector.get("aad", None) aad2 = vector.get("aad2", None) aad3 = vector.get("aad3", None) - aad = [] - for a in [aad1, aad2, aad3]: - if a is not None: - aad.append(binascii.unhexlify(a)) + aad = [ + binascii.unhexlify(a) + for a in (aad1, aad2, aad3) + if a is not None + ] ct = binascii.unhexlify(vector["ciphertext"]) aessiv = AESSIV(key) @@ -696,7 +771,7 @@ def test_vectors_invalid(self, backend, subtests): badkey = AESSIV(AESSIV.generate_key(256)) badkey.decrypt(ct, aad) with pytest.raises(InvalidTag): - aessiv.decrypt(ct, aad + [b""]) + aessiv.decrypt(ct, [*aad, b""]) with pytest.raises(InvalidTag): aessiv.decrypt(ct, [b"nonsense"]) with pytest.raises(InvalidTag): @@ -756,3 +831,155 @@ def test_buffer_protocol(self, backend): assert ct2 == ct computed_pt2 = aessiv.decrypt(ct2, ad) assert computed_pt2 == pt + + +@pytest.mark.skipif( + not _aead_supported(AESGCMSIV), + reason="Does not support AESGCMSIV", +) +class TestAESGCMSIV: + @pytest.mark.skipif( + sys.platform not in {"linux", "darwin"}, reason="mmap required" + ) + def test_data_too_large(self): + key = AESGCMSIV.generate_key(256) + nonce = os.urandom(12) + aesgcmsiv = AESGCMSIV(key) + + large_data = large_mmap() + + with pytest.raises(OverflowError): + aesgcmsiv.encrypt(nonce, large_data, None) + + with pytest.raises(OverflowError): + aesgcmsiv.encrypt(nonce, b"irrelevant", large_data) + + with pytest.raises(OverflowError): + aesgcmsiv.decrypt(nonce, b"very very irrelevant", large_data) + + def test_invalid_nonce_length(self, backend): + key = AESGCMSIV.generate_key(128) + aesgcmsiv = AESGCMSIV(key) + pt = b"hello" + nonce = os.urandom(14) + with pytest.raises(ValueError): + aesgcmsiv.encrypt(nonce, pt, None) + + with pytest.raises(ValueError): + aesgcmsiv.decrypt(nonce, pt, None) + + def test_no_empty_encryption(self): + key = AESGCMSIV.generate_key(256) + aesgcmsiv = AESGCMSIV(key) + nonce = os.urandom(12) + + with pytest.raises(ValueError): + aesgcmsiv.encrypt(nonce, b"", None) + + with pytest.raises(InvalidTag): + aesgcmsiv.decrypt(nonce, b"", None) + + def test_vectors(self, backend, subtests): + vectors = _load_all_params( + os.path.join("ciphers", "AES", "GCM-SIV"), + [ + "openssl.txt", + "aes-192-gcm-siv.txt", + ], + load_nist_vectors, + ) + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["iv"]) + aad = binascii.unhexlify(vector.get("aad", b"")) + ct = binascii.unhexlify(vector["ciphertext"]) + tag = binascii.unhexlify(vector["tag"]) + pt = binascii.unhexlify(vector.get("plaintext", b"")) + aesgcmsiv = AESGCMSIV(key) + computed_ct = aesgcmsiv.encrypt(nonce, pt, aad) + assert computed_ct[:-16] == ct + assert computed_ct[-16:] == tag + computed_pt = aesgcmsiv.decrypt(nonce, computed_ct, aad) + assert computed_pt == pt + + def test_vectors_invalid(self, backend, subtests): + vectors = _load_all_params( + os.path.join("ciphers", "AES", "GCM-SIV"), + [ + "openssl.txt", + "aes-192-gcm-siv.txt", + ], + load_nist_vectors, + ) + for vector in vectors: + with subtests.test(): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["iv"]) + aad = binascii.unhexlify(vector.get("aad", b"")) + ct = binascii.unhexlify(vector["ciphertext"]) + aesgcmsiv = AESGCMSIV(key) + with pytest.raises(InvalidTag): + badkey = AESGCMSIV(AESGCMSIV.generate_key(256)) + badkey.decrypt(nonce, ct, aad) + with pytest.raises(InvalidTag): + aesgcmsiv.decrypt(nonce, ct, b"nonsense") + with pytest.raises(InvalidTag): + aesgcmsiv.decrypt(nonce, b"nonsense", aad) + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()], + ], + ) + def test_params_not_bytes(self, nonce, data, associated_data, backend): + key = AESGCMSIV.generate_key(256) + aesgcmsiv = AESGCMSIV(key) + with pytest.raises(TypeError): + aesgcmsiv.encrypt(nonce, data, associated_data) + + with pytest.raises(TypeError): + aesgcmsiv.decrypt(nonce, data, associated_data) + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + AESGCMSIV(object()) # type:ignore[arg-type] + + with pytest.raises(ValueError): + AESGCMSIV(b"0" * 31) + + def test_bad_generate_key(self, backend): + with pytest.raises(TypeError): + AESGCMSIV.generate_key(object()) # type:ignore[arg-type] + + with pytest.raises(ValueError): + AESGCMSIV.generate_key(129) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = AESGCMSIV.generate_key(256) + aesgcmsiv = AESGCMSIV(key) + nonce = os.urandom(12) + ct1 = aesgcmsiv.encrypt(nonce, b"some_data", None) + ct2 = aesgcmsiv.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = aesgcmsiv.decrypt(nonce, ct1, None) + pt2 = aesgcmsiv.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + def test_buffer_protocol(self, backend): + key = AESGCMSIV.generate_key(256) + aesgcmsiv = AESGCMSIV(key) + nonce = os.urandom(12) + pt = b"encrypt me" + ad = b"additional" + ct = aesgcmsiv.encrypt(nonce, pt, ad) + computed_pt = aesgcmsiv.decrypt(nonce, ct, ad) + assert computed_pt == pt + aesgcmsiv = AESGCMSIV(bytearray(key)) + ct2 = aesgcmsiv.encrypt(nonce, pt, ad) + assert ct2 == ct + computed_pt2 = aesgcmsiv.decrypt(nonce, ct2, ad) + assert computed_pt2 == pt diff --git a/tests/hazmat/primitives/test_aes_gcm.py b/tests/hazmat/primitives/test_aes_gcm.py index c1154a96292b..d82e37470cae 100644 --- a/tests/hazmat/primitives/test_aes_gcm.py +++ b/tests/hazmat/primitives/test_aes_gcm.py @@ -14,6 +14,14 @@ from .utils import generate_aead_test +def _advance(ctx, n): + ctx._bytes_processed += n + + +def _advance_aad(ctx, n): + ctx._aad_bytes_processed += n + + @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) @@ -66,35 +74,45 @@ def test_gcm_ciphertext_with_no_aad(self, backend): assert encryptor.tag == tag def test_gcm_ciphertext_limit(self, backend): - encryptor = base.Cipher( + cipher = base.Cipher( algorithms.AES(b"\x00" * 16), modes.GCM(b"\x01" * 16), backend=backend, - ).encryptor() - new_max = modes.GCM._MAX_ENCRYPTED_BYTES - 16 - encryptor._bytes_processed = new_max # type: ignore[attr-defined] + ) + encryptor = cipher.encryptor() + _advance(encryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16) encryptor.update(b"0" * 16) - max = modes.GCM._MAX_ENCRYPTED_BYTES - assert encryptor._bytes_processed == max # type: ignore[attr-defined] with pytest.raises(ValueError): encryptor.update(b"0") + with pytest.raises(ValueError): + encryptor.update_into(b"0", bytearray(1)) + + decryptor = cipher.decryptor() + _advance(decryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16) + decryptor.update(b"0" * 16) + with pytest.raises(ValueError): + decryptor.update(b"0") + with pytest.raises(ValueError): + decryptor.update_into(b"0", bytearray(1)) def test_gcm_aad_limit(self, backend): - encryptor = base.Cipher( + cipher = base.Cipher( algorithms.AES(b"\x00" * 16), modes.GCM(b"\x01" * 16), backend=backend, - ).encryptor() - new_max = modes.GCM._MAX_AAD_BYTES - 16 - encryptor._aad_bytes_processed = new_max # type: ignore[attr-defined] - encryptor.authenticate_additional_data(b"0" * 16) - max = modes.GCM._MAX_AAD_BYTES - assert ( - encryptor._aad_bytes_processed == max # type: ignore[attr-defined] ) + encryptor = cipher.encryptor() + _advance_aad(encryptor, modes.GCM._MAX_AAD_BYTES - 16) + encryptor.authenticate_additional_data(b"0" * 16) with pytest.raises(ValueError): encryptor.authenticate_additional_data(b"0") + decryptor = cipher.decryptor() + _advance_aad(decryptor, modes.GCM._MAX_AAD_BYTES - 16) + decryptor.authenticate_additional_data(b"0" * 16) + with pytest.raises(ValueError): + decryptor.authenticate_additional_data(b"0") + def test_gcm_ciphertext_increments(self, backend): encryptor = base.Cipher( algorithms.AES(b"\x00" * 16), @@ -188,22 +206,24 @@ def test_gcm_tag_decrypt_finalize_tag_length(self, tag, backend): def test_buffer_protocol(self, backend): data = bytearray(b"helloworld") - enc = base.Cipher( + c = base.Cipher( algorithms.AES(bytearray(b"\x00" * 16)), modes.GCM(bytearray(b"\x00" * 12)), backend, - ).encryptor() + ) + enc = c.encryptor() enc.authenticate_additional_data(bytearray(b"foo")) ct = enc.update(data) + enc.finalize() - dec = base.Cipher( - algorithms.AES(bytearray(b"\x00" * 16)), - modes.GCM(bytearray(b"\x00" * 12), enc.tag), - backend, - ).decryptor() + + dec = c.decryptor() dec.authenticate_additional_data(bytearray(b"foo")) - pt = dec.update(ct) + dec.finalize() + pt = dec.update(ct) + dec.finalize_with_tag(enc.tag) assert pt == data + enc = c.encryptor() + with pytest.raises(ValueError): + enc.update_into(b"abc123", bytearray(0)) + @pytest.mark.parametrize("size", [8, 128]) def test_gcm_min_max_iv(self, size, backend): if backend._fips_enabled: diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index b831de176a0a..6233e197a50b 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -44,7 +44,9 @@ def test_instantiate_with_non_algorithm(self, backend): algorithm = object() with pytest.raises(TypeError): Cipher( - algorithm, mode=None, backend=backend # type: ignore[arg-type] + algorithm, # type: ignore[arg-type] + mode=None, + backend=backend, ) diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py index 5337465b99c1..7c52ad598d3c 100644 --- a/tests/hazmat/primitives/test_chacha20.py +++ b/tests/hazmat/primitives/test_chacha20.py @@ -26,14 +26,14 @@ class TestChaCha20: "vector", _load_all_params( os.path.join("ciphers", "ChaCha20"), - ["rfc7539.txt"], + ["counter-overflow.txt", "rfc7539.txt"], load_nist_vectors, ), ) def test_vectors(self, vector, backend): key = binascii.unhexlify(vector["key"]) nonce = binascii.unhexlify(vector["nonce"]) - ibc = struct.pack("" + assert ( + repr(pn) == "" + ) def test_ec_public_numbers_hash(): @@ -207,6 +210,16 @@ def test_ec_key_key_size(backend): assert key.public_key().key_size == 256 +def test_deprecated_generate_private_key_with_curve_class(backend): + # This test verifies that if you pass a curve _class_ instead of instance, + # you get a warning and then `key.curve` is still an instance. + _skip_curve_unsupported(backend, ec.SECP256R1()) + + with pytest.warns(utils.DeprecatedIn42): + key = ec.generate_private_key(ec.SECP256R1) # type: ignore[arg-type] + assert isinstance(key.curve, ec.SECP256R1) + + class TestECWithNumbers: def test_with_numbers(self, backend, subtests): vectors = itertools.product( @@ -220,16 +233,14 @@ def test_with_numbers(self, backend, subtests): ) for vector, hash_type in vectors: with subtests.test(): - curve_type: typing.Type[ec.EllipticCurve] = ec._CURVE_TYPES[ - vector["curve"] - ] + curve = ec._CURVE_TYPES[vector["curve"]] - _skip_ecdsa_vector(backend, curve_type, hash_type) + _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePrivateNumbers( vector["d"], ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() + vector["x"], vector["y"], curve ), ).private_key(backend) assert key @@ -238,7 +249,7 @@ def test_with_numbers(self, backend, subtests): assert priv_num.private_value == vector["d"] assert priv_num.public_numbers.x == vector["x"] assert priv_num.public_numbers.y == vector["y"] - assert curve_type().name == priv_num.public_numbers.curve.name + assert curve.name == priv_num.public_numbers.curve.name class TestECDSAVectors: @@ -254,14 +265,14 @@ def test_signing_with_example_keys(self, backend, subtests): ) for vector, hash_type in vectors: with subtests.test(): - curve_type = ec._CURVE_TYPES[vector["curve"]] + curve = ec._CURVE_TYPES[vector["curve"]] - _skip_ecdsa_vector(backend, curve_type, hash_type) + _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePrivateNumbers( vector["d"], ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() + vector["x"], vector["y"], curve ), ).private_key(backend) assert key @@ -279,16 +290,16 @@ def test_signing_with_example_keys(self, backend, subtests): @pytest.mark.parametrize("curve", ec._CURVE_TYPES.values()) def test_generate_vector_curves(self, backend, curve): - _skip_curve_unsupported(backend, curve()) + _skip_curve_unsupported(backend, curve) - key = ec.generate_private_key(curve(), backend) + key = ec.generate_private_key(curve, backend) assert key - assert isinstance(key.curve, curve) + assert type(key.curve) is type(curve) assert key.curve.key_size pkey = key.public_key() assert pkey - assert isinstance(pkey.curve, curve) + assert type(pkey.curve) is type(curve) assert key.curve.key_size == pkey.curve.key_size def test_generate_unknown_curve(self, backend): @@ -456,14 +467,12 @@ def test_signatures(self, backend, subtests): for vector in vectors: with subtests.test(): hash_type = _HASH_TYPES[vector["digest_algorithm"]] - curve_type: typing.Type[ec.EllipticCurve] = ec._CURVE_TYPES[ - vector["curve"] - ] + curve = ec._CURVE_TYPES[vector["curve"]] - _skip_ecdsa_vector(backend, curve_type, hash_type) + _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() + vector["x"], vector["y"], curve ).public_key(backend) signature = encode_dss_signature(vector["r"], vector["s"]) @@ -478,12 +487,12 @@ def test_signature_failures(self, backend, subtests): for vector in vectors: with subtests.test(): hash_type = _HASH_TYPES[vector["digest_algorithm"]] - curve_type = ec._CURVE_TYPES[vector["curve"]] + curve = ec._CURVE_TYPES[vector["curve"]] - _skip_ecdsa_vector(backend, curve_type, hash_type) + _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePublicNumbers( - vector["x"], vector["y"], curve_type() + vector["x"], vector["y"], curve ).public_key(backend) signature = encode_dss_signature(vector["r"], vector["s"]) @@ -507,6 +516,15 @@ def test_sign(self, backend): public_key = private_key.public_key() public_key.verify(signature, message, algorithm) + def test_sign_verify_buffers(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = bytearray(b"one little message") + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(bytearray(signature), message, algorithm) + def test_sign_prehashed(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" @@ -613,6 +631,19 @@ def test_public_key_equality(self, backend): assert key1 == key2 assert key1 != key3 assert key1 != object() + with pytest.raises(TypeError): + key1 < key2 # type: ignore[operator] + + def test_public_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).public_key() + key2 = copy.copy(key1) + + assert key1 == key2 class TestECSerialization: @@ -794,6 +825,13 @@ def test_private_bytes_traditional_der_encrypted_invalid(self, backend): serialization.BestAvailableEncryption(b"password"), ) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.SMIME, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption(), + ) + def test_private_bytes_invalid_encoding(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -870,6 +908,62 @@ def test_public_bytes_from_derived_public_key(self, backend): parsed_public = serialization.load_pem_public_key(pem, backend) assert parsed_public + def test_load_private_key_explicit_parameters(self): + with pytest.raises(ValueError, match="explicit parameters"): + load_vectors_from_file( + os.path.join( + "asymmetric", "EC", "explicit_parameters_private_key.pem" + ), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read(), password=None + ), + mode="rb", + ) + + with pytest.raises(ValueError, match="explicit parameters"): + load_vectors_from_file( + os.path.join( + "asymmetric", + "EC", + "explicit_parameters_wap_wsg_idm_ecid_wtls11_private_key.pem", + ), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read(), password=None + ), + mode="rb", + ) + + def test_load_private_key_unsupported_curve(self): + with pytest.raises((ValueError, exceptions.UnsupportedAlgorithm)): + load_vectors_from_file( + os.path.join("asymmetric", "EC", "secp128r1_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read(), password=None + ), + mode="rb", + ) + + @pytest.mark.parametrize( + ("key_file", "curve"), + [ + ("sect163k1-spki.pem", ec.SECT163K1), + ("sect163r2-spki.pem", ec.SECT163R2), + ("sect233k1-spki.pem", ec.SECT233K1), + ("sect233r1-spki.pem", ec.SECT233R1), + ], + ) + def test_load_public_keys(self, key_file, curve, backend): + _skip_curve_unsupported(backend, curve()) + key = load_vectors_from_file( + os.path.join("asymmetric", "EC", key_file), + lambda pemfile: serialization.load_pem_public_key( + pemfile.read(), + ), + mode="rb", + ) + assert isinstance(key, ec.EllipticCurvePublicKey) + assert isinstance(key.curve, curve) + class TestEllipticCurvePEMPublicKeySerialization: @pytest.mark.parametrize( @@ -1077,7 +1171,8 @@ def test_from_encoded_point_empty_byte_string(self): def test_from_encoded_point_not_a_curve(self): with pytest.raises(TypeError): ec.EllipticCurvePublicKey.from_encoded_point( - "notacurve", b"\x04data" # type: ignore[arg-type] + "notacurve", # type: ignore[arg-type] + b"\x04data", ) def test_from_encoded_point_unsupported_encoding(self): @@ -1140,7 +1235,7 @@ def test_key_exchange_with_vectors(self, backend, subtests): for vector in vectors: with subtests.test(): _skip_exchange_algorithm_unsupported( - backend, ec.ECDH(), ec._CURVE_TYPES[vector["curve"]]() + backend, ec.ECDH(), ec._CURVE_TYPES[vector["curve"]] ) key_numbers = vector["IUT"] @@ -1149,7 +1244,7 @@ def test_key_exchange_with_vectors(self, backend, subtests): ec.EllipticCurvePublicNumbers( key_numbers["x"], key_numbers["y"], - ec._CURVE_TYPES[vector["curve"]](), + ec._CURVE_TYPES[vector["curve"]], ), ) # Errno 5-7 indicates a bad public or private key, this @@ -1165,7 +1260,7 @@ def test_key_exchange_with_vectors(self, backend, subtests): public_numbers = ec.EllipticCurvePublicNumbers( peer_numbers["x"], peer_numbers["y"], - ec._CURVE_TYPES[vector["curve"]](), + ec._CURVE_TYPES[vector["curve"]], ) # Errno 1 and 2 indicates a bad public key, this doesn't test # the ECDH code at all @@ -1195,7 +1290,7 @@ def test_key_exchange_with_vectors(self, backend, subtests): ), ) def test_brainpool_kex(self, backend, vector): - curve = ec._CURVE_TYPES[vector["curve"].decode("ascii")]() + curve = ec._CURVE_TYPES[vector["curve"].decode("ascii")] _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) key = ec.EllipticCurvePrivateNumbers( int(vector["da"], 16), diff --git a/tests/hazmat/primitives/test_ed25519.py b/tests/hazmat/primitives/test_ed25519.py index 4b47e0a1657f..26f7d0c71b07 100644 --- a/tests/hazmat/primitives/test_ed25519.py +++ b/tests/hazmat/primitives/test_ed25519.py @@ -4,6 +4,7 @@ import binascii +import copy import os import pytest @@ -116,6 +117,12 @@ def test_invalid_signature(self, backend): with pytest.raises(InvalidSignature): key.public_key().verify(b"0" * 64, b"test data") + def test_sign_verify_buffer(self, backend): + key = Ed25519PrivateKey.generate() + data = bytearray(b"test data") + signature = key.sign(data) + key.public_key().verify(bytearray(signature), data) + def test_generate(self, backend): key = Ed25519PrivateKey.generate() assert key @@ -245,6 +252,13 @@ def test_invalid_public_bytes(self, backend): None, serialization.load_der_private_key, ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"\x00"), + b"\x00", + serialization.load_der_private_key, + ), ], ) def test_round_trip_private_serialization( @@ -287,3 +301,19 @@ def test_public_key_equality(backend): with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support", +) +def test_public_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).public_key() + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py index 650cdda7997c..6c7bdedea39d 100644 --- a/tests/hazmat/primitives/test_ed448.py +++ b/tests/hazmat/primitives/test_ed448.py @@ -4,6 +4,7 @@ import binascii +import copy import os import pytest @@ -85,6 +86,12 @@ def test_invalid_signature(self, backend): with pytest.raises(InvalidSignature): key.public_key().verify(b"0" * 64, b"test data") + def test_sign_verify_buffer(self, backend): + key = Ed448PrivateKey.generate() + data = bytearray(b"test data") + signature = key.sign(data) + key.public_key().verify(bytearray(signature), data) + def test_generate(self, backend): key = Ed448PrivateKey.generate() assert key @@ -288,3 +295,19 @@ def test_public_key_equality(backend): with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support", +) +def test_public_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).public_key() + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 78bb26254d9b..04c3e8588f01 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -38,7 +38,9 @@ def test_hmac_reject_unicode(self, backend): def test_hmac_algorithm_instance(self, backend): with pytest.raises(TypeError): hmac.HMAC( - b"key", hashes.SHA1, backend=backend # type: ignore[arg-type] + b"key", + hashes.SHA1, # type: ignore[arg-type] + backend=backend, ) def test_raises_after_finalize(self, backend): @@ -81,6 +83,9 @@ def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): hmac.HMAC(b"key", DummyHashAlgorithm(), backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + hmac.HMAC(b"key", hashes.SHAKE256(digest_size=256), backend) + def test_buffer_protocol(self, backend): key = bytearray(b"2b7e151628aed2a6abf7158809cf4f3c") h = hmac.HMAC(key, hashes.SHA256(), backend) diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index f44fdd115af3..cd9c279ac4b0 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -4,7 +4,7 @@ import os -from datetime import datetime +from datetime import datetime, timezone import pytest @@ -40,9 +40,7 @@ def _skip_curve_unsupported(backend, curve): if not backend.elliptic_curve_supported(curve): pytest.skip( - "Curve {} is not supported by this backend {}".format( - curve.name, backend - ) + f"Curve {curve.name} is not supported by this backend {backend}" ) @@ -51,20 +49,7 @@ def _skip_curve_unsupported(backend, curve): ) class TestPKCS12Loading: def _test_load_pkcs12_ec_keys(self, filename, password, backend): - cert = load_vectors_from_file( - os.path.join("x509", "custom", "ca", "ca.pem"), - lambda pemfile: x509.load_pem_x509_certificate( - pemfile.read(), backend - ), - mode="rb", - ) - key = load_vectors_from_file( - os.path.join("x509", "custom", "ca", "ca_key.pem"), - lambda pemfile: load_pem_private_key( - pemfile.read(), None, backend - ), - mode="rb", - ) + cert, key = _load_ca(backend) assert isinstance(key, ec.EllipticCurvePrivateKey) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", filename), @@ -103,13 +88,7 @@ def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): self._test_load_pkcs12_ec_keys(filename, password, backend) def test_load_pkcs12_cert_only(self, backend): - cert = load_vectors_from_file( - os.path.join("x509", "custom", "ca", "ca.pem"), - lambda pemfile: x509.load_pem_x509_certificate( - pemfile.read(), backend - ), - mode="rb", - ) + cert, _ = _load_ca(backend) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", "cert-aes256cbc-no-key.p12"), lambda data: load_key_and_certificates( @@ -122,13 +101,7 @@ def test_load_pkcs12_cert_only(self, backend): assert parsed_more_certs == [cert] def test_load_pkcs12_key_only(self, backend): - key = load_vectors_from_file( - os.path.join("x509", "custom", "ca", "ca_key.pem"), - lambda pemfile: load_pem_private_key( - pemfile.read(), None, backend - ), - mode="rb", - ) + _, key = _load_ca(backend) assert isinstance(key, ec.EllipticCurvePrivateKey) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"), @@ -145,7 +118,9 @@ def test_load_pkcs12_key_only(self, backend): def test_non_bytes(self, backend): with pytest.raises(TypeError): load_key_and_certificates( - b"irrelevant", object(), backend # type: ignore[arg-type] + b"irrelevant", + object(), # type: ignore[arg-type] + backend, ) def test_not_a_pkcs12(self, backend): @@ -290,9 +265,9 @@ def _load_cert(backend, path): def _load_ca(backend): - cert = _load_cert(backend, os.path.join("x509", "custom", "ca", "ca.pem")) + cert = _load_cert(backend, os.path.join("pkcs12", "ca", "ca.pem")) key = load_vectors_from_file( - os.path.join("x509", "custom", "ca", "ca_key.pem"), + os.path.join("pkcs12", "ca", "ca_key.pem"), lambda pemfile: load_pem_private_key(pemfile.read(), None, backend), mode="rb", ) @@ -356,7 +331,7 @@ def test_generate_each_supported_keytype( assert isinstance(key, ktype) cacert, cakey = _load_ca(backend) - now = datetime.utcnow() + now = datetime.now(timezone.utc).replace(tzinfo=None) cert = ( x509.CertificateBuilder() .subject_name(cacert.subject) @@ -600,7 +575,7 @@ def test_key_serialization_encryption( encryption = builder.build(b"password") key = ec.generate_private_key(ec.SECP256R1()) cacert, cakey = _load_ca(backend) - now = datetime.utcnow() + now = datetime.now(timezone.utc).replace(tzinfo=None) cert = ( x509.CertificateBuilder() .subject_name(cacert.subject) @@ -701,7 +676,7 @@ def make_cert(name): x509.NameAttribute(x509.NameOID.COMMON_NAME, name), ] ) - now = datetime.utcnow() + now = datetime.now(timezone.utc).replace(tzinfo=None) cert = ( x509.CertificateBuilder() .subject_name(subject) @@ -728,7 +703,7 @@ def make_cert(name): ) # Parse them out. The API should report them in the same order. - (key, cert, certs) = load_key_and_certificates(p12, None) + (_, cert, certs) = load_key_and_certificates(p12, None) assert cert == a_cert assert certs == [b_cert, c_cert] @@ -796,25 +771,31 @@ def test_certificate_repr(self, backend): cert = _load_cert(backend, os.path.join("x509", "cryptography.io.pem")) assert ( repr(PKCS12Certificate(cert, None)) - == f"" + == f"" ) assert ( repr(PKCS12Certificate(cert, b"a")) - == f"" + == f"" ) def test_key_and_certificates_constructor(self, backend): with pytest.raises(TypeError): PKCS12KeyAndCertificates( - "hello", None, [] # type:ignore[arg-type] + "hello", # type:ignore[arg-type] + None, + [], ) with pytest.raises(TypeError): PKCS12KeyAndCertificates( - None, "hello", [] # type:ignore[arg-type] + None, + "hello", # type:ignore[arg-type] + [], ) with pytest.raises(TypeError): PKCS12KeyAndCertificates( - None, None, ["hello"] # type:ignore[list-item] + None, + None, + ["hello"], # type:ignore[list-item] ) def test_key_and_certificates_equality(self, backend): diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 172cf40bd6e4..03b04cd389e5 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -12,7 +12,7 @@ from cryptography import x509 from cryptography.exceptions import _Reasons from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ed25519, rsa +from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa from cryptography.hazmat.primitives.serialization import pkcs7 from ...utils import load_vectors_from_file, raises_unsupported_algorithm @@ -89,6 +89,12 @@ def test_load_pkcs7_unsupported_type(self, backend): mode="rb", ) + def test_load_pkcs7_empty_certificates(self): + der = b"\x30\x0B\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02" + + with pytest.raises(ValueError): + pkcs7.load_der_pkcs7_certificates(der) + # We have no public verification API and won't be adding one until we get # some requirements from users so this function exists to give us basic @@ -194,14 +200,18 @@ def test_unsupported_hash_alg(self, backend): cert, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( - cert, key, hashes.SHA512_256() # type: ignore[arg-type] + cert, + key, + hashes.SHA512_256(), # type: ignore[arg-type] ) def test_not_a_cert(self, backend): - cert, key = _load_cert_key() + _, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( - b"notacert", key, hashes.SHA256() # type: ignore[arg-type] + b"notacert", # type: ignore[arg-type] + key, + hashes.SHA256(), ) @pytest.mark.supported( @@ -213,7 +223,9 @@ def test_unsupported_key_type(self, backend): key = ed25519.Ed25519PrivateKey.generate() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( - cert, key, hashes.SHA256() # type: ignore[arg-type] + cert, + key, # type: ignore[arg-type] + hashes.SHA256(), ) def test_sign_invalid_options(self, backend): @@ -618,6 +630,97 @@ def test_sign_no_certs(self, backend): sig_no = builder.sign(serialization.Encoding.DER, options) assert sig_no.count(cert.public_bytes(serialization.Encoding.DER)) == 0 + @pytest.mark.parametrize( + "pad", + [ + padding.PKCS1v15(), + None, + padding.PSS( + mgf=padding.MGF1(hashes.SHA512()), + salt_length=padding.PSS.DIGEST_LENGTH, + ), + ], + ) + def test_rsa_pkcs_padding_options(self, pad, backend): + data = b"hello world" + rsa_key = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "rsa_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read(), None, unsafe_skip_rsa_key_validation=True + ), + mode="rb", + ) + assert isinstance(rsa_key, rsa.RSAPrivateKey) + rsa_cert = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "rsa_ca.pem"), + loader=lambda pemfile: x509.load_pem_x509_certificate( + pemfile.read() + ), + mode="rb", + ) + builder = ( + pkcs7.PKCS7SignatureBuilder() + .set_data(data) + .add_signer(rsa_cert, rsa_key, hashes.SHA512(), rsa_padding=pad) + ) + options: typing.List[pkcs7.PKCS7Options] = [] + sig = builder.sign(serialization.Encoding.DER, options) + # This should be a pkcs1 sha512 signature + if isinstance(pad, padding.PSS): + # PKCS7_verify can't verify a PSS sig and we don't bind CMS so + # we instead just check that a few things are present in the + # output. + # There should be four SHA512 OIDs in this structure + assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 4 + # There should be one MGF1 OID in this structure + assert ( + sig.count(b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x08") == 1 + ) + else: + # This should be a pkcs1 sha512 signature + assert ( + sig.count(b"\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x0D") == 1 + ) + _pkcs7_verify( + serialization.Encoding.DER, + sig, + None, + [rsa_cert], + options, + backend, + ) + + def test_not_rsa_key_with_padding(self, backend): + cert, key = _load_cert_key() + with pytest.raises(TypeError): + pkcs7.PKCS7SignatureBuilder().add_signer( + cert, key, hashes.SHA512(), rsa_padding=padding.PKCS1v15() + ) + + def test_rsa_invalid_padding(self, backend): + rsa_key = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "rsa_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read(), None, unsafe_skip_rsa_key_validation=True + ), + mode="rb", + ) + assert isinstance(rsa_key, rsa.RSAPrivateKey) + rsa_cert = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "rsa_ca.pem"), + loader=lambda pemfile: x509.load_pem_x509_certificate( + pemfile.read() + ), + mode="rb", + ) + with pytest.raises(TypeError): + pkcs7.PKCS7SignatureBuilder().add_signer( + rsa_cert, + rsa_key, + hashes.SHA512(), + rsa_padding=object(), # type: ignore[arg-type] + ) + def test_multiple_signers(self, backend): data = b"hello world" cert, key = _load_cert_key() @@ -816,5 +919,19 @@ def test_invalid_types(self): with pytest.raises(TypeError): pkcs7.serialize_certificates( - certs, "not an encoding" # type: ignore[arg-type] + certs, + "not an encoding", # type: ignore[arg-type] ) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.pkcs7_supported(), + skip_message="Requires OpenSSL without PKCS7 support (BoringSSL)", +) +class TestPKCS7Unsupported: + def test_pkcs7_functions_unsupported(self): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + pkcs7.load_der_pkcs7_certificates(b"nonsense") + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + pkcs7.load_pem_pkcs7_certificates(b"nonsense") diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 017e02d424b2..8810f0f58e7e 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -4,6 +4,7 @@ import binascii +import copy import itertools import os @@ -82,15 +83,6 @@ def _check_fips_key_length(backend, private_key): pytest.skip(f"Key size not FIPS compliant: {private_key.key_size}") -def _check_rsa_private_numbers_if_serializable(key): - if isinstance(key, rsa.RSAPrivateKey): - _check_rsa_private_numbers(key.private_numbers()) - - -def test_check_rsa_private_numbers_if_serializable(): - _check_rsa_private_numbers_if_serializable("notserializable") - - def _flatten_pkcs1_examples(vectors): flattened_vectors = [] for vector in vectors: @@ -191,7 +183,7 @@ def test_generate_rsa_keys(self, backend, public_exponent, key_size): skey = rsa.generate_private_key(public_exponent, key_size, backend) assert skey.key_size == key_size - _check_rsa_private_numbers_if_serializable(skey) + _check_rsa_private_numbers(skey.private_numbers()) pkey = skey.public_key() assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) @@ -260,9 +252,12 @@ def test_load_pss_vect_example_keys(self, pkcs1_example): @pytest.mark.supported( only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + and ( + not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + or backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not backend._lib.CRYPTOGRAPHY_LIBRESSL_LESS_THAN_380 + ) ), skip_message="Does not support RSA PSS loading", ) @@ -297,14 +292,6 @@ def test_load_pss_keys_strips_constraints(self, path, backend): signature, b"whatever", padding.PKCS1v15(), hashes.SHA224() ) - @pytest.mark.supported( - only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E - ), - skip_message="Does not support RSA PSS loading", - ) def test_load_pss_pub_keys_strips_constraints(self, backend): key = load_vectors_from_file( filename=os.path.join( @@ -323,9 +310,12 @@ def test_load_pss_pub_keys_strips_constraints(self, backend): @pytest.mark.supported( only_if=lambda backend: ( - backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - or backend._lib.CRYPTOGRAPHY_IS_BORINGSSL - or backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + and ( + not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + or backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not backend._lib.CRYPTOGRAPHY_LIBRESSL_LESS_THAN_380 + ) ), skip_message="Test requires a backend without RSA-PSS key support", ) @@ -451,45 +441,6 @@ def test_oaep_wrong_label(self, rsa_key_2048, enclabel, declabel, backend): ), ) - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_encryption_supported( - padding.PKCS1v15() - ), - skip_message="Does not support PKCS1v1.5.", - ) - def test_lazy_blinding(self, backend): - # We don't want to reuse the rsa_key_2048 fixture here because lazy - # blinding mutates the object to add the blinding factor on - # the first call to decrypt/sign. Since we reuse rsa_key_2048 in - # many tests we can't properly test blinding, which will (likely) - # already be set on the fixture. - private_key = RSA_KEY_2048.private_key( - unsafe_skip_rsa_key_validation=True - ) - public_key = private_key.public_key() - msg = b"encrypt me!" - ct = public_key.encrypt( - msg, - padding.PKCS1v15(), - ) - assert private_key._blinded is False # type: ignore[attr-defined] - pt = private_key.decrypt( - ct, - padding.PKCS1v15(), - ) - assert private_key._blinded is True # type: ignore[attr-defined] - # Call a second time to cover the branch where blinding - # has already occurred and we don't want to do it again. - pt2 = private_key.decrypt( - ct, - padding.PKCS1v15(), - ) - assert pt == pt2 - assert private_key._blinded is True - # Private method call to cover the racy branch within the lock - private_key._non_threadsafe_enable_blinding() - assert private_key._blinded is True - class TestRSASignature: @pytest.mark.supported( @@ -747,9 +698,9 @@ def test_padding_incorrect_type( skip_message="Does not support PSS.", ) def test_unsupported_pss_mgf( - self, rsa_key_512: rsa.RSAPrivateKey, backend + self, rsa_key_2048: rsa.RSAPrivateKey, backend ): - private_key = rsa_key_512 + private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.sign( b"msg", @@ -812,9 +763,15 @@ def test_pkcs1_minimum_key_size(self, backend): ) private_key.sign(b"no failure", padding.PKCS1v15(), hashes.SHA512()) - def test_sign(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + @pytest.mark.parametrize( + "message", + [ + b"one little message", + bytearray(b"one little message"), + ], + ) + def test_sign(self, rsa_key_2048: rsa.RSAPrivateKey, message, backend): private_key = rsa_key_2048 - message = b"one little message" pkcs = padding.PKCS1v15() algorithm = hashes.SHA256() signature = private_key.sign(message, pkcs, algorithm) @@ -884,6 +841,21 @@ def test_unsupported_hash(self, rsa_key_512: rsa.RSAPrivateKey, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): private_key.sign(message, pss, hashes.BLAKE2s(32)) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) + ), + skip_message="Does not support PSS.", + ) + def test_unsupported_hash_pss_mgf1(self, rsa_key_2048: rsa.RSAPrivateKey): + private_key = rsa_key_2048 + message = b"my message" + pss = padding.PSS( + mgf=padding.MGF1(DummyHashAlgorithm()), salt_length=0 + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.sign(message, pss, hashes.SHA256()) + @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) @@ -1409,9 +1381,15 @@ def test_pss_verify_salt_length_too_long(self, backend): hashes.SHA1(), ) - def test_verify(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + @pytest.mark.parametrize( + "message", + [ + b"one little message", + bytearray(b"one little message"), + ], + ) + def test_verify(self, rsa_key_2048: rsa.RSAPrivateKey, message, backend): private_key = rsa_key_2048 - message = b"one little message" pkcs = padding.PKCS1v15() algorithm = hashes.SHA256() signature = private_key.sign(message, pkcs, algorithm) @@ -1690,7 +1668,8 @@ class TestPSS: def test_calculate_max_pss_salt_length(self): with pytest.raises(TypeError): padding.calculate_max_pss_salt_length( - object(), hashes.SHA256() # type:ignore[arg-type] + object(), # type:ignore[arg-type] + hashes.SHA256(), ) def test_invalid_salt_length_not_integer(self): @@ -1719,6 +1698,13 @@ def test_valid_pss_parameters_maximum(self): assert pss._mgf == mgf assert pss._salt_length == padding.PSS.MAX_LENGTH + def test_mgf_property(self): + algorithm = hashes.SHA1() + mgf = padding.MGF1(algorithm) + pss = padding.PSS(mgf=mgf, salt_length=padding.PSS.MAX_LENGTH) + assert pss.mgf == mgf + assert pss.mgf == pss._mgf + class TestMGF1: def test_invalid_hash_algorithm(self): @@ -1736,9 +1722,25 @@ def test_invalid_algorithm(self): mgf = padding.MGF1(hashes.SHA1()) with pytest.raises(TypeError): padding.OAEP( - mgf=mgf, algorithm=b"", label=None # type:ignore[arg-type] + mgf=mgf, + algorithm=b"", # type:ignore[arg-type] + label=None, ) + def test_algorithm_property(self): + algorithm = hashes.SHA1() + mgf = padding.MGF1(algorithm) + oaep = padding.OAEP(mgf=mgf, algorithm=algorithm, label=None) + assert oaep.algorithm == algorithm + assert oaep.algorithm == oaep._algorithm + + def test_mgf_property(self): + algorithm = hashes.SHA1() + mgf = padding.MGF1(algorithm) + oaep = padding.OAEP(mgf=mgf, algorithm=algorithm, label=None) + assert oaep.mgf == mgf + assert oaep.mgf == oaep._mgf + class TestRSADecryption: @pytest.mark.supported( @@ -1976,6 +1978,27 @@ def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): ), ) + def test_unsupported_oaep_hash(self, rsa_key_2048: rsa.RSAPrivateKey): + private_key = rsa_key_2048 + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.decrypt( + b"0" * 256, + padding.OAEP( + mgf=padding.MGF1(DummyHashAlgorithm()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.decrypt( + b"0" * 256, + padding.OAEP( + mgf=padding.MGF1(hashes.SHA256()), + algorithm=DummyHashAlgorithm(), + label=None, + ), + ) + def test_unsupported_oaep_mgf( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): @@ -2170,7 +2193,8 @@ def test_unsupported_padding( public_key.encrypt(b"somedata", DummyAsymmetricPadding()) with pytest.raises(TypeError): public_key.encrypt( - b"somedata", padding=object() # type: ignore[arg-type] + b"somedata", + padding=object(), # type: ignore[arg-type] ) def test_unsupported_oaep_mgf( @@ -2740,9 +2764,7 @@ def test_public_bytes_invalid_format( serialization.PublicFormat.SubjectPublicKeyInfo, ), (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), - ] - + list( - itertools.product( + *itertools.product( [ serialization.Encoding.Raw, serialization.Encoding.X962, @@ -2754,8 +2776,8 @@ def test_public_bytes_invalid_format( serialization.PublicFormat.UncompressedPoint, serialization.PublicFormat.CompressedPoint, ], - ) - ), + ), + ], ) def test_public_bytes_rejects_invalid( self, rsa_key_2048: rsa.RSAPrivateKey, encoding, fmt, backend @@ -2775,3 +2797,11 @@ def test_public_key_equality(self, rsa_key_2048: rsa.RSAPrivateKey): assert key1 == key2 assert key1 != key3 assert key1 != object() + with pytest.raises(TypeError): + key1 < key2 # type: ignore[operator] + + def test_public_key_copy(self, rsa_key_2048: rsa.RSAPrivateKey): + key1 = rsa_key_2048.public_key() + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 58693a4912d2..51fcc3563d8a 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -506,6 +506,11 @@ def test_load_pem_ec_private_key(self, key_path, password, backend): "asymmetric", "PEM_Serialization", "rsa_public_key.pem" ), os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), + os.path.join( + "asymmetric", + "PEM_Serialization", + "rsa_wrong_delimiter_public_key.pem", + ), ], ) def test_load_pem_rsa_public_key(self, key_file, backend): @@ -520,6 +525,17 @@ def test_load_pem_rsa_public_key(self, key_file, backend): numbers = key.public_numbers() assert numbers.e == 65537 + def test_load_pem_public_fails_with_ec_key_with_rsa_delimiter(self): + with pytest.raises(ValueError): + load_vectors_from_file( + os.path.join( + "asymmetric", + "PEM_Serialization", + "ec_public_key_rsa_delimiter.pem", + ), + lambda pemfile: load_pem_public_key(pemfile.read().encode()), + ) + def test_load_priv_key_with_public_key_api_fails( self, rsa_key_2048, backend ): diff --git a/tests/hazmat/primitives/test_sm4.py b/tests/hazmat/primitives/test_sm4.py index 53893eecedff..695987bc9604 100644 --- a/tests/hazmat/primitives/test_sm4.py +++ b/tests/hazmat/primitives/test_sm4.py @@ -7,9 +7,10 @@ import pytest -from cryptography.hazmat.primitives.ciphers import algorithms, modes +from cryptography.exceptions import InvalidTag +from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from ...utils import load_nist_vectors +from ...utils import load_nist_vectors, load_vectors_from_file from .utils import generate_encrypt_test @@ -91,3 +92,90 @@ class TestSM4ModeCTR: lambda key, **kwargs: algorithms.SM4(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)), ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.SM4(b"\x00" * 16), modes.GCM(b"\x00" * 16) + ), + skip_message="Does not support SM4 GCM", +) +class TestSM4ModeGCM: + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("ciphers", "SM4", "rfc8998.txt"), + load_nist_vectors, + ), + ) + def test_encryption(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + iv = binascii.unhexlify(vector["iv"]) + associated_data = binascii.unhexlify(vector["aad"]) + tag = binascii.unhexlify(vector["tag"]) + plaintext = binascii.unhexlify(vector["plaintext"]) + ciphertext = binascii.unhexlify(vector["ciphertext"]) + + cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv)) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(associated_data) + computed_ct = encryptor.update(plaintext) + encryptor.finalize() + assert computed_ct == ciphertext + assert encryptor.tag == tag + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("ciphers", "SM4", "rfc8998.txt"), + load_nist_vectors, + ), + ) + def test_decryption(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + iv = binascii.unhexlify(vector["iv"]) + associated_data = binascii.unhexlify(vector["aad"]) + tag = binascii.unhexlify(vector["tag"]) + plaintext = binascii.unhexlify(vector["plaintext"]) + ciphertext = binascii.unhexlify(vector["ciphertext"]) + + cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv, tag)) + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(associated_data) + computed_pt = decryptor.update(ciphertext) + decryptor.finalize() + assert computed_pt == plaintext + + cipher_no_tag = base.Cipher(algorithms.SM4(key), modes.GCM(iv)) + decryptor = cipher_no_tag.decryptor() + decryptor.authenticate_additional_data(associated_data) + computed_pt = decryptor.update( + ciphertext + ) + decryptor.finalize_with_tag(tag) + assert computed_pt == plaintext + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("ciphers", "SM4", "rfc8998.txt"), + load_nist_vectors, + ), + ) + def test_invalid_tag(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + iv = binascii.unhexlify(vector["iv"]) + associated_data = binascii.unhexlify(vector["aad"]) + tag = binascii.unhexlify(vector["tag"]) + ciphertext = binascii.unhexlify(vector["ciphertext"]) + + cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv, tag)) + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(associated_data) + decryptor.update(ciphertext[:-1]) + with pytest.raises(InvalidTag): + decryptor.finalize() + + cipher_no_tag = base.Cipher(algorithms.SM4(key), modes.GCM(iv)) + decryptor = cipher_no_tag.decryptor() + decryptor.authenticate_additional_data(associated_data) + decryptor.update(ciphertext[:-1]) + with pytest.raises(InvalidTag): + decryptor.finalize_with_tag(tag) diff --git a/tests/hazmat/primitives/test_ssh.py b/tests/hazmat/primitives/test_ssh.py index e5c58062d075..d3372566e93f 100644 --- a/tests/hazmat/primitives/test_ssh.py +++ b/tests/hazmat/primitives/test_ssh.py @@ -38,6 +38,9 @@ from ...utils import load_vectors_from_file, raises_unsupported_algorithm from .fixtures_rsa import RSA_KEY_2048 from .test_ec import _skip_curve_unsupported +from .test_rsa import rsa_key_2048 + +__all__ = ["rsa_key_2048"] class TestOpenSSHSerialization: @@ -401,12 +404,12 @@ def make_file( priv_type = pub_type pub = ssh._FragList() - for elem in (pub_type,) + pub_fields: + for elem in (pub_type, *pub_fields): pub.put_sshstr(elem) secret = ssh._FragList([checkval1, checkval2]) for i in range(nkeys): - for elem in (priv_type,) + priv_fields + (comment,): + for elem in (priv_type, *priv_fields, comment): secret.put_sshstr(elem) if pad is None: @@ -589,7 +592,9 @@ def test_serialize_ssh_private_key_errors_bad_curve(self, backend): Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption() ) - def test_serialize_ssh_private_key_errors(self, backend): + def test_serialize_ssh_private_key_errors( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): # bad encoding private_key = ec.generate_private_key(ec.SECP256R1(), backend) with pytest.raises(ValueError): @@ -615,6 +620,11 @@ def test_serialize_ssh_private_key_errors(self, backend): DummyKeySerializationEncryption(), ) + with pytest.raises(ValueError): + rsa_key_2048.private_bytes( + Encoding.DER, PrivateFormat.OpenSSH, NoEncryption() + ) + @pytest.mark.supported( only_if=lambda backend: ssh._bcrypt_supported, skip_message="Requires that bcrypt exists", @@ -636,34 +646,40 @@ def test_serialize_ssh_private_key_errors(self, backend): ], ) def test_serialize_ssh_private_key_with_password( - self, password, kdf_rounds, backend + self, password, kdf_rounds, rsa_key_2048: rsa.RSAPrivateKey, backend ): - original_key = ec.generate_private_key(ec.SECP256R1(), backend) - encoded_key_data = original_key.private_bytes( - Encoding.PEM, - PrivateFormat.OpenSSH, - ( - PrivateFormat.OpenSSH.encryption_builder() - .kdf_rounds(kdf_rounds) - .build(password) - ), - ) + for original_key in [ + ec.generate_private_key(ec.SECP256R1(), backend), + rsa_key_2048, + ]: + assert isinstance( + original_key, (ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey) + ) + encoded_key_data = original_key.private_bytes( + Encoding.PEM, + PrivateFormat.OpenSSH, + ( + PrivateFormat.OpenSSH.encryption_builder() + .kdf_rounds(kdf_rounds) + .build(password) + ), + ) - decoded_key = load_ssh_private_key( - data=encoded_key_data, - password=password, - backend=backend, - ) + decoded_key = load_ssh_private_key( + data=encoded_key_data, + password=password, + backend=backend, + ) - original_public_key = original_key.public_key().public_bytes( - Encoding.OpenSSH, PublicFormat.OpenSSH - ) + original_public_key = original_key.public_key().public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH + ) - decoded_public_key = decoded_key.public_key().public_bytes( - Encoding.OpenSSH, PublicFormat.OpenSSH - ) + decoded_public_key = decoded_key.public_key().public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH + ) - assert original_public_key == decoded_public_key + assert original_public_key == decoded_public_key @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), @@ -1115,26 +1131,28 @@ def test_loads_ssh_cert(self, backend): # secp256r1 public key, ed25519 signing key cert = load_ssh_public_identity( b"ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbm" - b"lzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgtdU+dl9vD4xPi8afxERYo" - b"s0c0d9/3m7XGY6fGeSkqn0AAAAIbmlzdHAyNTYAAABBBAsuVFNNj/mMyFm2xB99" - b"G4xiaUJE1lZNjcp+S2tXYW5KorcHpusSlSqOkUPZ2l0644dgiNPDKR/R+BtYENC" - b"8aq8AAAAAAAAAAAAAAAEAAAAUdGVzdEBjcnlwdG9ncmFwaHkuaW8AAAAaAAAACm" - b"NyeXB0b3VzZXIAAAAIdGVzdHVzZXIAAAAAY7KyZAAAAAB2frXAAAAAAAAAAIIAA" - b"AAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9y" - b"d2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGV" - b"ybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3" - b"NoLWVkMjU1MTkAAAAg3P0eyGf2crKGwSlnChbLzTVOFKwQELE1Ve+EZ6rXF18AA" - b"ABTAAAAC3NzaC1lZDI1NTE5AAAAQKoij8BsPj/XLb45+wHmRWKNqXeZYXyDIj8J" - b"IE6dIymjEqq0TP6ntu5t59hTmWlDO85GnMXAVGBjFbeikBMfAQc= reaperhulk" - b"@despoina.local" + b"lzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLfsFv9Gbc6LZSiJFWdYQl" + b"IMNI50GExXW0fBpgGVf+Y4AAAAIbmlzdHAyNTYAAABBBIzVyRgVLR4F38bIOLBN" + b"8CNm8Nf+eBHCVkKDKb9WDyLLD61CEmzjK/ORwFuSE4N60eIGbFidBf0D0xh7G6o" + b"TNxsAAAAAAAAAAAAAAAEAAAAUdGVzdEBjcnlwdG9ncmFwaHkuaW8AAAAaAAAACm" + b"NyeXB0b3VzZXIAAAAIdGVzdHVzZXIAAAAAY7KyZAAAAAB2frXAAAAAWAAAAA1mb" + b"3JjZS1jb21tYW5kAAAALAAAAChlY2hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh" + b"YWFhYWFhYWFhYWFhAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAACCAAAAFXBlcm1" + b"pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbm" + b"cAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wd" + b"HkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1" + b"NTE5AAAAICH6csEOmGbOfT2B/S/FJg3uyPsaPSZUZk2SVYlfs0KLAAAAUwAAAAt" + b"zc2gtZWQyNTUxOQAAAEDz2u7X5/TFbN7Ms7DP4yArhz1oWWYKkdAk7FGFkHfjtY" + b"/YfNQ8Oky3dCZRi7PnSzScEEjos7723dhF8/y99WwH reaperhulk@despoina." + b"local" ) assert isinstance(cert, SSHCertificate) cert.verify_cert_signature() signature_key = cert.signature_key() assert isinstance(signature_key, ed25519.Ed25519PublicKey) assert cert.nonce == ( - b"\xb5\xd5>v_o\x0f\x8cO\x8b\xc6\x9f\xc4DX\xa2\xcd\x1c\xd1\xdf" - b"\x7f\xden\xd7\x19\x8e\x9f\x19\xe4\xa4\xaa}" + b'-\xfb\x05\xbf\xd1\x9bs\xa2\xd9J"EY\xd6\x10\x94\x83\r#\x9d' + b"\x06\x13\x15\xd6\xd1\xf0i\x80e_\xf9\x8e" ) public_key = cert.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) @@ -1145,7 +1163,10 @@ def test_loads_ssh_cert(self, backend): assert cert.valid_principals == [b"cryptouser", b"testuser"] assert cert.valid_before == 1988015552 assert cert.valid_after == 1672655460 - assert cert.critical_options == {} + assert cert.critical_options == { + b"force-command": b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + b"verify-required": b"", + } assert cert.extensions == { b"permit-X11-forwarding": b"", b"permit-agent-forwarding": b"", @@ -1267,6 +1288,8 @@ def test_invalid_cert_type(self): "p256-p256-non-lexical-extensions.pub", "p256-p256-duplicate-crit-opts.pub", "p256-p256-non-lexical-crit-opts.pub", + "p256-ed25519-non-singular-crit-opt-val.pub", + "p256-ed25519-non-singular-ext-val.pub", ], ) def test_invalid_encodings(self, filename): @@ -1478,11 +1501,13 @@ def test_add_critical_option_errors(self): builder = SSHCertificateBuilder() with pytest.raises(TypeError): builder.add_critical_option( - "not bytes", b"test" # type: ignore[arg-type] + "not bytes", # type: ignore[arg-type] + b"test", ) with pytest.raises(TypeError): builder.add_critical_option( - b"test", object() # type: ignore[arg-type] + b"test", + object(), # type: ignore[arg-type] ) builder = builder.add_critical_option(b"test", b"test") with pytest.raises(ValueError): @@ -1492,7 +1517,8 @@ def test_add_extension_errors(self): builder = SSHCertificateBuilder() with pytest.raises(TypeError): builder.add_extension( - "not bytes", b"test" # type: ignore[arg-type] + "not bytes", # type: ignore[arg-type] + b"test", ) with pytest.raises(TypeError): builder.add_extension(b"test", object()) # type: ignore[arg-type] @@ -1693,6 +1719,11 @@ def test_sign_and_byte_compare_rsa(self, monkeypatch): .valid_after(1672531200) .valid_before(1672617600) .type(SSHCertificateType.USER) + .add_extension(b"permit-pty", b"") + .add_critical_option( + b"force-command", b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) + .add_critical_option(b"verify-required", b"") ) cert = builder.sign(private_key) sig_key = cert.signature_key() @@ -1707,19 +1738,21 @@ def test_sign_and_byte_compare_rsa(self, monkeypatch): b"4kyHpbLEIVloBjzetoqXK6u8Hjz/APuagONypNDCySDR6M7jM85HDcLoFFrbBb8" b"pruHSTxQejMeEmJxYf8b7rNl58/IWPB1ymbNlvHL/4oSOlnrtHkjcxRWzpQ7U3g" b"T9BThGyhCiI7EMyEHMgP3r7kTzEUwT6IavWDAAAAAAAAAAAAAAABAAAAAAAAAAA" - b"AAAAAY7DNAAAAAABjsh6AAAAAAAAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAw" - b"EAAQAAAQEAwXr8fndHTKpaqDA2FYo/+/e1IWhRuiIw5dar/MHGz+9Z6SPqEzC8W" - b"TtzgCq2CKbkozBlI6MRa6WqOWYUUXThO2xJ6beAYuRJ1y77EP1J6R+gi5bQUeeC" - b"6fWrxbWm95hIJ6245z2gDyKy79zbduq0btrZjtZWYnQ/3GwOM2pdDNuqfcKeU2N" - b"eJMh6WyxCFZaAY83raKlyurvB48/wD7moDjcqTQwskg0ejO4zPORw3C6BRa2wW/" - b"Ka7h0k8UHozHhJicWH/G+6zZefPyFjwdcpmzZbxy/+KEjpZ67R5I3MUVs6UO1N4" - b"E/QU4RsoQoiOxDMhBzID96+5E8xFME+iGr1gwAAARQAAAAMcnNhLXNoYTItNTEy" - b"AAABAKCRnfhn6MZs3jRgIDICUpUyWrDCbpStEbdzhmoxF8w2m8klR7owRH/rxOf" - b"nWhKMGnXnoERS+az3Zh9ckiQPujkuEToORKpzu6CEWlzHSzyK1o2X548KkW76HJ" - b"gqzwMas94HY7UOJUgKSFUI0S3jAgqXAKSa1DxvJBu5/n57aUqPq+BmAtoI8uNBo" - b"x4F1pNEop38+oD7rUt8bZ8K0VcrubJZz806K8UNiK0mOahaEIkvZXBfzPGvSNRj" - b"0OjDl1dLUZaP8C1o5lVRomEm7pLcgE9i+ZDq5iz+mvQrSBStlpQ5hPGuUOrZ/oY" - b"ZLZ1G30R5tWj212MHoNZjxFxM8+f2OT4=" + b"AAAAAY7DNAAAAAABjsh6AAAAAWAAAAA1mb3JjZS1jb21tYW5kAAAALAAAAChlY2" + b"hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhAAAAD3Zlcmlme" + b"S1yZXF1aXJlZAAAAAAAAAASAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAARcAAAAH" + b"c3NoLXJzYQAAAAMBAAEAAAEBAMF6/H53R0yqWqgwNhWKP/v3tSFoUboiMOXWq/z" + b"Bxs/vWekj6hMwvFk7c4Aqtgim5KMwZSOjEWulqjlmFFF04TtsSem3gGLkSdcu+x" + b"D9SekfoIuW0FHngun1q8W1pveYSCetuOc9oA8isu/c23bqtG7a2Y7WVmJ0P9xsD" + b"jNqXQzbqn3CnlNjXiTIelssQhWWgGPN62ipcrq7wePP8A+5qA43Kk0MLJINHozu" + b"MzzkcNwugUWtsFvymu4dJPFB6Mx4SYnFh/xvus2Xnz8hY8HXKZs2W8cv/ihI6We" + b"u0eSNzFFbOlDtTeBP0FOEbKEKIjsQzIQcyA/evuRPMRTBPohq9YMAAAEUAAAADH" + b"JzYS1zaGEyLTUxMgAAAQCYbbNzhflDqZAxyBpdLIX0nLAdnTeFNBudMqgo3KGND" + b"WlU9N17hqBEmcvIOrtNi+JKuKZW89zZrbORHvdjv6NjGSKzJD/XA25YrX1KgMEO" + b"wt5pzMZX+100drwrjQo+vZqeIN3FJNmT3wssge73v+JsxQrdIAz7YM2OZrFr5HM" + b"qZEZ5tMvAf/s5YEMDttEU4zMtmjubQyDM5KyYnZdoDT4sKi2rB8gfaigc4IdI/K" + b"8oXL/3Y7rHuOtejl3lUK4v6DxeRl4aqGYWmhUJc++Rh0cbDgC2S6Cq7gAfG2tND" + b"zbwL217Q93R08bJn1hDWuiTiaHGauSy2gPUI+cnkvlEocHM" ) @pytest.mark.supported( @@ -1745,6 +1778,11 @@ def test_sign_and_byte_compare_ed25519(self, monkeypatch, backend): .valid_after(1672531200) .valid_before(1672617600) .type(SSHCertificateType.USER) + .add_extension(b"permit-pty", b"") + .add_critical_option( + b"force-command", b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) + .add_critical_option(b"verify-required", b"") ) cert = builder.sign(private_key) sig_key = cert.signature_key() @@ -1754,8 +1792,11 @@ def test_sign_and_byte_compare_ed25519(self, monkeypatch, backend): b"ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdj" b"AxQG9wZW5zc2guY29tAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" b"AAAAAAAINdamAGCsQq31Uv+08lkBzoO4XLz2qYjJa8CGmj3B1EaAAAAAAAAAAAA" - b"AAABAAAAAAAAAAAAAAAAY7DNAAAAAABjsh6AAAAAAAAAAAAAAAAAAAAAMwAAAAt" - b"zc2gtZWQyNTUxOQAAACDXWpgBgrEKt9VL/tPJZAc6DuFy89qmIyWvAhpo9wdRGg" - b"AAAFMAAAALc3NoLWVkMjU1MTkAAABAAlF6Lxabxs+8fkOr7KjKYei9konIG13cQ" - b"gJ2tWf3yFcg3OuV5s/AkRmKdwHlQfTUrhRdOmDnGxeLEB0mvkVFCw==" + b"AAABAAAAAAAAAAAAAAAAY7DNAAAAAABjsh6AAAAAWAAAAA1mb3JjZS1jb21tYW5" + b"kAAAALAAAAChlY2hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYW" + b"FhAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAAASAAAACnBlcm1pdC1wdHkAAAAAA" + b"AAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAg11qYAYKxCrfVS/7TyWQHOg7hcvPa" + b"piMlrwIaaPcHURoAAABTAAAAC3NzaC1lZDI1NTE5AAAAQL2aUjeD60C2FrbgHcN" + b"t8yRa8IRbxvOyA9TZYDGG1dRE3DiR0fuudU20v6vqfTd1gx0S5QyEdECXLl9ZI3" + b"AwZgc=" ) diff --git a/tests/hazmat/primitives/test_x25519.py b/tests/hazmat/primitives/test_x25519.py index 2b86d3d5e22b..b68286e1e5f0 100644 --- a/tests/hazmat/primitives/test_x25519.py +++ b/tests/hazmat/primitives/test_x25519.py @@ -4,6 +4,7 @@ import binascii +import copy import os import pytest @@ -99,7 +100,8 @@ def test_public_bytes_bad_args(self, backend): key = X25519PrivateKey.generate().public_key() with pytest.raises(TypeError): key.public_bytes( - None, serialization.PublicFormat.Raw # type: ignore[arg-type] + None, # type: ignore[arg-type] + serialization.PublicFormat.Raw, ) with pytest.raises(ValueError): key.public_bytes( @@ -351,3 +353,19 @@ def test_public_key_equality(backend): assert key1 != object() with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] + + +@pytest.mark.supported( + only_if=lambda backend: backend.x25519_supported(), + skip_message="Requires OpenSSL with X25519 support", +) +def test_public_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).public_key() + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_x448.py b/tests/hazmat/primitives/test_x448.py index e2f840fa82fb..46f4856c180d 100644 --- a/tests/hazmat/primitives/test_x448.py +++ b/tests/hazmat/primitives/test_x448.py @@ -4,6 +4,7 @@ import binascii +import copy import os import pytest @@ -280,3 +281,19 @@ def test_public_key_equality(backend): assert key1 != object() with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support", +) +def test_public_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).public_key() + key2 = copy.copy(key1) + + assert key1 == key2 diff --git a/tests/hazmat/primitives/test_x963_vectors.py b/tests/hazmat/primitives/test_x963_vectors.py index 92f396e2c508..fcb3d8b02b56 100644 --- a/tests/hazmat/primitives/test_x963_vectors.py +++ b/tests/hazmat/primitives/test_x963_vectors.py @@ -19,14 +19,14 @@ def _skip_hashfn_unsupported(backend, hashfn): if not backend.hash_supported(hashfn): pytest.skip( - "Hash {} is not supported by this backend {}".format( - hashfn.name, backend - ) + f"Hash {hashfn.name} is not supported by this backend {backend}" ) class TestX963: - _algorithms_dict: typing.Dict[str, typing.Type[hashes.HashAlgorithm]] = { + _algorithms_dict: typing.ClassVar[ + typing.Dict[str, typing.Type[hashes.HashAlgorithm]] + ] = { "SHA-1": hashes.SHA1, "SHA-224": hashes.SHA224, "SHA-256": hashes.SHA256, diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 056b31ee55c8..b15955fd25fb 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -285,6 +285,8 @@ def aead_exception_test(backend, cipher_factory, mode_factory): ) decryptor = cipher.decryptor() decryptor.update(b"a" * 16) + with pytest.raises(AlreadyUpdated): + decryptor.authenticate_additional_data(b"b" * 16) with pytest.raises(AttributeError): decryptor.tag # type: ignore[attr-defined] diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 89908e2793b8..ef4ef70e25b0 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -127,7 +127,7 @@ def test_timestamp_ignored_no_ttl(self, monkeypatch, backend): monkeypatch.setattr(time, "time", pretend.raiser(ValueError)) assert f.decrypt(token, ttl=None) == pt - def test_ttl_required_in_decrypt_at_time(self, monkeypatch, backend): + def test_ttl_required_in_decrypt_at_time(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) pt = b"encrypt me" token = f.encrypt(pt) @@ -148,7 +148,7 @@ def test_bad_key(self, backend, key): with pytest.raises(ValueError): Fernet(key, backend=backend) - def test_extract_timestamp(self, monkeypatch, backend): + def test_extract_timestamp(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) current_time = 1526138327 token = f.encrypt_at_time(b"encrypt me", current_time) @@ -198,7 +198,9 @@ def test_decrypt_at_time(self, backend): f.decrypt_at_time(token, ttl=1, current_time=102) with pytest.raises(ValueError): f.decrypt_at_time( - token, ttl=None, current_time=100 # type: ignore[arg-type] + token, + ttl=None, # type: ignore[arg-type] + current_time=100, ) def test_no_fernets(self, backend): @@ -248,7 +250,7 @@ def test_rotate_str(self, backend): with pytest.raises(InvalidToken): mf1.decrypt(rotated) - def test_rotate_preserves_timestamp(self, backend, monkeypatch): + def test_rotate_preserves_timestamp(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) diff --git a/tests/test_rust_utils.py b/tests/test_rust_utils.py deleted file mode 100644 index 1ee68541e7fc..000000000000 --- a/tests/test_rust_utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -import gc -import threading - -from cryptography.hazmat.bindings._rust import FixedPool - - -class TestFixedPool: - def test_basic(self): - c = 0 - events = [] - - def create(): - nonlocal c - c += 1 - events.append(("create", c)) - return c - - pool = FixedPool(create) - assert events == [("create", 1)] - with pool.acquire() as c: - assert c == 1 - assert events == [("create", 1)] - - with pool.acquire() as c: - assert c == 2 - assert events == [("create", 1), ("create", 2)] - - assert events == [("create", 1), ("create", 2)] - - assert events == [("create", 1), ("create", 2)] - - del pool - gc.collect() - gc.collect() - gc.collect() - - assert events == [ - ("create", 1), - ("create", 2), - ] - - def test_thread_stress(self): - def create(): - return None - - pool = FixedPool(create) - - def thread_fn(): - with pool.acquire(): - pass - - threads = [] - for i in range(1024): - t = threading.Thread(target=thread_fn) - t.start() - threads.append(t) - - for t in threads: - t.join() diff --git a/tests/utils.py b/tests/utils.py index bad0f87da164..595e8dc04e1c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -918,8 +918,8 @@ def cache_value_to_group(self, cache_key: str, func): return cache_val -def load_wycheproof_tests(wycheproof, test_file): - path = os.path.join(wycheproof, "testvectors", test_file) +def load_wycheproof_tests(wycheproof, test_file, subdir): + path = os.path.join(wycheproof, subdir, test_file) with open(path) as f: data = json.load(f) for group in data.pop("testGroups"): diff --git a/tests/wycheproof/test_aes.py b/tests/wycheproof/test_aes.py index ce83fe3c0fa2..0d2c2d4445e8 100644 --- a/tests/wycheproof/test_aes.py +++ b/tests/wycheproof/test_aes.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_chacha20poly1305.py b/tests/wycheproof/test_chacha20poly1305.py index 06d6fc76a092..3b6aeb6c4adc 100644 --- a/tests/wycheproof/test_chacha20poly1305.py +++ b/tests/wycheproof/test_chacha20poly1305.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_cmac.py b/tests/wycheproof/test_cmac.py index bca84805d7b9..f1508c046f56 100644 --- a/tests/wycheproof/test_cmac.py +++ b/tests/wycheproof/test_cmac.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_dsa.py b/tests/wycheproof/test_dsa.py index fd76a938bfd3..c15a198839d0 100644 --- a/tests/wycheproof/test_dsa.py +++ b/tests/wycheproof/test_dsa.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_ecdh.py b/tests/wycheproof/test_ecdh.py index e2624a45a53c..851cd7d240f1 100644 --- a/tests/wycheproof/test_ecdh.py +++ b/tests/wycheproof/test_ecdh.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_ecdsa.py b/tests/wycheproof/test_ecdsa.py index 0b0308393511..c0e9b6a44a71 100644 --- a/tests/wycheproof/test_ecdsa.py +++ b/tests/wycheproof/test_ecdsa.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest @@ -79,8 +78,11 @@ def test_ecdsa_signature(backend, wycheproof): ) digest = _DIGESTS[wycheproof.testgroup["sha"]] - if not backend.hash_supported(digest): - pytest.skip(f"Hash {digest} not supported") + alg = ec.ECDSA(digest) + if not backend.elliptic_curve_signature_algorithm_supported( + alg, key.curve + ): + pytest.skip(f"Signature with {digest} and {key.curve} not supported") if wycheproof.valid or ( wycheproof.acceptable and not wycheproof.has_flag("MissingZero") @@ -88,12 +90,12 @@ def test_ecdsa_signature(backend, wycheproof): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), - ec.ECDSA(digest), + alg, ) else: with pytest.raises(InvalidSignature): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), - ec.ECDSA(digest), + alg, ) diff --git a/tests/wycheproof/test_eddsa.py b/tests/wycheproof/test_eddsa.py index 3b5dae37749f..624f99fff004 100644 --- a/tests/wycheproof/test_eddsa.py +++ b/tests/wycheproof/test_eddsa.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_hkdf.py b/tests/wycheproof/test_hkdf.py index 3d54e44ffc6e..ccfe8e4cde70 100644 --- a/tests/wycheproof/test_hkdf.py +++ b/tests/wycheproof/test_hkdf.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_hmac.py b/tests/wycheproof/test_hmac.py index 4a42dc1eda5f..a99d34f37608 100644 --- a/tests/wycheproof/test_hmac.py +++ b/tests/wycheproof/test_hmac.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_keywrap.py b/tests/wycheproof/test_keywrap.py index 7aec26989b20..da3744be1059 100644 --- a/tests/wycheproof/test_keywrap.py +++ b/tests/wycheproof/test_keywrap.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_pbkdf2.py b/tests/wycheproof/test_pbkdf2.py new file mode 100644 index 000000000000..f5f0da18ed38 --- /dev/null +++ b/tests/wycheproof/test_pbkdf2.py @@ -0,0 +1,42 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import binascii + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +from .utils import wycheproof_tests + +_HASH_ALGORITHMS = { + "PBKDF2-HMACSHA1": hashes.SHA1(), + "PBKDF2-HMACSHA224": hashes.SHA224(), + "PBKDF2-HMACSHA256": hashes.SHA256(), + "PBKDF2-HMACSHA384": hashes.SHA384(), + "PBKDF2-HMACSHA512": hashes.SHA512(), +} + + +@wycheproof_tests( + "pbkdf2_hmacsha1_test.json", + "pbkdf2_hmacsha224_test.json", + "pbkdf2_hmacsha256_test.json", + "pbkdf2_hmacsha384_test.json", + "pbkdf2_hmacsha512_test.json", + subdir="testvectors_v1", +) +def test_pbkdf2(backend, wycheproof): + assert wycheproof.valid + + algorithm = _HASH_ALGORITHMS[wycheproof.testfiledata["algorithm"]] + + p = PBKDF2HMAC( + algorithm=algorithm, + length=wycheproof.testcase["dkLen"], + salt=binascii.unhexlify(wycheproof.testcase["salt"]), + iterations=wycheproof.testcase["iterationCount"], + ) + assert p.derive( + binascii.unhexlify(wycheproof.testcase["password"]) + ) == binascii.unhexlify(wycheproof.testcase["dk"]) diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index 48d20f316a1d..c85eb6e7a669 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest @@ -19,7 +18,8 @@ "SHA-256": hashes.SHA256(), "SHA-384": hashes.SHA384(), "SHA-512": hashes.SHA512(), - # Not supported by OpenSSL for RSA signing + # Not supported by OpenSSL<3 for RSA signing. + # Enable these when we require CRYPTOGRAPHY_OPENSSL_300_OR_GREATER "SHA-512/224": None, "SHA-512/256": None, "SHA3-224": hashes.SHA3_224(), diff --git a/tests/wycheproof/test_utils.py b/tests/wycheproof/test_utils.py index b0c36d4797d8..f186fb368588 100644 --- a/tests/wycheproof/test_utils.py +++ b/tests/wycheproof/test_utils.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - from ..utils import WycheproofTest diff --git a/tests/wycheproof/test_x25519.py b/tests/wycheproof/test_x25519.py index 17aef36fe2e1..571c1d137573 100644 --- a/tests/wycheproof/test_x25519.py +++ b/tests/wycheproof/test_x25519.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/test_x448.py b/tests/wycheproof/test_x448.py index 8e7b321484c3..bdad0cb8510f 100644 --- a/tests/wycheproof/test_x448.py +++ b/tests/wycheproof/test_x448.py @@ -2,7 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. - import binascii import pytest diff --git a/tests/wycheproof/utils.py b/tests/wycheproof/utils.py index eebbe7ce3bf6..7644b52a8ee9 100644 --- a/tests/wycheproof/utils.py +++ b/tests/wycheproof/utils.py @@ -1,16 +1,22 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import pytest + from ..utils import load_wycheproof_tests -def wycheproof_tests(*paths): +def wycheproof_tests(*paths, subdir="testvectors"): def wrapper(func): - def run_wycheproof(backend, subtests, pytestconfig): + @pytest.mark.parametrize("path", paths) + def run_wycheproof(backend, subtests, pytestconfig, path): wycheproof_root = pytestconfig.getoption( "--wycheproof-root", skip=True ) - for path in paths: - for test in load_wycheproof_tests(wycheproof_root, path): - with subtests.test(): - func(backend, test) + for test in load_wycheproof_tests(wycheproof_root, path, subdir): + with subtests.test(): + func(backend, test) return run_wycheproof diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py index 2c595db324f5..335694c7f9a9 100644 --- a/tests/x509/test_ocsp.py +++ b/tests/x509/test_ocsp.py @@ -203,7 +203,10 @@ def test_add_cert_by_hash_bad_hash(self): builder = ocsp.OCSPRequestBuilder() with pytest.raises(ValueError): builder.add_certificate_by_hash( - b"0" * 20, b"0" * 20, 1, "notahash" # type:ignore[arg-type] + b"0" * 20, + b"0" * 20, + 1, + "notahash", # type:ignore[arg-type] ) with pytest.raises(ValueError): builder.add_certificate_by_hash( @@ -304,7 +307,7 @@ def test_create_ocsp_request_with_extension(self, ext, critical): assert req.extensions[0].critical is critical def test_add_cert_by_hash(self): - cert, issuer = _cert_and_issuer() + cert, _ = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() h = hashes.Hash(hashes.SHA1()) h.update(cert.issuer.public_bytes()) @@ -355,7 +358,9 @@ def test_add_response_twice(self): def test_invalid_add_response(self): cert, issuer = _cert_and_issuer() - time = datetime.datetime.utcnow() + time = datetime.datetime.now(datetime.timezone.utc).replace( + tzinfo=None + ) reason = x509.ReasonFlags.cessation_of_operation builder = ocsp.OCSPResponseBuilder() with pytest.raises(TypeError): @@ -514,13 +519,18 @@ def test_invalid_extension(self): builder = ocsp.OCSPResponseBuilder() with pytest.raises(TypeError): builder.add_extension( - "notanextension", True # type: ignore[arg-type] + "notanextension", # type: ignore[arg-type] + True, ) def test_unsupported_extension(self): root_cert, private_key = _generate_root() cert, issuer = _cert_and_issuer() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) @@ -555,7 +565,9 @@ def test_sign_no_responder_id(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() _, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + current_time = ( + datetime.datetime.now().replace(tzinfo=None).replace(microsecond=0) + ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.add_response( @@ -575,7 +587,9 @@ def test_sign_invalid_hash_algorithm(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + current_time = ( + datetime.datetime.now().replace(tzinfo=None).replace(microsecond=0) + ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( @@ -597,7 +611,11 @@ def test_sign_good_cert(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = builder.responder_id( @@ -633,7 +651,11 @@ def test_sign_revoked_cert(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) revoked_date = this_update - datetime.timedelta(days=300) @@ -663,7 +685,11 @@ def test_sign_unknown_cert(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = builder.responder_id( @@ -690,7 +716,11 @@ def test_sign_with_appended_certs(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = ( @@ -714,7 +744,11 @@ def test_sign_revoked_no_next_update(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + current_time = ( + datetime.datetime.now(datetime.timezone.utc) + .replace(tzinfo=None) + .replace(microsecond=0) + ) this_update = current_time - datetime.timedelta(days=1) revoked_date = this_update - datetime.timedelta(days=300) builder = builder.responder_id( @@ -743,7 +777,11 @@ def test_sign_revoked_with_reason(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) revoked_date = this_update - datetime.timedelta(days=300) @@ -773,7 +811,11 @@ def test_sign_responder_id_key_hash(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = builder.responder_id( @@ -800,8 +842,12 @@ def test_sign_responder_id_key_hash(self): def test_invalid_sign_responder_cert_does_not_match_private_key(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() - root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + root_cert, _ = _generate_root() + 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) builder = builder.responder_id( @@ -826,7 +872,11 @@ def test_sign_with_extension(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = ( @@ -882,7 +932,11 @@ def test_sign_unknown_private_key(self, backend): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, _ = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = builder.responder_id( @@ -910,7 +964,11 @@ def test_sign_unrecognized_hash_algorithm(self, backend): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = builder.responder_id( @@ -933,7 +991,11 @@ def test_sign_none_hash_not_eddsa(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) builder = builder.responder_id( @@ -1380,7 +1442,11 @@ def test_invalid_algorithm(self, backend): cert, issuer = _cert_and_issuer() private_key = ed25519.Ed25519PrivateKey.generate() root_cert, _ = _generate_root(private_key, None) - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) revoked_date = this_update - datetime.timedelta(days=300) @@ -1408,7 +1474,11 @@ def test_sign_ed25519(self, backend): cert, issuer = _cert_and_issuer() private_key = ed25519.Ed25519PrivateKey.generate() root_cert, _ = _generate_root(private_key, None) - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) revoked_date = this_update - datetime.timedelta(days=300) @@ -1447,7 +1517,11 @@ def test_sign_ed448(self, backend): cert, issuer = _cert_and_issuer() private_key = ed448.Ed448PrivateKey.generate() root_cert, _ = _generate_root(private_key, None) - current_time = datetime.datetime.utcnow().replace(microsecond=0) + 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) revoked_date = this_update - datetime.timedelta(days=300) diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 662cb9af2b8e..1a6fc7b437cc 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -135,6 +135,42 @@ def _break_cert_sig(cert: x509.Certificate) -> x509.Certificate: return x509.load_pem_x509_certificate(bytes(cert_bad_sig)) +def _check_cert_times( + cert: x509.Certificate, + not_valid_before: typing.Optional[datetime.datetime], + not_valid_after: typing.Optional[datetime.datetime], +) -> None: + if not_valid_before: + with pytest.warns(utils.DeprecatedIn42): + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_before_utc == not_valid_before.replace( + tzinfo=datetime.timezone.utc + ) + if not_valid_after: + with pytest.warns(utils.DeprecatedIn42): + assert cert.not_valid_after == not_valid_after + assert cert.not_valid_after_utc == not_valid_after.replace( + tzinfo=datetime.timezone.utc + ) + + +def _check_crl_times( + crl: x509.CertificateRevocationList, + last_update: datetime.datetime, + next_update: datetime.datetime, +) -> None: + with pytest.warns(utils.DeprecatedIn42): + assert crl.last_update == last_update + assert crl.next_update == next_update + + assert crl.last_update_utc == last_update.replace( + tzinfo=datetime.timezone.utc + ) + assert crl.next_update_utc == next_update.replace( + tzinfo=datetime.timezone.utc + ) + + class TestCertificateRevocationList: def test_load_pem_crl(self, backend): crl = _load_cert( @@ -275,18 +311,26 @@ def test_update_dates(self, backend): x509.load_pem_x509_crl, ) - assert isinstance(crl.next_update, datetime.datetime) - assert isinstance(crl.last_update, datetime.datetime) + with pytest.warns(utils.DeprecatedIn42): + assert isinstance(crl.next_update, datetime.datetime) + assert isinstance(crl.last_update, datetime.datetime) + assert crl.next_update.isoformat() == "2016-01-01T00:00:00" + assert crl.last_update.isoformat() == "2015-01-01T00:00:00" - assert crl.next_update.isoformat() == "2016-01-01T00:00:00" - assert crl.last_update.isoformat() == "2015-01-01T00:00:00" + assert isinstance(crl.next_update_utc, datetime.datetime) + assert isinstance(crl.last_update_utc, datetime.datetime) + assert crl.next_update_utc.isoformat() == "2016-01-01T00:00:00+00:00" + assert crl.last_update_utc.isoformat() == "2015-01-01T00:00:00+00:00" def test_no_next_update(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_no_next_update.pem"), x509.load_pem_x509_crl, ) - assert crl.next_update is None + + with pytest.warns(utils.DeprecatedIn42): + assert crl.next_update is None + assert crl.next_update_utc is None def test_unrecognized_extension(self, backend): crl = _load_cert( @@ -339,7 +383,13 @@ def test_revoked_cert_retrieval_retain_only_revoked(self, backend): os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, )[11] - assert revoked.revocation_date == datetime.datetime(2015, 1, 1, 0, 0) + with pytest.warns(utils.DeprecatedIn42): + assert revoked.revocation_date == datetime.datetime( + 2015, 1, 1, 0, 0 + ) + assert revoked.revocation_date_utc == datetime.datetime( + 2015, 1, 1, 0, 0, tzinfo=datetime.timezone.utc + ) assert revoked.serial_number == 11 def test_extensions(self, backend): @@ -444,8 +494,11 @@ def test_public_bytes_pem(self, backend): ) assert len(crl) == 0 - assert crl.last_update == datetime.datetime(2015, 12, 20, 23, 44, 47) - assert crl.next_update == datetime.datetime(2015, 12, 28, 0, 44, 47) + _check_crl_times( + crl, + last_update=datetime.datetime(2015, 12, 20, 23, 44, 47), + next_update=datetime.datetime(2015, 12, 28, 0, 44, 47), + ) def test_public_bytes_der(self, backend): crl = _load_cert( @@ -461,8 +514,11 @@ def test_public_bytes_der(self, backend): ) assert len(crl) == 12 - assert crl.last_update == datetime.datetime(2015, 1, 1, 0, 0, 0) - assert crl.next_update == datetime.datetime(2016, 1, 1, 0, 0, 0) + _check_crl_times( + crl, + last_update=datetime.datetime(2015, 1, 1, 0, 0, 0), + next_update=datetime.datetime(2016, 1, 1, 0, 0, 0), + ) @pytest.mark.parametrize( ("cert_path", "loader_func", "encoding"), @@ -557,11 +613,18 @@ def test_revoked_basics(self, backend): for i, rev in enumerate(crl): assert isinstance(rev, x509.RevokedCertificate) assert isinstance(rev.serial_number, int) - assert isinstance(rev.revocation_date, datetime.datetime) + with pytest.warns(utils.DeprecatedIn42): + assert isinstance(rev.revocation_date, datetime.datetime) + assert isinstance(rev.revocation_date_utc, datetime.datetime) assert isinstance(rev.extensions, x509.Extensions) assert rev.serial_number == i - assert rev.revocation_date.isoformat() == "2015-01-01T00:00:00" + with pytest.warns(utils.DeprecatedIn42): + assert rev.revocation_date.isoformat() == "2015-01-01T00:00:00" + assert ( + rev.revocation_date_utc.isoformat() + == "2015-01-01T00:00:00+00:00" + ) def test_revoked_extensions(self, backend): crl = _load_cert( @@ -729,14 +792,6 @@ def test_get_revoked_certificate_doesnt_reorder( assert crl[2].serial_number == 3 -@pytest.mark.supported( - only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E - ), - skip_message="Does not support RSA PSS loading", -) class TestRSAPSSCertificate: def test_load_cert_pub_key(self, backend): cert = _load_cert( @@ -1054,7 +1109,7 @@ def test_tbs_precertificate_bytes_no_extensions_raises(self, backend): with pytest.raises( ValueError, - match="Could not find any extensions in TBS certificate", + match="Could not find pre-certificate SCT list extension", ): cert.tbs_precertificate_bytes @@ -1274,8 +1329,11 @@ def test_load_good_ca_cert(self, backend): x509.load_der_x509_certificate, ) - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + _check_cert_times( + cert, + not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), + not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), + ) assert cert.serial_number == 2 public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) @@ -1294,7 +1352,11 @@ def test_utc_pre_2000_not_before_cert(self, backend): x509.load_der_x509_certificate, ) - assert cert.not_valid_before == datetime.datetime(1950, 1, 1, 12, 1) + _check_cert_times( + cert, + not_valid_before=datetime.datetime(1950, 1, 1, 12, 1), + not_valid_after=None, + ) def test_pre_2000_utc_not_after_cert(self, backend): cert = _load_cert( @@ -1307,18 +1369,21 @@ def test_pre_2000_utc_not_after_cert(self, backend): x509.load_der_x509_certificate, ) - assert cert.not_valid_after == datetime.datetime(1999, 1, 1, 12, 1) + _check_cert_times( + cert, + not_valid_before=None, + not_valid_after=datetime.datetime(1999, 1, 1, 12, 1), + ) def test_post_2000_utc_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) - assert cert.not_valid_before == datetime.datetime( - 2014, 11, 26, 21, 41, 20 - ) - assert cert.not_valid_after == datetime.datetime( - 2014, 12, 26, 21, 41, 20 + _check_cert_times( + cert, + not_valid_before=datetime.datetime(2014, 11, 26, 21, 41, 20), + not_valid_after=datetime.datetime(2014, 12, 26, 21, 41, 20), ) def test_generalized_time_not_before_cert(self, backend): @@ -1331,8 +1396,11 @@ def test_generalized_time_not_before_cert(self, backend): ), x509.load_der_x509_certificate, ) - assert cert.not_valid_before == datetime.datetime(2002, 1, 1, 12, 1) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + _check_cert_times( + cert, + not_valid_before=datetime.datetime(2002, 1, 1, 12, 1), + not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), + ) assert cert.version is x509.Version.v3 def test_generalized_time_not_after_cert(self, backend): @@ -1345,8 +1413,11 @@ def test_generalized_time_not_after_cert(self, backend): ), x509.load_der_x509_certificate, ) - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2050, 1, 1, 12, 1) + _check_cert_times( + cert, + not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), + not_valid_after=datetime.datetime(2050, 1, 1, 12, 1), + ) assert cert.version is x509.Version.v3 def test_invalid_version_cert(self, backend): @@ -1414,7 +1485,7 @@ def test_ordering_unsupported(self, backend): os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) - with pytest.raises(TypeError, match="cannot be ordered"): + with pytest.raises(TypeError, match="'>' not supported"): cert > cert2 # type: ignore[operator] def test_hash(self, backend): @@ -1486,8 +1557,11 @@ def test_public_bytes_pem(self, backend): ) # We should recover what we had to start with. - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + _check_cert_times( + cert, + not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), + not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), + ) assert cert.serial_number == 2 public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) @@ -1510,8 +1584,11 @@ def test_public_bytes_der(self, backend): ) # We should recover what we had to start with. - assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) - assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + _check_cert_times( + cert, + not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), + not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), + ) assert cert.serial_number == 2 public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) @@ -2079,7 +2156,7 @@ def test_ordering_unsupported(self, backend): os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, ) - with pytest.raises(TypeError, match="cannot be ordered"): + with pytest.raises(TypeError, match="'>' not supported"): csr > csr2 # type: ignore[operator] def test_hash(self, backend): @@ -2175,8 +2252,11 @@ def test_build_cert( assert cert.version is x509.Version.v3 assert cert.signature_algorithm_oid == hashalg_oid assert type(cert.signature_hash_algorithm) is hashalg - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after + _check_cert_times( + cert, + not_valid_before=not_valid_before, + not_valid_after=not_valid_after, + ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) @@ -2470,8 +2550,11 @@ def test_extreme_times( .not_valid_after(not_valid_after) ) cert = builder.sign(private_key, hashes.SHA256(), backend) - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after + _check_cert_times( + cert, + not_valid_before=not_valid_before, + not_valid_after=not_valid_after, + ) parsed = asn1.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) @@ -2923,7 +3006,9 @@ def test_aware_not_valid_after( ) cert = cert_builder.sign(private_key, hashes.SHA256(), backend) - assert cert.not_valid_after == utc_time + _check_cert_times( + cert, not_valid_before=None, not_valid_after=utc_time + ) def test_earliest_time(self, rsa_key_2048: rsa.RSAPrivateKey, backend): time = datetime.datetime(1950, 1, 1) @@ -2942,8 +3027,7 @@ def test_earliest_time(self, rsa_key_2048: rsa.RSAPrivateKey, backend): .not_valid_after(time) ) cert = cert_builder.sign(private_key, hashes.SHA256(), backend) - assert cert.not_valid_before == time - assert cert.not_valid_after == time + _check_cert_times(cert, not_valid_before=time, not_valid_after=time) parsed = asn1.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) @@ -2996,7 +3080,9 @@ def test_aware_not_valid_before( ) cert = cert_builder.sign(private_key, hashes.SHA256(), backend) - assert cert.not_valid_before == utc_time + _check_cert_times( + cert, not_valid_before=utc_time, not_valid_after=None + ) def test_invalid_not_valid_before(self): with pytest.raises(TypeError): @@ -3169,7 +3255,9 @@ def test_sign_ec_with_md5(self, backend): ) with pytest.raises(UnsupportedAlgorithm): builder.sign( - private_key, hashes.MD5(), backend # type: ignore[arg-type] + private_key, + hashes.MD5(), # type: ignore[arg-type] + backend, ) @pytest.mark.supported( @@ -3220,8 +3308,11 @@ def test_build_cert_with_dsa_private_key( assert cert.version is x509.Version.v3 assert cert.signature_algorithm_oid == hashalg_oid - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after + _check_cert_times( + cert, + not_valid_before=not_valid_before, + not_valid_after=not_valid_after, + ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) @@ -3291,8 +3382,11 @@ def test_build_cert_with_ec_private_key( assert cert.version is x509.Version.v3 assert cert.signature_algorithm_oid == hashalg_oid assert type(cert.signature_hash_algorithm) is hashalg - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after + _check_cert_times( + cert, + not_valid_before=not_valid_before, + not_valid_after=not_valid_after, + ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) @@ -3387,8 +3481,11 @@ def test_build_cert_with_ed25519(self, backend): assert cert.signature_hash_algorithm is None assert isinstance(cert.public_key(), ed25519.Ed25519PublicKey) assert cert.version is x509.Version.v3 - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after + _check_cert_times( + cert, + not_valid_before=not_valid_before, + not_valid_after=not_valid_after, + ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) @@ -3487,8 +3584,11 @@ def test_build_cert_with_ed448(self, backend): assert cert.signature_hash_algorithm is None assert isinstance(cert.public_key(), ed448.Ed448PublicKey) assert cert.version is x509.Version.v3 - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after + _check_cert_times( + cert, + not_valid_before=not_valid_before, + not_valid_after=not_valid_after, + ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) @@ -4194,7 +4294,9 @@ def test_sign_invalid_hash_algorithm( ) with pytest.raises(TypeError): builder.sign( - private_key, "NotAHash", backend # type: ignore[arg-type] + private_key, + "NotAHash", # type: ignore[arg-type] + backend, ) @pytest.mark.supported( @@ -4920,6 +5022,104 @@ def test_rsa_key_too_small(self, rsa_key_512: rsa.RSAPrivateKey, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) + @pytest.mark.parametrize( + ("alg", "mgf_alg"), + [ + (hashes.SHA512(), hashes.SHA256()), + (hashes.SHA3_512(), hashes.SHA3_256()), + ], + ) + def test_sign_pss( + self, rsa_key_2048: rsa.RSAPrivateKey, alg, mgf_alg, backend + ): + if not backend.signature_hash_supported(alg): + pytest.skip(f"{alg} signature not supported") + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + pss = padding.PSS( + mgf=padding.MGF1(mgf_alg), salt_length=alg.digest_size + ) + csr = builder.sign(rsa_key_2048, alg, rsa_padding=pss) + pk = csr.public_key() + assert isinstance(pk, rsa.RSAPublicKey) + assert isinstance(csr.signature_hash_algorithm, type(alg)) + cert_params = csr.signature_algorithm_parameters + assert isinstance(cert_params, padding.PSS) + assert cert_params._salt_length == pss._salt_length + assert isinstance(cert_params._mgf, padding.MGF1) + assert isinstance(cert_params._mgf._algorithm, type(mgf_alg)) + pk.verify( + csr.signature, + csr.tbs_certrequest_bytes, + cert_params, + alg, + ) + + @pytest.mark.parametrize( + ("padding_len", "computed_len"), + [ + (padding.PSS.MAX_LENGTH, 222), + (padding.PSS.DIGEST_LENGTH, 32), + ], + ) + def test_sign_pss_length_options( + self, + rsa_key_2048: rsa.RSAPrivateKey, + padding_len, + computed_len, + backend, + ): + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len + ) + csr = builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) + assert isinstance(csr.signature_algorithm_parameters, padding.PSS) + assert csr.signature_algorithm_parameters._salt_length == computed_len + + def test_sign_pss_auto_unsupported( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO + ) + with pytest.raises(TypeError): + builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) + + def test_sign_invalid_padding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + with pytest.raises(TypeError): + builder.sign( + rsa_key_2048, + hashes.SHA256(), + rsa_padding=b"notapadding", # type: ignore[arg-type] + ) + eckey = ec.generate_private_key(ec.SECP256R1()) + with pytest.raises(TypeError): + builder.sign( + eckey, hashes.SHA256(), rsa_padding=padding.PKCS1v15() + ) + + def test_sign_pss_hash_none( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32) + with pytest.raises(TypeError): + builder.sign(rsa_key_2048, None, rsa_padding=pss) + @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), @@ -4982,6 +5182,21 @@ def test_load_dsa_cert(self, backend): "822ff5d234e073b901cf5941f58e1f538e71d40d", 16 ) + def test_load_dsa_cert_null_alg_params(self, backend): + """ + This test verifies that we successfully load certificates with encoded + null parameters in the signature AlgorithmIdentifier. This is invalid, + but all versions of Java less than 21 generate certificates with this + encoding so we need to tolerate it at the moment. + """ + with pytest.warns(utils.DeprecatedIn41): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_null_alg_params.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + assert isinstance(cert.public_key(), dsa.DSAPublicKey) + def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), @@ -5199,6 +5414,21 @@ def test_load_ecdsa_cert(self, backend): cert.signature_algorithm_parameters, ) + def test_load_ecdsa_cert_null_alg_params(self, backend): + """ + This test verifies that we successfully load certificates with encoded + null parameters in the signature AlgorithmIdentifier. This is invalid, + but Java 11 (up to at least 11.0.19) generates certificates with this + encoding so we need to tolerate it at the moment. + """ + with pytest.warns(utils.DeprecatedIn41): + cert = _load_cert( + os.path.join("x509", "custom", "ecdsa_null_alg.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + assert isinstance(cert.public_key(), ec.EllipticCurvePublicKey) + def test_load_bitstring_dn(self, backend): cert = _load_cert( os.path.join("x509", "scottishpower-bitstring-dn.pem"), @@ -5422,7 +5652,9 @@ def test_bad_time_in_validity(self, backend): class TestNameAttribute: - EXPECTED_TYPES = [ + EXPECTED_TYPES: typing.ClassVar[ + typing.List[typing.Tuple[x509.ObjectIdentifier, _ASN1Type]] + ] = [ (NameOID.COMMON_NAME, _ASN1Type.UTF8String), (NameOID.COUNTRY_NAME, _ASN1Type.PrintableString), (NameOID.LOCALITY_NAME, _ASN1Type.UTF8String), @@ -5490,7 +5722,8 @@ def test_init_bitstring_not_allowed_random_oid(self): def test_init_none_value(self): with pytest.raises(TypeError): x509.NameAttribute( - NameOID.ORGANIZATION_NAME, None # type:ignore[arg-type] + NameOID.ORGANIZATION_NAME, + None, # type:ignore[arg-type] ) def test_init_bad_country_code_value(self): @@ -6058,7 +6291,9 @@ def test_csr_signing_check(self, backend): def test_crl_signing_check(self, backend): private_key = self.load_key(backend) - last_time = datetime.datetime.utcnow().replace(microsecond=0) + last_time = ( + datetime.datetime.now().replace(tzinfo=None).replace(microsecond=0) + ) next_time = last_time builder = ( x509.CertificateRevocationListBuilder() diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py index 95c0677bb777..afa8380216c5 100644 --- a/tests/x509/test_x509_crlbuilder.py +++ b/tests/x509/test_x509_crlbuilder.py @@ -7,10 +7,16 @@ import pytest -from cryptography import x509 +from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa +from cryptography.hazmat.primitives.asymmetric import ( + ec, + ed448, + ed25519, + padding, + rsa, +) from cryptography.x509.oid import ( AuthorityInformationAccessOID, NameOID, @@ -65,7 +71,11 @@ def test_aware_last_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): ) crl = builder.sign(private_key, hashes.SHA256(), backend) - assert crl.last_update == utc_last + with pytest.warns(utils.DeprecatedIn42): + assert crl.last_update == utc_last + assert crl.last_update_utc == utc_last.replace( + tzinfo=datetime.timezone.utc + ) def test_last_update_invalid(self): builder = x509.CertificateRevocationListBuilder() @@ -106,7 +116,11 @@ def test_aware_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): ) crl = builder.sign(private_key, hashes.SHA256(), backend) - assert crl.next_update == utc_next + with pytest.warns(utils.DeprecatedIn42): + assert crl.next_update == utc_next + assert crl.next_update_utc == utc_next.replace( + tzinfo=datetime.timezone.utc + ) def test_next_update_invalid(self): builder = x509.CertificateRevocationListBuilder() @@ -196,6 +210,38 @@ def test_no_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) + def test_sign_invalid_padding( + self, rsa_key_2048: rsa.RSAPrivateKey, 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( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, "cryptography.io CA" + ) + ] + ) + ) + .last_update(last_update) + .next_update(next_update) + ) + + with pytest.raises(TypeError): + builder.sign( + rsa_key_2048, + hashes.SHA256(), + rsa_padding=b"notapadding", # type: ignore[arg-type] + ) + eckey = ec.generate_private_key(ec.SECP256R1()) + with pytest.raises(TypeError): + builder.sign( + eckey, hashes.SHA256(), rsa_padding=padding.PKCS1v15() + ) + def test_sign_empty_list(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) @@ -217,8 +263,49 @@ def test_sign_empty_list(self, rsa_key_2048: rsa.RSAPrivateKey, backend): crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 - assert crl.last_update == last_update - assert crl.next_update == next_update + with pytest.warns(utils.DeprecatedIn42): + assert crl.last_update == last_update + assert crl.next_update == next_update + assert crl.last_update_utc == last_update.replace( + tzinfo=datetime.timezone.utc + ) + assert crl.next_update_utc == next_update.replace( + tzinfo=datetime.timezone.utc + ) + + def test_sign_pss(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = ( + x509.CertificateRevocationListBuilder() + .issuer_name( + x509.Name( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, "cryptography.io CA" + ) + ] + ) + ) + .last_update(last_update) + .next_update(next_update) + ) + + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ) + crl = builder.sign(private_key, hashes.SHA256(), rsa_padding=pss) + assert len(crl) == 0 + assert isinstance(crl.signature_algorithm_parameters, padding.PSS) + assert crl.signature_algorithm_parameters._salt_length == 32 + private_key.public_key().verify( + crl.signature, + crl.tbs_certlist_bytes, + crl.signature_algorithm_parameters, + hashes.SHA256(), + ) @pytest.mark.parametrize( "extension", @@ -402,7 +489,11 @@ def test_add_unsupported_entry_extension( .add_revoked_certificate( x509.RevokedCertificateBuilder() .serial_number(1234) - .revocation_date(datetime.datetime.utcnow()) + .revocation_date( + datetime.datetime.now(datetime.timezone.utc).replace( + tzinfo=None + ) + ) .add_extension(DummyExtension(), critical=False) .build() ) @@ -457,7 +548,9 @@ def test_sign_with_invalid_hash( with pytest.raises(TypeError): builder.sign( - private_key, object(), backend # type: ignore[arg-type] + private_key, + object(), # type: ignore[arg-type] + backend, ) @pytest.mark.supported( @@ -570,7 +663,9 @@ def test_sign_dsa_key(self, backend): == ian ) assert crl[0].serial_number == revoked_cert0.serial_number - assert crl[0].revocation_date == revoked_cert0.revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False @@ -619,7 +714,9 @@ def test_sign_ec_key(self, backend): == ian ) assert crl[0].serial_number == revoked_cert0.serial_number - assert crl[0].revocation_date == revoked_cert0.revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False @@ -673,7 +770,9 @@ def test_sign_ed25519_key(self, backend): == ian ) assert crl[0].serial_number == revoked_cert0.serial_number - assert crl[0].revocation_date == revoked_cert0.revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False @@ -727,7 +826,9 @@ def test_sign_ed448_key(self, backend): == ian ) assert crl[0].serial_number == revoked_cert0.serial_number - assert crl[0].revocation_date == revoked_cert0.revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False @@ -754,7 +855,9 @@ def test_dsa_key_sign_md5(self, backend): with pytest.raises(UnsupportedAlgorithm): builder.sign( - private_key, hashes.MD5(), backend # type: ignore[arg-type] + private_key, + hashes.MD5(), # type: ignore[arg-type] + backend, ) def test_ec_key_sign_md5(self, backend): @@ -779,7 +882,9 @@ def test_ec_key_sign_md5(self, backend): with pytest.raises(UnsupportedAlgorithm): builder.sign( - private_key, hashes.MD5(), backend # type: ignore[arg-type] + private_key, + hashes.MD5(), # type: ignore[arg-type] + backend, ) def test_sign_with_revoked_certificates( @@ -835,13 +940,24 @@ def test_sign_with_revoked_certificates( crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 3 - assert crl.last_update == last_update - assert crl.next_update == next_update + with pytest.warns(utils.DeprecatedIn42): + assert crl.last_update == last_update + assert crl.next_update == next_update + assert crl.last_update_utc == last_update.replace( + tzinfo=datetime.timezone.utc + ) + assert crl.next_update_utc == next_update.replace( + tzinfo=datetime.timezone.utc + ) assert crl[0].serial_number == revoked_cert0.serial_number - assert crl[0].revocation_date == revoked_cert0.revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 0 assert crl[1].serial_number == revoked_cert1.serial_number - assert crl[1].revocation_date == revoked_cert1.revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert crl[1].revocation_date == revoked_cert1.revocation_date + assert crl[1].revocation_date_utc == revoked_cert1.revocation_date_utc assert len(crl[1].extensions) == 2 ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py index fd7ff957b1dd..fc3e3e06f00e 100644 --- a/tests/x509/test_x509_ext.py +++ b/tests/x509/test_x509_ext.py @@ -221,7 +221,8 @@ class TestUnrecognizedExtension: def test_invalid_oid(self): with pytest.raises(TypeError): x509.UnrecognizedExtension( - "notanoid", b"somedata" # type:ignore[arg-type] + "notanoid", # type:ignore[arg-type] + b"somedata", ) def test_eq(self): @@ -448,7 +449,8 @@ class TestNoticeReference: def test_notice_numbers_not_all_int(self): with pytest.raises(TypeError): x509.NoticeReference( - "org", [1, 2, "three"] # type:ignore[list-item] + "org", + [1, 2, "three"], # type:ignore[list-item] ) def test_notice_numbers_none(self): @@ -1223,7 +1225,9 @@ class TestAuthorityKeyIdentifier: def test_authority_cert_issuer_not_generalname(self): with pytest.raises(TypeError): x509.AuthorityKeyIdentifier( - b"identifier", ["notname"], 3 # type:ignore[list-item] + b"identifier", + ["notname"], # type:ignore[list-item] + 3, ) def test_authority_cert_serial_number_not_integer(self): @@ -1241,7 +1245,9 @@ def test_authority_cert_serial_number_not_integer(self): ) with pytest.raises(TypeError): x509.AuthorityKeyIdentifier( - b"identifier", [dirname], "notanint" # type:ignore[arg-type] + b"identifier", + [dirname], + "notanint", # type:ignore[arg-type] ) def test_authority_issuer_none_serial_not_none(self): @@ -1354,7 +1360,8 @@ class TestBasicConstraints: def test_ca_not_boolean(self): with pytest.raises(TypeError): x509.BasicConstraints( - ca="notbool", path_length=None # type:ignore[arg-type] + ca="notbool", # type:ignore[arg-type] + path_length=None, ) def test_path_length_not_ca(self): @@ -1364,12 +1371,14 @@ def test_path_length_not_ca(self): def test_path_length_not_int(self): with pytest.raises(TypeError): x509.BasicConstraints( - ca=True, path_length=1.1 # type:ignore[arg-type] + ca=True, + path_length=1.1, # type:ignore[arg-type] ) with pytest.raises(TypeError): x509.BasicConstraints( - ca=True, path_length="notint" # type:ignore[arg-type] + ca=True, + path_length="notint", # type:ignore[arg-type] ) def test_path_length_negative(self): @@ -1749,22 +1758,6 @@ def test_invalid_bit_string_padding_from_public_key(self, backend): with pytest.raises(ValueError, match="Invalid public key encoding"): _key_identifier_from_public_key(pretend_key) - def test_no_optional_params_allowed_from_public_key(self, backend): - data = load_vectors_from_file( - filename=os.path.join( - "asymmetric", - "DER_Serialization", - "dsa_public_key_no_params.der", - ), - loader=lambda data: data.read(), - mode="rb", - ) - pretend_key = pretend.stub(public_bytes=lambda x, y: data) - key_identifier = _key_identifier_from_public_key(pretend_key) - assert key_identifier == binascii.unhexlify( - b"24c0133a6a492f2c48a18c7648e515db5ac76749" - ) - def test_from_ec_public_key(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) cert = _load_cert( @@ -2670,7 +2663,7 @@ def test_other_name(self, backend): x509.ObjectIdentifier("1.2.3.4"), b"\x16\x0bHello World" ) assert len(ext.value) == 1 - assert list(ext.value)[0] == expected + assert next(iter(ext.value)) == expected othernames = ext.value.get_values_for_type(x509.OtherName) assert othernames == [expected] @@ -2723,7 +2716,8 @@ class TestAccessDescription: def test_invalid_access_method(self): with pytest.raises(TypeError): x509.AccessDescription( - "notanoid", x509.DNSName("test") # type:ignore[arg-type] + "notanoid", # type:ignore[arg-type] + x509.DNSName("test"), ) def test_invalid_access_location(self): @@ -3910,19 +3904,30 @@ class TestDistributionPoint: def test_distribution_point_full_name_not_general_names(self): with pytest.raises(TypeError): x509.DistributionPoint( - ["notgn"], None, None, None # type:ignore[list-item] + ["notgn"], # type:ignore[list-item] + None, + None, + None, ) def test_distribution_point_relative_name_not_name(self): with pytest.raises(TypeError): x509.DistributionPoint( - None, "notname", None, None # type:ignore[arg-type] + None, + "notname", # type:ignore[arg-type] + None, + None, ) def test_distribution_point_full_and_relative_not_none(self): with pytest.raises(ValueError): x509.DistributionPoint( - "data", "notname", None, None # type:ignore[arg-type] + [x509.UniformResourceIdentifier("http://crypt.og/crl")], + x509.RelativeDistinguishedName( + [x509.NameAttribute(NameOID.TITLE, "Test")] + ), + None, + None, ) def test_no_full_name_relative_name_or_crl_issuer(self): @@ -3932,7 +3937,10 @@ def test_no_full_name_relative_name_or_crl_issuer(self): def test_crl_issuer_not_general_names(self): with pytest.raises(TypeError): x509.DistributionPoint( - None, None, None, ["notgn"] # type:ignore[list-item] + None, + None, + None, + ["notgn"], # type:ignore[list-item] ) def test_reason_not_reasonflags(self): @@ -6203,16 +6211,22 @@ class TestMSCertificateTemplate: def test_invalid_type(self): with pytest.raises(TypeError): x509.MSCertificateTemplate( - "notanoid", None, None # type:ignore[arg-type] + "notanoid", # type:ignore[arg-type] + None, + None, ) oid = x509.ObjectIdentifier("1.2.3.4") with pytest.raises(TypeError): x509.MSCertificateTemplate( - oid, "notanint", None # type:ignore[arg-type] + oid, + "notanint", # type:ignore[arg-type] + None, ) with pytest.raises(TypeError): x509.MSCertificateTemplate( - oid, None, "notanint" # type:ignore[arg-type] + oid, + None, + "notanint", # type:ignore[arg-type] ) def test_eq(self): diff --git a/tests/x509/test_x509_revokedcertbuilder.py b/tests/x509/test_x509_revokedcertbuilder.py index e0f53f856f02..c3c063beabb4 100644 --- a/tests/x509/test_x509_revokedcertbuilder.py +++ b/tests/x509/test_x509_revokedcertbuilder.py @@ -7,7 +7,7 @@ import pytest -from cryptography import x509 +from cryptography import utils, x509 class TestRevokedCertificateBuilder: @@ -68,7 +68,11 @@ def test_aware_revocation_date(self, backend): ) revoked_certificate = builder.build(backend) - assert revoked_certificate.revocation_date == utc_time + with pytest.warns(utils.DeprecatedIn42): + assert revoked_certificate.revocation_date == utc_time + assert revoked_certificate.revocation_date_utc == utc_time.replace( + tzinfo=datetime.timezone.utc + ) def test_revocation_date_invalid(self): with pytest.raises(TypeError): @@ -102,7 +106,8 @@ def test_add_extension_checks_for_duplicates(self): def test_add_invalid_extension(self): with pytest.raises(TypeError): x509.RevokedCertificateBuilder().add_extension( - "notanextension", False # type: ignore[arg-type] + "notanextension", # type: ignore[arg-type] + False, ) def test_no_serial_number(self, backend): @@ -130,7 +135,12 @@ def test_create_revoked(self, backend): revoked_certificate = builder.build(backend) assert revoked_certificate.serial_number == serial_number - assert revoked_certificate.revocation_date == revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert revoked_certificate.revocation_date == revocation_date + assert ( + revoked_certificate.revocation_date_utc + == revocation_date.replace(tzinfo=datetime.timezone.utc) + ) assert len(revoked_certificate.extensions) == 0 @pytest.mark.parametrize( @@ -153,7 +163,12 @@ def test_add_extensions(self, backend, extension): revoked_certificate = builder.build(backend) assert revoked_certificate.serial_number == serial_number - assert revoked_certificate.revocation_date == revocation_date + with pytest.warns(utils.DeprecatedIn42): + assert revoked_certificate.revocation_date == revocation_date + assert ( + revoked_certificate.revocation_date_utc + == revocation_date.replace(tzinfo=datetime.timezone.utc) + ) assert len(revoked_certificate.extensions) == 1 ext = revoked_certificate.extensions.get_extension_for_class( type(extension) diff --git a/tests/x509/verification/__init__.py b/tests/x509/verification/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/x509/verification/test_limbo.py b/tests/x509/verification/test_limbo.py new file mode 100644 index 000000000000..194b64f1f0bd --- /dev/null +++ b/tests/x509/verification/test_limbo.py @@ -0,0 +1,151 @@ +# 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 datetime +import ipaddress +import json +import os + +import pytest + +from cryptography import x509 +from cryptography.x509 import load_pem_x509_certificate +from cryptography.x509.verification import ( + PolicyBuilder, + Store, + VerificationError, +) + +LIMBO_UNSUPPORTED_FEATURES = { + # NOTE: Path validation is required to reject wildcards on public suffixes, + # however this isn't practical and most implementations make no attempt to + # comply with this. + "pedantic-public-suffix-wildcard", + # TODO: We don't support Distinguished Name Constraints yet. + "name-constraint-dn", + # Our support for custom EKUs is limited, and we (like most impls.) don't + # handle all EKU conditions under CABF. + "pedantic-webpki-eku", + # Similarly: contains tests that fail based on a strict reading of RFC 5280 + # but are widely ignored by validators. + "pedantic-rfc5280", + # In rare circumstances, CABF relaxes RFC 5280's prescriptions in + # incompatible ways. Our validator always tries (by default) to comply + # closer to CABF, so we skip these. + "rfc5280-incompatible-with-webpki", + # We do not support policy constraints. + "has-policy-constraints", +} + +LIMBO_SKIP_TESTCASES = { + # We unconditionally count intermediate certificates for pathlen and max + # depth constraint purposes, even when self-issued. + # This is a violation of RFC 5280, but is consistent with Go's crypto/x509 + # and Rust's webpki crate do. + "pathlen::self-issued-certs-pathlen", + "pathlen::max-chain-depth-1-self-issued", + # We allow certificates with serial numbers of zero. This is + # invalid under RFC 5280 but is widely violated by certs in common + # trust stores. + "rfc5280::serial::zero", + # We allow CAs that don't have AKIs, which is forbidden under + # RFC 5280. This is consistent with what Go's crypto/x509 and Rust's + # webpki crate do. + "rfc5280::ski::root-missing-ski", + "rfc5280::ski::intermediate-missing-ski", + # We currently allow intermediate CAs that don't have AKIs, which + # is technically forbidden under CABF. This is consistent with what + # Go's crypto/x509 and Rust's webpki crate do. + "rfc5280::aki::intermediate-missing-aki", + # We allow root CAs where the AKI and SKI mismatch, which is technically + # forbidden under CABF. This is consistent with what + # Go's crypto/x509 and Rust's webpki crate do. + "webpki::aki::root-with-aki-ski-mismatch", + # We disallow CAs in the leaf position, which is explicitly forbidden + # by CABF (but implicitly permitted under RFC 5280). This is consistent + # with what webpki and rustls do, but inconsistent with Go and OpenSSL. + "rfc5280::ca-as-leaf", + "pathlen::validation-ignores-pathlen-in-leaf", +} + + +def _get_limbo_peer(expected_peer): + kind = expected_peer["kind"] + assert kind in ("DNS", "IP") + value = expected_peer["value"] + if kind == "DNS": + return x509.DNSName(value) + else: + return x509.IPAddress(ipaddress.ip_address(value)) + + +def _limbo_testcase(id_, testcase): + if id_ in LIMBO_SKIP_TESTCASES: + return + + features = testcase["features"] + if LIMBO_UNSUPPORTED_FEATURES.intersection(features): + return + assert testcase["validation_kind"] == "SERVER" + assert testcase["signature_algorithms"] == [] + assert testcase["extended_key_usage"] == [] or testcase[ + "extended_key_usage" + ] == ["serverAuth"] + assert testcase["expected_peer_names"] == [] + + trusted_certs = [ + load_pem_x509_certificate(cert.encode()) + for cert in testcase["trusted_certs"] + ] + untrusted_intermediates = [ + load_pem_x509_certificate(cert.encode()) + for cert in testcase["untrusted_intermediates"] + ] + peer_certificate = load_pem_x509_certificate( + testcase["peer_certificate"].encode() + ) + peer_name = _get_limbo_peer(testcase["expected_peer_name"]) + validation_time = testcase["validation_time"] + validation_time = ( + datetime.datetime.fromisoformat(validation_time) + if validation_time is not None + else None + ) + max_chain_depth = testcase["max_chain_depth"] + should_pass = testcase["expected_result"] == "SUCCESS" + + builder = PolicyBuilder().store(Store(trusted_certs)) + if validation_time is not None: + builder = builder.time(validation_time) + if max_chain_depth is not None: + builder = builder.max_chain_depth(max_chain_depth) + + verifier = builder.build_server_verifier(peer_name) + + if should_pass: + built_chain = verifier.verify( + peer_certificate, untrusted_intermediates + ) + + # Assert that the verifier returns chains in [EE, ..., TA] order. + assert built_chain[0] == peer_certificate + for intermediate in built_chain[1:-1]: + assert intermediate in untrusted_intermediates + assert built_chain[-1] in trusted_certs + else: + with pytest.raises(VerificationError): + verifier.verify(peer_certificate, untrusted_intermediates) + + +def test_limbo(subtests, pytestconfig): + limbo_root = pytestconfig.getoption("--x509-limbo-root", skip=True) + limbo_path = os.path.join(limbo_root, "limbo.json") + with open(limbo_path, mode="rb") as limbo_file: + limbo = json.load(limbo_file) + testcases = limbo["testcases"] + for testcase in testcases: + with subtests.test(): + # NOTE: Pass in the id separately to make pytest + # error renderings slightly nicer. + _limbo_testcase(testcase["id"], testcase) diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py new file mode 100644 index 000000000000..8c2be7054227 --- /dev/null +++ b/tests/x509/verification/test_verification.py @@ -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. + +import datetime +import os +from functools import lru_cache +from ipaddress import IPv4Address + +import pytest + +from cryptography import x509 +from cryptography.x509.general_name import DNSName, IPAddress +from cryptography.x509.verification import PolicyBuilder, Store +from tests.x509.test_x509 import _load_cert + + +@lru_cache(maxsize=1) +def dummy_store() -> Store: + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + ) + return Store([cert]) + + +class TestPolicyBuilder: + def test_time_already_set(self): + with pytest.raises(ValueError): + PolicyBuilder().time(datetime.datetime.now()).time( + datetime.datetime.now() + ) + + def test_store_already_set(self): + with pytest.raises(ValueError): + PolicyBuilder().store(dummy_store()).store(dummy_store()) + + def test_max_chain_depth_already_set(self): + with pytest.raises(ValueError): + PolicyBuilder().max_chain_depth(8).max_chain_depth(9) + + def test_ipaddress_subject(self): + policy = ( + PolicyBuilder() + .store(dummy_store()) + .build_server_verifier(IPAddress(IPv4Address("0.0.0.0"))) + ) + assert policy.subject == IPAddress(IPv4Address("0.0.0.0")) + + def test_dnsname_subject(self): + policy = ( + PolicyBuilder() + .store(dummy_store()) + .build_server_verifier(DNSName("cryptography.io")) + ) + assert policy.subject == DNSName("cryptography.io") + + def test_subject_bad_types(self): + # Subject must be a supported GeneralName type + with pytest.raises(TypeError): + PolicyBuilder().store(dummy_store()).build_server_verifier( + "cryptography.io" # type: ignore[arg-type] + ) + with pytest.raises(TypeError): + PolicyBuilder().store(dummy_store()).build_server_verifier( + "0.0.0.0" # type: ignore[arg-type] + ) + with pytest.raises(TypeError): + PolicyBuilder().store(dummy_store()).build_server_verifier( + IPv4Address("0.0.0.0") # type: ignore[arg-type] + ) + with pytest.raises(TypeError): + PolicyBuilder().store(dummy_store()).build_server_verifier(None) # type: ignore[arg-type] + + def test_builder_pattern(self): + now = datetime.datetime.now().replace(microsecond=0) + store = dummy_store() + max_chain_depth = 16 + + builder = PolicyBuilder() + builder = builder.time(now) + 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 + assert verifier.store == store + assert verifier.max_chain_depth == max_chain_depth + + def test_build_server_verifier_missing_store(self): + with pytest.raises( + ValueError, match="A server verifier must have a trust store" + ): + PolicyBuilder().build_server_verifier(DNSName("cryptography.io")) + + +class TestStore: + def test_store_rejects_empty_list(self): + with pytest.raises(ValueError): + Store([]) + + def test_store_rejects_non_certificates(self): + with pytest.raises(TypeError): + Store(["not a cert"]) # type: ignore[list-item] + + +class TestServerVerifier: + @pytest.mark.parametrize( + ("validation_time", "valid"), + [ + # 03:15:02 UTC+2, or 1 second before expiry in UTC + ("2018-11-16T03:15:02+02:00", True), + # 00:15:04 UTC-1, or 1 second after expiry in UTC + ("2018-11-16T00:15:04-01:00", False), + ], + ) + def test_verify_tz_aware(self, validation_time, valid): + # expires 2018-11-16 01:15:03 UTC + leaf = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + ) + + store = Store([leaf]) + + builder = PolicyBuilder().store(store) + builder = builder.time( + datetime.datetime.fromisoformat(validation_time) + ) + verifier = builder.build_server_verifier(DNSName("cryptography.io")) + + if valid: + assert verifier.verify(leaf, []) == [leaf] + else: + with pytest.raises( + x509.verification.VerificationError, + match="cert is not valid at validation time", + ): + verifier.verify(leaf, []) diff --git a/vectors/MANIFEST.in b/vectors/MANIFEST.in deleted file mode 100644 index 6d1e5ff66c70..000000000000 --- a/vectors/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -recursive-include cryptography_vectors * -include LICENSE -include LICENSE.APACHE -include LICENSE.BSD diff --git a/vectors/cryptography_vectors/__about__.py b/vectors/cryptography_vectors/__about__.py index 6030fab339b0..c3ca4f6e267b 100644 --- a/vectors/cryptography_vectors/__about__.py +++ b/vectors/cryptography_vectors/__about__.py @@ -6,4 +6,4 @@ "__version__", ] -__version__ = "41.0.0" +__version__ = "42.0.2" diff --git a/vectors/cryptography_vectors/asymmetric/EC/explicit_parameters_private_key.pem b/vectors/cryptography_vectors/asymmetric/EC/explicit_parameters_private_key.pem new file mode 100644 index 000000000000..f54b9fe60bb8 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/explicit_parameters_private_key.pem @@ -0,0 +1,10 @@ +-----BEGIN EC PRIVATE KEY----- +MIIBaAIBAQQgoIAlsArFMdyIAGre7kgA0D4fvM+Dibt9XSdtFxhuPrWggfowgfcC +AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA///////////////+ +MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr +vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE +axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W +K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 +YyVRAgEBoUQDQgAEhIXBZutCVz1ULBu1Mq1Hg1FV0wgYADGMRvYdC1zR1nqvVsmB +yYka/ElVXwRwUAKxwhbXXt2kTvpZEAG/wjOn3Q== +-----END EC PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/explicit_parameters_wap_wsg_idm_ecid_wtls11_private_key.pem b/vectors/cryptography_vectors/asymmetric/EC/explicit_parameters_wap_wsg_idm_ecid_wtls11_private_key.pem new file mode 100644 index 000000000000..3e300a4740d6 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/explicit_parameters_wap_wsg_idm_ecid_wtls11_private_key.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PRIVATE KEY----- +MIIBSAIBAQQeAOgdbe7dchFPZAojhztGgDWQqwyZHjLneCvhSvBfoIHgMIHdAgEB +MB0GByqGSM49AQIwEgICAOkGCSqGSM49AQIDAgIBSjBXBB4AAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAEEHgBmZH7ebDMsf4wJI7tYITszOyDpzkKB/hFffY+Q +rQMVAHTVn/B/a0E9DqFLNEsgotsEm1DDBD0EAPrJ38usgxO7ITnxu3Vf72W8OR+L +Nvj463Nx/VWLAQBqCKQZAzUGeOWFKL6/igvv+GenyjZxb34B+BBSAh4BAAAAAAAA +AAAAAAAAAAAT6XTnL4ppIgMdJgPP4NcCAQKhQAM+AAQAITc5rTBkBHaMSOuhKb8z +c/hoCZIQEQp0F3fawnMBi82rKn67H56ZrXX7dWzL5yFGmleInGphYwDo+2A= +-----END EC PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/secp128r1_private_key.pem b/vectors/cryptography_vectors/asymmetric/EC/secp128r1_private_key.pem new file mode 100644 index 000000000000..da151cc53add --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/secp128r1_private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MEQCAQEEEGqA3EQW0B/63PyiwCa4bg2gBwYFK4EEAByhJAMiAASL133VyEjU3FUh +9sq37xm62q/GWxp1Q4t2iOpuBzBrBQ== +-----END EC PRIVATE KEY----- + diff --git a/vectors/cryptography_vectors/asymmetric/EC/sect163k1-spki.pem b/vectors/cryptography_vectors/asymmetric/EC/sect163k1-spki.pem new file mode 100644 index 000000000000..a69945b39cb9 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/sect163k1-spki.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEAxGAaICwgq0YOcgiIg1qIBU/tmU3AS4t +jG+YV5KpVbVoZrj9Z+fb24Pg +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/sect163r2-spki.pem b/vectors/cryptography_vectors/asymmetric/EC/sect163r2-spki.pem new file mode 100644 index 000000000000..18bac0a8c9d3 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/sect163r2-spki.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MEAwEAYHKoZIzj0CAQYFK4EEAA8DLAAEAkMQD2BC7lzGH0cqllPPPtNl1kqRBXhT +JmwDP66hW6PMFl3ldz4ZlvkK +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/sect233k1-spki.pem b/vectors/cryptography_vectors/asymmetric/EC/sect233k1-spki.pem new file mode 100644 index 000000000000..d9fe3cb27b88 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/sect233k1-spki.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFIwEAYHKoZIzj0CAQYFK4EEABoDPgAEAbCYgpNMrLez2VEmv+xSGQLxtnWoDDvK +4oh4XfQEAPETU2P//4hH7hiDxo1jfe104nG45sbYJQke8+OK +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/EC/sect233r1-spki.pem b/vectors/cryptography_vectors/asymmetric/EC/sect233r1-spki.pem new file mode 100644 index 000000000000..96bb20c64134 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/EC/sect233r1-spki.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFIwEAYHKoZIzj0CAQYFK4EEABsDPgAEAVfRTJ18T67P5XD5HXs9dv7NuO+FQwNl +9/COeQIjAWjajHoGNjsris/W25ZMPcq240TdudpXmHC5gFiV +-----END PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-crit-opt-val.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-crit-opt-val.pub new file mode 100644 index 000000000000..5510bd5f0f35 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-crit-opt-val.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIbmlzdHAyNTYAAABBBCZWRs4GYIHGJpyXuqvfFGWN49dnJRkZJLDkFrHf6mNHhIMI3vtrLfCZwxPSfnCYWK6YofssZ1FYA6TkVJq8Xi8AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAABjsM0AAAAAAGOyHoAAAABtAAAADWZvcmNlLWNvbW1hbmQAAABBAAAAKGVjaG8gYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEAAAARaW52YWxpZF9taXNjX2RhdGEAAAAPdmVyaWZ5LXJlcXVpcmVkAAAAAAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDdZgztgAFFC7T5PifrUy/kMu0Pnwq1au3vStKHe7FFMAAAAFMAAAALc3NoLWVkMjU1MTkAAABAt/0pBSDBFy1crBPHOBoKFoxRjKd1tKVdOrD3QVgbBfpaHfxi4vrgYe6JfQ54+vu5P+8yrMyACekT8H6hhvxHDw== \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-ext-val.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-ext-val.pub new file mode 100644 index 000000000000..c44b49fceccd --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/certs/p256-ed25519-non-singular-ext-val.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIbmlzdHAyNTYAAABBBCZWRs4GYIHGJpyXuqvfFGWN49dnJRkZJLDkFrHf6mNHhIMI3vtrLfCZwxPSfnCYWK6YofssZ1FYA6TkVJq8Xi8AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAABjsM0AAAAAAGOyHoAAAAAXAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAAAvAAAAFGNvbnRhaW5zLWV4dHJhLXZhbHVlAAAAEwAAAAVoZWxsbwAAAAYgd29ybGQAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDdZgztgAFFC7T5PifrUy/kMu0Pnwq1au3vStKHe7FFMAAAAFMAAAALc3NoLWVkMjU1MTkAAABAY80oIEvooz/k3x9a+yVkjSNRfi4y/q87wVYiT7keTpP4n9JV/Vlc0u7O2QYOHfb4DUkcrvbsksKVsiqoQu5qDg== \ No newline at end of file diff --git a/vectors/cryptography_vectors/asymmetric/PEM_Serialization/ec_public_key_rsa_delimiter.pem b/vectors/cryptography_vectors/asymmetric/PEM_Serialization/ec_public_key_rsa_delimiter.pem new file mode 100644 index 000000000000..565ece176bf5 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PEM_Serialization/ec_public_key_rsa_delimiter.pem @@ -0,0 +1,4 @@ +-----BEGIN RSA PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJLzzbuz2tRnLFlOL+6bTX6giVavA +sc6NDFFT0IMCd2ibTTNUDDkFGsgq0cH5JYPg/6xUlMBFKrWYe3yQ4has9w== +-----END RSA PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PEM_Serialization/rsa_wrong_delimiter_public_key.pem b/vectors/cryptography_vectors/asymmetric/PEM_Serialization/rsa_wrong_delimiter_public_key.pem new file mode 100644 index 000000000000..78053b4e6ed9 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PEM_Serialization/rsa_wrong_delimiter_public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnR4AZ+tgWYql+S3MaTQ6 +zeIO1fKzFIoau9Q0zGuv/1oCAewXwxeDSSxw+/Z3GL1NpuuS9CpbR5EQ3d71bD0v +0G+Sf+mShSl0oljG7YqnNSPzKl+EQ3/KE+eEButcwas6KGof2BA4bFNCw/fPbuhk +u/d8sIIEgdzBMiGRMdW33uci3rsdOenMZQA7uWsM/q/pu85YLAVOxq6wlUCzP4FM +Tw/RKzayrPkn3Jfbqcy1aM2HDlFVx24vaN+RRbPSnVoQbo5EQYkUMXE8WmadSyHl +pXGRnWsJSV9AdGyDrbU+6tcFwcIwnW22jb/OJy8swHdqKGkuR1kQ0XqokK1yGKFZ +8wIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/ciphers/AES/GCM-SIV/aes-192-gcm-siv.txt b/vectors/cryptography_vectors/ciphers/AES/GCM-SIV/aes-192-gcm-siv.txt new file mode 100644 index 000000000000..c4ba6703c1b4 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/GCM-SIV/aes-192-gcm-siv.txt @@ -0,0 +1,399 @@ + +COUNT = 0 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0100000000000000 +Tag = 6b0606875a845eec145f44ae5b92e834 +Ciphertext = 0e49fb119666c8ae + + +COUNT = 1 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 010000000000000000000000 +Tag = 9f2131df8b794bc6d9af9e5a8a96318e +Ciphertext = 3938f3fe1dad8464114dc42a + + +COUNT = 2 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 01000000000000000000000000000000 +Tag = 82e6a81be803dc33f56a637fcaa70fec +Ciphertext = 75a96f1f1cbfa93e2cd69e8a18bf3bab + + +COUNT = 3 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0100000000000000000000000000000002000000000000000000000000000000 +Tag = def094dd94cb68942b1b96a85a8eab28 +Ciphertext = 3022f43d5ca420345420c52de08ddaa28b8fb840aeb41bd44addc78d07e0835b + + +COUNT = 4 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 010000000000000000000000000000000200000000000000000000000000000003000000000000000000000000000000 +Tag = 6c64163e992cd475d847b9348ff1798a +Ciphertext = b2848264495ddec52a6f28a0b8112e031b78f4b78eb6590c54d68f14232850e2e4c4fdf78b8c63770ee0f07d43deb520 + + +COUNT = 5 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 01000000000000000000000000000000020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +AAD = 01 +Tag = c7eb28d9cd1fe3b3b2bd75705e747c9b +Ciphertext = bbec4e4329672818200ae2185c45dfa8e21757d044298da5f2a1ae8157737b4934ab76fb05fcba19b641971270c012c3d223ba5150687128e702fd0a656e2644 + + +COUNT = 6 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0200000000000000 +AAD = 01 +Tag = 894e72c67363ad0eab00784b92b10cba +Ciphertext = 8b2eed8f172b2227 + + +COUNT = 7 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 020000000000000000000000 +AAD = 01 +Tag = 41d21d1b764e3ffdd253c2b0a4695e2a +Ciphertext = 307a6cdfcaa3ca0d9f8a9c31 + + +COUNT = 8 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 02000000000000000000000000000000 +AAD = 01 +Tag = aafde8488bdcb22dfa65fad6e094c6da +Ciphertext = 30d9630474420eea90bee4dbca3c4ae0 + + +COUNT = 9 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0200000000000000000000000000000003000000000000000000000000000000 +AAD = 01 +Tag = 03902234c1db8adcc4b6a2bc09c28401 +Ciphertext = abc2b17cc5a7a89745b684844b1699757528f3a008090cdb0dd6bfbdfea9550e + + +COUNT = 10 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +AAD = 01 +Tag = bd5a9b90b763e05e69ea0ffe3d850abf +Ciphertext = 335910f4402db51cecc5c35fb49eda857f705de55c9a69824598420431dd0ad3f1d01db404118f0b48b1e405ca4360f6 + + +COUNT = 11 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 02000000000000000000000000000000030000000000000000000000000000000400000000000000000000000000000005000000000000000000000000000000 +AAD = 010000000000000000000000 +Tag = fa6414f191bf4d9150463ea5576419e7 +Ciphertext = 4a079678501f40450cec428417910b3193a222cbb123dfdbc813da04e1e1b4d8d103bc2e50f732b2f5426adfe97a8c7be4c9469781e84db13a2d20701187da52 + + +COUNT = 12 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 02000000 +AAD = 010000000000000000000000000000000200 +Tag = a55b45c4a6eae8d73458616043f2e613 +Ciphertext = ff75102b + + +COUNT = 13 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0300000000000000000000000000000004000000 +AAD = 0100000000000000000000000000000002000000 +Tag = eb23533e1cfa48dd66312068522ffbcd +Ciphertext = 800a331bc9fd057346f967d0b74b6e0c28f87dde + + +COUNT = 14 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 030000000000000000000000000000000400 +AAD = 46bb91c3c5 +Tag = 1262580dd140a8f15a3cfaa61dae6228 +Ciphertext = 5fcd136fbe93ff59312419db8dc88592d04d + + +COUNT = 15 +Key = 36864200e0eaf5284d884a0e77d316460000000000000000 +IV = bae8e37fc83441b16034566b +Plaintext = 7a806c +AAD = fc880c94a95198874296 +Tag = 0b12efedd62766899d8d71dd2b60efb7 +Ciphertext = e9d247 + + +COUNT = 16 +Key = aedb64a6c590bc84d1a5e269e4b478010000000000000000 +IV = afc0577e34699b9e671fdd4f +Plaintext = bdc66f146545 +AAD = 046787f3ea22c127aaf195d1894728 +Tag = 300a8b293f061d9a240f28e3e8d01c80 +Ciphertext = c3fe5dc13711 + + +COUNT = 17 +Key = d5cc1fd161320b6920ce07787f86743b0000000000000000 +IV = 275d1ab32f6d1f0434d8848c +Plaintext = 1177441f195495860f +AAD = c9882e5386fd9f92ec489c8fde2be2cf97e74e93 +Tag = 2f836454f4067fd55487b2ee9f98969f +Ciphertext = b2adb4f1ee87054334 + + +COUNT = 18 +Key = b3fed1473c528b8426a582995929a1490000000000000000 +IV = 9e9ad8780c8d63d0ab4149c0 +Plaintext = 9f572c614b4745914474e7c7 +AAD = 2950a70d5a1db2316fd568378da107b52b0da55210cc1c1b0a +Tag = e807b12ad6e986df56634d368618736b +Ciphertext = 55bbffc06e088d5e84a91645 + + +COUNT = 19 +Key = 2d4ed87da44102952ef94b02b805249b0000000000000000 +IV = ac80e6f61455bfac8308a2d4 +Plaintext = 0d8c8451178082355c9e940fea2f58 +AAD = 1860f762ebfbd08284e421702de0de18baa9c9596291b08466f37de21c7f +Tag = b17d76ae290eb80fab2ce7b442e5eff8 +Ciphertext = f253ab63b9ddb0a504bb89d7433af2 + + +COUNT = 20 +Key = bde3b2f204d1e9f8b06bc47f9745b3d10000000000000000 +IV = ae06556fb6aa7890bebc18fe +Plaintext = 6b3db4da3d57aa94842b9803a96e07fb6de7 +AAD = 7576f7028ec6eb5ea7e298342a94d4b202b370ef9768ec6561c4fe6b7e7296fa859c21 +Tag = 052d42f547b265c1c3df2ef3825e98f9 +Ciphertext = 2cf3f09fe425322a2780ec29ddb7223157e2 + + +COUNT = 21 +Key = f901cfe8a69615a93fdf7a98cad481790000000000000000 +IV = 6245709fb18853f68d833640 +Plaintext = e42a3c02c25b64869e146d7b233987bddfc240871d +Tag = ef016675dc2db7d0b99fc180ab22a3a9 +Ciphertext = 8fb444f874828ddc73c2fa86bfd0458da27919b1a9 + + +COUNT = 22 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0100000000000000 +Tag = 6b0606875a845eec145f44ae5b92e834 +Ciphertext = 0e49fb119666c8ae + + +COUNT = 23 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 010000000000000000000000 +Tag = 9f2131df8b794bc6d9af9e5a8a96318e +Ciphertext = 3938f3fe1dad8464114dc42a + + +COUNT = 24 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 01000000000000000000000000000000 +Tag = 82e6a81be803dc33f56a637fcaa70fec +Ciphertext = 75a96f1f1cbfa93e2cd69e8a18bf3bab + + +COUNT = 25 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0100000000000000000000000000000002000000000000000000000000000000 +Tag = def094dd94cb68942b1b96a85a8eab28 +Ciphertext = 3022f43d5ca420345420c52de08ddaa28b8fb840aeb41bd44addc78d07e0835b + + +COUNT = 26 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 010000000000000000000000000000000200000000000000000000000000000003000000000000000000000000000000 +Tag = 6c64163e992cd475d847b9348ff1798a +Ciphertext = b2848264495ddec52a6f28a0b8112e031b78f4b78eb6590c54d68f14232850e2e4c4fdf78b8c63770ee0f07d43deb520 + + +COUNT = 27 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 01000000000000000000000000000000020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +AAD = 01 +Tag = c7eb28d9cd1fe3b3b2bd75705e747c9b +Ciphertext = bbec4e4329672818200ae2185c45dfa8e21757d044298da5f2a1ae8157737b4934ab76fb05fcba19b641971270c012c3d223ba5150687128e702fd0a656e2644 + + +COUNT = 28 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0200000000000000 +AAD = 01 +Tag = 894e72c67363ad0eab00784b92b10cba +Ciphertext = 8b2eed8f172b2227 + + +COUNT = 29 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 020000000000000000000000 +AAD = 01 +Tag = 41d21d1b764e3ffdd253c2b0a4695e2a +Ciphertext = 307a6cdfcaa3ca0d9f8a9c31 + + +COUNT = 30 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 02000000000000000000000000000000 +AAD = 01 +Tag = aafde8488bdcb22dfa65fad6e094c6da +Ciphertext = 30d9630474420eea90bee4dbca3c4ae0 + + +COUNT = 31 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0200000000000000000000000000000003000000000000000000000000000000 +AAD = 01 +Tag = 03902234c1db8adcc4b6a2bc09c28401 +Ciphertext = abc2b17cc5a7a89745b684844b1699757528f3a008090cdb0dd6bfbdfea9550e + + +COUNT = 32 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +AAD = 01 +Tag = bd5a9b90b763e05e69ea0ffe3d850abf +Ciphertext = 335910f4402db51cecc5c35fb49eda857f705de55c9a69824598420431dd0ad3f1d01db404118f0b48b1e405ca4360f6 + + +COUNT = 33 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 02000000000000000000000000000000030000000000000000000000000000000400000000000000000000000000000005000000000000000000000000000000 +AAD = 010000000000000000000000 +Tag = fa6414f191bf4d9150463ea5576419e7 +Ciphertext = 4a079678501f40450cec428417910b3193a222cbb123dfdbc813da04e1e1b4d8d103bc2e50f732b2f5426adfe97a8c7be4c9469781e84db13a2d20701187da52 + + +COUNT = 34 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 02000000 +AAD = 010000000000000000000000000000000200 +Tag = a55b45c4a6eae8d73458616043f2e613 +Ciphertext = ff75102b + + +COUNT = 35 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 0300000000000000000000000000000004000000 +AAD = 0100000000000000000000000000000002000000 +Tag = eb23533e1cfa48dd66312068522ffbcd +Ciphertext = 800a331bc9fd057346f967d0b74b6e0c28f87dde + + +COUNT = 36 +Key = 010000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Plaintext = 030000000000000000000000000000000400 +AAD = 4fbdc66f14 +Tag = d4a18ab742502dcae4dd17390cb2acd9 +Ciphertext = 4f566a1cfa8b324963dc1d50193dff4c9188 + + +COUNT = 37 +Key = bae8e37fc83441b16034566b7a806c46bb91c3c5aedb64a6 +IV = e4b47801afc0577e34699b9e +Plaintext = 671fdd +AAD = 6787f3ea22c127aaf195 +Tag = 638269643cda66e000734bf9f0c821f4 +Ciphertext = c76264 + + +COUNT = 38 +Key = 6545fc880c94a95198874296d5cc1fd161320b6920ce0778 +IV = 2f6d1f0434d8848c1177441f +Plaintext = 195495860f04 +AAD = 489c8fde2be2cf97e74e932d4ed87d +Tag = 4925f70d2e7ed024a7c7a4c6cb2cce2d +Ciphertext = 36583f07a52f + + +COUNT = 39 +Key = d1894728b3fed1473c528b8426a582995929a1499e9ad878 +IV = 9f572c614b4745914474e7c7 +Plaintext = c9882e5386fd9f92ec +AAD = 0da55210cc1c1b0abde3b2f204d1e9f8b06bc47f +Tag = 4cc78a269c9a89853cdb774a666af987 +Ciphertext = cb04c5de6874c6a146 + + +COUNT = 40 +Key = a44102952ef94b02b805249bac80e6f61455bfac8308a2d4 +IV = 5c9e940fea2f582950a70d5a +Plaintext = 1db2316fd568378da107b52b +AAD = f37de21c7ff901cfe8a69615a93fdf7a98cad481796245709f +Tag = c8039defe751d0376d2dfe270098087b +Ciphertext = 754f8b19e96a82d3f2d65fc9 + + +COUNT = 41 +Key = 9745b3d1ae06556fb6aa7890bebc18fe6b3db4da3d57aa94 +IV = 6de71860f762ebfbd08284e4 +Plaintext = 21702de0de18baa9c9596291b08466 +AAD = 9c2159058b1f0fe91433a5bdc20e214eab7fecef4454a10ef0657df21ac7 +Tag = 7b3f0297b430ea449da03edbd733c09f +Ciphertext = b435bc3278d03b21c9617fe61e5d38 + + +COUNT = 42 +Key = b18853f68d833640e42a3c02c25b64869e146d7b233987bd +IV = 028ec6eb5ea7e298342a94d4 +Plaintext = b202b370ef9768ec6561c4fe6b7e7296fa85 +AAD = 734320ccc9d9bbbb19cb81b2af4ecbc3e72834321f7aa0f70b7282b4f33df23f167541 +Tag = 5202d95e07513016c4297bb6931645fa +Ciphertext = a1b8a596c157c466388807c4a4dae95cbca9 + + +COUNT = 43 +Key = 3c535de192eaed3822a2fbbe2ca9dfc88255e14a661b8aa8 +IV = 688089e55540db1872504e1c +Plaintext = ced532ce4159b035277d4dfbb7db62968b13cd4eec +Tag = dfea23246312c3a465c07c31181b843e +Ciphertext = 240a8f822438d841f7762d8ff7d5491abf1a522cfa + + +COUNT = 44 +Key = 000000000000000000000000000000000000000000000000 +IV = 000000000000000000000000 +Plaintext = 000000000000000000000000000000004db923dc793ee6497c76dcc03a98e108 +Tag = 186ba2cd0e9b336b7ff602360de21986 +Ciphertext = f6ec502b997e31fd7760f9c775db0a88597efe1053d343775195f0e3416e51b2 + + +COUNT = 45 +Key = 000000000000000000000000000000000000000000000000 +IV = 000000000000000000000000 +Plaintext = eb3640277c7ffd1303c7a542d02d3e4c0000000000000000 +Tag = f23ebe966130dcc9e2a8eb7a91193ac8 +Ciphertext = c67f39b25f3dc2d5a9d400dd29275f10b4291b0efb6d32de diff --git a/vectors/cryptography_vectors/ciphers/AES/GCM-SIV/openssl.txt b/vectors/cryptography_vectors/ciphers/AES/GCM-SIV/openssl.txt new file mode 100644 index 000000000000..148dd47483db --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/AES/GCM-SIV/openssl.txt @@ -0,0 +1,492 @@ +#Cipher = aes-128-gcm-siv +COUNT = 0 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 578782fff6013b815b287c22493a364c +Plaintext = 0100000000000000 +Ciphertext = b5d839330ac7b786 + + + +#Cipher = aes-128-gcm-siv +COUNT = 1 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = a4978db357391a0bc4fdec8b0d106639 +Plaintext = 010000000000000000000000 +Ciphertext = 7323ea61d05932260047d942 + + + +#Cipher = aes-128-gcm-siv +COUNT = 2 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 303aaf90f6fe21199c6068577437a0c4 +Plaintext = 01000000000000000000000000000000 +Ciphertext = 743f7c8077ab25f8624e2e948579cf77 + + + +#Cipher = aes-128-gcm-siv +COUNT = 3 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 1a8e45dcd4578c667cd86847bf6155ff +Plaintext = 0100000000000000000000000000000002000000000000000000000000000000 +Ciphertext = 84e07e62ba83a6585417245d7ec413a9fe427d6315c09b57ce45f2e3936a9445 + + + +#Cipher = aes-128-gcm-siv +COUNT = 4 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 5e6e311dbf395d35b0fe39c2714388f8 +Plaintext = 010000000000000000000000000000000200000000000000000000000000000003000000000000000000000000000000 +Ciphertext = 3fd24ce1f5a67b75bf2351f181a475c7b800a5b4d3dcf70106b1eea82fa1d64df42bf7226122fa92e17a40eeaac1201b + + + +#Cipher = aes-128-gcm-siv +COUNT = 5 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 8a263dd317aa88d56bdf3936dba75bb8 +Plaintext = 01000000000000000000000000000000020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +Ciphertext = 2433668f1058190f6d43e360f4f35cd8e475127cfca7028ea8ab5c20f7ab2af02516a2bdcbc08d521be37ff28c152bba36697f25b4cd169c6590d1dd39566d3f + + + +#Cipher = aes-128-gcm-siv +COUNT = 6 +AAD = 01 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 3b0a1a2560969cdf790d99759abd1508 +Plaintext = 0200000000000000 +Ciphertext = 1e6daba35669f427 + + + +#Cipher = aes-128-gcm-siv +COUNT = 7 +AAD = 01 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 08299c5102745aaa3a0c469fad9e075a +Plaintext = 020000000000000000000000 +Ciphertext = 296c7889fd99f41917f44620 + + + +#Cipher = aes-128-gcm-siv +COUNT = 8 +AAD = 01 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 8f8936ec039e4e4bb97ebd8c4457441f +Plaintext = 02000000000000000000000000000000 +Ciphertext = e2b0c5da79a901c1745f700525cb335b + + + +#Cipher = aes-128-gcm-siv +COUNT = 9 +AAD = 01 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = e6af6a7f87287da059a71684ed3498e1 +Plaintext = 0200000000000000000000000000000003000000000000000000000000000000 +Ciphertext = 620048ef3c1e73e57e02bb8562c416a319e73e4caac8e96a1ecb2933145a1d71 + + + +#Cipher = aes-128-gcm-siv +COUNT = 10 +AAD = 01 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 6a8cc3865f76897c2e4b245cf31c51f2 +Plaintext = 020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +Ciphertext = 50c8303ea93925d64090d07bd109dfd9515a5a33431019c17d93465999a8b0053201d723120a8562b838cdff25bf9d1e + + + +#Cipher = aes-128-gcm-siv +COUNT = 11 +AAD = 01 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = cdc46ae475563de037001ef84ae21744 +Plaintext = 02000000000000000000000000000000030000000000000000000000000000000400000000000000000000000000000005000000000000000000000000000000 +Ciphertext = 2f5c64059db55ee0fb847ed513003746aca4e61c711b5de2e7a77ffd02da42feec601910d3467bb8b36ebbaebce5fba30d36c95f48a3e7980f0e7ac299332a80 + + + +#Cipher = aes-128-gcm-siv +COUNT = 12 +AAD = 010000000000000000000000 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 07eb1f84fb28f8cb73de8e99e2f48a14 +Plaintext = 02000000 +Ciphertext = a8fe3e87 + + + +#Cipher = aes-128-gcm-siv +COUNT = 13 +AAD = 010000000000000000000000000000000200 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 24afc9805e976f451e6d87f6fe106514 +Plaintext = 0300000000000000000000000000000004000000 +Ciphertext = 6bb0fecf5ded9b77f902c7d5da236a4391dd0297 + + + +#Cipher = aes-128-gcm-siv +COUNT = 14 +AAD = 0100000000000000000000000000000002000000 +Key = 01000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = bff9b2ef00fb47920cc72a0c0f13b9fd +Plaintext = 030000000000000000000000000000000400 +Ciphertext = 44d0aaf6fb2f1f34add5e8064e83e12a2ada + + +#Cipher = aes-128-gcm-siv +COUNT = 15 +AAD = 46bb91c3c5 +Key = 36864200e0eaf5284d884a0e77d31646 +IV = bae8e37fc83441b16034566b +Tag = 711bd85bc1e4d3e0a462e074eea428a8 +Plaintext = 7a806c +Ciphertext = af60eb + + + +#Cipher = aes-128-gcm-siv +COUNT = 16 +AAD = fc880c94a95198874296 +Key = aedb64a6c590bc84d1a5e269e4b47801 +IV = afc0577e34699b9e671fdd4f +Tag = d6a9c45545cfc11f03ad743dba20f966 +Plaintext = bdc66f146545 +Ciphertext = bb93a3e34d3c + + + +#Cipher = aes-128-gcm-siv +COUNT = 17 +AAD = 046787f3ea22c127aaf195d1894728 +Key = d5cc1fd161320b6920ce07787f86743b +IV = 275d1ab32f6d1f0434d8848c +Tag = 1d02fd0cd174c84fc5dae2f60f52fd2b +Plaintext = 1177441f195495860f +Ciphertext = 4f37281f7ad12949d0 + + + +#Cipher = aes-128-gcm-siv +COUNT = 18 +AAD = c9882e5386fd9f92ec489c8fde2be2cf97e74e93 +Key = b3fed1473c528b8426a582995929a149 +IV = 9e9ad8780c8d63d0ab4149c0 +Tag = c1dc2f871fb7561da1286e655e24b7b0 +Plaintext = 9f572c614b4745914474e7c7 +Ciphertext = f54673c5ddf710c745641c8b + + + +#Cipher = aes-128-gcm-siv +COUNT = 19 +AAD = 2950a70d5a1db2316fd568378da107b52b0da55210cc1c1b0a +Key = 2d4ed87da44102952ef94b02b805249b +IV = ac80e6f61455bfac8308a2d4 +Tag = 83b3449b9f39552de99dc214a1190b0b +Plaintext = 0d8c8451178082355c9e940fea2f58 +Ciphertext = c9ff545e07b88a015f05b274540aa1 + + + +#Cipher = aes-128-gcm-siv +COUNT = 20 +AAD = 1860f762ebfbd08284e421702de0de18baa9c9596291b08466f37de21c7f +Key = bde3b2f204d1e9f8b06bc47f9745b3d1 +IV = ae06556fb6aa7890bebc18fe +Tag = 3e377094f04709f64d7b985310a4db84 +Plaintext = 6b3db4da3d57aa94842b9803a96e07fb6de7 +Ciphertext = 6298b296e24e8cc35dce0bed484b7f30d580 + + + +#Cipher = aes-128-gcm-siv +COUNT = 21 +AAD = 7576f7028ec6eb5ea7e298342a94d4b202b370ef9768ec6561c4fe6b7e7296fa859c21 +Key = f901cfe8a69615a93fdf7a98cad48179 +IV = 6245709fb18853f68d833640 +Tag = 2d15506c84a9edd65e13e9d24a2a6e70 +Plaintext = e42a3c02c25b64869e146d7b233987bddfc240871d +Ciphertext = 391cc328d484a4f46406181bcd62efd9b3ee197d05 + + +# AES_256_GCM_SIV + + + +#Cipher = aes-256-gcm-siv +COUNT = 22 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 843122130f7364b761e0b97427e3df28 +Plaintext = 0100000000000000 +Ciphertext = c2ef328e5c71c83b + + + +#Cipher = aes-256-gcm-siv +COUNT = 23 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 8ca50da9ae6559e48fd10f6e5c9ca17e +Plaintext = 010000000000000000000000 +Ciphertext = 9aab2aeb3faa0a34aea8e2b1 + + + +#Cipher = aes-256-gcm-siv +COUNT = 24 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = c9eac6fa700942702e90862383c6c366 +Plaintext = 01000000000000000000000000000000 +Ciphertext = 85a01b63025ba19b7fd3ddfc033b3e76 + + + +#Cipher = aes-256-gcm-siv +COUNT = 25 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = e819e63abcd020b006a976397632eb5d +Plaintext = 0100000000000000000000000000000002000000000000000000000000000000 +Ciphertext = 4a6a9db4c8c6549201b9edb53006cba821ec9cf850948a7c86c68ac7539d027f + + + +#Cipher = aes-256-gcm-siv +COUNT = 26 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 790bc96880a99ba804bd12c0e6a22cc4 +Plaintext = 010000000000000000000000000000000200000000000000000000000000000003000000000000000000000000000000 +Ciphertext = c00d121893a9fa603f48ccc1ca3c57ce7499245ea0046db16c53c7c66fe717e39cf6c748837b61f6ee3adcee17534ed5 + + + +#Cipher = aes-256-gcm-siv +COUNT = 27 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 112864c269fc0d9d88c61fa47e39aa08 +Plaintext = 01000000000000000000000000000000020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +Ciphertext = c2d5160a1f8683834910acdafc41fbb1632d4a353e8b905ec9a5499ac34f96c7e1049eb080883891a4db8caaa1f99dd004d80487540735234e3744512c6f90ce + + + +#Cipher = aes-256-gcm-siv +COUNT = 28 +AAD = 01 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 91213f267e3b452f02d01ae33e4ec854 +Plaintext = 0200000000000000 +Ciphertext = 1de22967237a8132 + + + +#Cipher = aes-256-gcm-siv +COUNT = 29 +AAD = 01 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = c1a4a19ae800941ccdc57cc8413c277f +Plaintext = 020000000000000000000000 +Ciphertext = 163d6f9cc1b346cd453a2e4c + + + +#Cipher = aes-256-gcm-siv +COUNT = 30 +AAD = 01 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = b292d28ff61189e8e49f3875ef91aff7 +Plaintext = 02000000000000000000000000000000 +Ciphertext = c91545823cc24f17dbb0e9e807d5ec17 + + + +#Cipher = aes-256-gcm-siv +COUNT = 31 +AAD = 01 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = aea1bad12702e1965604374aab96dbbc +Plaintext = 0200000000000000000000000000000003000000000000000000000000000000 +Ciphertext = 07dad364bfc2b9da89116d7bef6daaaf6f255510aa654f920ac81b94e8bad365 + + + +#Cipher = aes-256-gcm-siv +COUNT = 32 +AAD = 01 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 03332742b228c647173616cfd44c54eb +Plaintext = 020000000000000000000000000000000300000000000000000000000000000004000000000000000000000000000000 +Ciphertext = c67a1f0f567a5198aa1fcc8e3f21314336f7f51ca8b1af61feac35a86416fa47fbca3b5f749cdf564527f2314f42fe25 + + + +#Cipher = aes-256-gcm-siv +COUNT = 33 +AAD = 01 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 5bde0285037c5de81e5b570a049b62a0 +Plaintext = 02000000000000000000000000000000030000000000000000000000000000000400000000000000000000000000000005000000000000000000000000000000 +Ciphertext = 67fd45e126bfb9a79930c43aad2d36967d3f0e4d217c1e551f59727870beefc98cb933a8fce9de887b1e40799988db1fc3f91880ed405b2dd298318858467c89 + + + +#Cipher = aes-256-gcm-siv +COUNT = 34 +AAD = 010000000000000000000000 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = 1835e517741dfddccfa07fa4661b74cf +Plaintext = 02000000 +Ciphertext = 22b3f4cd + + + +#Cipher = aes-256-gcm-siv +COUNT = 35 +AAD = 010000000000000000000000000000000200 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = b879ad976d8242acc188ab59cabfe307 +Plaintext = 0300000000000000000000000000000004000000 +Ciphertext = 43dd0163cdb48f9fe3212bf61b201976067f342b + + + +#Cipher = aes-256-gcm-siv +COUNT = 36 +AAD = 0100000000000000000000000000000002000000 +Key = 0100000000000000000000000000000000000000000000000000000000000000 +IV = 030000000000000000000000 +Tag = cfcdf5042112aa29685c912fc2056543 +Plaintext = 030000000000000000000000000000000400 +Ciphertext = 462401724b5ce6588d5a54aae5375513a075 + + +#Cipher = aes-256-gcm-siv +COUNT = 37 +AAD = 4fbdc66f14 +Key = bae8e37fc83441b16034566b7a806c46bb91c3c5aedb64a6c590bc84d1a5e269 +IV = e4b47801afc0577e34699b9e +Tag = 93da9bb81333aee0c785b240d319719d +Plaintext = 671fdd +Ciphertext = 0eaccb + + + +#Cipher = aes-256-gcm-siv +COUNT = 38 +AAD = 6787f3ea22c127aaf195 +Key = 6545fc880c94a95198874296d5cc1fd161320b6920ce07787f86743b275d1ab3 +IV = 2f6d1f0434d8848c1177441f +Tag = 6b62b84dc40c84636a5ec12020ec8c2c +Plaintext = 195495860f04 +Ciphertext = a254dad4f3f9 + + + +#Cipher = aes-256-gcm-siv +COUNT = 39 +AAD = 489c8fde2be2cf97e74e932d4ed87d +Key = d1894728b3fed1473c528b8426a582995929a1499e9ad8780c8d63d0ab4149c0 +IV = 9f572c614b4745914474e7c7 +Tag = c0fd3dc6628dfe55ebb0b9fb2295c8c2 +Plaintext = c9882e5386fd9f92ec +Ciphertext = 0df9e308678244c44b + + + +#Cipher = aes-256-gcm-siv +COUNT = 40 +AAD = 0da55210cc1c1b0abde3b2f204d1e9f8b06bc47f +Key = a44102952ef94b02b805249bac80e6f61455bfac8308a2d40d8c845117808235 +IV = 5c9e940fea2f582950a70d5a +Tag = 404099c2587f64979f21826706d497d5 +Plaintext = 1db2316fd568378da107b52b +Ciphertext = 8dbeb9f7255bf5769dd56692 + + + +#Cipher = aes-256-gcm-siv +COUNT = 41 +AAD = f37de21c7ff901cfe8a69615a93fdf7a98cad481796245709f +Key = 9745b3d1ae06556fb6aa7890bebc18fe6b3db4da3d57aa94842b9803a96e07fb +IV = 6de71860f762ebfbd08284e4 +Tag = b3080d28f6ebb5d3648ce97bd5ba67fd +Plaintext = 21702de0de18baa9c9596291b08466 +Ciphertext = 793576dfa5c0f88729a7ed3c2f1bff + + + +#Cipher = aes-256-gcm-siv +COUNT = 42 +AAD = 9c2159058b1f0fe91433a5bdc20e214eab7fecef4454a10ef0657df21ac7 +Key = b18853f68d833640e42a3c02c25b64869e146d7b233987bddfc240871d7576f7 +IV = 028ec6eb5ea7e298342a94d4 +Tag = 454fc2a154fea91f8363a39fec7d0a49 +Plaintext = b202b370ef9768ec6561c4fe6b7e7296fa85 +Ciphertext = 857e16a64915a787637687db4a9519635cdd + + + +#Cipher = aes-256-gcm-siv +COUNT = 43 +AAD = 734320ccc9d9bbbb19cb81b2af4ecbc3e72834321f7aa0f70b7282b4f33df23f167541 +Key = 3c535de192eaed3822a2fbbe2ca9dfc88255e14a661b8aa82cc54236093bbc23 +IV = 688089e55540db1872504e1c +Tag = 9d6c7029675b89eaf4ba1ded1a286594 +Plaintext = ced532ce4159b035277d4dfbb7db62968b13cd4eec +Ciphertext = 626660c26ea6612fb17ad91e8e767639edd6c9faee + +# The tests in this section use AEAD_AES_256_GCM_SIV and are crafted to +# test correct wrapping of the block counter. + + +#Cipher = aes-256-gcm-siv +COUNT = 44 +Key = 0000000000000000000000000000000000000000000000000000000000000000 +IV = 000000000000000000000000 +Tag = ffffffff000000000000000000000000 +Plaintext = 000000000000000000000000000000004db923dc793ee6497c76dcc03a98e108 +Ciphertext = f3f80f2cf0cb2dd9c5984fcda908456cc537703b5ba70324a6793a7bf218d3ea + + + +#Cipher = aes-256-gcm-siv +COUNT = 45 +Key = 0000000000000000000000000000000000000000000000000000000000000000 +IV = 000000000000000000000000 +Tag = ffffffff000000000000000000000000 +Plaintext = eb3640277c7ffd1303c7a542d02d3e4c0000000000000000 +Ciphertext = 18ce4f0b8cb4d0cac65fea8f79257b20888e53e72299e56d diff --git a/vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt b/vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt new file mode 100644 index 000000000000..8201e30dfa88 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt @@ -0,0 +1,70 @@ + +COUNT = 0 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 4294967295 +PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d + +COUNT = 1 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 18446744073709551615 +PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b17689 + +COUNT = 2 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 4294967295 +PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a + +COUNT = 3 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 18446744073709551615 +PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7 + +COUNT = 4 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 4294967295 +PLAINTEXT = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a + +COUNT = 5 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 18446744073709551615 +PLAINTEXT = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586 + +COUNT = 6 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 4294967295 +PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a46f0f6efee15c8f1b198cb49d92b990867905159440cc723916dc00128269810 + +COUNT = 7 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 18446744073709551615 +PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee65869f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed + +COUNT = 8 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 4294967295 +PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a46f0f6efee15c8f1b198cb49d92b990867905159440cc723916dc0012826981039ce1766aa2542b05db3bd809ab142489d5dbfe1273e7399637b4b3213768aaa + +COUNT = 9 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +NONCE = 0000000000000000 +INITIAL_BLOCK_COUNTER = 18446744073709551615 +PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee65869f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f \ No newline at end of file diff --git a/vectors/cryptography_vectors/ciphers/ChaCha20/rfc7539.txt b/vectors/cryptography_vectors/ciphers/ChaCha20/rfc7539.txt index c1541cf8bbbf..2b344b73c127 100644 --- a/vectors/cryptography_vectors/ciphers/ChaCha20/rfc7539.txt +++ b/vectors/cryptography_vectors/ciphers/ChaCha20/rfc7539.txt @@ -1,23 +1,25 @@ # The vectors are from RFC 7539 Appendix A.2. They are reformatted into NIST -# form for our vector loaders. +# form for our vector loaders, and adapted to use a 64/64 bit counter/nonce +# split (matching our implementation), rather than the 32/96 split defined +# in the RFC. COUNT = 0 KEY = 0000000000000000000000000000000000000000000000000000000000000000 -NONCE = 000000000000000000000000 +NONCE = 0000000000000000 INITIAL_BLOCK_COUNTER = 0 PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 CIPHERTEXT = 76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586 COUNT = 1 KEY = 0000000000000000000000000000000000000000000000000000000000000001 -NONCE = 000000000000000000000002 +NONCE = 0000000000000002 INITIAL_BLOCK_COUNTER = 1 PLAINTEXT = 416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e7472696275746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e20224945544620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c2073746174656d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207768696368206172652061646472657373656420746f CIPHERTEXT = a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221 COUNT = 2 KEY = 1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0 -NONCE = 000000000000000000000002 +NONCE = 0000000000000002 INITIAL_BLOCK_COUNTER = 42 PLAINTEXT = 2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e CIPHERTEXT = 62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1 diff --git a/vectors/cryptography_vectors/ciphers/SM4/rfc8998.txt b/vectors/cryptography_vectors/ciphers/SM4/rfc8998.txt new file mode 100644 index 000000000000..9f2e8aff8ef1 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/SM4/rfc8998.txt @@ -0,0 +1,11 @@ +# Vectors from rfc8998.txt. Reformatted to work with the NIST loader +# SM4 GCM + +# A.2 +COUNT = 1 +KEY = 0123456789abcdeffedcba9876543210 +IV = 00001234567800000000abcd +AAD = feedfacedeadbeeffeedfacedeadbeefabaddad2 +TAG = 83de3541e4c2b58177e065a9bf7b62ec +PLAINTEXT = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccddddddddddddddddeeeeeeeeeeeeeeeeffffffffffffffffeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaa +CIPHERTEXT = 17f399f08c67d5ee19d0dc9969c4bb7d5fd46fd3756489069157b282bb200735d82710ca5c22f0ccfa7cbf93d496ac15a56834cbcf98c397b4024a2691233b8d diff --git a/vectors/cryptography_vectors/pkcs12/ca/ca.pem b/vectors/cryptography_vectors/pkcs12/ca/ca.pem new file mode 100644 index 000000000000..5ca80286ecc5 --- /dev/null +++ b/vectors/cryptography_vectors/pkcs12/ca/ca.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBUTCB96ADAgECAgIDCTAKBggqhkjOPQQDAjAnMQswCQYDVQQGEwJVUzEYMBYG +A1UEAwwPY3J5cHRvZ3JhcGh5IENBMB4XDTE3MDEwMTEyMDEwMFoXDTM4MTIzMTA4 +MzAwMFowJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBj/z7v5Obj13cPuwECLBnUGq0/N2CxS +JE4f4BBGZ7VfFblivTvPDG++Gve0oQ+0uctuhrNQ+WxRv8GC177F+QWjEzARMA8G +A1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhANES742XWm64tkGnz8Dn +pG6u2lHkZFQr3oaVvPcemvlbAiEA0WGGzmYx5C9UvfXIK7NEziT4pQtyESE0uRVK +Xw4nMqk= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/pkcs12/ca/ca_key.pem b/vectors/cryptography_vectors/pkcs12/ca/ca_key.pem new file mode 100644 index 000000000000..2fb5394195cb --- /dev/null +++ b/vectors/cryptography_vectors/pkcs12/ca/ca_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgA8Zqz5vLeR0ePZUe +jBfdyMmnnI4U5uAJApWTsMn/RuWhRANCAAQY/8+7+Tm49d3D7sBAiwZ1BqtPzdgs +UiROH+AQRme1XxW5Yr07zwxvvhr3tKEPtLnLboazUPlsUb/Bgte+xfkF +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/py.typed b/vectors/cryptography_vectors/py.typed new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/vectors/cryptography_vectors/py.typed @@ -0,0 +1 @@ + diff --git a/vectors/cryptography_vectors/x509/custom/ca/ca.pem b/vectors/cryptography_vectors/x509/custom/ca/ca.pem index 5ca80286ecc5..0574924b5d66 100644 --- a/vectors/cryptography_vectors/x509/custom/ca/ca.pem +++ b/vectors/cryptography_vectors/x509/custom/ca/ca.pem @@ -1,10 +1,10 @@ -----BEGIN CERTIFICATE----- -MIIBUTCB96ADAgECAgIDCTAKBggqhkjOPQQDAjAnMQswCQYDVQQGEwJVUzEYMBYG -A1UEAwwPY3J5cHRvZ3JhcGh5IENBMB4XDTE3MDEwMTEyMDEwMFoXDTM4MTIzMTA4 -MzAwMFowJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTBZ -MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBj/z7v5Obj13cPuwECLBnUGq0/N2CxS -JE4f4BBGZ7VfFblivTvPDG++Gve0oQ+0uctuhrNQ+WxRv8GC177F+QWjEzARMA8G -A1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhANES742XWm64tkGnz8Dn -pG6u2lHkZFQr3oaVvPcemvlbAiEA0WGGzmYx5C9UvfXIK7NEziT4pQtyESE0uRVK -Xw4nMqk= +MIIBUzCB+aADAgECAgIDCTAKBggqhkjOPQQDAjAnMQswCQYDVQQGEwJVUzEYMBYG +A1UEAwwPY3J5cHRvZ3JhcGh5IENBMCAXDTE3MDEwMTAxMDAwMFoYDzIxMDAwMTAx +MDAwMDAwWjAnMQswCQYDVQQGEwJVUzEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5IENB +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGP/Pu/k5uPXdw+7AQIsGdQarT83Y +LFIkTh/gEEZntV8VuWK9O88Mb74a97ShD7S5y26Gs1D5bFG/wYLXvsX5BaMTMBEw +DwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEAvbYZS/FHzNtLGGyt +HRNVDdcwLWISWOBz6p9ZvS6C42sCIQDThR22DuYZPUMQ3/AEylxYnMN+yBHiUUfU +7hDv+IKvTA== -----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/dsa_null_alg_params.pem b/vectors/cryptography_vectors/x509/custom/dsa_null_alg_params.pem new file mode 100644 index 000000000000..724aafac3c7d --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/dsa_null_alg_params.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEJDCCA9CgAwIBAgIJANXeXtYKjIwEMA0GCWCGSAFlAwQDAgUAMBIxEDAOBgNV +BAMTB2V4YW1wbGUwHhcNMjMwNzE4MTQ0OTEwWhcNMjMxMDE2MTQ0OTEwWjASMRAw +DgYDVQQDEwdleGFtcGxlMIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarp +v6vtiHrPSVG28y7FnjuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdAR +rbi0eYd1SYRpXKwOjxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBX +sZWtzQAjPbpUhLYpH51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHn +M423krcw17njSVkvaAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHE +UOThjBopo33fXqFD3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5I +TAV2OTlgHNZnAh0AuvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxY +IEhQcE51AqOXVwQNNNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQ +Q8tkL4gAQWDt+coJsyB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielO +D6BojjJCilx4xHjGjQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9L +VZmDS7TtsC98kOmkltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh+ ++1/et1SEMWsiMt7lU92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoEL +xoi34u1DFuHvF9veA4IBBQACggEASGXf9G1yAHOU7G/su00m3SricigX9zPfUh89 +sl9lj5Ht6c545WGSkg2vjPbK4KFfDeWTzz2neKoM9xWxJUjGBfURX3+b6BpAj86x ++vQok87c16mpw8Cf3MtAgc0oHOM1I7pGDO/9/ZvKbSMMB0S7Sv5Q99VYWGN2od+m +yVzz/oYNy9IRSFfNaHPOweMP7oQFYNNfoc9jXCXayICoj9IDFDSvvtC71wc8Z7Ey +b2VAUUMwpSQ+3nrurTCfoCkE9MQSC01GICeIK8Es+EUuV8jUfV+XAmBpul8jwsxi ++XcM4arXoru/9V8qaS+qGsz1dCqAp4hcqq3D20OkJvI3JMu316MhMB8wHQYDVR0O +BBYEFMaC7UTR+brmFDATYd5Oss2eshvCMA0GCWCGSAFlAwQDAgUAAz8AMDwCHFqb +CNDLKq90HzhWw88QgwcwlGnu6W5RCeS8PCICHGqdeHyfftezEfGWp3qIpzlUa2oG +GizOpATUqSQ= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/ecdsa_null_alg.pem b/vectors/cryptography_vectors/x509/custom/ecdsa_null_alg.pem new file mode 100644 index 000000000000..327ad553ae7f --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/ecdsa_null_alg.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBNDCB2aADAgECAgRnI7YfMAwGCCqGSM49BAMCBQAwDzENMAsGA1UEAxMEdGVz +dDAeFw0yMzA1MzExMjI5MDNaFw0yNDA1MjUxMjI5MDNaMA8xDTALBgNVBAMTBHRl +c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS2LuMFnF5OcuYcldiufvppacg2 +8fF/KeJ/4QLMOTbnkatgx5wNPOUvlkzfT31MscwYyzkv1oTqe58iQ+R75C27oyEw +HzAdBgNVHQ4EFgQUD6COpW8C9Ns86r2BDE0jP0teCTswDAYIKoZIzj0EAwIFAANI +ADBFAiBKOlNsFpW6Bz7CK7Z5zXrCetnMiSH3NrbKSZBXJV62KQIhAKmjGu3rxlJr +xXpK+Uz8AsoFJ0BlgqPpdMtTGSrDq1AN +-----END CERTIFICATE----- diff --git a/vectors/pyproject.toml b/vectors/pyproject.toml index 44d517f0560e..69c3511e0e73 100644 --- a/vectors/pyproject.toml +++ b/vectors/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" [project] name = "cryptography_vectors" -version = "41.0.0" +version = "42.0.2" authors = [ {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} ] @@ -14,9 +14,5 @@ license = {text = "Apache-2.0 OR BSD-3-Clause"} [project.urls] homepage = "https://github.com/pyca/cryptography" -[tool.setuptools] -zip-safe = false -include-package-data = true - -[tool.distutils.bdist_wheel] -universal = true +[tool.flit.sdist] +include = ["LICENSE", "LICENSE.APACHE", "LICENSE.BSD"]