diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml index 6cf0f08e56a8..2dbeca46e270 100644 --- a/.github/actions/cache/action.yml +++ b/.github/actions/cache/action.yml @@ -15,7 +15,7 @@ runs: id: normalized-key run: echo "key=$(echo "${{ inputs.key }}" | tr -d ',')" >> $GITHUB_OUTPUT shell: bash - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: - key: ${{ steps.normalized-key.outputs.key }}-1 + key: ${{ steps.normalized-key.outputs.key }}-3 workspaces: "./src/rust/ -> target" diff --git a/.github/actions/fetch-vectors/action.yml b/.github/actions/fetch-vectors/action.yml index c1df58824014..b567db8a316a 100644 --- a/.github/actions/fetch-vectors/action.yml +++ b/.github/actions/fetch-vectors/action.yml @@ -5,16 +5,16 @@ runs: using: "composite" steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - repository: "google/wycheproof" + repository: "C2SP/wycheproof" path: "wycheproof" - # Latest commit on the wycheproof master branch, as of Oct 28, 2023. - ref: "d9f6ec7d8bd8c96da05368999094e4a75ba5cb3d" # wycheproof-ref + # Latest commit on the wycheproof master branch, as of Apr 09, 2024. + ref: "cd27d6419bedd83cbd24611ec54b6d4bfdb0cdca" # wycheproof-ref - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: "C2SP/x509-limbo" path: "x509-limbo" - # Latest commit on the x509-limbo main branch, as of Jan 23, 2024. - ref: "cf66142f5c27b64c987c6f0aa4c10b8c9677b41c" # x509-limbo-ref + # Latest commit on the x509-limbo main branch, as of Nov 27, 2024. + ref: "793e65108940143e97abff5250aecd02f1d5316d" # x509-limbo-ref diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml index a005d6b7462d..c1fa04df3208 100644 --- a/.github/actions/upload-coverage/action.yml +++ b/.github/actions/upload-coverage/action.yml @@ -13,10 +13,11 @@ runs: fi id: coverage-uuid shell: bash - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: coverage-data + name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }} path: | .coverage.* *.lcov if-no-files-found: ignore + include-hidden-files: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 225922bd21a6..0411a7d15804 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,29 +1,9 @@ version: 2 updates: - package-ecosystem: "github-actions" - 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/fetch-vectors/" + directories: + - "/" + - "/.github/actions/*/" schedule: interval: "daily" time: "06:00" @@ -31,17 +11,6 @@ updates: 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 - open-pull-requests-limit: 1024 - - - package-ecosystem: pip directory: "/" schedule: interval: daily @@ -53,7 +22,9 @@ updates: open-pull-requests-limit: 1024 - package-ecosystem: pip - directory: "/.github/requirements/" + directories: + - "/" + - "/.github/requirements/" schedule: interval: daily time: "06:00" diff --git a/.github/downstream.d/aws-encryption-sdk.sh b/.github/downstream.d/aws-encryption-sdk.sh index 4992282cbaad..27cb8aa1edb3 100755 --- a/.github/downstream.d/aws-encryption-sdk.sh +++ b/.github/downstream.d/aws-encryption-sdk.sh @@ -10,7 +10,7 @@ case "${1}" in ;; run) cd aws-encryption-sdk-python - pytest -m local test/ + pytest -m local test/ --ignore test/mpl/ ;; *) exit 1 diff --git a/.github/downstream.d/certbot-josepy.sh b/.github/downstream.d/certbot-josepy.sh index c27568ffe4f1..f172dd0088a3 100755 --- a/.github/downstream.d/certbot-josepy.sh +++ b/.github/downstream.d/certbot-josepy.sh @@ -6,6 +6,7 @@ case "${1}" in cd josepy git rev-parse HEAD curl -sSL https://install.python-poetry.org | python3 - + "${HOME}/.local/bin/poetry" self add poetry-plugin-export "${HOME}/.local/bin/poetry" export -f constraints.txt --dev --without-hashes -o constraints.txt pip install -e . pytest -c constraints.txt ;; diff --git a/.github/requirements/build-requirements.in b/.github/requirements/build-requirements.in index bdf6916690ca..fe9e9fb68d57 100644 --- a/.github/requirements/build-requirements.in +++ b/.github/requirements/build-requirements.in @@ -1,8 +1,10 @@ # Must be kept sync with build-system.requires at pyproject.toml -setuptools>=61.0.0 -wheel +setuptools!=74.0.0 cffi>=1.12; platform_python_implementation != 'PyPy' -setuptools-rust>=0.11.4 +maturin>=1,<2 + +# Must be kept sync with build-system.requires at vectors/pyproject.toml +flit_core >=3.2,<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 index 1b6bb11dcd3b..875330958ca0 100644 --- a/.github/requirements/build-requirements.txt +++ b/.github/requirements/build-requirements.txt @@ -4,85 +4,105 @@ # # 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 +cffi==1.17.1 ; platform_python_implementation != "PyPy" \ + --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ + --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ + --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ + --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ + --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ + --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ + --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ + --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ + --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ + --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ + --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ + --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ + --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ + --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ + --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ + --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ + --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ + --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ + --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ + --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ + --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ + --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ + --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ + --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ + --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ + --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ + --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ + --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ + --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ + --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ + --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ + --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ + --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ + --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ + --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ + --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ + --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ + --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ + --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ + --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ + --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ + --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ + --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ + --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ + --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ + --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # 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 +flit-core==3.10.1 \ + --hash=sha256:66e5b87874a0d6e39691f0e22f09306736b633548670ad3c09ec9db03c5662f7 \ + --hash=sha256:cb31a76e8b31ad3351bb89e531f64ef2b05d1e65bd939183250bf81ddf4922a8 # 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 +maturin==1.7.5 \ + --hash=sha256:0d2d04ab5f47c1bc2b075a5d8255d9a72921e8dceebf9f9e9884f09d67f7cdd6 \ + --hash=sha256:5563d61cfa2fcd7d1552022df6566300f229fa3aed62020c93a750fa3dca9a99 \ + --hash=sha256:71cbcfd4a74aac3eafe99a1cd73d83af8049f572986ff4e0e5e4d8fec9c66a93 \ + --hash=sha256:742cd76a50104fdd832b010a205199e9b02333879f750c0cfca6c93e9472623f \ + --hash=sha256:76a78284a96c24cd2d0ac3eac865315b4b0be7a443463fd5b3ebea3c6f147703 \ + --hash=sha256:9044e5e2eb68bbf8ad86c4ffeab365b78b54bf342ba346dc93775531d3a4e647 \ + --hash=sha256:c1002ca9a23c45123af752d353f6b221151a6eab2b5b65d57a79298b7d8ca6d4 \ + --hash=sha256:c38e585555be525ebc2602ea7189c7ef3e1c3001c94893e5bc71f934468ff124 \ + --hash=sha256:c441fe54945fe8077f17cb116834980391169cf712b63631d8380c8c3de781a1 \ + --hash=sha256:e31c4d25b56346c7872417d58cca81e52387a37469cdb79f7225bae9ad75daf9 \ + --hash=sha256:e773ade7a1383c24eaf6b665340a91278c80ab544c18687aa69e9661b289cf48 \ + --hash=sha256:f05ccbdfe96ad58d70dba9c3eed090726db8ccbaf07ec03852113ca2fec6d84b \ + --hash=sha256:f6c80fa7d67f58fd2cecbcdf309e2c3c5cd6f965216191de73af6cf947ef2ab8 # via -r build-requirements.in +pycparser==2.22 \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc + # via cffi +tomli==2.1.0 \ + --hash=sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8 \ + --hash=sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391 + # via maturin # The following packages are considered to be unsafe in a requirements file: -setuptools==69.0.3 \ - --hash=sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05 \ - --hash=sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78 - # via - # -r build-requirements.in - # setuptools-rust +setuptools==73.0.1 \ + --hash=sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e \ + --hash=sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193 + # via -r build-requirements.in diff --git a/.github/requirements/publish-requirements.in b/.github/requirements/publish-requirements.in deleted file mode 100644 index dd98b8990e7b..000000000000 --- a/.github/requirements/publish-requirements.in +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 2de251b3aa5b..000000000000 --- a/.github/requirements/publish-requirements.txt +++ /dev/null @@ -1,577 +0,0 @@ -# -# 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/requirements/uv-requirements.txt b/.github/requirements/uv-requirements.txt new file mode 100644 index 000000000000..6a799fcaa391 --- /dev/null +++ b/.github/requirements/uv-requirements.txt @@ -0,0 +1,21 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --universal -p 3.8 --generate-hashes - +uv==0.5.4 \ + --hash=sha256:05b45c7eefb178dcdab0d49cd642fb7487377d00727102a8d6d306cc034c0d83 \ + --hash=sha256:2118bb99cbc9787cb5e5cc4a507201e25a3fe88a9f389e8ffb84f242d96038c2 \ + --hash=sha256:30ce031e36c54d4ba791d743d992d0a4fd8d70480db781d30a2f6f5125f39194 \ + --hash=sha256:4432215deb8d5c1ccab17ee51cb80f5de1a20865ee02df47532f87442a3d6a58 \ + --hash=sha256:493aedc3c758bbaede83ecc8d5f7e6a9279ebec151c7f756aa9ea898c73f8ddb \ + --hash=sha256:69079e900bd26b0f65069ac6fa684c74662ed87121c076f2b1cbcf042539034c \ + --hash=sha256:8d7a4a3df943a7c16cd032ccbaab8ed21ff64f4cb090b3a0a15a8b7502ccd876 \ + --hash=sha256:928ed95fefe4e1338d0a7ad2f6b635de59e2ec92adaed4a267f7501a3b252263 \ + --hash=sha256:a79a0885df364b897da44aae308e6ed9cca3a189d455cf1c205bd6f7b03daafa \ + --hash=sha256:ca72e6a4c3c6b8b5605867e16a7f767f5c99b7f526de6bbb903c60eb44fd1e01 \ + --hash=sha256:cd7a5a3a36f975a7678f27849a2d49bafe7272143d938e9b6f3bf28392a3ba00 \ + --hash=sha256:dd2df2ba823e6684230ab4c581f2320be38d7f46de11ce21d2dbba631470d7b6 \ + --hash=sha256:df3cb58b7da91f4fc647d09c3e96006cd6c7bd424a81ce2308a58593c6887c39 \ + --hash=sha256:ed5659cde099f39995f4cb793fd939d2260b4a26e4e29412c91e7537f53d8d25 \ + --hash=sha256:f07e5e0df40a09154007da41b76932671333f9fecb0735c698b19da25aa08927 \ + --hash=sha256:f40c6c6c3a1b398b56d3a8b28f7b455ac1ce4cbb1469f8d35d3bbc804d83daa4 \ + --hash=sha256:f511faf719b797ef0f14688f1abe20b3fd126209cf58512354d1813249745119 \ + --hash=sha256:f806af0ee451a81099c449c4cff0e813056fdf7dd264f3d3a8fd321b17ff9efc diff --git a/.github/workflows/auto-close-stale.yml b/.github/workflows/auto-close-stale.yml index de269c8aceac..d982491e0352 100644 --- a/.github/workflows/auto-close-stale.yml +++ b/.github/workflows/auto-close-stale.yml @@ -4,14 +4,14 @@ on: schedule: - cron: '0 0 * * *' -permissions: - issues: "write" - pull-requests: "write" - jobs: auto-close: if: github.repository_owner == 'pyca' runs-on: ubuntu-latest + permissions: + issues: "write" + pull-requests: "write" + steps: - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 with: diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d494688db74f..2a3f2357b7ef 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -5,6 +5,11 @@ on: - '.github/workflows/benchmark.yml' - 'src/**' - 'tests/**' + workflow_dispatch: + inputs: + base_commit: + description: The base commit to compare against + permissions: contents: read @@ -21,24 +26,25 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false path: "cryptography-pr" - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: + persist-credentials: false repository: "pyca/cryptography" path: "cryptography-base" - ref: "${{ github.base_ref }}" + ref: "${{ github.event.inputs.base_commit || 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@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: "3.11" diff --git a/.github/workflows/boring-open-version-bump.yml b/.github/workflows/boring-open-version-bump.yml index 9a6ba2ae81bc..2a5fac7d494d 100644 --- a/.github/workflows/boring-open-version-bump.yml +++ b/.github/workflows/boring-open-version-bump.yml @@ -13,7 +13,10 @@ jobs: if: github.repository_owner == 'pyca' runs-on: ubuntu-latest steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Needed so we can push back to the repo + persist-credentials: true - id: check-sha-boring run: | SHA=$(git ls-remote https://boringssl.googlesource.com/boringssl refs/heads/master | cut -f1) @@ -40,17 +43,21 @@ jobs: set -xe CURRENT_DATE=$(date "+%b %d, %Y") sed -E -i "s/Latest commit on the BoringSSL master branch.*/Latest commit on the BoringSSL master branch, as of ${CURRENT_DATE}./" .github/workflows/ci.yml - sed -E -i "s/TYPE: \"boringssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"boringssl\", VERSION: \"${{ steps.check-sha-boring.outputs.COMMIT_SHA }}\"/" .github/workflows/ci.yml + sed -E -i "s/TYPE: \"boringssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"boringssl\", VERSION: \"${COMMIT_SHA}\"/" .github/workflows/ci.yml git status if: steps.check-sha-boring.outputs.COMMIT_SHA + env: + COMMIT_SHA: ${{ steps.check-sha-boring.outputs.COMMIT_SHA }} - name: Update OpenSSL run: | set -xe CURRENT_DATE=$(date "+%b %d, %Y") sed -E -i "s/Latest commit on the OpenSSL master branch.*/Latest commit on the OpenSSL master branch, as of ${CURRENT_DATE}./" .github/workflows/ci.yml - sed -E -i "s/TYPE: \"openssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"openssl\", VERSION: \"${{ steps.check-sha-openssl.outputs.COMMIT_SHA }}\"/" .github/workflows/ci.yml + sed -E -i "s/TYPE: \"openssl\", VERSION: \"[0-9a-f]{40}\"/TYPE: \"openssl\", VERSION: \"${COMMIT_SHA}\"/" .github/workflows/ci.yml git status if: steps.check-sha-openssl.outputs.COMMIT_SHA + env: + COMMIT_SHA: ${{ steps.check-sha-openssl.outputs.COMMIT_SHA }} - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 id: generate-token with: @@ -58,8 +65,9 @@ jobs: 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@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 + uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 with: + branch: "bump-openssl-boringssl" commit-message: "Bump BoringSSL and/or OpenSSL in CI" title: "Bump BoringSSL and/or OpenSSL in CI" author: "pyca-boringbot[bot] " diff --git a/.github/workflows/build_openssl.sh b/.github/workflows/build_openssl.sh index 013fcf42698a..72b06e0b8f3e 100755 --- a/.github/workflows/build_openssl.sh +++ b/.github/workflows/build_openssl.sh @@ -20,7 +20,7 @@ if [[ "${TYPE}" == "openssl" ]]; then pushd openssl git checkout "${VERSION}" else - curl -O "https://www.openssl.org/source/openssl-${VERSION}.tar.gz" + curl -LO "https://github.com/openssl/openssl/releases/download/openssl-${VERSION}/openssl-${VERSION}.tar.gz" tar zxf "openssl-${VERSION}.tar.gz" pushd "openssl-${VERSION}" fi @@ -57,12 +57,11 @@ if [[ "${TYPE}" == "openssl" ]]; then fi popd elif [[ "${TYPE}" == "libressl" ]]; then - curl -O "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${VERSION}.tar.gz" + curl -LO "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${VERSION}.tar.gz" tar zxf "libressl-${VERSION}.tar.gz" pushd "libressl-${VERSION}" - ./config -Wl -Wl,-Bsymbolic-functions -fPIC shared --prefix="${OSSL_PATH}" - shlib_sed - make -j"$(nproc)" install + cmake -B build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="${OSSL_PATH}" + make -C build -j"$(nproc)" install # delete binaries, libtls, and docs we don't need. can't skip install/compile sadly rm -rf "${OSSL_PATH}/bin" rm -rf "${OSSL_PATH}/share" @@ -73,8 +72,7 @@ elif [[ "${TYPE}" == "boringssl" ]]; then pushd boringssl git checkout "${VERSION}" cmake -B build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX="${OSSL_PATH}" - make -C build -j"$(nproc)" - make -C build install + make -C build -j"$(nproc)" install # delete binaries we don't need rm -rf "${OSSL_PATH}/bin" popd diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9d3ab950244..f7bda38773f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,41 +29,44 @@ jobs: PYTHON: - {VERSION: "3.12", NOXSESSION: "flake"} - {VERSION: "3.12", NOXSESSION: "rust"} - - {VERSION: "3.12", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.2.0"}} - - {VERSION: "pypy-3.9", NOXSESSION: "tests-nocoverage"} + - {VERSION: "3.12", NOXSESSION: "docs", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3"}} + - {VERSION: "3.13", NOXSESSION: "tests"} + - {VERSION: "3.14-dev", NOXSESSION: "tests"} - {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.12"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.4"}} - - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.2.0"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.0", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.0", 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.4"}} - - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.2.0"}} - - {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", OPENSSL: {TYPE: "openssl", VERSION: "3.0.15"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.7"}} + - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3"}} + - {VERSION: "3.12", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.3.2"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.3", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} + - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.1.7"}} + - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.2.3"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.4.0"}} + - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "3.9.2"}} + - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "4.0.0"}} - {VERSION: "3.12", NOXSESSION: "tests-randomorder"} - # Latest commit on the BoringSSL master branch, as of 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"}} + # Latest commit on the BoringSSL master branch, as of Nov 27, 2024. + - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "boringssl", VERSION: "fcef13a49852397a0d39c00be8d7bc2ba1ab6fb9"}} + # Latest commit on the OpenSSL master branch, as of Nov 26, 2024. + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "b9886a6f3483e0525596d3b3956416282038da82"}} # 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"} + # potential future MSRV. + # - 1.70: crates.io sparse protocol by default + # - 1.77: offset_of! in std (for pyo3) + # - 1.80: LazyLock in std + - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "1.65.0"} - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "beta"} - {VERSION: "3.12", NOXSESSION: "rust,tests", RUST: "nightly"} + - {VERSION: "3.12", NOXSESSION: "tests-rust-debug"} timeout-minutes: 15 steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Setup python id: setup-python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON.VERSION }} cache: pip @@ -95,7 +98,7 @@ jobs: CONFIG_FLAGS: ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }} if: matrix.PYTHON.OPENSSL - name: Load OpenSSL cache - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: ossl-cache timeout-minutes: 2 with: @@ -103,7 +106,7 @@ jobs: # When altering the openssl build process you may need to increment # the value on the end of this cache key so that you can prevent it # from fetching the cache and skipping the build step. - key: ${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.OPENSSL_HASH }}-9 + key: ${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.OPENSSL_HASH }}-14 if: matrix.PYTHON.OPENSSL - name: Build custom OpenSSL/LibreSSL run: .github/workflows/build_openssl.sh @@ -117,6 +120,8 @@ jobs: echo "CFLAGS=${CFLAGS} -Werror=implicit-function-declaration" >> $GITHUB_ENV echo "RUSTFLAGS=-Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib -Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib64" >> $GITHUB_ENV if: matrix.PYTHON.OPENSSL + - run: sudo apt-get install -y bindgen + if: matrix.PYTHON.OPENSSL.TYPE == 'boringssl' - name: Cache rust and pip uses: ./.github/actions/cache timeout-minutes: 2 @@ -126,7 +131,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' 'tomli; python_version < "3.11"' + - run: python -m pip install -c ci-constraints-requirements.txt 'nox' 'nox[uv]; python_version >= "3.8"' 'tomli; python_version < "3.11"' - name: Create nox environment run: | nox -v --install-only @@ -153,21 +158,23 @@ jobs: IMAGE: - {IMAGE: "rhel8", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "rhel8-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} - - {IMAGE: "buster", NOXSESSION: "tests-nocoverage", RUNNER: "ubuntu-latest"} - {IMAGE: "bullseye", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "bookworm", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "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"} + - {IMAGE: "ubuntu-noble", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "ubuntu-rolling", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "fedora", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "alpine", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "centos-stream9", NOXSESSION: "tests", RUNNER: "ubuntu-latest"} - {IMAGE: "centos-stream9-fips", NOXSESSION: "tests", RUNNER: "ubuntu-latest", FIPS: true} - - {IMAGE: "ubuntu-jammy:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} - - {IMAGE: "alpine:aarch64", NOXSESSION: "tests", RUNNER: [self-hosted, Linux, ARM64]} + - {IMAGE: "ubuntu-rolling:aarch64", NOXSESSION: "tests", RUNNER: "ubuntu-24.04-arm"} + - {IMAGE: "alpine:aarch64", NOXSESSION: "tests", RUNNER: "ubuntu-24.04-arm"} + + - {IMAGE: "ubuntu-rolling:armv7l", NOXSESSION: "tests", RUNNER: "ubuntu-24.04-arm"} timeout-minutes: 15 env: RUSTUP_HOME: /root/.rustup @@ -181,7 +188,7 @@ jobs: sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release if: matrix.IMAGE.IMAGE == 'alpine:aarch64' - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false @@ -200,7 +207,7 @@ 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' 'tomli; python_version < "3.11"' + - run: /venv/bin/python -m pip install -c ci-constraints-requirements.txt 'nox' 'nox[uv]; python_version >= "3.8"' 'tomli; python_version < "3.11"' - run: '/venv/bin/nox -v --install-only' env: CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} @@ -222,17 +229,17 @@ jobs: matrix: RUNNER: - {OS: 'macos-13', ARCH: 'x86_64'} - - {OS: [self-hosted, macos, ARM64, tart], ARCH: 'arm64'} + - {OS: 'macos-14', ARCH: 'arm64'} PYTHON: - - {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} - - {VERSION: "3.12", NOXSESSION: "tests"} + - {VERSION: "3.7", NOXSESSION: "tests"} + - {VERSION: "3.13", 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'} + - PYTHON: {VERSION: "3.7", NOXSESSION: "tests"} + RUNNER: {OS: 'macos-14', ARCH: 'arm64'} timeout-minutes: 15 steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false @@ -243,22 +250,21 @@ jobs: key: ${{ matrix.PYTHON.NOXSESSION }}-${{ matrix.PYTHON.VERSION }} - name: Setup python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.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' 'tomli; python_version < "3.11"' + - run: python -m pip install -c ci-constraints-requirements.txt 'nox' 'nox[uv]; python_version >= "3.8"' 'tomli; python_version < "3.11"' - name: Clone test vectors timeout-minutes: 2 uses: ./.github/actions/fetch-vectors - - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 + - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: repo: pyca/infra workflow: build-macos-openssl.yml @@ -271,11 +277,12 @@ jobs: run: | OPENSSL_DIR=$(readlink -f ../openssl-macos-universal2/) \ OPENSSL_STATIC=1 \ - CFLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=incompatible-pointer-types-discards-qualifiers -Wno-error=unused-function -mmacosx-version-min=10.12" \ + CFLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=incompatible-pointer-types-discards-qualifiers -Wno-error=unused-function" \ nox -v --install-only env: NOXSESSION: ${{ matrix.PYTHON.NOXSESSION }} CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} + MACOSX_DEPLOYMENT_TARGET: "10.13" - name: Tests run: nox --no-install -- --color=yes --wycheproof-root=wycheproof --x509-limbo-root=x509-limbo env: @@ -294,16 +301,16 @@ jobs: - {ARCH: 'x64', WINDOWS: 'win64'} PYTHON: - {VERSION: "3.7", NOXSESSION: "tests-nocoverage"} - - {VERSION: "3.12", NOXSESSION: "tests"} + - {VERSION: "3.13", NOXSESSION: "tests"} timeout-minutes: 15 steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false - name: Setup python id: setup-python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} @@ -316,9 +323,9 @@ jobs: 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" "tomli; python_version < '3.11'" + - run: python -m pip install -c ci-constraints-requirements.txt "nox" "nox[uv]; python_version >= '3.8'" "tomli; python_version < '3.11'" - - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 + - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: repo: pyca/infra workflow: build-windows-openssl.yml @@ -371,7 +378,7 @@ jobs: name: "Downstream tests for ${{ matrix.DOWNSTREAM }}" timeout-minutes: 15 steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false @@ -379,14 +386,14 @@ jobs: uses: ./.github/actions/cache timeout-minutes: 2 - name: Setup python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.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 . setuptools + - run: pip install . env: CARGO_TARGET_DIR: ${{ format('{0}/src/rust/target/', github.workspace) }} # cryptography main has a version of "(X+1).0.0.dev1" where X is the @@ -396,15 +403,15 @@ jobs: # dist-info directory to pretend to be an older version to "solve" this. - run: | import json - import pkg_resources + import importlib.metadata import shutil import urllib.request - d = pkg_resources.get_distribution("cryptography") + d = importlib.metadata.distribution("cryptography") with urllib.request.urlopen("https://pypi.org/pypi/cryptography/json") as r: latest_version = json.load(r)["info"]["version"] - new_path = d.egg_info.replace(d.version, latest_version) - shutil.move(d.egg_info, new_path) + new_path = d.locate_file(f"cryptography-{latest_version}.dist-info") + shutil.move(d.locate_file(f"cryptography-{d.version}.dist-info"), new_path) shell: python - run: ./.github/downstream.d/${{ matrix.DOWNSTREAM }}.sh run @@ -415,7 +422,7 @@ jobs: if: ${{ always() }} timeout-minutes: 3 steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 timeout-minutes: 3 with: persist-credentials: false @@ -425,7 +432,7 @@ jobs: jobs: ${{ toJSON(needs) }} - name: Setup python if: ${{ always() }} - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: '3.12' cache: pip @@ -435,9 +442,10 @@ jobs: if: ${{ always() }} - name: Download coverage data if: ${{ always() }} - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - name: coverage-data + pattern: coverage-data-* + merge-multiple: true - name: Combine coverage and fail if it's <100%. if: ${{ always() }} id: combinecoverage @@ -477,14 +485,14 @@ jobs: run: python -m coverage html if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }} - name: Upload HTML report. - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 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@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: _html-rust-report path: rust-coverage diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml index d4fb20e091f5..1faf3bcbc2db 100644 --- a/.github/workflows/linkcheck.yml +++ b/.github/workflows/linkcheck.yml @@ -4,9 +4,9 @@ on: paths: - docs/conf.py - .github/workflows/linkcheck.yml - push: - branches: - - main + schedule: + # Run once a week on Fridays + - cron: "0 0 * * FRI" permissions: contents: read @@ -20,12 +20,12 @@ jobs: name: "linkcheck" timeout-minutes: 10 steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Setup python id: setup-python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: 3.11 - name: Cache rust and pip diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 88379415f801..f58867b59e2a 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,15 +2,15 @@ name: Lock Issues on: workflow_dispatch: schedule: - - cron: '0 0 * * *' - -permissions: - issues: "write" + - cron: '0 3 * * *' jobs: lock: if: github.repository_owner == 'pyca' runs-on: ubuntu-latest + permissions: + issues: "write" + steps: - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 with: diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index a7f75070628e..cc2470ceb0ba 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -30,65 +30,34 @@ jobs: if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.event == 'push' && github.event.workflow_run.conclusion == 'success') permissions: id-token: "write" + attestations: "write" steps: - - 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: echo "$EVENT_CONTEXT" + env: + EVENT_CONTEXT: ${{ toJson(github.event) }} - run: | - echo "OIDC_AUDIENCE=pypi" >> $GITHUB_ENV - echo "PYPI_DOMAIN=pypi.org" >> $GITHUB_ENV - echo "TWINE_REPOSITORY=pypi" >> $GITHUB_ENV - echo "TWINE_USERNAME=__token__" >> $GITHUB_ENV + echo "PYPI_URL=https://upload.pypi.org/legacy/" >> $GITHUB_ENV if: github.event_name == 'workflow_run' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'pypi') - run: | - echo "OIDC_AUDIENCE=testpypi" >> $GITHUB_ENV - echo "PYPI_DOMAIN=test.pypi.org" >> $GITHUB_ENV - echo "TWINE_REPOSITORY=testpypi" >> $GITHUB_ENV - echo "TWINE_USERNAME=__token__" >> $GITHUB_ENV + echo "PYPI_URL=https://test.pypi.org/legacy/" >> $GITHUB_ENV if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'testpypi' + - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 + with: + path: tmpdist/ + run_id: ${{ github.event.inputs.run_id || github.event.workflow_run.id }} + - run: mkdir dist/ - run: | - import os + find tmpdist/ -type f -name 'cryptography*' -exec mv {} dist/ \; - import requests - - response = requests.get( - os.environ["ACTIONS_ID_TOKEN_REQUEST_URL"], - params={"audience": os.environ["OIDC_AUDIENCE"]}, - headers={"Authorization": f"bearer {os.environ['ACTIONS_ID_TOKEN_REQUEST_TOKEN']}"} - ) - response.raise_for_status() - token = response.json()["value"] - - response = requests.post(f"https://{os.environ['PYPI_DOMAIN']}/_/oidc/mint-token", json={"token": token}) - response.raise_for_status() - pypi_token = response.json()["token"] - - with open(os.environ["GITHUB_ENV"], "a") as f: - print(f"::add-mask::{pypi_token}") - f.write(f"TWINE_PASSWORD={pypi_token}\n") - shell: python - - - run: twine upload --skip-existing $(find dist/ -type f -name 'cryptography*') - - # Do not perform sigstore signatures for things for TestPyPI. This is - # because there's nothing that would prevent a malicious PyPI from - # serving a signed TestPyPI asset in place of a release intended for - # PyPI. - - run: sigstore sign $(find dist/ -type f -name 'cryptography*') - if: env.TWINE_REPOSITORY == 'pypi' + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 + with: + repository-url: ${{ env.PYPI_URL }} + skip-existing: true + # Do not perform attestation for things for TestPyPI. This is + # because there's nothing that would prevent a malicious PyPI from + # serving a signed TestPyPI asset in place of a release intended for + # PyPI. + attestations: ${{ env.PYPI_URL == 'https://upload.pypi.org/legacy/' }} diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index 0d2c5774721f..706a034cc627 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -21,30 +21,34 @@ on: env: BUILD_REQUIREMENTS_PATH: .github/requirements/build-requirements.txt + UV_REQUIREMENTS_PATH: .github/requirements/uv-requirements.txt jobs: sdist: runs-on: ubuntu-latest name: sdists steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # The tag to build or the tag received by the tag event ref: ${{ github.event.inputs.version || github.ref }} persist-credentials: false - - run: python -m venv .venv - - name: Install Python dependencies - run: .venv/bin/pip install -U pip build + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + with: + python-version: "3.13" + timeout-minutes: 3 + - run: python -m pip install -r $UV_REQUIREMENTS_PATH + - name: Make sdist (cryptography) - run: .venv/bin/python -m build --sdist + run: uv build --build-constraint=$BUILD_REQUIREMENTS_PATH --require-hashes --sdist - name: Make sdist and wheel (vectors) - run: cd vectors/ && ../.venv/bin/python -m build - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + run: uv build --build-constraint=$BUILD_REQUIREMENTS_PATH --require-hashes vectors/ + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "cryptography-sdist" path: dist/cryptography* - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "vectors-sdist-wheel" path: vectors/dist/cryptography* @@ -52,56 +56,51 @@ jobs: manylinux: needs: [sdist] runs-on: ${{ matrix.MANYLINUX.RUNNER }} - container: ghcr.io/pyca/${{ matrix.MANYLINUX.CONTAINER }} + container: + image: ghcr.io/pyca/${{ matrix.MANYLINUX.CONTAINER }} + volumes: + - /staticnodehost:/staticnodecontainer:rw,rshared + - /staticnodehost:/__e/node20:ro,rshared strategy: fail-fast: false matrix: PYTHON: - - { VERSION: "cp311-cp311", ABI_VERSION: 'cp37' } - - { VERSION: "cp311-cp311", ABI_VERSION: 'cp39' } - - { VERSION: "pp39-pypy39_pp73" } + - { VERSION: "cp311-cp311", ABI_VERSION: 'py37' } + - { VERSION: "cp311-cp311", ABI_VERSION: 'py39' } - { 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: "manylinux_2_34_x86_64", CONTAINER: "cryptography-manylinux_2_34:x86_64", RUNNER: "ubuntu-latest"} - { NAME: "musllinux_1_2_x86_64", CONTAINER: "cryptography-musllinux_1_2:x86_64", RUNNER: "ubuntu-latest"} - - { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: [self-hosted, Linux, ARM64] } - - { NAME: "manylinux_2_28_aarch64", CONTAINER: "cryptography-manylinux_2_28:aarch64", RUNNER: [self-hosted, Linux, ARM64]} - - { NAME: "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]} + - { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: "ubuntu-24.04-arm" } + - { NAME: "manylinux_2_28_aarch64", CONTAINER: "cryptography-manylinux_2_28:aarch64", RUNNER: "ubuntu-24.04-arm" } + - { NAME: "manylinux_2_34_aarch64", CONTAINER: "cryptography-manylinux_2_34:aarch64", RUNNER: "ubuntu-24.04-arm" } + - { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: "ubuntu-24.04-arm" } + + - { NAME: "manylinux_2_31_armv7l", CONTAINER: "cryptography-manylinux_2_31:armv7l", RUNNER: "ubuntu-24.04-arm" } exclude: # There are no readily available musllinux PyPy distributions - - PYTHON: { VERSION: "pp39-pypy39_pp73" } - MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} - - PYTHON: { VERSION: "pp310-pypy310_pp73" } - MANYLINUX: { NAME: "musllinux_1_1_x86_64", CONTAINER: "cryptography-musllinux_1_1:x86_64", RUNNER: "ubuntu-latest"} - - PYTHON: { VERSION: "pp39-pypy39_pp73" } - MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} - - PYTHON: { VERSION: "pp310-pypy310_pp73" } - MANYLINUX: { NAME: "musllinux_1_1_aarch64", CONTAINER: "cryptography-musllinux_1_1:aarch64", RUNNER: [self-hosted, Linux, ARM64]} - - - 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]} + MANYLINUX: { NAME: "musllinux_1_2_aarch64", CONTAINER: "cryptography-musllinux_1_2:aarch64", RUNNER: "ubuntu-24.04-arm"} # We also don't build pypy wheels for anything except the latest manylinux - - PYTHON: { VERSION: "pp39-pypy39_pp73" } - MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} - PYTHON: { VERSION: "pp310-pypy310_pp73" } MANYLINUX: { NAME: "manylinux2014_x86_64", CONTAINER: "cryptography-manylinux2014:x86_64", RUNNER: "ubuntu-latest"} - - PYTHON: { VERSION: "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]} + MANYLINUX: { NAME: "manylinux2014_aarch64", CONTAINER: "cryptography-manylinux2014_aarch64", RUNNER: "ubuntu-24.04-arm" } + + # No PyPy on armv7l either + - PYTHON: { VERSION: "pp310-pypy310_pp73" } + MANYLINUX: { NAME: "manylinux_2_31_armv7l", CONTAINER: "cryptography-manylinux_2_31:armv7l", RUNNER: "ubuntu-24.04-arm" } name: "${{ matrix.PYTHON.VERSION }} for ${{ matrix.MANYLINUX.NAME }}" steps: + - name: Ridiculous-er workaround for static node20 + run: | + cp -R /staticnode/* /staticnodecontainer/ - name: Ridiculous alpine workaround for actions support on arm64 run: | # This modifies /etc/os-release so the JS actions @@ -112,7 +111,7 @@ jobs: if: startsWith(matrix.MANYLINUX.NAME, 'musllinux') && endsWith(matrix.MANYLINUX.NAME, 'aarch64') - name: Get build-requirements.txt from repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # The tag to build or the tag received by the tag event ref: ${{ github.event.inputs.version || github.ref }} @@ -120,25 +119,23 @@ jobs: 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 --require-hashes -r ${{ env.BUILD_REQUIREMENTS_PATH }} - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: cryptography-sdist - run: mkdir tmpwheelhouse - name: Build the wheel run: | if [ -n "${{ matrix.PYTHON.ABI_VERSION }}" ]; then - PY_LIMITED_API="--config-settings=--build-option=--py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} --no-build-isolation" + PY_LIMITED_API="--config-settings=build-args=--features=pyo3/abi3-${{ matrix.PYTHON.ABI_VERSION }}" fi + OPENSSL_DIR="/opt/pyca/cryptography/openssl" \ OPENSSL_STATIC=1 \ - .venv/bin/python -m pip wheel -v $PY_LIMITED_API cryptograph*.tar.gz -w dist/ && mv dist/cryptography*.whl tmpwheelhouse + uv build --python=/opt/python/${{ matrix.PYTHON.VERSION }}/bin/python --wheel --require-hashes --build-constraint=$BUILD_REQUIREMENTS_PATH $PY_LIMITED_API cryptography*.tar.gz -o tmpwheelhouse/ env: RUSTUP_HOME: /root/.rustup - - run: auditwheel repair --plat ${{ matrix.MANYLINUX.NAME }} tmpwheelhouse/cryptograph*.whl -w wheelhouse/ + - run: auditwheel repair --plat ${{ matrix.MANYLINUX.NAME }} tmpwheelhouse/cryptography*.whl -w wheelhouse/ - run: unzip wheelhouse/*.whl -d execstack.check - run: | results=$(readelf -lW execstack.check/cryptography/hazmat/bindings/*.so) @@ -148,15 +145,17 @@ jobs: else exit 0 fi - - run: .venv/bin/pip install cryptography --no-index -f wheelhouse/ + + - run: uv venv --python=/opt/python/${{ matrix.PYTHON.VERSION }}/bin/python + - run: uv pip install --require-hashes -r $BUILD_REQUIREMENTS_PATH + - run: uv pip install cryptography --no-index -f wheelhouse/ - run: | - .venv/bin/python -c "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" - - run: mkdir cryptography-wheelhouse - - run: mv wheelhouse/cryptography*.whl cryptography-wheelhouse/ - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + echo "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" | uv run - + + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.MANYLINUX.NAME }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION }}" - path: cryptography-wheelhouse/ + path: wheelhouse/ macos: needs: [sdist] @@ -166,11 +165,11 @@ jobs: matrix: PYTHON: - VERSION: '3.11' - ABI_VERSION: 'cp37' + ABI_VERSION: 'py37' # 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' + DEPLOYMENT_TARGET: '10.13' # 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 @@ -178,47 +177,33 @@ jobs: # build against _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' - VERSION: '3.11' - ABI_VERSION: 'cp39' + ABI_VERSION: 'py39' # 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' + DEPLOYMENT_TARGET: '10.13' # This archflags is default, but let's be explicit ARCHFLAGS: '-arch x86_64 -arch arm64' # See https://github.com/pypa/cibuildwheel/blob/c8876b5c54a6c6b08de5d4b1586906b56203bd9e/cibuildwheel/macos.py#L257-L269 # This will change in the future as we change the base Python we # build against _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' - - VERSION: '3.11' - ABI_VERSION: 'cp37' - DOWNLOAD_URL: 'https://www.python.org/ftp/python/3.11.3/python-3.11.3-macos11.pkg' - BIN_PATH: '/Library/Frameworks/Python.framework/Versions/3.11/bin/python3' - DEPLOYMENT_TARGET: '10.12' - # We continue to build a non-universal2 for a bit to see metrics on - # download counts (this is a proxy for pip version since universal2 - # requires a 21.x pip) - ARCHFLAGS: '-arch x86_64' - _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' - - VERSION: 'pypy-3.9' - BIN_PATH: 'pypy3' - DEPLOYMENT_TARGET: '10.12' - _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' - ARCHFLAGS: '-arch x86_64' - VERSION: 'pypy-3.10' BIN_PATH: 'pypy3' - DEPLOYMENT_TARGET: '10.12' + DEPLOYMENT_TARGET: '10.13' _PYTHON_HOST_PLATFORM: 'macosx-10.9-x86_64' ARCHFLAGS: '-arch x86_64' name: "${{ matrix.PYTHON.VERSION }} ABI ${{ matrix.PYTHON.ABI_VERSION }} macOS ${{ matrix.PYTHON.ARCHFLAGS }}" steps: - name: Get build-requirements.txt from repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 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 }} + ${{ env.UV_REQUIREMENTS_PATH }} sparse-checkout-cone-mode: false - name: Setup python run: | @@ -228,11 +213,11 @@ jobs: PYTHON_DOWNLOAD_URL: ${{ matrix.PYTHON.DOWNLOAD_URL }} if: contains(matrix.PYTHON.VERSION, 'pypy') == false - name: Setup pypy - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON.VERSION }} if: contains(matrix.PYTHON.VERSION, 'pypy') - - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 + - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: repo: pyca/infra workflow: build-macos-openssl.yml @@ -246,42 +231,41 @@ jobs: 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 - - name: Install Python dependencies - run: venv/bin/pip install --require-hashes -r ${{ env.BUILD_REQUIREMENTS_PATH }} - - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: cryptography-sdist + + - run: ${{ matrix.PYTHON.BIN_PATH }} -m pip install -r "${UV_REQUIREMENTS_PATH}" - run: mkdir wheelhouse - name: Build the wheel run: | if [ -n "${{ matrix.PYTHON.ABI_VERSION }}" ]; then - PY_LIMITED_API="--config-settings=--build-option=--py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} --no-build-isolation" + PY_LIMITED_API="--config-settings=build-args=--features=pyo3/abi3-${{ matrix.PYTHON.ABI_VERSION }}" fi OPENSSL_DIR="$(readlink -f ../openssl-macos-universal2/)" \ OPENSSL_STATIC=1 \ - venv/bin/python -m pip wheel -v $PY_LIMITED_API cryptograph*.tar.gz -w dist/ && mv dist/cryptography*.whl wheelhouse + uv build --wheel --require-hashes --build-constraint=$BUILD_REQUIREMENTS_PATH $PY_LIMITED_API cryptography*.tar.gz -o wheelhouse/ env: MACOSX_DEPLOYMENT_TARGET: ${{ matrix.PYTHON.DEPLOYMENT_TARGET }} ARCHFLAGS: ${{ matrix.PYTHON.ARCHFLAGS }} _PYTHON_HOST_PLATFORM: ${{ matrix.PYTHON._PYTHON_HOST_PLATFORM }} - - run: venv/bin/pip install -f wheelhouse/ --no-index cryptography + + - run: uv venv + - run: uv pip install --require-hashes -r $BUILD_REQUIREMENTS_PATH + - run: uv pip install cryptography --no-index -f wheelhouse/ - name: Show the wheel's minimum macOS SDK and architectures run: | - find venv/lib/*/site-packages/cryptography/hazmat/bindings -name '*.so' -exec vtool -show {} \; + find .venv/lib/*/site-packages/cryptography/hazmat/bindings -name '*.so' -exec vtool -show {} \; - run: | - venv/bin/python -c "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" + echo "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" | uv run - - - run: mkdir cryptography-wheelhouse - - run: mv wheelhouse/cryptography*.whl cryptography-wheelhouse/ - run: | - echo "CRYPTOGRAPHY_WHEEL_NAME=$(basename $(ls cryptography-wheelhouse/cryptography*.whl))" >> $GITHUB_ENV - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + echo "CRYPTOGRAPHY_WHEEL_NAME=$(basename $(ls wheelhouse/cryptography*.whl))" >> $GITHUB_ENV + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "${{ env.CRYPTOGRAPHY_WHEEL_NAME }}" - path: cryptography-wheelhouse/ + path: wheelhouse/ windows: needs: [sdist] @@ -293,34 +277,32 @@ jobs: - {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} - {ARCH: 'x64', WINDOWS: 'win64', RUST_TRIPLE: 'x86_64-pc-windows-msvc'} PYTHON: - - {VERSION: "3.11", "ABI_VERSION": "cp37"} - - {VERSION: "3.11", "ABI_VERSION": "cp39"} - - {VERSION: "pypy-3.9"} + - {VERSION: "3.11", "ABI_VERSION": "py37"} + - {VERSION: "3.11", "ABI_VERSION": "py39"} - {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.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: - name: Get build-requirements.txt from repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 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 }} + ${{ env.UV_REQUIREMENTS_PATH }} sparse-checkout-cone-mode: false - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: cryptography-sdist - name: Setup python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} @@ -329,7 +311,7 @@ jobs: toolchain: stable target: ${{ matrix.WINDOWS.RUST_TRIPLE }} - - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 + - uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: repo: pyca/infra workflow: build-windows-openssl.yml @@ -343,24 +325,27 @@ jobs: echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV echo "OPENSSL_STATIC=1" >> $GITHUB_ENV shell: bash - - name: Install Python dependencies - run: python -m pip install --require-hashes -r ${{ env.BUILD_REQUIREMENTS_PATH }} + + - run: pip install -r "${UV_REQUIREMENTS_PATH}" + shell: bash - run: mkdir wheelhouse - run: | if [ -n "${{ matrix.PYTHON.ABI_VERSION }}" ]; then - PY_LIMITED_API="--config-settings=--build-option=--py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} --no-build-isolation" + PY_LIMITED_API="--config-settings=build-args=--features=pyo3/abi3-${{ matrix.PYTHON.ABI_VERSION }}" fi - python -m pip wheel -v cryptography*.tar.gz $PY_LIMITED_API -w dist/ && mv dist/cryptography*.whl wheelhouse/ + uv build --wheel --require-hashes --build-constraint=$BUILD_REQUIREMENTS_PATH cryptography*.tar.gz $PY_LIMITED_API -o wheelhouse/ + shell: bash + + - run: uv venv + - run: uv pip install --require-hashes -r "${BUILD_REQUIREMENTS_PATH}" shell: bash - - run: pip install -f wheelhouse --no-index cryptography + - run: uv pip install cryptography --no-index -f wheelhouse/ - name: Print the OpenSSL we built and linked against run: | - 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'))" + echo "from cryptography.hazmat.backends.openssl.backend import backend;print('Loaded: ' + backend.openssl_version_text());print('Linked Against: ' + backend._ffi.string(backend._lib.OPENSSL_VERSION_TEXT).decode('ascii'))" | uv run - - - run: mkdir cryptography-wheelhouse - - run: move wheelhouse\cryptography*.whl cryptography-wheelhouse\ - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "cryptography-${{ github.event.inputs.version }}-${{ matrix.WINDOWS.WINDOWS }}-${{ matrix.PYTHON.VERSION }}-${{ matrix.PYTHON.ABI_VERSION }}" - path: cryptography-wheelhouse\ + path: wheelhouse\ diff --git a/.github/workflows/x509-limbo-version-bump.yml b/.github/workflows/x509-limbo-version-bump.yml index e4a42bf3155f..94c7ec8926f7 100644 --- a/.github/workflows/x509-limbo-version-bump.yml +++ b/.github/workflows/x509-limbo-version-bump.yml @@ -13,7 +13,10 @@ jobs: if: github.repository_owner == 'pyca' runs-on: ubuntu-latest steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Needed so we can push back to the repo + persist-credentials: true - id: check-sha-x509-limbo run: | SHA=$(git ls-remote https://github.com/C2SP/x509-limbo refs/heads/main | cut -f1) @@ -29,27 +32,31 @@ jobs: 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 + sed -E -i "s/ref: \"[0-9a-f]{40}\" # x509-limbo-ref/ref: \"${COMMIT_SHA}\" # x509-limbo-ref/" .github/actions/fetch-vectors/action.yml git status if: steps.check-sha-x509-limbo.outputs.COMMIT_SHA + env: + COMMIT_SHA: ${{ 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) + SHA=$(git ls-remote https://github.com/C2SP/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 -e "## wycheproof\n[Commit: ${SHA}](https://github.com/C2SP/wycheproof/commit/${SHA})\n\n[Diff](https://github.com/C2SP/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 + sed -E -i "s/Latest commit on the wycheproof master branch.*/Latest commit on the wycheproof master branch, as of ${CURRENT_DATE}./" .github/actions/fetch-vectors/action.yml + sed -E -i "s/ref: \"[0-9a-f]{40}\" # wycheproof-ref/ref: \"${COMMIT_SHA}\" # wycheproof-ref/" .github/actions/fetch-vectors/action.yml git status if: steps.check-sha-wycheproof.outputs.COMMIT_SHA + env: + COMMIT_SHA: ${{ steps.check-sha-wycheproof.outputs.COMMIT_SHA }} - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 id: generate-token with: @@ -57,8 +64,9 @@ jobs: private_key: ${{ secrets.BORINGBOT_PRIVATE_KEY }} if: steps.check-sha-x509-limbo.outputs.COMMIT_SHA || steps.check-sha-wycheproof.outputs.COMMIT_SHA - name: Create Pull Request - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 + uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 with: + branch: "bump-vectors" commit-message: "Bump x509-limbo and/or wycheproof in CI" title: "Bump x509-limbo and/or wycheproof in CI" author: "pyca-boringbot[bot] " diff --git a/.gitignore b/.gitignore index 035b15ccd025..1d4ebfbc597a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ htmlcov/ *.py[cdo] .hypothesis/ target/ -.rust-cov/ \ No newline at end of file +.rust-cov/ +*.lcov +*.profdata diff --git a/.readthedocs.yml b/.readthedocs.yml index 8a37ec36404d..f97891f9c3c9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -6,16 +6,16 @@ sphinx: # The config file overrides the UI settings: # https://github.com/pyca/cryptography/issues/5863#issuecomment-817828152 builder: dirhtml + configuration: docs/conf.py formats: - pdf build: - # readdocs master now includes a rust toolchain - os: "ubuntu-22.04" + os: "ubuntu-24.04" tools: - python: "3.11" - rust: "1.70" + python: "3.13" + rust: "latest" python: install: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b11a81f3fbc5..a1ffe6e8a7f7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,212 @@ Changelog ========= +.. _v44-0-1: + +44.0.1 - 2025-02-11 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.4.1. +* We now build ``armv7l`` ``manylinux`` wheels and publish them to PyPI. +* We now build ``manylinux_2_34`` wheels and publish them to PyPI. + +.. _v44-0-0: + +44.0.0 - 2024-11-27 +~~~~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.9. +* Deprecated Python 3.7 support. Python 3.7 is no longer supported by the + Python core team. Support for Python 3.7 will be removed in a future + ``cryptography`` release. +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.4.0. +* macOS wheels are now built against the macOS 10.13 SDK. Users on older + versions of macOS should upgrade, or they will need to build + ``cryptography`` themselves. +* Enforce the :rfc:`5280` requirement that extended key usage extensions must + not be empty. +* Added support for timestamp extraction to the + :class:`~cryptography.fernet.MultiFernet` class. +* Relax the Authority Key Identifier requirements on root CA certificates + during X.509 verification to allow fields permitted by :rfc:`5280` but + forbidden by the CA/Browser BRs. +* Added support for :class:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id` + when using OpenSSL 3.2.0+. +* Added support for the :class:`~cryptography.x509.Admissions` certificate extension. +* Added basic support for PKCS7 decryption (including S/MIME 3.2) via + :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_der`, + :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_pem`, and + :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_smime`. + +.. _v43-0-3: + +43.0.3 - 2024-10-18 +~~~~~~~~~~~~~~~~~~~ + +* Fixed release metadata for ``cryptography-vectors`` + +.. _v43-0-2: + +43.0.2 - 2024-10-18 +~~~~~~~~~~~~~~~~~~~ + +* Fixed compilation when using LibreSSL 4.0.0. + +.. _v43-0-1: + +43.0.1 - 2024-09-03 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.3.2. + +.. _v43-0-0: + +43.0.0 - 2024-07-20 +~~~~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** Support for OpenSSL less than 1.1.1e has been + removed. Users on older version of OpenSSL will need to upgrade. +* **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.8. +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.3.1. +* Updated the minimum supported Rust version (MSRV) to 1.65.0, from 1.63.0. +* :func:`~cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key` + now enforces a minimum RSA key size of 1024-bit. Note that 1024-bit is still + considered insecure, users should generally use a key size of 2048-bits. +* :func:`~cryptography.hazmat.primitives.serialization.pkcs7.serialize_certificates` + now emits ASN.1 that more closely follows the recommendations in :rfc:`2315`. +* Added new :doc:`/hazmat/decrepit/index` module which contains outdated and + insecure cryptographic primitives. + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5`, + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SEED`, + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA`, and + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.Blowfish`, which were + deprecated in 37.0.0, have been added to this module. They will be removed + from the ``cipher`` module in 45.0.0. +* Moved :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` + and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ARC4` into + :doc:`/hazmat/decrepit/index` and deprecated them in the ``cipher`` module. + They will be removed from the ``cipher`` module in 48.0.0. +* Added support for deterministic + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` (:rfc:`6979`) +* Added support for client certificate verification to the + :mod:`X.509 path validation ` APIs in the + form of :class:`~cryptography.x509.verification.ClientVerifier`, + :class:`~cryptography.x509.verification.VerifiedClient`, and + ``PolicyBuilder`` + :meth:`~cryptography.x509.verification.PolicyBuilder.build_client_verifier`. +* Added Certificate + :attr:`~cryptography.x509.Certificate.public_key_algorithm_oid` + and Certificate Signing Request + :attr:`~cryptography.x509.CertificateSigningRequest.public_key_algorithm_oid` + to determine the :class:`~cryptography.hazmat._oid.PublicKeyAlgorithmOID` + Object Identifier of the public key found inside the certificate. +* Added :attr:`~cryptography.x509.InvalidityDate.invalidity_date_utc`, a + timezone-aware alternative to the naïve ``datetime`` attribute + :attr:`~cryptography.x509.InvalidityDate.invalidity_date`. +* Added support for parsing empty DN string in + :meth:`~cryptography.x509.Name.from_rfc4514_string`. +* Added the following properties that return timezone-aware ``datetime`` objects: + :meth:`~cryptography.x509.ocsp.OCSPResponse.produced_at_utc`, + :meth:`~cryptography.x509.ocsp.OCSPResponse.revocation_time_utc`, + :meth:`~cryptography.x509.ocsp.OCSPResponse.this_update_utc`, + :meth:`~cryptography.x509.ocsp.OCSPResponse.next_update_utc`, + :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.revocation_time_utc`, + :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.this_update_utc`, + :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.next_update_utc`, + These are timezone-aware variants of existing properties that return naïve + ``datetime`` objects. +* Added + :func:`~cryptography.hazmat.primitives.asymmetric.rsa.rsa_recover_private_exponent` +* Added :meth:`~cryptography.hazmat.primitives.ciphers.CipherContext.reset_nonce` + for altering the ``nonce`` of a cipher context without initializing a new + instance. See the docs for additional restrictions. +* :class:`~cryptography.x509.NameAttribute` now raises an exception when + attempting to create a common name whose length is shorter or longer than + :rfc:`5280` permits. +* Added basic support for PKCS7 encryption (including SMIME) via + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7EnvelopeBuilder`. + +.. _v42-0-8: + +42.0.8 - 2024-06-04 +~~~~~~~~~~~~~~~~~~~ + +* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.2.2. + +.. _v42-0-7: + +42.0.7 - 2024-05-06 +~~~~~~~~~~~~~~~~~~~ + +* Restored Windows 7 compatibility for our pre-built wheels. Note that we do + not test on Windows 7 and wheels for our next release will not support it. + Microsoft no longer provides support for Windows 7 and users are encouraged + to upgrade. + +.. _v42-0-6: + +42.0.6 - 2024-05-04 +~~~~~~~~~~~~~~~~~~~ + +* Fixed compilation when using LibreSSL 3.9.1. + +.. _v42-0-5: + +42.0.5 - 2024-02-23 +~~~~~~~~~~~~~~~~~~~ + +* Limit the number of name constraint checks that will be performed in + :mod:`X.509 path validation ` to protect + against denial of service attacks. +* Upgrade ``pyo3`` version, which fixes building on PowerPC. + +.. _v42-0-4: + +42.0.4 - 2024-02-20 +~~~~~~~~~~~~~~~~~~~ + +* Fixed a null-pointer-dereference and segfault that could occur when creating + a PKCS#12 bundle. Credit to **Alexander-Programming** for reporting the + issue. **CVE-2024-26130** +* Fixed ASN.1 encoding for PKCS7/SMIME signed messages. The fields ``SMIMECapabilities`` + and ``SignatureAlgorithmIdentifier`` should now be correctly encoded according to the + definitions in :rfc:`2633` :rfc:`3370`. + +.. _v42-0-3: + +42.0.3 - 2024-02-15 +~~~~~~~~~~~~~~~~~~~ + +* Fixed an initialization issue that caused key loading failures for some + users. + +.. _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 @@ -1076,7 +1282,7 @@ Changelog :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point`, which immediately checks if the point is on the curve and supports compressed points. Deprecated the previous method - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point`. + ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point``. * Added :attr:`~cryptography.x509.ocsp.OCSPResponse.signature_hash_algorithm` to ``OCSPResponse``. * Updated :doc:`/hazmat/primitives/asymmetric/x25519` support to allow @@ -1786,7 +1992,7 @@ Changelog form using ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point`` and - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point`. + ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point``. * Added :meth:`~cryptography.x509.Extensions.get_extension_for_class`. * :class:`~cryptography.x509.CertificatePolicies` are now supported in the :class:`~cryptography.x509.CertificateBuilder`. diff --git a/src/rust/Cargo.lock b/Cargo.lock similarity index 51% rename from src/rust/Cargo.lock rename to Cargo.lock index b2e0ac4aad38..b9a109617b13 100644 --- a/src/rust/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,19 @@ version = 3 [[package]] name = "asn1" -version = "0.15.5" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae3ecbce89a22627b5e8e6e11d69715617138290289e385cde773b1fe50befdb" +checksum = "2d8b84b4ea1de2bf1dcd2a759737ddb328fb6695b2a95eb7e44fed67e3406f32" dependencies = [ "asn1_derive", + "itoa", ] [[package]] name = "asn1_derive" -version = "0.15.5" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "861af988fac460ac69a09f41e6217a8fb9178797b76fcc9478444be6a59be19c" +checksum = "a200809d0138620b3dba989f1d08d0620e76248bc1e62a2ec1b2df5eb1ee08ad" dependencies = [ "proc-macro2", "quote", @@ -24,35 +25,29 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.6.0" 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" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -70,6 +65,13 @@ dependencies = [ "pyo3", ] +[[package]] +name = "cryptography-keepalive" +version = "0.1.0" +dependencies = [ + "pyo3", +] + [[package]] name = "cryptography-key-parsing" version = "0.1.0" @@ -85,6 +87,7 @@ dependencies = [ name = "cryptography-openssl" version = "0.1.0" dependencies = [ + "cfg-if", "foreign-types", "foreign-types-shared", "openssl", @@ -96,9 +99,9 @@ name = "cryptography-rust" version = "0.1.0" dependencies = [ "asn1", - "cc", "cfg-if", "cryptography-cffi", + "cryptography-keepalive", "cryptography-key-parsing", "cryptography-openssl", "cryptography-x509", @@ -124,6 +127,7 @@ name = "cryptography-x509-verification" version = "0.1.0" dependencies = [ "asn1", + "cryptography-key-parsing", "cryptography-x509", "once_cell", "pem", @@ -146,54 +150,50 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] -name = "libc" -version = "0.2.152" +name = "itoa" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] -name = "lock_api" -version = "0.4.11" +name = "libc" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.4.2", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -225,64 +225,48 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "pem" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ "base64", ] [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.20.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", - "parking_lot", + "once_cell", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -291,9 +275,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" dependencies = [ "once_cell", "target-lexicon", @@ -301,9 +285,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" dependencies = [ "libc", "pyo3-build-config", @@ -311,9 +295,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -323,57 +307,43 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.20.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" dependencies = [ "heck", "proc-macro2", + "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "self_cell" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] -name = "smallvec" -version = "1.13.1" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.48" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -382,15 +352,15 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.13" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unindent" @@ -403,60 +373,3 @@ name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000000..8bae3163d938 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[workspace] +resolver = "2" +members = [ + "src/rust/", + "src/rust/cryptography-cffi", + "src/rust/cryptography-keepalive", + "src/rust/cryptography-key-parsing", + "src/rust/cryptography-openssl", + "src/rust/cryptography-x509", + "src/rust/cryptography-x509-verification", +] + +[workspace.package] +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.65.0" + +[workspace.dependencies] +asn1 = { version = "0.20.0", default-features = false } +pyo3 = { version = "0.23.4", features = ["abi3"] } + +[profile.release] +overflow-checks = true diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index dcffd6024d1c..000000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,24 +0,0 @@ -include CHANGELOG.rst -include CONTRIBUTING.rst -include LICENSE -include LICENSE.APACHE -include LICENSE.BSD -include README.rst -include noxfile.py - -include pyproject.toml -recursive-include src py.typed *.pyi - -recursive-include docs * -recursive-include src/_cffi_src *.py *.c *.h -recursive-include src/rust Cargo.toml Cargo.lock *.rs -prune docs/_build -recursive-include tests *.py -exclude vectors -recursive-exclude vectors * -exclude src/rust/target -recursive-exclude src/rust/target * - -recursive-exclude .github * - -exclude release.py .readthedocs.yml ci-constraints-requirements.txt mypy.ini diff --git a/ci-constraints-requirements.txt b/ci-constraints-requirements.txt index 19110a231d8e..d67c26b2e87b 100644 --- a/ci-constraints-requirements.txt +++ b/ci-constraints-requirements.txt @@ -1,76 +1,142 @@ -# This is named ambigiously, but it's a pip constraints file, named like a -# requirements file so dependabot will update the pins. -# It was originally generated with; -# pip-compile --extra=docs --extra=docstest --extra=pep8test --extra=test --extra=test-randomorder --extra=nox --extra=sdist --resolver=backtracking --strip-extras --unsafe-package=cffi --unsafe-package=pycparser --unsafe-package=setuptools pyproject.toml -# and then manually massaged to add version specifiers to packages whose -# versions vary by Python version - -alabaster==0.7.16 +# This file was autogenerated by uv via the following command: +# uv pip compile --universal -p 3.7 --extra=docs --extra=docstest --extra=pep8test --extra=test --extra=test-randomorder --extra=nox --extra=sdist --unsafe-package=cffi --unsafe-package=pycparser --unsafe-package=setuptools --unsafe-package=cryptography-vectors pyproject.toml +alabaster==0.7.13 ; python_full_version < '3.9' + # via sphinx +alabaster==0.7.16 ; python_full_version == '3.9.*' # via sphinx -argcomplete==3.2.1; python_version >= "3.8" +alabaster==1.0.0 ; python_full_version >= '3.10' + # via sphinx +argcomplete==3.1.2 ; python_full_version < '3.8' + # via nox +argcomplete==3.5.1 ; python_full_version >= '3.8' # via nox -babel==2.14.0 +babel==2.14.0 ; python_full_version < '3.8' # via sphinx -build==1.0.3 +babel==2.16.0 ; python_full_version >= '3.8' + # via sphinx +bleach==6.0.0 ; python_full_version < '3.8' + # via readme-renderer +build==1.1.1 ; python_full_version < '3.8' + # via cryptography (pyproject.toml) +build==1.2.2.post1 ; python_full_version >= '3.8' # via + # cryptography (pyproject.toml) # check-sdist +certifi==2024.8.30 + # via # cryptography (pyproject.toml) -certifi==2023.11.17 - # via requests -charset-normalizer==3.3.2 + # requests +charset-normalizer==3.4.0 # via requests -check-sdist==0.1.3 +check-sdist==1.2.0 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) click==8.1.7 # via cryptography (pyproject.toml) -colorlog==6.8.0 - # via nox -coverage==7.4.0; python_version >= "3.8" +colorama==0.4.6 ; (platform_system != 'Windows' and sys_platform == 'win32') or platform_system == 'Windows' or os_name == 'nt' # via - # coverage - # pytest-cov -distlib==0.3.8 + # build + # click + # colorlog + # pytest + # sphinx +colorlog==6.9.0 + # via nox +coverage==7.2.7 ; python_full_version < '3.8' + # via pytest-cov +coverage==7.6.1 ; python_full_version == '3.8.*' + # via pytest-cov +coverage==7.6.8 ; python_full_version >= '3.9' + # via pytest-cov +distlib==0.3.9 # via virtualenv -docutils==0.20.1 +docutils==0.19 ; python_full_version < '3.8' + # via + # readme-renderer + # sphinx +docutils==0.20.1 ; python_full_version == '3.8.*' # via # readme-renderer # sphinx # sphinx-rtd-theme -exceptiongroup==1.2.0 +docutils==0.21.2 ; python_full_version >= '3.9' + # via + # readme-renderer + # sphinx + # sphinx-rtd-theme +exceptiongroup==1.2.2 ; python_full_version < '3.11' # via pytest -execnet==2.0.2 +execnet==2.0.2 ; python_full_version < '3.8' + # via pytest-xdist +execnet==2.1.1 ; python_full_version >= '3.8' # via pytest-xdist -filelock==3.13.1; python_version >= "3.8" +filelock==3.12.2 ; python_full_version < '3.8' + # via virtualenv +filelock==3.16.1 ; python_full_version >= '3.8' # via virtualenv -idna==3.6 +idna==3.10 # via requests imagesize==1.4.1 # via sphinx +importlib-metadata==6.7.0 ; python_full_version < '3.8' + # via + # argcomplete + # build + # click + # nox + # pluggy + # pytest + # pytest-randomly + # sphinx + # sphinxcontrib-spelling + # virtualenv +importlib-metadata==8.5.0 ; python_full_version >= '3.8' and python_full_version < '3.10.2' + # via + # build + # pytest-randomly + # sphinx +importlib-resources==6.4.5 ; python_full_version == '3.8.*' + # via check-sdist iniconfig==2.0.0 # via pytest -jinja2==3.1.3 +jinja2==3.1.4 # via sphinx -markupsafe==2.1.4 +markupsafe==2.1.5 ; python_full_version < '3.9' + # via jinja2 +markupsafe==3.0.2 ; python_full_version >= '3.9' # via jinja2 -mypy==1.8.0 +mypy==1.4.1 ; python_full_version < '3.8' + # via cryptography (pyproject.toml) +mypy==1.13.0 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) mypy-extensions==1.0.0 # via mypy -nh3==0.2.15 +nh3==0.2.18 ; python_full_version >= '3.8' # via readme-renderer -nox==2023.4.22 +nox==2024.4.15 ; python_full_version < '3.8' + # via cryptography (pyproject.toml) +nox==2024.10.9 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) -packaging==23.2 +packaging==24.0 ; python_full_version < '3.8' + # via + # build + # nox + # pytest + # sphinx +packaging==24.2 ; python_full_version >= '3.8' # via # build # nox # pytest # sphinx -pathspec==0.12.1 +pathspec==0.12.1 ; python_full_version >= '3.8' # via check-sdist -platformdirs==4.1.0; python_version >= "3.8" +platformdirs==4.0.0 ; python_full_version < '3.8' # via virtualenv -pluggy==1.3.0; python_version >= "3.8" +platformdirs==4.3.6 ; python_full_version >= '3.8' + # via virtualenv +pluggy==1.2.0 ; python_full_version < '3.8' + # via pytest +pluggy==1.5.0 ; python_full_version >= '3.8' # via pytest pretend==1.0.9 # via cryptography (pyproject.toml) @@ -80,79 +146,170 @@ pyenchant==3.2.2 # via # cryptography (pyproject.toml) # sphinxcontrib-spelling -pygments==2.17.2 +pygments==2.17.2 ; python_full_version < '3.8' + # via + # readme-renderer + # sphinx +pygments==2.18.0 ; python_full_version >= '3.8' # via # readme-renderer # sphinx -pyproject-hooks==1.0.0 +pyproject-hooks==1.2.0 # via build -pytest==7.4.4 +pytest==7.4.4 ; python_full_version < '3.8' # via # cryptography (pyproject.toml) # pytest-benchmark # pytest-cov # pytest-randomly # pytest-xdist -pytest-benchmark==4.0.0 +pytest==8.3.3 ; python_full_version >= '3.8' + # via + # cryptography (pyproject.toml) + # pytest-benchmark + # pytest-cov + # pytest-randomly + # pytest-xdist +pytest-benchmark==4.0.0 ; python_full_version < '3.9' + # via cryptography (pyproject.toml) +pytest-benchmark==5.1.0 ; python_full_version >= '3.9' + # via cryptography (pyproject.toml) +pytest-cov==4.1.0 ; python_full_version < '3.8' + # via cryptography (pyproject.toml) +pytest-cov==5.0.0 ; python_full_version == '3.8.*' + # via cryptography (pyproject.toml) +pytest-cov==6.0.0 ; python_full_version >= '3.9' + # via cryptography (pyproject.toml) +pytest-randomly==3.12.0 ; python_full_version < '3.8' + # via cryptography (pyproject.toml) +pytest-randomly==3.15.0 ; python_full_version == '3.8.*' + # via cryptography (pyproject.toml) +pytest-randomly==3.16.0 ; python_full_version >= '3.9' + # via cryptography (pyproject.toml) +pytest-xdist==3.5.0 ; python_full_version < '3.8' # via cryptography (pyproject.toml) -pytest-cov==4.1.0 +pytest-xdist==3.6.1 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) -pytest-randomly==3.15.0 +pytz==2024.2 ; python_full_version < '3.9' + # via babel +readme-renderer==37.3 ; python_full_version < '3.8' # via cryptography (pyproject.toml) -pytest-xdist==3.5.0 +readme-renderer==43.0 ; python_full_version == '3.8.*' # via cryptography (pyproject.toml) -readme-renderer==42.0 +readme-renderer==44.0 ; python_full_version >= '3.9' # via cryptography (pyproject.toml) -requests==2.31.0 +requests==2.31.0 ; python_full_version < '3.8' # via sphinx -ruff==0.1.14 +requests==2.32.3 ; python_full_version >= '3.8' + # via sphinx +ruff==0.8.0 # via cryptography (pyproject.toml) +six==1.16.0 ; python_full_version < '3.8' + # via bleach snowballstemmer==2.2.0 # via sphinx -sphinx==7.2.6 +sphinx==5.3.0 ; python_full_version < '3.8' + # via + # cryptography (pyproject.toml) + # sphinxcontrib-spelling +sphinx==7.1.2 ; python_full_version == '3.8.*' # via # cryptography (pyproject.toml) # sphinx-rtd-theme - # sphinxcontrib-applehelp - # sphinxcontrib-devhelp - # sphinxcontrib-htmlhelp # sphinxcontrib-jquery - # sphinxcontrib-qthelp - # sphinxcontrib-serializinghtml # sphinxcontrib-spelling -sphinx-rtd-theme==2.0.0 +sphinx==7.4.7 ; python_full_version == '3.9.*' + # via + # cryptography (pyproject.toml) + # sphinx-rtd-theme + # sphinxcontrib-jquery + # sphinxcontrib-spelling +sphinx==8.1.3 ; python_full_version >= '3.10' + # via + # cryptography (pyproject.toml) + # sphinx-rtd-theme + # sphinxcontrib-jquery + # sphinxcontrib-spelling +sphinx-rtd-theme==3.0.2 ; python_full_version >= '3.8' # via cryptography (pyproject.toml) -sphinxcontrib-applehelp==1.0.8 +sphinxcontrib-applehelp==1.0.2 ; python_full_version < '3.8' + # via sphinx +sphinxcontrib-applehelp==1.0.4 ; python_full_version == '3.8.*' + # via sphinx +sphinxcontrib-applehelp==2.0.0 ; python_full_version >= '3.9' + # via sphinx +sphinxcontrib-devhelp==1.0.2 ; python_full_version < '3.9' # via sphinx -sphinxcontrib-devhelp==1.0.6 +sphinxcontrib-devhelp==2.0.0 ; python_full_version >= '3.9' # via sphinx -sphinxcontrib-htmlhelp==2.0.5 +sphinxcontrib-htmlhelp==2.0.0 ; python_full_version < '3.8' # via sphinx -sphinxcontrib-jquery==4.1 +sphinxcontrib-htmlhelp==2.0.1 ; python_full_version == '3.8.*' + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 ; python_full_version >= '3.9' + # via sphinx +sphinxcontrib-jquery==4.1 ; python_full_version >= '3.8' # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.7 +sphinxcontrib-qthelp==1.0.3 ; python_full_version < '3.9' + # via sphinx +sphinxcontrib-qthelp==2.0.0 ; python_full_version >= '3.9' # via sphinx -sphinxcontrib-serializinghtml==1.1.10 +sphinxcontrib-serializinghtml==1.1.5 ; python_full_version < '3.9' + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 ; python_full_version >= '3.9' # via sphinx sphinxcontrib-spelling==8.0.0 # via cryptography (pyproject.toml) -tomli==2.0.1 +tomli==2.0.1 ; python_full_version < '3.8' # via # build - # check-manifest # coverage # mypy - # pyproject-hooks + # nox # pytest -typing-extensions==4.9.0; python_version >= "3.8" +tomli==2.1.0 ; python_full_version >= '3.8' and python_full_version <= '3.11' + # via + # build + # check-sdist + # coverage + # mypy + # nox + # pytest + # sphinx +typed-ast==1.5.5 ; python_full_version < '3.8' + # via mypy +typing-extensions==4.7.1 ; python_full_version < '3.8' + # via + # importlib-metadata + # mypy + # nox + # platformdirs +typing-extensions==4.12.2 ; python_full_version >= '3.8' # via mypy -urllib3==2.1.0 +urllib3==2.0.7 ; python_full_version < '3.8' + # via requests +urllib3==2.2.3 ; python_full_version >= '3.8' # via requests -virtualenv==20.25.0 +uv==0.5.29 ; python_full_version >= '3.8' # via nox +virtualenv==20.26.6 ; python_full_version < '3.8' + # via nox +virtualenv==20.28.0 ; python_full_version >= '3.8' + # via nox +webencodings==0.5.1 ; python_full_version < '3.8' + # via bleach +zipp==3.15.0 ; python_full_version < '3.8' + # via importlib-metadata +zipp==3.20.2 ; python_full_version == '3.8.*' + # via + # importlib-metadata + # importlib-resources +zipp==3.21.0 ; python_full_version >= '3.9' and python_full_version < '3.10.2' + # via importlib-metadata -# The following packages are considered to be unsafe in a requirements file: +# The following packages were excluded from the output: # cffi # pycparser +# cryptography-vectors diff --git a/docs/_ext/linkcode_res.py b/docs/_ext/linkcode_res.py index 9b6f427d4e88..9239252935b9 100644 --- a/docs/_ext/linkcode_res.py +++ b/docs/_ext/linkcode_res.py @@ -94,7 +94,7 @@ def linkcode_resolve(domain, info): fn = os.path.relpath(fn, start=os.path.dirname(cryptography.__file__)) if lineno: - linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) + linespec = f"#L{lineno}-L{lineno + len(source) - 1}" else: linespec = "" diff --git a/docs/conf.py b/docs/conf.py index cf0f25abcaa9..1a00ac736683 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -199,11 +199,14 @@ r"https://speakerdeck.com", r"https://\w+.stackexchange.com", r"https://stackoverflow.com", + r"https://webstore.ansi.org", # 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", + # CMU doesn't know how to send intermediates + r"https://wiki.sei.cmu.edu", ] autosectionlabel_prefix_document = True diff --git a/docs/development/custom-vectors/arc4/generate_arc4.py b/docs/development/custom-vectors/arc4/generate_arc4.py index 208d18585ac6..3f81691e817a 100644 --- a/docs/development/custom-vectors/arc4/generate_arc4.py +++ b/docs/development/custom-vectors/arc4/generate_arc4.py @@ -80,9 +80,8 @@ def _build_vectors(): output.append(f"OFFSET = {offset}") output.append(f"PLAINTEXT = {binascii.hexlify(plaintext)}") output.append( - "CIPHERTEXT = {}".format( - binascii.hexlify(encryptor.update(plaintext)) - ) + f"CIPHERTEXT = " + f"{binascii.hexlify(encryptor.update(plaintext))}" ) current_offset += len(plaintext) assert not encryptor.finalize() diff --git a/docs/development/custom-vectors/rc2.rst b/docs/development/custom-vectors/rc2.rst new file mode 100644 index 000000000000..6c7bb9ccdeb9 --- /dev/null +++ b/docs/development/custom-vectors/rc2.rst @@ -0,0 +1,24 @@ +RC2 vector creation +=================== + +This page documents the code that was used to generate the RC2 CBC test vector. +The CBC vector was generated using Go's internal RC2 implementation and +verified using Go and OpenSSL. + +Creation/Verification +--------------------- + +The program below outputs a test vector in the standard format we use and +also verifies that the encrypted value round trips as expected. The output +was also checked against OpenSSL by modifying ``cryptography`` to support +the algorithm. If you wish to run this program we recommend cloning the +repository, which also contains the requisite ``go.mod`` file. + +.. literalinclude:: /development/custom-vectors/rc2/genrc2.go + :language: go + +Download link: :download:`genrc2.go +` + +Download link: :download:`rc2.go +` diff --git a/docs/development/custom-vectors/rc2/genrc2.go b/docs/development/custom-vectors/rc2/genrc2.go new file mode 100644 index 000000000000..eaacf7510232 --- /dev/null +++ b/docs/development/custom-vectors/rc2/genrc2.go @@ -0,0 +1,35 @@ +package main + +import ( + "bytes" + "crypto/cipher" + "encoding/hex" + "fmt" + "rc2sucks/rc2" +) + +func main() { + // Generate + count := 1 + key := []byte("0000000000000000") + iv := []byte("00000000") + plaintext := []byte("the quick brown fox jumped over the lazy dog!!!!") + ciphertext := make([]byte, len(plaintext)) + block, _ := rc2.New(key, 128) + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext, plaintext) + fmt.Printf("COUNT = %v\n", count) + fmt.Printf("Key = %s\n", hex.EncodeToString(key)) + fmt.Printf("IV = %s\n", hex.EncodeToString(iv)) + fmt.Printf("Plaintext = %s\n", hex.EncodeToString(plaintext)) + fmt.Printf("Ciphertext = %s\n", hex.EncodeToString(ciphertext)) + // Verify + decrypted := make([]byte, len(plaintext)) + decmode := cipher.NewCBCDecrypter(block, iv) + decmode.CryptBlocks(decrypted, ciphertext) + if bytes.Equal(decrypted, plaintext) { + fmt.Println("Success") + } else { + fmt.Println("Failed") + } +} diff --git a/docs/development/custom-vectors/rc2/go.mod b/docs/development/custom-vectors/rc2/go.mod new file mode 100644 index 000000000000..ebc124b48faf --- /dev/null +++ b/docs/development/custom-vectors/rc2/go.mod @@ -0,0 +1,3 @@ +module rc2sucks + +go 1.21.7 diff --git a/docs/development/custom-vectors/rc2/rc2/rc2.go b/docs/development/custom-vectors/rc2/rc2/rc2.go new file mode 100644 index 000000000000..25025fa71101 --- /dev/null +++ b/docs/development/custom-vectors/rc2/rc2/rc2.go @@ -0,0 +1,269 @@ +// From https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.19.0:pkcs12/internal/rc2/rc2.go +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rc2 implements the RC2 cipher +/* +https://www.ietf.org/rfc/rfc2268.txt +http://people.csail.mit.edu/rivest/pubs/KRRR98.pdf + +This code is licensed under the MIT license. +*/ +package rc2 + +import ( + "crypto/cipher" + "encoding/binary" + "math/bits" +) + +// The rc2 block size in bytes +const BlockSize = 8 + +type rc2Cipher struct { + k [64]uint16 +} + +// New returns a new rc2 cipher with the given key and effective key length t1 +func New(key []byte, t1 int) (cipher.Block, error) { + // TODO(dgryski): error checking for key length + return &rc2Cipher{ + k: expandKey(key, t1), + }, nil +} + +func (*rc2Cipher) BlockSize() int { return BlockSize } + +var piTable = [256]byte{ + 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, + 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, + 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, + 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, + 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, + 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, + 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, + 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, + 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, + 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, + 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, + 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, + 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, + 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, + 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, + 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad, +} + +func expandKey(key []byte, t1 int) [64]uint16 { + + l := make([]byte, 128) + copy(l, key) + + var t = len(key) + var t8 = (t1 + 7) / 8 + var tm = byte(255 % uint(1<<(8+uint(t1)-8*uint(t8)))) + + for i := len(key); i < 128; i++ { + l[i] = piTable[l[i-1]+l[uint8(i-t)]] + } + + l[128-t8] = piTable[l[128-t8]&tm] + + for i := 127 - t8; i >= 0; i-- { + l[i] = piTable[l[i+1]^l[i+t8]] + } + + var k [64]uint16 + + for i := range k { + k[i] = uint16(l[2*i]) + uint16(l[2*i+1])*256 + } + + return k +} + +func (c *rc2Cipher) Encrypt(dst, src []byte) { + + r0 := binary.LittleEndian.Uint16(src[0:]) + r1 := binary.LittleEndian.Uint16(src[2:]) + r2 := binary.LittleEndian.Uint16(src[4:]) + r3 := binary.LittleEndian.Uint16(src[6:]) + + var j int + + for j <= 16 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = bits.RotateLeft16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = bits.RotateLeft16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = bits.RotateLeft16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = bits.RotateLeft16(r3, 5) + j++ + + } + + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + for j <= 40 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = bits.RotateLeft16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = bits.RotateLeft16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = bits.RotateLeft16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = bits.RotateLeft16(r3, 5) + j++ + + } + + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + for j <= 60 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = bits.RotateLeft16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = bits.RotateLeft16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = bits.RotateLeft16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = bits.RotateLeft16(r3, 5) + j++ + } + + binary.LittleEndian.PutUint16(dst[0:], r0) + binary.LittleEndian.PutUint16(dst[2:], r1) + binary.LittleEndian.PutUint16(dst[4:], r2) + binary.LittleEndian.PutUint16(dst[6:], r3) +} + +func (c *rc2Cipher) Decrypt(dst, src []byte) { + + r0 := binary.LittleEndian.Uint16(src[0:]) + r1 := binary.LittleEndian.Uint16(src[2:]) + r2 := binary.LittleEndian.Uint16(src[4:]) + r3 := binary.LittleEndian.Uint16(src[6:]) + + j := 63 + + for j >= 44 { + // unmix r3 + r3 = bits.RotateLeft16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = bits.RotateLeft16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = bits.RotateLeft16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = bits.RotateLeft16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + } + + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + for j >= 20 { + // unmix r3 + r3 = bits.RotateLeft16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = bits.RotateLeft16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = bits.RotateLeft16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = bits.RotateLeft16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + + } + + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + for j >= 0 { + // unmix r3 + r3 = bits.RotateLeft16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = bits.RotateLeft16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = bits.RotateLeft16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = bits.RotateLeft16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + + } + + binary.LittleEndian.PutUint16(dst[0:], r0) + binary.LittleEndian.PutUint16(dst[2:], r1) + binary.LittleEndian.PutUint16(dst[4:], r2) + binary.LittleEndian.PutUint16(dst[6:], r3) +} diff --git a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py index f9e79122686e..42975ff1a07a 100644 --- a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py +++ b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py @@ -82,9 +82,8 @@ def build_vectors(mgf1alg, hashalg, filename): ), ) output.append( - "# OAEP Example {} alg={} mgf1={}".format( - count, hashalg.name, mgf1alg.name - ) + f"# OAEP Example {count} alg={hashalg.name} " + f"mgf1={mgf1alg.name}" ) count += 1 output.append("# Message:") diff --git a/docs/development/getting-started.rst b/docs/development/getting-started.rst index 2cb1bb478bff..c7cf265b8b22 100644 --- a/docs/development/getting-started.rst +++ b/docs/development/getting-started.rst @@ -19,7 +19,7 @@ handled by the use of ``nox``, which can be installed with ``pip``. OpenSSL on macOS ~~~~~~~~~~~~~~~~ -You must have installed `OpenSSL`_ (via `Homebrew`_ , `MacPorts`_) before +You must have installed `OpenSSL`_ (via `Homebrew`_ or `MacPorts`_) before invoking ``nox`` or else pip will fail to compile. Running tests @@ -41,6 +41,17 @@ You can also specify a subset of tests to run as positional arguments: $ # run the whole x509 testsuite, plus the fernet tests $ nox -e local -- tests/x509/ tests/test_fernet.py +Building the docs +----------------- + +Building the docs on non-Windows platforms requires manually installing +the C library ``libenchant`` (`installation instructions`_). +The docs can be built using ``nox``: + +.. code-block:: console + + $ nox -e docs + .. _`Homebrew`: https://brew.sh .. _`MacPorts`: https://www.macports.org @@ -50,3 +61,4 @@ You can also specify a subset of tests to run as positional arguments: .. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _`pip`: https://pypi.org/project/pip/ .. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic +.. _`installation instructions`: https://pyenchant.github.io/pyenchant/install.html#installing-the-enchant-c-library diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 1255688840f3..b5097cbb1b77 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -21,9 +21,6 @@ for various cryptographic algorithms. These are not included in the repository (or ``cryptography_vectors`` package), but rather cloned from Git in our continuous integration environments. -We have ensured all test vectors are used as of commit -``d9f6ec7d8bd8c96da05368999094e4a75ba5cb3d``. - Asymmetric ciphers ~~~~~~~~~~~~~~~~~~ @@ -34,8 +31,6 @@ Asymmetric ciphers * 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`_. -* OpenSSL PEM RSA serialization vectors from the `OpenSSL example key`_ and - `GnuTLS key parsing tests`_. * ``asymmetric/PEM_Serialization/rsa-bad-1025-q-is-2.pem`` from `badkeys`_. * OpenSSL PEM DSA serialization vectors from the `GnuTLS example keys`_. * PKCS #8 PEM serialization vectors from @@ -51,6 +46,7 @@ Asymmetric ciphers * X25519 and X448 test vectors from :rfc:`7748`. * RSA OAEP with custom label from the `BoringSSL evp tests`_. * Ed448 test vectors from :rfc:`8032`. +* Deterministic ECDSA (:rfc:`6979`) from `OpenSSL's RFC 6979 test vectors`_. Custom asymmetric vectors @@ -72,12 +68,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 @@ -99,8 +101,7 @@ Custom asymmetric vectors * ``asymmetric/PKCS8/unenc-dsa-pkcs8.pub.pem`` and ``asymmetric/DER_Serialization/unenc-dsa-pkcs8.pub.der`` - Contains a DSA 2048 bit public key generated using OpenSSL from ``unenc-dsa-pkcs8.pem``. -* DER conversions of the `GnuTLS example keys`_ for DSA as well as the - `OpenSSL example key`_ for RSA. +* DER conversions of the `GnuTLS example keys`_ for DSA. * DER conversions of `enc-rsa-pkcs8.pem`_, `enc2-rsa-pkcs8.pem`_, and `unenc-rsa-pkcs8.pem`_. * ``asymmetric/public/PKCS1/rsa.pub.pem`` and @@ -171,6 +172,21 @@ Custom asymmetric vectors * ``asymmetric/PKCS8/rsa_pss_2048_hash_mask_salt.pem`` - A 2048-bit RSA PSS key with the hash (SHA256), mask algorithm (SHA256), and salt length (32) PSS parameters set. +* ``asymmetric/Traditional_OpenSSL_Serialization/testrsa.pem`` - A 2048-bit RSA + key, encoded as a "traditional" ``RSA PRIVATE KEY`` PEM block, rather than a + ``PRIVATE KEY`` block. +* ``asymmetric/Traditional_OpenSSL_Serialization/testrsa-encrypted.pem`` - The + above, encrypted at the PEM level with AES-128-CBC and password "password". +* ``asymmetric/Traditional_OpenSSL_Serialization/key1.pem`` - The above, + encrypted at the PEM level with DES-EDE3-CBC and password "123456". +* ``asymmetric/Traditional_OpenSSL_Serialization/key2.pem`` - The above, + encrypted at the PEM level with AES-128-CBC and password "a123456". +* ``asymmetric/DER_Serialization/testrsa.der`` - The above as a DER-encoded + RSAPrivateKey structure. +* ``asymmetric/DSA/custom/nilpotent.pem`` -- A key where the field is actually + a ring and the generator of the multiplicative subgroup is actually + nilpotent with low degree. Taken from BoringSSL (see + ``TEST(DSATest, NilpotentGenerator)``). Key exchange @@ -220,6 +236,10 @@ Key exchange * ``vectors/cryptoraphy_vectors/asymmetric/ECDH/brainpool.txt`` contains Brainpool vectors from :rfc:`7027`. +* ``vectors/cryptography_vectors/asymmetric/DH/dhpub_cryptography_old.pem`` + contains a Diffie-Hellman public key generated with a previous version of + ``cryptography``. + X.509 ~~~~~ @@ -492,6 +512,8 @@ Custom X.509 Vectors using ``ed448-pkcs8.pem`` as key. * ``ca/rsa_ca.pem`` - A self-signed RSA certificate with ``basicConstraints`` set to true. Its private key is ``ca/rsa_key.pem``. +* ``ca/rsae_ca.pem`` - A self-signed RSA certificate using a (non-PSS) RSA + public key and a RSA PSS signature. Its private key is ``ca/rsa_key.pem``. * ``invalid-sct-version.der`` - A certificate with an SCT with an unknown version. * ``invalid-sct-length.der`` - A certificate with an SCT with an internal @@ -522,6 +544,22 @@ Custom X.509 Vectors 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. +* ``ekucrit-testuser-cert.pem`` - A leaf certificate containing a critical EKU. + This is an invalid certificate per CA/B 7.1.2.7.6. +* ``empty-eku.pem`` - A leaf certificate containing an empty EKU extension. + This is an invalid certificate per :rfc:`5280` 4.2.1.12. +* ``malformed-san.pem`` - A certificate with a malformed SAN. +* ``malformed-ian.pem`` - A certificate with a malformed IAN. +* ``admissions_extension_optional_data_not_provided.pem`` - + A certificate containing the ``Admissions`` extension with multiple admissions, + signed by ``x509/custom/ca/rsa_ca.pem`` CA. The admissions in this certificate + are prepared using synthetic data to verify the possible corner cases are handled + by the parser correctly (an admission missing naming authority or admission + authority, a profession info missing naming authority or profession OIDs + or the registration number etc). +* ``admissions_extension_authority_not_provided.pem`` - A certificate containing + the ``Admissions`` extension with no admissions and no admission authority, + signed by ``x509/custom/ca/rsa_ca.pem`` CA. Custom X.509 Request Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -574,6 +612,8 @@ Custom X.509 Request Vectors invalid. * ``long-form-attribute.pem`` - A certificate signing request containing an attribute whose value's tag is encoded in the long form. +* ``zero-element-attribute.pem`` - A certificate signing request containing an + attribute whose value has zero elements. Custom X.509 Certificate Revocation List Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -774,7 +814,7 @@ Custom PKCS12 Test Vectors * ``pkcs12/name-2-3-pwd.p12`` - A PKCS12 file containing a cert (``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 + 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 @@ -836,6 +876,15 @@ Custom PKCS7 Test Vectors * ``pkcs7/amazon-roots.der`` - A DER encoded PCKS7 file containing Amazon Root CA 2 and 3 generated by OpenSSL. * ``pkcs7/enveloped.pem`` - A PEM encoded PKCS7 file with enveloped data. +* ``pkcs7/enveloped-aes-256-cbc.pem`` - A PEM encoded PKCS7 file with + enveloped data, with content encrypted using AES-256-CBC, under the public key of + ``x509/custom/ca/rsa_ca.pem``. +* ``pkcs7/enveloped-rsa-oaep.pem``- A PEM encoded PKCS7 file with + enveloped data, with key encrypted using RSA-OAEP, under the public key of + ``x509/custom/ca/rsa_ca.pem``. +* ``pkcs7/enveloped-no-content.der``- A DER encoded PKCS7 file with + enveloped data, without encrypted content, with key encrypted under the + public key of ``x509/custom/ca/rsa_ca.pem``. Custom OpenSSH Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -941,6 +990,8 @@ Key derivation functions * X9.63 KDF from `NIST CAVP`_. * SP 800-108 Counter Mode KDF (HMAC-SHA1, HMAC-SHA224, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512) from `NIST CAVP`_. +* argon2id from :rfc:`9106`, OpenSSL's `evpkdf_argon2.txt`_, and the + argon2 command line application. Key wrapping ~~~~~~~~~~~~ @@ -980,6 +1031,7 @@ Symmetric ciphers * IDEA (ECB) from the `NESSIE IDEA vectors`_ created by `NESSIE`_. * IDEA (CBC, CFB, OFB) generated by this project. See: :doc:`/development/custom-vectors/idea` +* RC2-128-CBC generated by this project. See: :doc:`/development/custom-vectors/rc2` * SEED (ECB) from :rfc:`4269`. * SEED (CBC) from :rfc:`4196`. * SEED (CFB, OFB) generated by this project. @@ -1023,6 +1075,7 @@ Created Vectors custom-vectors/idea custom-vectors/seed custom-vectors/hkdf + custom-vectors/rc2 If official test vectors appear in the future the custom generated vectors @@ -1040,7 +1093,7 @@ header format (substituting the correct information): .. _`NIST`: https://www.nist.gov/ .. _`IETF`: https://www.ietf.org/ -.. _`Project Wycheproof`: https://github.com/google/wycheproof +.. _`Project Wycheproof`: https://github.com/C2SP/wycheproof .. _`NIST CAVP`: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program .. _`Bruce Schneier's vectors`: https://www.schneier.com/wp-content/uploads/2015/12/vectors-2.txt .. _`Camellia page`: https://info.isl.ntt.co.jp/crypt/eng/camellia/ @@ -1053,8 +1106,6 @@ header format (substituting the correct information): .. _`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 -.. _`GnuTLS key parsing tests`: https://gitlab.com/gnutls/gnutls/-/commit/f16ef39ef0303b02d7fa590a37820440c466ce8d .. _`enc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/encpkcs8.pem .. _`enc2-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/enc2pkcs8.pem .. _`unenc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/unencpkcs8.pem @@ -1086,3 +1137,5 @@ header format (substituting the correct information): .. _`dkg's additional OCB3 vectors`: https://gitlab.com/dkg/ocb-test-vectors .. _`OpenSSL's OCB vectors`: https://github.com/openssl/openssl/commit/2f19ab18a29cf9c82cdd68bc8c7e5be5061b19be .. _`badkeys`: https://github.com/vcsjones/badkeys/tree/50f1cc5f8d13bf3a2046d689f6452decb15d9c3c +.. _`evpkdf_argon2.txt`: https://github.com/openssl/openssl/blob/01f4b44e075a796d62d3b007a80c5c04d0e77bfb/test/recipes/30-test_evp_data/evpkdf_argon2.txt +.. _`OpenSSL's RFC 6979 test vectors`: https://github.com/openssl/openssl/blob/01690a7ff36c4d18c48b301cdf375c954105a1d9/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt diff --git a/docs/fernet.rst b/docs/fernet.rst index b55ecea3206a..80e06db9341a 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -33,7 +33,7 @@ has support for implementing key rotation via :class:`MultiFernet`. Generates a fresh fernet key. Keep this some place safe! If you lose it you'll no longer be able to decrypt messages; if anyone else gains access to it, they'll be able to decrypt all of your messages, and - they'll also be able forge arbitrary messages that will be + they'll also be able to forge arbitrary messages that will be authenticated and decrypted. .. method:: encrypt(data) diff --git a/docs/glossary.rst b/docs/glossary.rst index 86718cc0d675..3c2272a4da7c 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -94,7 +94,7 @@ Glossary A bytes-like object contains binary data and supports the `buffer protocol`_. This includes ``bytes``, ``bytearray``, and ``memoryview`` objects. It is :term:`unsafe` to pass a mutable object - (e.g., a ``bytearray`` or other implementor of the buffer protocol) + (e.g., a ``bytearray`` or other implementer of the buffer protocol) and to `mutate it concurrently`_ with the operation it has been provided for. diff --git a/docs/hazmat/decrepit/ciphers.rst b/docs/hazmat/decrepit/ciphers.rst new file mode 100644 index 000000000000..8ae0178df2f1 --- /dev/null +++ b/docs/hazmat/decrepit/ciphers.rst @@ -0,0 +1,132 @@ +.. hazmat:: + + +Decrepit Symmetric algorithms +============================= + +.. module:: cryptography.hazmat.decrepit.ciphers + +This module contains decrepit symmetric encryption algorithms. These +are algorithms that should not be used unless necessary for backwards +compatibility or interoperability with legacy systems. Their use is +**strongly discouraged**. + +These algorithms require you to use a :class:`~cryptography.hazmat.primitives.ciphers.Cipher` +object along with the appropriate :mod:`~cryptography.hazmat.primitives.ciphers.modes`. + +.. class:: ARC4(key) + + .. versionadded:: 43.0.0 + + ARC4 (Alleged RC4) is a stream cipher with serious weaknesses in its + initial stream output. Its use is strongly discouraged. ARC4 does not use + mode constructions. + + :param key: The secret key. This must be kept secret. Either ``40``, + ``56``, ``64``, ``80``, ``128``, ``192``, or ``256`` :term:`bits` in + length. + :type key: :term:`bytes-like` + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.decrepit.ciphers.algorithms import ARC4 + >>> from cryptography.hazmat.primitives.ciphers import Cipher, modes + >>> key = os.urandom(16) + >>> algorithm = ARC4(key) + >>> cipher = Cipher(algorithm, mode=None) + >>> encryptor = cipher.encryptor() + >>> ct = encryptor.update(b"a secret message") + >>> decryptor = cipher.decryptor() + >>> decryptor.update(ct) + b'a secret message' + +.. class:: TripleDES(key) + + .. versionadded:: 43.0.0 + + Triple DES (Data Encryption Standard), sometimes referred to as 3DES, is a + block cipher standardized by NIST. Triple DES has known crypto-analytic + flaws, however none of them currently enable a practical attack. + Nonetheless, Triple DES is not recommended for new applications because it + is incredibly slow; old applications should consider moving away from it. + + :param key: The secret key. This must be kept secret. Either ``64``, + ``128``, or ``192`` :term:`bits` long. DES only uses ``56``, ``112``, + or ``168`` bits of the key as there is a parity byte in each component + of the key. Some writing refers to there being up to three separate + keys that are each ``56`` bits long, they can simply be concatenated + to produce the full key. + :type key: :term:`bytes-like` + +.. class:: CAST5(key) + + .. versionadded:: 43.0.0 + + CAST5 (also known as CAST-128) is a block cipher approved for use in the + Canadian government by the `Communications Security Establishment`_. It is + a variable key length cipher and supports keys from 40-128 :term:`bits` in + length. + + :param key: The secret key, This must be kept secret. 40 to 128 + :term:`bits` in length in increments of 8 bits. + :type key: :term:`bytes-like` + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.decrepit.ciphers.algorithms import CAST5 + >>> from cryptography.hazmat.primitives.ciphers import Cipher, modes + >>> key = os.urandom(16) + >>> iv = os.urandom(8) + >>> algorithm = CAST5(key) + >>> cipher = Cipher(algorithm, modes.CBC(iv)) + >>> encryptor = cipher.encryptor() + >>> ct = encryptor.update(b"a secret message") + >>> decryptor = cipher.decryptor() + >>> decryptor.update(ct) + b'a secret message' + +.. class:: SEED(key) + + .. versionadded:: 43.0.0 + + SEED is a block cipher developed by the Korea Information Security Agency + (KISA). It is defined in :rfc:`4269` and is used broadly throughout South + Korean industry, but rarely found elsewhere. + + :param key: The secret key. This must be kept secret. ``128`` + :term:`bits` in length. + :type key: :term:`bytes-like` + + +.. class:: Blowfish(key) + + .. versionadded:: 43.0.0 + + Blowfish is a block cipher developed by Bruce Schneier. It is known to be + susceptible to attacks when using weak keys. The author has recommended + that users of Blowfish move to newer algorithms. + + :param key: The secret key. This must be kept secret. 32 to 448 + :term:`bits` in length in increments of 8 bits. + :type key: :term:`bytes-like` + +.. class:: IDEA(key) + + .. versionadded:: 43.0.0 + + IDEA (`International Data Encryption Algorithm`_) is a block cipher created + in 1991. It is an optional component of the `OpenPGP`_ standard. This cipher + is susceptible to attacks when using weak keys. It is recommended that you + do not use this cipher for new applications. + + :param key: The secret key. This must be kept secret. ``128`` + :term:`bits` in length. + :type key: :term:`bytes-like` + + + +.. _`Communications Security Establishment`: https://www.cse-cst.gc.ca +.. _`International Data Encryption Algorithm`: https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm +.. _`OpenPGP`: https://www.openpgp.org/ diff --git a/docs/hazmat/decrepit/index.rst b/docs/hazmat/decrepit/index.rst new file mode 100644 index 000000000000..f0e541a496ef --- /dev/null +++ b/docs/hazmat/decrepit/index.rst @@ -0,0 +1,14 @@ +.. hazmat:: + +Decrepit cryptography +===================== + +This module holds old, deprecated, and/or insecure cryptographic +algorithms that may be needed in exceptional cases for backwards +compatibility or interoperability reasons. Unless necessary +their use is **strongly discouraged**. + +.. toctree:: + :maxdepth: 2 + + ciphers diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index bcd4c993d20a..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 diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 561218c35c72..a22a64be5c41 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -47,6 +47,19 @@ Elliptic Curve Signature Algorithms :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + :param bool deterministic_signing: A boolean flag defaulting to ``False`` + that specifies whether the signing procedure should be deterministic + or not, as defined in :rfc:`6979`. This only impacts the signing + process, verification is not affected (the verification process + is the same for both deterministic and non-deterministic signed + messages). + + .. versionadded:: 43.0.0 + + :raises cryptography.exceptions.UnsupportedAlgorithm: If + ``deterministic_signing`` is set to ``True`` and the version of + OpenSSL does not support ECDSA with deterministic signing. + .. doctest:: >>> from cryptography.hazmat.primitives import hashes @@ -187,31 +200,6 @@ Elliptic Curve Signature Algorithms :raises ValueError: Raised if the point is invalid for the curve. :returns: A new instance of :class:`EllipticCurvePublicKey`. - .. classmethod:: from_encoded_point(curve, data) - - .. versionadded:: 1.1 - - .. note:: - - This has been deprecated in favor of - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point` - - Decodes a byte string as described in `SEC 1 v2.0`_ section 2.3.3 and - returns an :class:`EllipticCurvePublicNumbers`. This method only - supports uncompressed points. - - :param curve: An - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve` - instance. - - :param bytes data: The serialized point byte string. - - :returns: An :class:`EllipticCurvePublicNumbers` instance. - - :raises ValueError: Raised on invalid point type or data length. - - :raises TypeError: Raised when curve is not an - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. Elliptic Curve Key Exchange algorithm ------------------------------------- @@ -569,7 +557,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 +667,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`. 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 b8f2acacdf8f..d712b2226459 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -554,6 +554,23 @@ this without having to do the math themselves. Computes the ``dmq1`` parameter from the RSA private exponent (``d``) and prime ``q``. +.. function:: rsa_recover_private_exponent(e, p, q) + + .. versionadded:: 43.0.0 + + Computes the RSA private_exponent (``d``) given the public exponent (``e``) + and the RSA primes ``p`` and ``q``. + + .. note:: + + This implementation uses the Carmichael totient function to return the + smallest working value of ``d``. Older RSA implementations, including the + original RSA paper, often used the Euler totient function, which results + in larger but equally functional private exponents. The private exponents + resulting from the Carmichael totient function, as returned here, are + slightly more computationally efficient to use, and some modern standards + require them. + .. function:: rsa_recover_prime_factors(n, e, d) .. versionadded:: 0.8 @@ -620,7 +637,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`. @@ -739,9 +757,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 402915c45540..6d1130cbc729 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -103,7 +103,7 @@ Key dumping The ``serialization`` module contains functions for loading keys from ``bytes``. To dump a ``key`` object to ``bytes``, you must call the appropriate -method on the key object. Documentation for these methods in found in the +method on the key object. Documentation for these methods is found in the :mod:`~cryptography.hazmat.primitives.asymmetric.rsa`, :mod:`~cryptography.hazmat.primitives.asymmetric.dsa`, and :mod:`~cryptography.hazmat.primitives.asymmetric.ec` module documentation. @@ -1001,11 +1001,6 @@ PKCS7 is a format described in :rfc:`2315`, among other specifications. It can contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, ``p7m``, or ``p7s`` file suffix but other suffixes are also seen in the wild. -.. note:: - - ``cryptography`` only supports parsing certificates from PKCS7 files at - this time. - .. data:: PKCS7HashTypes .. versionadded:: 40.0.0 @@ -1095,6 +1090,91 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, -----END CERTIFICATE----- """.strip() + ca_cert_rsa = b""" + -----BEGIN CERTIFICATE----- + MIIExzCCAq+gAwIBAgIJAOcS06ClbtbJMA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNV + BAMMD2NyeXB0b2dyYXBoeSBDQTAeFw0yMDA5MTQyMTQwNDJaFw00ODAxMzEyMTQw + NDJaMBoxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTCCAiIwDQYJKoZIhvcNAQEB + BQADggIPADCCAgoCggIBANBIheRc1HT4MzV5GvUbDk9CFU6DTomRApNqRmizriRq + m6OY4Ht3d71BXog6/IBkqAnZ4/XJQ40G4sVDb52k11oPvfJ/F5pc+6UqPBL+QGzY + GkJoubAqXFpI6ow0qayFNQLv0T9o4yh0QQOoGvgCmv91qmitLrZNXu4U9S76G+Di + GST+QyMkMxj+VsGRsRRBufV1urcnvFWjU6Q2+cr2cp0mMAG96NTyIskYiJ8vL03W + z4DX4klO4X47fPmDnU/OMn4SbvMZ896j1L0J04S+uVThTkxQWcFcqXhX5qM8kzcj + JUmybFlbf150j3WiucW48K/j7fJ0x9q3iUo4Gva0coScglJWcgo/BBCwFDw8NVba + 7npxSRMiaS3qTv0dEFcRnvByc+7hyGxxlWdTE9tHisUI1eZVk9P9ziqNOZKscY8Z + X1+/C4M9X69Y7A8I74F5dO27IRycEgOrSo2z1NhfSwbqJr9a2TBtRsFinn8rjKBI + zNn0E5p9jO1WjxtkcjHfXXpLN8FFMvoYI9l/K+ZWDm9sboaF8jrgozSc004AFemA + H79mmCGVRKXn1vDAo4DLC6p3NiBFYQcYbW9V+beGD6srsF6xJtuY/UwtPROLWSzu + CCrZ/4BlmpNsR0ehIFFvzEKjX6rR2yp3YKlguDbMBMKMpfSGxAFwcZ7OiaxR20UH + AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADSveDS4 + y2V/N6Li2n9ChGNdCMr/45M0cl+GpL55aA36AWYMRLv0wip7MWV3yOj4mkjGBlTE + awKHH1FtetsE6B4a7M2hHhOXyXE60uUdptEx6ckGrJ1iyqu5cQUX1P+VnXbmOxfF + bl+Ugzjbgirx239rA4ezkDRuOvKcCbDOFV/gw3ZHfJ/IQeRXIQRl/y51wcnFUvFM + JEESYiijeDbEcY8r1/phmVQL0CO7WLMmTxlFj4X/TR3MTZWJQIap9GiLs5+n3QiO + jsZ3GuFOomB8oTebYkXniwbNu5hgLP/seRQzGA7B9VDZryAhCtvGgjtQh0eW2Qxt + sgmDJGOPKnKT3O5U0v3+IPLEYpe8JSzgAhhh6H1rAJRUNwP2gRcO4eOUJSkdl218 + fRNT0ILzosuWxwprER9ciMQF8q0JJKMhcfHRMH0S5mWVJAIkj68KY05oCy2zNyYa + oruopKSWXe0Bzr40znm40P7xIkui2BGQMlDPpbCaEfLsLqyctfbdmMlxac/QgIfY + TltrbqmI3MNy5uqGViGFpWPCB+kD8EsJF9nlKJXlu/i55qgUr/2/2CdeWlZDBP8A + 1fdzmpYpWnwhE0KobzLS2z3AwDxiY/RSWUfypLZA0K/lpaEtYB6UHMDZ0/8WqgZV + gNucCuty0cA4Kf7eX1TlAKVwH8hTkVmJc2rX + -----END CERTIFICATE----- + """.strip() + + ca_key_rsa = b""" + -----BEGIN PRIVATE KEY----- + MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDQSIXkXNR0+DM1 + eRr1Gw5PQhVOg06JkQKTakZos64kapujmOB7d3e9QV6IOvyAZKgJ2eP1yUONBuLF + Q2+dpNdaD73yfxeaXPulKjwS/kBs2BpCaLmwKlxaSOqMNKmshTUC79E/aOModEED + qBr4Apr/daporS62TV7uFPUu+hvg4hkk/kMjJDMY/lbBkbEUQbn1dbq3J7xVo1Ok + NvnK9nKdJjABvejU8iLJGIifLy9N1s+A1+JJTuF+O3z5g51PzjJ+Em7zGfPeo9S9 + CdOEvrlU4U5MUFnBXKl4V+ajPJM3IyVJsmxZW39edI91ornFuPCv4+3ydMfat4lK + OBr2tHKEnIJSVnIKPwQQsBQ8PDVW2u56cUkTImkt6k79HRBXEZ7wcnPu4chscZVn + UxPbR4rFCNXmVZPT/c4qjTmSrHGPGV9fvwuDPV+vWOwPCO+BeXTtuyEcnBIDq0qN + s9TYX0sG6ia/WtkwbUbBYp5/K4ygSMzZ9BOafYztVo8bZHIx3116SzfBRTL6GCPZ + fyvmVg5vbG6GhfI64KM0nNNOABXpgB+/ZpghlUSl59bwwKOAywuqdzYgRWEHGG1v + Vfm3hg+rK7BesSbbmP1MLT0Ti1ks7ggq2f+AZZqTbEdHoSBRb8xCo1+q0dsqd2Cp + YLg2zATCjKX0hsQBcHGezomsUdtFBwIDAQABAoICAQDH6YQRvwPwzTWhkn7MWU6v + xjbbJ+7e3T9CrNOttSBlNanzKU31U6KrFS4dxbgLqBEde3Rwud/LYZuRSPu9rLVC + bS+crF3EPJEQY2xLspu1nOn/abMoolAIHEp7jiR5QVWzXulRWmQFtSed0eEowJ9y + qMaKOAdI1RRToev/TfIqM/l8Z0ubVChzSdONcUAsuDU7ouc22r3K2Lv0Nwwkwc0a + hse3NEdg9JNsvs6LM2fM52w9N3ircjm+xmxatPft3HTcSucREIzg2hDb7K2HkOQj + 0ykq2Eh97ml+56eocADBAEvO46FZVxf2WhxEBY8Xdz4VJMmDWJFmnZj5ksZWmrX6 + U5BfFY7DZvE2EpoZ5ph1Fm6dcXrJFkaZEyJLlzFKehXMipVenjCanIPpEEUvIz+p + m0QVoNJRj/GcNyIEZ0BCXedBOUWU4XE1pG4r6oZqwUvcjsVrqXP5kbJMVybiS6Kd + 6T8ve+4qsn3ZvGRVKjInqf2WI0Wvum2sTF+4OAkYvFel9dKNjpYnnj4tLFc/EKWz + 9+pE/Zz5fMOyMD9qXM6bdVkPjWjy1vXmNW4qFCZljrb395hTvsAPMsO6bbAM+lu6 + YcdOAf8k7awTb79kPMrPcbCygyKSGN9C9T3a/Nhrbr3TPi9SD9hC5Q8bL9uSHcR2 + hgRQcApxsfDRrGwy2lheEQKCAQEA/Hrynao+k6sYtlDc/ueCjb323EzsuhOxPqUZ + fKtGeFkJzKuaKTtymasvVpAAqJBEhTALrptGWlJQ0Y/EVaPpZ9pmk791EWNXdXsX + wwufbHxm6K9aOeogev8cd+B/9wUAQPQVotyRzCcOfbVe7t81cBNktqam5Zb9Y4Zr + qu63gBB1UttdmIF5qitl3JcFztlBjiza2UrqgVdKE+d9vLR84IBRy3dyQIOi6C1c + y37GNgObjx8ZcUVV54/KgvoVvDkvN6TEbUdC9eQz7FW7DA7MMVqyDvWZrSjBzVhK + 2bTrd+Pi6S4n/ETvA6XRufHC8af4bdE2hzuq5VZO1kkgH37djwKCAQEA0y/YU0b4 + vCYpZ1MNhBFI6J9346DHD55Zu5dWFRqNkC0PiO6xEMUaUMbG4gxkiQPNT5WvddQs + EbRQTnd4FFdqB7XWoH+wERN7zjbT+BZVrHVC4gxEEy33s5oXGn7/ATxaowo7I4oq + 15MwgZu3hBNxVUtuePZ6D9/ePNGOGOUtdMRrusmVX7gZEXxwvlLJXyVepl2V4JV1 + otI8EZCcoRhSfeYNEs4VhN0WmfMSV7ge0eFfVb6Lb+6PCcasYED8S0tBN2vjzvol + zCMv8skPATm7SopqBDoBPcXCHwN/gUFXHf/lrvE6bbeX1ZMxnRYKdQLLNYyQK9cr + nCUJXuNM21tVCQKCAQBapCkFwWDF0t8EVPOB78tG57QAUv2JsBgpzUvhHfwmqJCE + Efc+ZkE2Oea8xOX3nhN7XUxUWxpewr6Q/XQW6smYpye8UzfMDkYPvylAtKN/Zwnq + 70kNEainf37Q6qAGJp14tCgwV89f44WoS7zRNQESQ2QczqeMNTCy0kdFDn6CU2ZL + YMWxQopTNVFUaEOFhympySCoceTOmm/VxX22iXVrg6XZzgAOeTO69s4hoFm4eoMW + Vqvjpmi4wT6K1w2GjWEOMPDz6ml3rX2WkxCbu5RDA7R4+mM5bzBkcBYvImyGliGY + ZSGlx3mnbZhlkQ3Tg+IESt+wnRM1Uk7rT0VhCUKxAoIBABWYuPibM2iaRnWoiqNM + 2TXgyPPgRzsTqH2ElmsGEiACW6pXLohWf8Bu83u+ZLGWT/Kpjg3wqqkM1YGQuhjq + b49mSxKSvECiy3BlLvwZ3J0MSNCxDG0hsEkPovk0r4NC1soBi9awlH0DMlyuve+l + xVtBoYSBQC5LaICztWJaXXGpfJLXdo0ZWIbvQOBVuv4d5jYBMAiNgEAsW7Q4I6xd + vmHdmsyngo/ZxCvuLZwG2jAAai1slPnXXY1UYeBeBO72PS8bu2o5LpBXsNmVMhGg + A8U1rm3MOMBGbvmY8/sV4YDR4H0pch4yPja7HMHBtUQOCxXoz/2LvYv0RacMe5mb + F3ECggEAWxQZnT8pObxKrISZpHSKi54VxuLYbemS63Tdr4HE/KuiFAvbM6AeZOki + jbiMnqrCTOhJRS/i9HV78zSxRZZyVm961tnsjqMyaamX/S4yD7v3Vzu1mfsdVCa2 + Sl+JUUxsEgs/G3Fu6I/0TsCSn/HgNLM8b3f8TDkbpnOqKX165ddojXqSCfxjuYau + Szih/+jF1dz2/zBye1ARkLRdY/SzlzGl0cVn8bfkE0YEde7wvQ624Biy7r9i1o40 + 7cy/8EQBR2FcXpOAZ7UgOqgGLNhXnd4FPsX4ldKOf5De8FErQOFirJ8pCUxFGr0U + fDWXtBuybAb5u+ZaVwHgqaaPCkKkVQ== + -----END PRIVATE KEY----- + """.strip() .. class:: PKCS7SignatureBuilder @@ -1174,23 +1254,260 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :returns bytes: The signed PKCS7 message. +.. class:: PKCS7EnvelopeBuilder + + The PKCS7 envelope builder can create encrypted S/MIME messages, + which are commonly used in email. S/MIME has multiple versions, + but this implements a subset of :rfc:`5751`, also known as S/MIME + Version 3.2. + + .. versionadded:: 43.0.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.primitives import serialization + >>> from cryptography.hazmat.primitives.serialization import pkcs7 + >>> cert = x509.load_pem_x509_certificate(ca_cert_rsa) + >>> options = [pkcs7.PKCS7Options.Text] + >>> pkcs7.PKCS7EnvelopeBuilder().set_data( + ... b"data to encrypt" + ... ).add_recipient( + ... cert + ... ).encrypt( + ... serialization.Encoding.SMIME, options + ... ) + b'...' + + .. method:: set_data(data) + + :param data: The data to be encrypted. + :type data: :term:`bytes-like` + + .. method:: add_recipient(certificate) + + Add a recipient for the message. Recipients will be able to use their private keys + to decrypt the message. This method may be called multiple times to add as many recipients + as desired. + + :param certificate: A :class:`~cryptography.x509.Certificate` for an intended + recipient of the encrypted message. Only certificates with public RSA keys + are currently supported. + + .. method:: encrypt(encoding, options) + + The message is encrypted using AES-128-CBC. The encryption key used is included in + the envelope, encrypted using the recipient's public RSA key. If multiple recipients + are specified, the key is encrypted once with each recipient's public key, and all + encrypted keys are included in the envelope (one per recipient). + + :param encoding: :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, + or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME`. + + :param options: A list of + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For + this operation only + :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` and + :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Binary` + are supported, and cannot be used at the same time. + + :returns bytes: The enveloped PKCS7 message. + +.. function:: pkcs7_decrypt_der(data, certificate, private_key, options) + + .. versionadded:: 44.0.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.primitives import serialization + >>> from cryptography.hazmat.primitives.serialization import pkcs7 + >>> cert = x509.load_pem_x509_certificate(ca_cert_rsa) + >>> key = serialization.load_pem_private_key(ca_key_rsa, None) + >>> options = [pkcs7.PKCS7Options.Text] + >>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data( + ... b"data to encrypt" + ... ).add_recipient( + ... cert + ... ).encrypt( + ... serialization.Encoding.DER, options + ... ) + >>> pkcs7.pkcs7_decrypt_der(enveloped, cert, key, options) + b'data to encrypt' + + Deserialize and decrypt a DER-encoded PKCS7 message. PKCS7 (or S/MIME) has multiple versions, + but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2. + + :param data: The data, encoded in DER format. + :type data: bytes + + :param certificate: A :class:`~cryptography.x509.Certificate` for an intended + recipient of the encrypted message. Only certificates with public RSA keys + are currently supported. + + :param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + associated with the certificate provided. Only private RSA keys are supported. + + :param options: A list of + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For + this operation only + :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported. + + :returns bytes: The decrypted message. + + :raises ValueError: If the recipient certificate does not match any of the encrypted keys in the + PKCS7 data. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted + with another algorithm than RSA with PKCS1 v1.5 padding. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with + another algorithm than AES-128-CBC. + + :raises ValueError: If the PKCS7 data does not contain encrypted content. + + :raises ValueError: If the PKCS7 data is not of the enveloped data type. + +.. function:: pkcs7_decrypt_pem(data, certificate, private_key, options) + + .. versionadded:: 44.0.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.primitives import serialization + >>> from cryptography.hazmat.primitives.serialization import pkcs7 + >>> cert = x509.load_pem_x509_certificate(ca_cert_rsa) + >>> key = serialization.load_pem_private_key(ca_key_rsa, None) + >>> options = [pkcs7.PKCS7Options.Text] + >>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data( + ... b"data to encrypt" + ... ).add_recipient( + ... cert + ... ).encrypt( + ... serialization.Encoding.PEM, options + ... ) + >>> pkcs7.pkcs7_decrypt_pem(enveloped, cert, key, options) + b'data to encrypt' + + Deserialize and decrypt a PEM-encoded PKCS7E message. PKCS7 (or S/MIME) has multiple versions, + but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2. + + :param data: The data, encoded in PEM format. + :type data: bytes + + :param certificate: A :class:`~cryptography.x509.Certificate` for an intended + recipient of the encrypted message. Only certificates with public RSA keys + are currently supported. + + :param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + associated with the certificate provided. Only private RSA keys are supported. + + :param options: A list of + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For + this operation only + :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported. + + :returns bytes: The decrypted message. + + :raises ValueError: If the PEM data does not have the PKCS7 tag. + + :raises ValueError: If the recipient certificate does not match any of the encrypted keys in the + PKCS7 data. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted + with another algorithm than RSA with PKCS1 v1.5 padding. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with + another algorithm than AES-128-CBC. + + :raises ValueError: If the PKCS7 data does not contain encrypted content. + + :raises ValueError: If the PKCS7 data is not of the enveloped data type. + +.. function:: pkcs7_decrypt_smime(data, certificate, private_key, options) + + .. versionadded:: 44.0.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.primitives import serialization + >>> from cryptography.hazmat.primitives.serialization import pkcs7 + >>> cert = x509.load_pem_x509_certificate(ca_cert_rsa) + >>> key = serialization.load_pem_private_key(ca_key_rsa, None) + >>> options = [pkcs7.PKCS7Options.Text] + >>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data( + ... b"data to encrypt" + ... ).add_recipient( + ... cert + ... ).encrypt( + ... serialization.Encoding.SMIME, options + ... ) + >>> pkcs7.pkcs7_decrypt_smime(enveloped, cert, key, options) + b'data to encrypt' + + Deserialize and decrypt a S/MIME-encoded PKCS7 message. PKCS7 (or S/MIME) has multiple versions, + but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2. + + :param data: The data. It should be in S/MIME format, meaning MIME with content type + ``application/pkcs7-mime`` or ``application/x-pkcs7-mime``. + :type data: bytes + + :param certificate: A :class:`~cryptography.x509.Certificate` for an intended + recipient of the encrypted message. Only certificates with public RSA keys + are currently supported. + + :param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + associated with the certificate provided. Only private RSA keys are supported. + + :param options: A list of + :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For + this operation only + :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported. + + :returns bytes: The decrypted message. + + :raises ValueError: If the S/MIME data is not one of the correct content types. + + :raises ValueError: If the recipient certificate does not match any of the encrypted keys in the + PKCS7 data. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted + with another algorithm than RSA with PKCS1 v1.5 padding. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with + another algorithm than AES-128-CBC. + + :raises ValueError: If the PKCS7 data does not contain encrypted content. + + :raises ValueError: If the PKCS7 data is not of the enveloped data type. + + .. class:: PKCS7Options .. versionadded:: 3.2 - An enumeration of options for PKCS7 signature creation. + An enumeration of options for PKCS7 signature, envelope creation, and decryption. .. attribute:: Text - The text option adds ``text/plain`` headers to an S/MIME message when - serializing to + For signing, the text option adds ``text/plain`` headers to an S/MIME message when + serializing to :attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME`. This option is disallowed with ``DER`` serialization. + For envelope creation, it adds ``text/plain`` headers to the encrypted content, regardless + of the specified encoding. + For envelope decryption, it parses the decrypted content headers (if any), checks if the + content type is 'text/plain', then removes all headers (keeping only the payload) of this + decrypted content. If there is no header, or the content type is not "text/plain", it + raises an error. .. attribute:: Binary - Signing normally converts line endings (LF to CRLF). When - passing this option the data will not be converted. + Signature and envelope creation normally converts line endings (LF to CRLF). When + passing this option, the data will not be converted. .. attribute:: DetachedSignature @@ -1265,7 +1582,10 @@ Serialization Formats .. versionadded:: 2.5 - A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a + A raw format used by :doc:`/hazmat/primitives/asymmetric/ed25519`, + :doc:`/hazmat/primitives/asymmetric/ed448`, + :doc:`/hazmat/primitives/asymmetric/x25519`, and + :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. .. attribute:: OpenSSH @@ -1379,7 +1699,10 @@ Serialization Formats .. versionadded:: 2.5 - A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a + A raw format used by :doc:`/hazmat/primitives/asymmetric/ed25519`, + :doc:`/hazmat/primitives/asymmetric/ed448`, + :doc:`/hazmat/primitives/asymmetric/x25519`, and + :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. .. attribute:: CompressedPoint @@ -1452,7 +1775,10 @@ Serialization Encodings .. versionadded:: 2.5 - A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a + A raw format used by :doc:`/hazmat/primitives/asymmetric/ed25519`, + :doc:`/hazmat/primitives/asymmetric/ed448`, + :doc:`/hazmat/primitives/asymmetric/x25519`, and + :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. .. attribute:: X962 diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst index 72e5b26ce33d..98d597be9c99 100644 --- a/docs/hazmat/primitives/index.rst +++ b/docs/hazmat/primitives/index.rst @@ -4,7 +4,7 @@ Primitives ========== .. toctree:: - :maxdepth: 1 + :maxdepth: 2 aead asymmetric/index diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 2715e3e56c5d..113b1bf7f87d 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -30,6 +30,106 @@ Different KDFs are suitable for different tasks such as: Variable cost algorithms ~~~~~~~~~~~~~~~~~~~~~~~~ +Argon2id +-------- + +.. currentmodule:: cryptography.hazmat.primitives.kdf.argon2 + +.. class:: Argon2id(*, salt, length, iterations, lanes, memory_cost, ad=None, secret=None) + + .. versionadded:: 44.0.0 + + Argon2id is a KDF designed for password storage. It is designed to be + resistant to hardware attacks and is described in :rfc:`9106`. + + This class conforms to the + :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` + interface. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives.kdf.argon2 import Argon2id + >>> salt = os.urandom(16) + >>> # derive + >>> kdf = Argon2id( + ... salt=salt, + ... length=32, + ... iterations=1, + ... lanes=4, + ... memory_cost=64 * 1024, + ... ad=None, + ... secret=None, + ... ) + >>> key = kdf.derive(b"my great password") + >>> # verify + >>> kdf = Argon2id( + ... salt=salt, + ... length=32, + ... iterations=1, + ... lanes=4, + ... memory_cost=64 * 1024, + ... ad=None, + ... secret=None, + ... ) + >>> kdf.verify(b"my great password", key) + + **All arguments to the constructor are keyword-only.** + + :param bytes salt: A salt should be unique (and randomly generated) per + password and is recommended to be 16 bytes or longer + :param int length: The desired length of the derived key in bytes. + :param int iterations: Also known as passes, this is used to tune + the running time independently of the memory size. + :param int lanes: The number of lanes (parallel threads) to use. Also + known as parallelism. + :param int memory_cost: The amount of memory to use in kibibytes. + 1 kibibyte (KiB) is 1024 bytes. This must be at minimum ``8 * lanes``. + :param bytes ad: Optional associated data. + :param bytes secret: Optional secret data; used for keyed hashing. + + :rfc:`9106` has recommendations for `parameter choice`_. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If Argon2id is not + supported by the OpenSSL version ``cryptography`` is using. + + .. method:: derive(key_material) + + :param key_material: The input key material. + :type key_material: :term:`bytes-like` + :return bytes: the derived key. + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This generates and returns a new key from the supplied password. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. This can be used for + checking whether the password a user provides matches the stored derived + key. + PBKDF2 ------ @@ -1039,3 +1139,4 @@ Interface .. _`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/ +.. _`parameter choice`: https://datatracker.ietf.org/doc/html/rfc9106#section-4 diff --git a/docs/hazmat/primitives/mac/cmac.rst b/docs/hazmat/primitives/mac/cmac.rst index c7eabd9d953f..f5e8b59c0f4d 100644 --- a/docs/hazmat/primitives/mac/cmac.rst +++ b/docs/hazmat/primitives/mac/cmac.rst @@ -28,6 +28,7 @@ A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. >>> from cryptography.hazmat.primitives import cmac >>> from cryptography.hazmat.primitives.ciphers import algorithms + >>> key = b"\x00" * 16 # A real key should come from os.urandom(16) >>> c = cmac.CMAC(algorithms.AES(key)) >>> c.update(b"message to authenticate") >>> c.finalize() diff --git a/docs/hazmat/primitives/mac/poly1305.rst b/docs/hazmat/primitives/mac/poly1305.rst index e3240f5baccf..cc7f9e2b7a58 100644 --- a/docs/hazmat/primitives/mac/poly1305.rst +++ b/docs/hazmat/primitives/mac/poly1305.rst @@ -31,6 +31,7 @@ messages allows an attacker to forge tags. Poly1305 is described in .. doctest:: >>> from cryptography.hazmat.primitives import poly1305 + >>> key = b"\x01" * 32 # A real key should come from os.urandom(32) >>> p = poly1305.Poly1305(key) >>> p.update(b"message to authenticate") >>> p.finalize() diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst index ecd70e6d5084..a1be2abf968f 100644 --- a/docs/hazmat/primitives/padding.rst +++ b/docs/hazmat/primitives/padding.rst @@ -24,16 +24,13 @@ multiple of the block size. >>> from cryptography.hazmat.primitives import padding >>> padder = padding.PKCS7(128).padder() >>> padded_data = padder.update(b"11111111111111112222222222") - >>> padded_data - b'1111111111111111' >>> padded_data += padder.finalize() >>> padded_data b'11111111111111112222222222\x06\x06\x06\x06\x06\x06' >>> unpadder = padding.PKCS7(128).unpadder() >>> data = unpadder.update(padded_data) + >>> data += unpadder.finalize() >>> data - b'1111111111111111' - >>> data + unpadder.finalize() b'11111111111111112222222222' :param block_size: The size of the block in :term:`bits` that the data is @@ -67,16 +64,13 @@ multiple of the block size. >>> padder = padding.ANSIX923(128).padder() >>> padded_data = padder.update(b"11111111111111112222222222") - >>> padded_data - b'1111111111111111' >>> padded_data += padder.finalize() >>> padded_data b'11111111111111112222222222\x00\x00\x00\x00\x00\x06' >>> unpadder = padding.ANSIX923(128).unpadder() >>> data = unpadder.update(padded_data) + >>> data += unpadder.finalize() >>> data - b'1111111111111111' - >>> data + unpadder.finalize() b'11111111111111112222222222' :param block_size: The size of the block in :term:`bits` that the data is diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index e12ccac6ecf5..a648238b6f36 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -174,6 +174,7 @@ Algorithms >>> import struct, os >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + >>> key = os.urandom(32) >>> nonce = os.urandom(8) >>> counter = 0 >>> full_nonce = struct.pack("` for information about installing a newer Rust. @@ -311,7 +313,7 @@ Rust a Rust toolchain. Building ``cryptography`` requires having a working Rust toolchain. The current -minimum supported Rust version is 1.63.0. **This is newer than the Rust some +minimum supported Rust version is 1.65.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/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 933e781308ed..8cbe187e3e3f 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,6 +1,8 @@ AArch accessor affine +argon2 +argon2id Authenticator authenticator backend @@ -15,6 +17,7 @@ Botan Brainpool Bullseye Capitan +Carmichael CentOS changelog Changelog @@ -51,6 +54,7 @@ Docstrings El Encodings endian +Euler extendable facto fallback @@ -62,7 +66,6 @@ hazmat Homebrew hostname hostnames -implementor incrementing indistinguishability initialisms @@ -74,6 +77,9 @@ iOS iterable Kerberos Keychain +KiB +kibibyte +kibibytes Koblitz Lange logins @@ -86,6 +92,7 @@ namespace namespaces macOS naïve +nilpotent Nonces nonces online @@ -106,6 +113,7 @@ preprocessor preprocessors presentational pseudorandom +PSS pyOpenSSL pytest relicensed @@ -128,6 +136,7 @@ Thawte timestamp timestamps toolchain +totient Trixie tunable Ubuntu @@ -135,6 +144,7 @@ unencrypted unicode unpadded unpadding +validator Ventura verifier Verifier diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst index 94605c2e499f..beaa3537cc2c 100644 --- a/docs/x509/ocsp.rst +++ b/docs/x509/ocsp.rst @@ -539,11 +539,28 @@ Interfaces :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.ocsp.OCSPResponse.produced_at_utc`. + A naïve datetime representing the time when the response was produced. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + .. attribute:: produced_at_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the time when the response was produced. + + :raises ValueError: If ``response_status`` is not + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. + .. attribute:: certificate_status :type: :class:`~cryptography.x509.ocsp.OCSPCertStatus` @@ -558,6 +575,12 @@ Interfaces :type: :class:`datetime.datetime` or None + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.ocsp.OCSPResponse.revocation_time_utc`. + A naïve datetime representing the time when the certificate was revoked or ``None`` if the certificate has not been revoked. @@ -565,6 +588,20 @@ Interfaces :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. + .. attribute:: revocation_time_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` or None + + A timezone-aware datetime representing the time when the certificate was + revoked or ``None`` if the certificate has not been revoked. + + :raises ValueError: If ``response_status`` is not + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. + + .. attribute:: revocation_reason :type: :class:`~cryptography.x509.ReasonFlags` or None @@ -580,6 +617,12 @@ Interfaces :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.ocsp.OCSPResponse.this_update_utc`. + A naïve datetime representing the most recent time at which the status being indicated is known by the responder to have been correct. @@ -587,10 +630,29 @@ Interfaces :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. + .. attribute:: this_update_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the most recent time at which the status + being indicated is known by the responder to have been correct. + + :raises ValueError: If ``response_status`` is not + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. + .. attribute:: next_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.ocsp.OCSPResponse.next_update_utc`. + A naïve datetime representing the time when newer information will be available. @@ -598,6 +660,21 @@ Interfaces :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. + + .. attribute:: next_update_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the time when newer information will + be available. + + :raises ValueError: If ``response_status`` is not + :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or + if multiple SINGLERESPs are present. + + .. attribute:: issuer_key_hash :type: bytes @@ -759,9 +836,24 @@ Interfaces :type: :class:`datetime.datetime` or None + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.revocation_time_utc`. + A naïve datetime representing the time when the certificate was revoked or ``None`` if the certificate has not been revoked. + .. attribute:: revocation_time_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` or None + + A timezone-aware datetime representing the time when the certificate was revoked + or ``None`` if the certificate has not been revoked. + .. attribute:: revocation_reason :type: :class:`~cryptography.x509.ReasonFlags` or None @@ -773,16 +865,46 @@ Interfaces :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.ocsp.OCSPSingleResponse.this_update_utc`. + A naïve datetime representing the most recent time at which the status being indicated is known by the responder to have been correct. + .. attribute:: this_update_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the most recent time at which the status + being indicated is known by the responder to have been correct. + .. attribute:: next_update :type: :class:`datetime.datetime` + .. warning:: + + This property is deprecated and will be removed in a future + version. Please switch to the timezone-aware variant + :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.next_update_utc`. + A naïve datetime representing the time when newer information will be available. + .. attribute:: next_update_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` + + A timezone-aware datetime representing the time when newer information will + be available. + .. attribute:: issuer_key_hash :type: bytes diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 166c01f9a58a..a9f655085bb6 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -364,6 +364,21 @@ X.509 Certificate Object >>> isinstance(public_key, rsa.RSAPublicKey) True + .. attribute:: public_key_algorithm_oid + + .. versionadded:: 43.0.0 + + :type: :class:`ObjectIdentifier` + + Returns the :class:`ObjectIdentifier` of the public key algorithm found + inside the certificate. This will be one of the OIDs from + :class:`~cryptography.x509.oid.PublicKeyAlgorithmOID`. + + .. doctest:: + + >>> cert.public_key_algorithm_oid + + .. attribute:: not_valid_before :type: :class:`datetime.datetime` @@ -1033,6 +1048,21 @@ X.509 CSR (Certificate Signing Request) Object >>> isinstance(public_key, rsa.RSAPublicKey) True + .. attribute:: public_key_algorithm_oid + + .. versionadded:: 43.0.0 + + :type: :class:`ObjectIdentifier` + + Returns the :class:`ObjectIdentifier` of the public key algorithm found + inside the certificate. This will be one of the OIDs from + :class:`~cryptography.x509.oid.PublicKeyAlgorithmOID`. + + .. doctest:: + + >>> csr.public_key_algorithm_oid + + .. attribute:: subject :type: :class:`Name` @@ -2198,7 +2228,7 @@ X.509 Extensions public key corresponding to the private key used to sign a certificate. This extension is typically used to assist in determining the appropriate certificate chain. For more information about generation and use of this - extension see `RFC 5280 section 4.2.1.1`_. + extension see :rfc:`5280#section-4.2.1.1`. .. attribute:: oid @@ -2375,6 +2405,7 @@ X.509 Extensions >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.x509.oid import ExtensionOID >>> cert = x509.load_pem_x509_certificate(cryptography_cert_pem) >>> # Get the subjectAltName extension from the certificate >>> ext = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME) @@ -2964,6 +2995,28 @@ X.509 Extensions Returns :attr:`~cryptography.x509.oid.ExtensionOID.CERTIFICATE_POLICIES`. +.. class:: Admissions(authority, admissions) + :canonical: cryptography.x509.extensions.Admissions + + .. versionadded:: 44.0.0 + + The admissions extension contains information on registration and professional admission, + as specified by `Common PKI v2`_. + It is an iterable, containing one or more :class:`~cryptography.x509.Admission` instances. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns :attr:`~cryptography.x509.oid.ExtensionOID.ADMISSIONS`. + + .. attribute:: authority + + :type: :class:`GeneralName` or None + + An optional identifier of the institution who granted the admissions. This serves as the default value + for the admission authority in a single :class:`~cryptography.x509.Admission` if it is not specified there. + Certificate Policies Classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3034,6 +3087,98 @@ These classes may be present within a :class:`CertificatePolicies` instance. A list of integers. +Admissions Classes +~~~~~~~~~~~~~~~~~~ + +These classes may be present within an :class:`Admissions` instance. + +.. class:: Admission(admission_authority, naming_authority, profession_infos) + :canonical: cryptography.x509.extensions.Admission + + .. versionadded:: 44.0.0 + + Contains professional information and optionally the authorization information. + + .. attribute:: admission_authority + + :type: :class:`GeneralName` or None + + An optional identifier of the institution who granted the admission. + + .. attribute:: naming_authority + + :type: :class:`NamingAuthority` or None + + An optional identifier of the institution who is administering the information of the professions in this admission. + This serves as the default value for the naming authority in a single :class:`~cryptography.x509.ProfessionInfo` + if it is not specified there. + + .. attribute:: profession_infos + + :type: list + + An information on the professions that are part of this admission. This is a list of :class:`ProfessionInfo` objects. + +.. class:: ProfessionInfo(naming_authority, profession_items, profession_oids, registration_number, add_profession_info) + :canonical: cryptography.x509.extensions.ProfessionInfo + + .. versionadded:: 44.0.0 + + Contains the information for a single profession in the admission. + + .. attribute:: naming_authority + + :type: :class:`NamingAuthority` or None + + An optional identifier of the institution who is administering the information of this profession. + + .. attribute:: profession_items + + :type: list + + One or more text strings identifying the profession. + + .. attribute:: profession_oids + + :type: list or None + + An optional list of :class:`ObjectIdentifier` elements. Each element in the list corresponds to the resp. + text string in the :attr:`profession_items` list. + + .. attribute:: registration_number + + :type: str or None + + An optional registration number for the profession. + + .. attribute:: add_profession_info + + :type: bytes or None + + Optional additional application-specific information in DER-encoded form. + +.. class:: NamingAuthority(id, url, text) + :canonical: cryptography.x509.extensions.NamingAuthority + + .. versionadded:: 44.0.0 + + Identifies an institution who is responsible for the administration of title registers in an admission. The naming + authority can be identified by an object identifier in the field :attr:`id`, by the text in the field :attr:`text`, + by a URL address in the field :attr:`url`, or by a combination of them. + + .. attribute:: id + + :type: :class:`ObjectIdentifier` or None + + .. attribute:: url + + :type: str or None + + .. attribute:: text + + :type: str or None + + .. _crl_entry_extensions: CRL Entry Extensions @@ -3118,6 +3263,14 @@ These extensions are only valid within a :class:`RevokedCertificate` object. :type: :class:`datetime.datetime` + .. attribute:: invalidity_date_utc + + .. versionadded:: 43.0.0 + + :type: :class:`datetime.datetime` + + The invalidity date in UTC as a timezone-aware datetime object. + OCSP Extensions ~~~~~~~~~~~~~~~ @@ -3792,6 +3945,12 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"1.3.6.1.4.1.311.21.7"``. + .. attribute:: ADMISSIONS + + .. versionadded:: 44.0.0 + + Corresponds to the dotted string ``"1.3.36.8.3.3"``. + .. class:: CRLEntryExtensionOID :canonical: cryptography.hazmat._oid.CRLEntryExtensionOID @@ -3840,6 +3999,65 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"1.2.840.113549.1.9.2"``. + +.. class:: PublicKeyAlgorithmOID + :canonical: cryptography.hazmat._oid.PublicKeyAlgorithmOID + + .. versionadded:: 43.0.0 + + .. attribute:: DSA + + Corresponds to the dotted string ``"1.2.840.10040.4.1"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + public key. + + .. attribute:: EC_PUBLIC_KEY + + Corresponds to the dotted string ``"1.2.840.10045.2.1"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + public key. + + .. attribute:: RSAES_PKCS1_v1_5 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.1"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + public key with + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` + padding. + + .. attribute:: RSASSA_PSS + + Corresponds to the dotted string ``"1.2.840.113549.1.1.10"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + public key with + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + padding. + + .. attribute:: X25519 + + Corresponds to the dotted string ``"1.3.101.110"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey` + public key. + + .. attribute:: X448 + + Corresponds to the dotted string ``"1.3.101.111"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey` + public key. + + .. attribute:: ED25519 + + Corresponds to the dotted string ``"1.3.101.112"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` + public key. + + .. attribute:: ED448 + + Corresponds to the dotted string ``"1.3.101.113"``. This is a + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey` + public key. + + Helper Functions ~~~~~~~~~~~~~~~~ .. currentmodule:: cryptography.x509 @@ -3915,9 +4133,8 @@ Exceptions :type: int The integer value of the unsupported type. The complete list of - types can be found in `RFC 5280 section 4.2.1.6`_. + types can be found in :rfc:`5280#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/ +.. _`Common PKI v2`: https://www.elektronische-vertrauensdienste.de/EVD/SharedDocuments/Downloads/QES/Common_PKI_v2.0_02.pdf diff --git a/docs/x509/tutorial.rst b/docs/x509/tutorial.rst index 45729f28ce15..a71ed1e64f79 100644 --- a/docs/x509/tutorial.rst +++ b/docs/x509/tutorial.rst @@ -150,6 +150,198 @@ Then we generate the certificate itself: And now we have a private key and certificate that can be used for local testing. +Creating a CA hierarchy +----------------------- + +When building your own root hierarchy you need to generate a CA and then +issue certificates (typically intermediates) using it. This example shows +how to generate a root CA, a signing intermediate, and issues a leaf +certificate off that intermediate. X.509 is a complex specification so +this example will require adaptation (typically different extensions) +for specific operating environments. + +Note that this example does not add CRL distribution point or OCSP AIA +extensions, nor does it save the key/certs to persistent storage. + +.. doctest:: + + >>> import datetime + >>> from cryptography.hazmat.primitives.asymmetric import ec + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.x509.oid import NameOID + >>> from cryptography import x509 + >>> # Generate our key + >>> root_key = ec.generate_private_key(ec.SECP256R1()) + >>> subject = issuer = x509.Name([ + ... 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, "PyCA Docs Root CA"), + ... ]) + >>> root_cert = x509.CertificateBuilder().subject_name( + ... subject + ... ).issuer_name( + ... issuer + ... ).public_key( + ... root_key.public_key() + ... ).serial_number( + ... x509.random_serial_number() + ... ).not_valid_before( + ... datetime.datetime.now(datetime.timezone.utc) + ... ).not_valid_after( + ... # Our certificate will be valid for ~10 years + ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*10) + ... ).add_extension( + ... x509.BasicConstraints(ca=True, path_length=None), + ... critical=True, + ... ).add_extension( + ... x509.KeyUsage( + ... digital_signature=True, + ... content_commitment=False, + ... key_encipherment=False, + ... data_encipherment=False, + ... key_agreement=False, + ... key_cert_sign=True, + ... crl_sign=True, + ... encipher_only=False, + ... decipher_only=False, + ... ), + ... critical=True, + ... ).add_extension( + ... x509.SubjectKeyIdentifier.from_public_key(root_key.public_key()), + ... critical=False, + ... ).sign(root_key, hashes.SHA256()) + +With a root certificate created we now want to create our intermediate. + +.. doctest:: + + >>> # Generate our intermediate key + >>> int_key = ec.generate_private_key(ec.SECP256R1()) + >>> subject = x509.Name([ + ... 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, "PyCA Docs Intermediate CA"), + ... ]) + >>> int_cert = x509.CertificateBuilder().subject_name( + ... subject + ... ).issuer_name( + ... root_cert.subject + ... ).public_key( + ... int_key.public_key() + ... ).serial_number( + ... x509.random_serial_number() + ... ).not_valid_before( + ... datetime.datetime.now(datetime.timezone.utc) + ... ).not_valid_after( + ... # Our intermediate will be valid for ~3 years + ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*3) + ... ).add_extension( + ... # Allow no further intermediates (path length 0) + ... x509.BasicConstraints(ca=True, path_length=0), + ... critical=True, + ... ).add_extension( + ... x509.KeyUsage( + ... digital_signature=True, + ... content_commitment=False, + ... key_encipherment=False, + ... data_encipherment=False, + ... key_agreement=False, + ... key_cert_sign=True, + ... crl_sign=True, + ... encipher_only=False, + ... decipher_only=False, + ... ), + ... critical=True, + ... ).add_extension( + ... x509.SubjectKeyIdentifier.from_public_key(int_key.public_key()), + ... critical=False, + ... ).add_extension( + ... x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( + ... root_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value + ... ), + ... critical=False, + ... ).sign(root_key, hashes.SHA256()) + +Now we can issue an end entity certificate off this chain. + +.. doctest:: + + >>> ee_key = ec.generate_private_key(ec.SECP256R1()) + >>> subject = x509.Name([ + ... 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"), + ... ]) + >>> ee_cert = x509.CertificateBuilder().subject_name( + ... subject + ... ).issuer_name( + ... int_cert.subject + ... ).public_key( + ... ee_key.public_key() + ... ).serial_number( + ... x509.random_serial_number() + ... ).not_valid_before( + ... datetime.datetime.now(datetime.timezone.utc) + ... ).not_valid_after( + ... # Our cert will be valid for 10 days + ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10) + ... ).add_extension( + ... x509.SubjectAlternativeName([ + ... # Describe what sites we want this certificate for. + ... x509.DNSName("cryptography.io"), + ... x509.DNSName("www.cryptography.io"), + ... ]), + ... critical=False, + ... ).add_extension( + ... x509.BasicConstraints(ca=False, path_length=None), + ... critical=True, + ... ).add_extension( + ... x509.KeyUsage( + ... digital_signature=True, + ... content_commitment=False, + ... key_encipherment=True, + ... data_encipherment=False, + ... key_agreement=False, + ... key_cert_sign=False, + ... crl_sign=True, + ... encipher_only=False, + ... decipher_only=False, + ... ), + ... critical=True, + ... ).add_extension( + ... x509.ExtendedKeyUsage([ + ... x509.ExtendedKeyUsageOID.CLIENT_AUTH, + ... x509.ExtendedKeyUsageOID.SERVER_AUTH, + ... ]), + ... critical=False, + ... ).add_extension( + ... x509.SubjectKeyIdentifier.from_public_key(ee_key.public_key()), + ... critical=False, + ... ).add_extension( + ... x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( + ... int_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value + ... ), + ... critical=False, + ... ).sign(int_key, hashes.SHA256()) + +And finally we use the verification APIs to validate the chain. + +.. doctest:: + + >>> from cryptography.x509 import DNSName + >>> from cryptography.x509.verification import PolicyBuilder, Store + >>> store = Store([root_cert]) + >>> builder = PolicyBuilder().store(store) + >>> verifier = builder.build_server_verifier(DNSName("cryptography.io")) + >>> chain = verifier.verify(ee_cert, [int_cert]) + >>> len(chain) + 3 + Determining Certificate or Certificate Signing Request Key Type --------------------------------------------------------------- diff --git a/docs/x509/verification.rst b/docs/x509/verification.rst index 6afc75f289e5..70aafd48f94c 100644 --- a/docs/x509/verification.rst +++ b/docs/x509/verification.rst @@ -76,6 +76,9 @@ the root of trust: >>> with open(certifi.where(), "rb") as pems: ... store = Store(load_pem_x509_certificates(pems.read())) >>> builder = PolicyBuilder().store(store) + >>> # See the documentation on `time` below for more details. If + >>> # significant time passes between creating a verifier and performing a + >>> # verification, you may encounter issues with certificate expiration. >>> builder = builder.time(verification_time) >>> verifier = builder.build_server_verifier(DNSName("cryptography.io")) >>> # NOTE: peer and untrusted_intermediates are Certificate and @@ -104,6 +107,76 @@ the root of trust: :class:`cryptography.x509.general_name.DNSName`, :class:`cryptography.x509.general_name.IPAddress`. +.. class:: VerifiedClient + + .. versionadded:: 43.0.0 + + .. versionchanged:: 44.0.0 + Made ``subjects`` optional with the addition of custom extension policies. + + .. attribute:: subjects + + :type: list of :class:`~cryptography.x509.GeneralName` or None + + The subjects presented in the verified client's Subject Alternative Name + extension or ``None`` if the extension is not present. + + .. attribute:: chain + + :type: A list of :class:`~cryptography.x509.Certificate`, in leaf-first order + + The chain of certificates that forms the valid chain to the client + certificate. + + +.. class:: ClientVerifier + + .. versionadded:: 43.0.0 + + A ClientVerifier verifies client certificates. + + It contains and describes various pieces of configurable path + validation logic, such as how deep prospective validation chains may go, + which signature algorithms are allowed, and so forth. + + ClientVerifier instances cannot be constructed directly; + :class:`PolicyBuilder` must be used. + + .. 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 new instance of :class:`VerifiedClient` + + :raises VerificationError: If a valid chain cannot be constructed + + :raises UnsupportedGeneralNameType: If a valid chain exists, but contains an unsupported general name type + .. class:: ServerVerifier .. versionadded:: 42.0.0 @@ -174,7 +247,8 @@ the root of trust: 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. + when :meth:`build_server_verifier` or :meth:`build_client_verifier` + is called. :param new_time: The :class:`datetime.datetime` to use in the verifier @@ -209,3 +283,17 @@ the root of trust: :param subject: A :class:`Subject` to use in the verifier :returns: An instance of :class:`ServerVerifier` + + .. method:: build_client_verifier() + + .. versionadded:: 43.0.0 + + Builds a verifier for verifying client certificates. + + .. warning:: + + This API is not suitable for website (i.e. server) certificate + verification. You **must** use :meth:`build_server_verifier` + for server verification. + + :returns: An instance of :class:`ClientVerifier` diff --git a/noxfile.py b/noxfile.py index f1117d7fee3b..93ac329a0001 100644 --- a/noxfile.py +++ b/noxfile.py @@ -20,16 +20,21 @@ import tomli as tomllib # type: ignore[import-not-found,no-redef] nox.options.reuse_existing_virtualenvs = True +nox.options.default_venv_backend = "uv|virtualenv" -def install(session: nox.Session, *args: str, silent: bool = False) -> None: - if not silent: +def install( + session: nox.Session, + *args: str, + verbose: bool = True, +) -> None: + if verbose: args += ("-v",) session.install( "-c", "ci-constraints-requirements.txt", *args, - silent=silent, + silent=False, ) @@ -42,6 +47,7 @@ def load_pyproject_toml() -> dict: @nox.session(name="tests-ssh") @nox.session(name="tests-randomorder") @nox.session(name="tests-nocoverage") +@nox.session(name="tests-rust-debug") def tests(session: nox.Session) -> None: extras = "test" if session.name == "tests-ssh": @@ -61,10 +67,20 @@ def tests(session: nox.Session) -> None: } ) - install(session, f".[{extras}]") install(session, "-e", "./vectors") + if session.name == "tests-rust-debug": + install( + session, + "--config-settings=build-args=--profile=dev", + f".[{extras}]", + ) + else: + install(session, f".[{extras}]") - session.run("pip", "list") + if session.venv_backend == "uv": + session.run("uv", "pip", "list") + else: + session.run("pip", "list") if session.name != "tests-nocoverage": cov_args = [ @@ -91,7 +107,7 @@ def tests(session: nox.Session) -> None: if session.name != "tests-nocoverage": [rust_so] = glob.glob( - f"{session.virtualenv.location}/**/cryptography/hazmat/bindings/_rust.*", + f"{session.virtualenv.location}/lib/**/cryptography/hazmat/bindings/_rust.*", recursive=True, ) process_rust_coverage(session, [rust_so], prof_location) @@ -149,6 +165,14 @@ def docs(session: nox.Session) -> None: session.run( "python3", "-m", "readme_renderer", "README.rst", "-o", "/dev/null" ) + session.run( + "python3", + "-m", + "readme_renderer", + "vectors/README.rst", + "-o", + "/dev/null", + ) @nox.session(name="docs-linkcheck") @@ -165,6 +189,7 @@ def flake(session: nox.Session) -> None: # TODO: Ideally there'd be a pip flag to install just our dependencies, # but not install us. pyproject_data = load_pyproject_toml() + install(session, "-e", "vectors/") install( session, *pyproject_data["build-system"]["requires"], @@ -173,9 +198,8 @@ def flake(session: nox.Session) -> None: *pyproject_data["project"]["optional-dependencies"]["ssh"], *pyproject_data["project"]["optional-dependencies"]["nox"], ) - install(session, "-e", "vectors/") - session.run("ruff", ".") + session.run("ruff", "check", ".") session.run("ruff", "format", "--check", ".") session.run( "mypy", @@ -207,34 +231,33 @@ def rust(session: nox.Session) -> None: 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) - if session.name != "rust-noclippy": - session.run( - "cargo", - "clippy", - "--all", - "--", - "-D", - "warnings", - external=True, - ) - - build_output = session.run( + session.run("cargo", "fmt", "--all", "--", "--check", external=True) + if session.name != "rust-noclippy": + session.run( "cargo", - "test", - "--no-default-features", + "clippy", "--all", - "--no-run", - "-q", - "--message-format=json", + "--", + "-D", + "warnings", external=True, - silent=True, - ) - session.run( - "cargo", "test", "--no-default-features", "--all", external=True ) + build_output = session.run( + "cargo", + "test", + "--no-default-features", + "--all", + "--no-run", + "-q", + "--message-format=json", + external=True, + silent=True, + ) + session.run( + "cargo", "test", "--no-default-features", "--all", external=True + ) + # It's None on install-only invocations if build_output is not None: assert isinstance(build_output, str) @@ -250,6 +273,7 @@ def rust(session: nox.Session) -> None: @nox.session def local(session): pyproject_data = load_pyproject_toml() + install(session, "-e", "./vectors", verbose=False) install( session, *pyproject_data["build-system"]["requires"], @@ -257,27 +281,23 @@ def local(session): *pyproject_data["project"]["optional-dependencies"]["test"], *pyproject_data["project"]["optional-dependencies"]["ssh"], *pyproject_data["project"]["optional-dependencies"]["nox"], - "flit", - silent=True, + verbose=False, ) - with session.cd("vectors/"): - session.run("flit", "install", "-s", silent=True) session.run("ruff", "format", ".") - session.run("ruff", ".") + session.run("ruff", "check", ".") - 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("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", @@ -288,7 +308,12 @@ def local(session): "noxfile.py", ) - install(session, ".[test]") + session.run( + "maturin", + "develop", + "--release", + *(["--uv"] if session.venv_backend == "uv" else []), + ) if session.posargs: tests = session.posargs @@ -304,10 +329,9 @@ def local(session): *tests, ) - with session.chdir("src/rust/"): - session.run( - "cargo", "test", "--no-default-features", "--all", external=True - ) + session.run( + "cargo", "test", "--no-default-features", "--all", external=True + ) LCOV_SOURCEFILE_RE = re.compile( diff --git a/pyproject.toml b/pyproject.toml index 6369bebf7620..759ceba86b8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,18 +1,20 @@ [build-system] -# These requirements must be kept sync with the requirements on ./github/requirements/build-requirements files +# These requirements must be kept sync with the requirements in +# ./.github/requirements/build-requirements.{in,txt} requires = [ - # First version of setuptools to support pyproject.toml configuration - "setuptools>=61.0.0", - "wheel", + "maturin>=1,<2", + # Must be kept in sync with `project.dependencies` "cffi>=1.12; platform_python_implementation != 'PyPy'", - "setuptools-rust>=1.7.0", + # Needed because cffi imports distutils, and in Python 3.12, distutils has + # been removed from the stdlib, but installing setuptools puts it back. + "setuptools!=74.0.0,!=74.1.0,!=74.1.1,!=74.1.2", ] -build-backend = "setuptools.build_meta" +build-backend = "maturin" [project] name = "cryptography" -version = "42.0.0" +version = "44.0.1" authors = [ {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} ] @@ -39,11 +41,12 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Security :: Cryptography", ] -requires-python = ">=3.7" +requires-python = ">=3.7,!=3.9.0,!=3.9.1" dependencies = [ # Must be kept in sync with `build-system.requires` "cffi>=1.12; platform_python_implementation != 'PyPy'", @@ -56,40 +59,63 @@ source = "https://github.com/pyca/cryptography/" issues = "https://github.com/pyca/cryptography/issues" changelog = "https://cryptography.io/en/latest/changelog/" -[tool.setuptools] -zip-safe = false -package-dir = {"" = "src"} - -[tool.setuptools.packages.find] -where = ["src"] -include = ["cryptography*"] - [project.optional-dependencies] ssh = ["bcrypt >=3.1.5"] # All the following are used for our own testing. -nox = ["nox"] +nox = ["nox >=2024.04.15", "nox[uv] >=2024.03.02; python_version >= '3.8'"] test = [ - "pytest >=6.2.0", - "pytest-benchmark", - "pytest-cov", - "pytest-xdist", - "pretend", - "certifi", + "cryptography_vectors==44.0.1", + "pytest >=7.4.0", + "pytest-benchmark >=4.0", + "pytest-cov >=2.10.1", + "pytest-xdist >=3.5.0", + "pretend >=0.7", + "certifi >=2024", ] test-randomorder = ["pytest-randomly"] -docs = ["sphinx >=5.3.0", "sphinx-rtd-theme >=1.1.1"] -docstest = ["pyenchant >=1.6.11", "readme-renderer", "sphinxcontrib-spelling >=4.0.1"] -sdist = ["build"] +docs = ["sphinx >=5.3.0", "sphinx-rtd-theme >=3.0.0; python_version >= '3.8'"] +docstest = ["pyenchant >=3", "readme-renderer >=30.0", "sphinxcontrib-spelling >=7.3.1"] +sdist = ["build >=1.0.0"] # `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" - +pep8test = ["ruff >=0.3.6", "mypy >=1.4", "check-sdist; python_version >= '3.8'", "click >=8.0.1"] + +[tool.maturin] +python-source = "src" +python-packages = ["cryptography"] +manifest-path = "src/rust/Cargo.toml" +module-name = "cryptography.hazmat.bindings._rust" +locked = true +sdist-generator = "git" +features = ["pyo3/abi3-py37"] +include = [ + "CHANGELOG.rst", + "CONTRIBUTING.rst", + "LICENSE", + "LICENSE.APACHE", + "LICENSE.BSD", + + "docs/**/*", + + "src/_cffi_src/**/*.py", + "src/_cffi_src/**/*.c", + "src/_cffi_src/**/*.h", + + "**/Cargo.toml", + "**/Cargo.lock", + "src/rust/**/*.rs", + + "tests/**/*.py", +] +exclude = [ + "vectors/**/*", + "src/rust/target/**/*", + "docs/_build/**/*", + ".github/**/*", + ".readthedocs.yml", + "ci-constraints-requirements.txt", + "mypy.ini", +] [tool.pytest.ini_options] addopts = "-r s --capture=no --strict-markers --benchmark-disable" @@ -124,14 +150,14 @@ source = [ [tool.coverage.paths] source = [ - "src/cryptography", - "*.nox/*/lib*/python*/site-packages/cryptography", - "*.nox\\*\\Lib\\site-packages\\cryptography", - "*.nox/pypy/site-packages/cryptography", + "src/cryptography", + "*.nox/*/lib*/python*/site-packages/cryptography", + "*.nox\\*\\Lib\\site-packages\\cryptography", + "*.nox/pypy/site-packages/cryptography", ] -tests =[ - "tests/", - "*tests\\", +tests = [ + "tests/", + "*tests\\", ] [tool.coverage.report] @@ -142,11 +168,12 @@ exclude_lines = [ ] [tool.ruff] -ignore = ['N818'] -select = ['E', 'F', 'I', 'N', 'W', 'UP', 'RUF'] line-length = 79 -[tool.ruff.isort] +lint.ignore = ['N818'] +lint.select = ['E', 'F', 'I', 'N', 'W', 'UP', 'RUF'] + +[tool.ruff.lint.isort] known-first-party = ["cryptography", "cryptography_vectors", "tests"] [tool.check-sdist] @@ -157,3 +184,13 @@ git-only = [ ".gitattributes", ".gitignore", ] + +[tool.uv] +# These cover all Python versions, but by expressing multiple environments we +# force uv's resolver to pick the latest versions of packages for each version. +environments = [ + "python_version >= '3.10'", + "python_version >= '3.9' and python_version < '3.10'", + "python_version >= '3.8' and python_version < '3.9'", + "python_version < '3.8'", +] diff --git a/release.py b/release.py index 4abac1a2ed3e..120a6c445738 100644 --- a/release.py +++ b/release.py @@ -22,7 +22,6 @@ def cli(): @cli.command() -@click.argument("version") def release() -> None: base_dir = pathlib.Path(__file__).parent with (base_dir / "pyproject.toml").open("rb") as f: @@ -39,23 +38,24 @@ def release() -> None: run("git", "push", "--tags", "git@github.com:pyca/cryptography.git") -def replace_version( - p: pathlib.Path, variable_name: str, new_version: str -) -> None: +def replace_pattern(p: pathlib.Path, pattern: str, replacement: str) -> None: content = p.read_text() - - pattern = rf"^{variable_name}\s*=\s*.*$" match = re.search(pattern, content, re.MULTILINE) assert match is not None start, end = match.span() - new_content = ( - content[:start] + f'{variable_name} = "{new_version}"' + content[end:] - ) - + new_content = content[:start] + replacement + content[end:] p.write_text(new_content) +def replace_version( + p: pathlib.Path, variable_name: str, new_version: str +) -> None: + replace_pattern( + p, rf"^{variable_name}\s*=\s*.*$", f'{variable_name} = "{new_version}"' + ) + + @cli.command() @click.argument("new_version") def bump_version(new_version: str) -> None: @@ -76,6 +76,19 @@ def bump_version(new_version: str) -> None: new_version, ) + if Version(new_version).is_prerelease: + replace_pattern( + base_dir / "pyproject.toml", + r'"cryptography_vectors(==.*?)?"', + '"cryptography_vectors"', + ) + else: + replace_pattern( + base_dir / "pyproject.toml", + r'"cryptography_vectors(==.*?)?"', + f'"cryptography_vectors=={new_version}"', + ) + if __name__ == "__main__": cli() diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index 6065e7aeed37..7c3bab20f3a0 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -21,8 +21,6 @@ modules=[ # This goes first so we can define some cryptography-wide symbols. "cryptography", - # Provider comes early as well so we define OSSL_LIB_CTX - "provider", "asn1", "bignum", "bio", @@ -37,7 +35,6 @@ "objects", "opensslv", "pem", - "pkcs12", "rand", "rsa", "ssl", @@ -45,7 +42,6 @@ "x509name", "x509v3", "x509_vfy", - "pkcs7", ], ) diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py index d2be452a687b..b1278f36f025 100644 --- a/src/_cffi_src/openssl/asn1.py +++ b/src/_cffi_src/openssl/asn1.py @@ -22,15 +22,9 @@ typedef struct asn1_string_st ASN1_OCTET_STRING; typedef struct asn1_string_st ASN1_IA5STRING; -typedef struct asn1_string_st ASN1_BIT_STRING; typedef struct asn1_string_st ASN1_TIME; typedef ... ASN1_OBJECT; typedef struct asn1_string_st ASN1_STRING; -typedef struct asn1_string_st ASN1_UTF8STRING; -typedef struct { - int type; - ...; -} ASN1_TYPE; typedef ... ASN1_GENERALIZEDTIME; typedef ... ASN1_ENUMERATED; diff --git a/src/_cffi_src/openssl/bio.py b/src/_cffi_src/openssl/bio.py index 1742e348122a..7cd94e37fd15 100644 --- a/src/_cffi_src/openssl/bio.py +++ b/src/_cffi_src/openssl/bio.py @@ -29,7 +29,6 @@ int BIO_should_write(BIO *); int BIO_should_io_special(BIO *); int BIO_should_retry(BIO *); -int BIO_reset(BIO *); BIO_ADDR *BIO_ADDR_new(void); void BIO_ADDR_free(BIO_ADDR *); diff --git a/src/_cffi_src/openssl/crypto.py b/src/_cffi_src/openssl/crypto.py index b81b5de1da27..5284f329619c 100644 --- a/src/_cffi_src/openssl/crypto.py +++ b/src/_cffi_src/openssl/crypto.py @@ -9,8 +9,6 @@ """ TYPES = """ -static const long Cryptography_HAS_MEM_FUNCTIONS; - static const int OPENSSL_VERSION; static const int OPENSSL_CFLAGS; static const int OPENSSL_BUILT_ON; @@ -26,50 +24,7 @@ void *OPENSSL_malloc(size_t); void OPENSSL_free(void *); - - -/* Signature is significantly different in LibreSSL, so expose via different - symbol name */ -int Cryptography_CRYPTO_set_mem_functions( - void *(*)(size_t, const char *, int), - void *(*)(void *, size_t, const char *, int), - void (*)(void *, const char *, int)); - -void *Cryptography_malloc_wrapper(size_t, const char *, int); -void *Cryptography_realloc_wrapper(void *, size_t, const char *, int); -void Cryptography_free_wrapper(void *, const char *, int); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL -static const long Cryptography_HAS_MEM_FUNCTIONS = 0; -int (*Cryptography_CRYPTO_set_mem_functions)( - void *(*)(size_t, const char *, int), - void *(*)(void *, size_t, const char *, int), - void (*)(void *, const char *, int)) = NULL; - -#else -static const long Cryptography_HAS_MEM_FUNCTIONS = 1; - -int Cryptography_CRYPTO_set_mem_functions( - void *(*m)(size_t, const char *, int), - void *(*r)(void *, size_t, const char *, int), - void (*f)(void *, const char *, int) -) { - return CRYPTO_set_mem_functions(m, r, f); -} -#endif - -void *Cryptography_malloc_wrapper(size_t size, const char *path, int line) { - return malloc(size); -} - -void *Cryptography_realloc_wrapper(void *ptr, size_t size, const char *path, - int line) { - return realloc(ptr, size); -} - -void Cryptography_free_wrapper(void *ptr, const char *path, int line) { - free(ptr); -} """ diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index b3543ade73cb..e90a71b375ff 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -4,7 +4,7 @@ from __future__ import annotations -INCLUDES = """ +INCLUDES = r""" /* define our OpenSSL API compatibility level to 1.1.0. Any symbols older than that will raise an error during compilation. */ #define OPENSSL_API_COMPAT 0x10100000L @@ -42,37 +42,12 @@ #define CRYPTOGRAPHY_IS_BORINGSSL 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) - -#if CRYPTOGRAPHY_IS_LIBRESSL -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_380 \ - (LIBRESSL_VERSION_NUMBER < 0x3080000f) - -#else -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_380 (0) +#if OPENSSL_VERSION_NUMBER < 0x10101050 + #error "pyca/cryptography MUST be linked with Openssl 1.1.1e or later" #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_LIBRESSL_LESS_THAN_380; - -static const int CRYPTOGRAPHY_IS_LIBRESSL; -static const int CRYPTOGRAPHY_IS_BORINGSSL; """ FUNCTIONS = """ diff --git a/src/_cffi_src/openssl/ec.py b/src/_cffi_src/openssl/ec.py index 8b9558f8d311..9450b1262609 100644 --- a/src/_cffi_src/openssl/ec.py +++ b/src/_cffi_src/openssl/ec.py @@ -11,8 +11,6 @@ TYPES = """ typedef ... EC_KEY; -typedef ... EC_GROUP; -typedef ... EC_POINT; typedef struct { int nid; const char *comment; @@ -25,8 +23,6 @@ void EC_KEY_free(EC_KEY *); EC_KEY *EC_KEY_new_by_curve_name(int); - -const char *EC_curve_nid2nist(int); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py index 9629a2c8f929..f47e20327003 100644 --- a/src/_cffi_src/openssl/engine.py +++ b/src/_cffi_src/openssl/engine.py @@ -5,7 +5,9 @@ from __future__ import annotations INCLUDES = """ +#if !defined(OPENSSL_NO_ENGINE) || CRYPTOGRAPHY_IS_LIBRESSL #include +#endif """ TYPES = """ diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index 2bb2545fc932..a86e560a659c 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -9,18 +9,10 @@ """ TYPES = """ -static const int CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; - static const int EVP_F_EVP_ENCRYPTFINAL_EX; static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; -static const int EVP_R_BAD_DECRYPT; -static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM; -static const int PKCS12_R_PKCS12_CIPHERFINAL_ERROR; -static const int EVP_R_XTS_DUPLICATED_KEYS; static const int ERR_LIB_EVP; -static const int ERR_LIB_PROV; -static const int ERR_LIB_PKCS12; static const int SSL_TLSEXT_ERR_OK; static const int SSL_TLSEXT_ERR_ALERT_FATAL; @@ -44,25 +36,9 @@ """ CUSTOMIZATIONS = """ -/* This define is tied to provider support and is conditionally - removed if Cryptography_HAS_PROVIDERS is false */ -#ifndef ERR_LIB_PROV -#define ERR_LIB_PROV 0 -#endif - -#ifndef EVP_R_XTS_DUPLICATED_KEYS -static const int EVP_R_XTS_DUPLICATED_KEYS = 0; -#endif - #if CRYPTOGRAPHY_IS_BORINGSSL -static const int ERR_LIB_PKCS12 = 0; static const int EVP_F_EVP_ENCRYPTFINAL_EX = 0; -static const int EVP_R_BAD_DECRYPT = 0; static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH = 0; -static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM = 0; -static const int PKCS12_R_PKCS12_CIPHERFINAL_ERROR = 0; -#else -static const int CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH = 0; #endif /* SSL_R_UNEXPECTED_EOF_WHILE_READING is needed for pyOpenSSL diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py index 54f5388b83d0..f25c9bb52a66 100644 --- a/src/_cffi_src/openssl/evp.py +++ b/src/_cffi_src/openssl/evp.py @@ -10,47 +10,22 @@ TYPES = """ typedef ... EVP_CIPHER; -typedef ... EVP_CIPHER_CTX; typedef ... EVP_MD; typedef ... EVP_MD_CTX; typedef ... EVP_PKEY; typedef ... EVP_PKEY_CTX; static const int EVP_PKEY_RSA; -static const int EVP_PKEY_RSA_PSS; static const int EVP_PKEY_DSA; static const int EVP_PKEY_DH; 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_MAX_MD_SIZE; -static const int EVP_CTRL_AEAD_SET_IVLEN; -static const int EVP_CTRL_AEAD_GET_TAG; -static const int EVP_CTRL_AEAD_SET_TAG; -static const int Cryptography_HAS_SCRYPT; static const int Cryptography_HAS_EVP_PKEY_DHX; -static const long Cryptography_HAS_300_FIPS; -static const long Cryptography_HAS_300_EVP_CIPHER; """ FUNCTIONS = """ const EVP_CIPHER *EVP_get_cipherbyname(const char *); -EVP_CIPHER *EVP_CIPHER_fetch(OSSL_LIB_CTX *, const char *, const char *); -void EVP_CIPHER_free(EVP_CIPHER *); - -int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int); -int EVP_CipherInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, - const unsigned char *, const unsigned char *, int); -int EVP_CipherUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, - const unsigned char *, int); -int EVP_CipherFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); -int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *); -EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void); -void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *); -int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *, int); const EVP_MD *EVP_get_digestbyname(const char *); @@ -81,10 +56,6 @@ int EVP_PKEY_bits(const EVP_PKEY *); int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); - -int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *, int, int, void *); - -int EVP_default_properties_enable_fips(OSSL_LIB_CTX *, int); """ CUSTOMIZATIONS = """ @@ -93,35 +64,4 @@ #else const long Cryptography_HAS_EVP_PKEY_DHX = 0; #endif - -#if CRYPTOGRAPHY_IS_LIBRESSL || defined(OPENSSL_NO_SCRYPT) -static const long Cryptography_HAS_SCRYPT = 0; -#else -static const long Cryptography_HAS_SCRYPT = 1; -#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. */ -#ifndef EVP_PKEY_X448 -#define EVP_PKEY_X448 NID_X448 -#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 0 -#endif - -#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER -static const long Cryptography_HAS_300_FIPS = 1; -static const long Cryptography_HAS_300_EVP_CIPHER = 1; -#else -static const long Cryptography_HAS_300_FIPS = 0; -static const long Cryptography_HAS_300_EVP_CIPHER = 0; -int (*EVP_default_properties_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 """ diff --git a/src/_cffi_src/openssl/nid.py b/src/_cffi_src/openssl/nid.py index 0a38fe038da7..9051977f0ab6 100644 --- a/src/_cffi_src/openssl/nid.py +++ b/src/_cffi_src/openssl/nid.py @@ -9,31 +9,14 @@ """ TYPES = """ -static const int Cryptography_HAS_ED448; -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_subject_alt_name; static const int NID_crl_reason; - -static const int NID_pkcs7_signed; """ FUNCTIONS = """ """ CUSTOMIZATIONS = """ -#ifndef NID_ED448 -static const long Cryptography_HAS_ED448 = 0; -#else -static const long Cryptography_HAS_ED448 = 1; -#endif -#ifndef NID_poly1305 -static const long Cryptography_HAS_POLY1305 = 0; -#else -static const long Cryptography_HAS_POLY1305 = 1; -#endif """ diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py index e069d6126999..04badc47af1b 100644 --- a/src/_cffi_src/openssl/pem.py +++ b/src/_cffi_src/openssl/pem.py @@ -22,8 +22,6 @@ EVP_PKEY *PEM_read_bio_PrivateKey(BIO *, EVP_PKEY **, pem_password_cb *, void *); -PKCS7 *d2i_PKCS7_bio(BIO *, PKCS7 **); - int PEM_write_bio_X509_REQ(BIO *, X509_REQ *); X509_REQ *PEM_read_bio_X509_REQ(BIO *, X509_REQ **, pem_password_cb *, void *); @@ -32,8 +30,6 @@ int PEM_write_bio_X509_CRL(BIO *, X509_CRL *); -PKCS7 *PEM_read_bio_PKCS7(BIO *, PKCS7 **, pem_password_cb *, void *); - DH *PEM_read_bio_DHparams(BIO *, DH **, pem_password_cb *, void *); EVP_PKEY *PEM_read_bio_PUBKEY(BIO *, EVP_PKEY **, pem_password_cb *, void *); diff --git a/src/_cffi_src/openssl/pkcs12.py b/src/_cffi_src/openssl/pkcs12.py deleted file mode 100644 index 234f97b3ea65..000000000000 --- a/src/_cffi_src/openssl/pkcs12.py +++ /dev/null @@ -1,38 +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_PKCS12_SET_MAC; - -typedef ... PKCS12; -""" - -FUNCTIONS = """ -void PKCS12_free(PKCS12 *); - -PKCS12 *d2i_PKCS12_bio(BIO *, PKCS12 **); -int i2d_PKCS12_bio(BIO *, PKCS12 *); -int PKCS12_parse(PKCS12 *, const char *, EVP_PKEY **, X509 **, - Cryptography_STACK_OF_X509 **); -PKCS12 *PKCS12_create(char *, char *, EVP_PKEY *, X509 *, - Cryptography_STACK_OF_X509 *, int, int, int, int, int); -int PKCS12_set_mac(PKCS12 *, const char *, int, unsigned char *, int, int, - const EVP_MD *); -""" - -CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_BORINGSSL -static const long Cryptography_HAS_PKCS12_SET_MAC = 0; -int (*PKCS12_set_mac)(PKCS12 *, const char *, int, unsigned char *, int, int, - const EVP_MD *) = NULL; -#else -static const long Cryptography_HAS_PKCS12_SET_MAC = 1; -#endif -""" diff --git a/src/_cffi_src/openssl/pkcs7.py b/src/_cffi_src/openssl/pkcs7.py deleted file mode 100644 index cce06c6ec0c8..000000000000 --- a/src/_cffi_src/openssl/pkcs7.py +++ /dev/null @@ -1,62 +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_PKCS7_FUNCS; - -typedef struct { - Cryptography_STACK_OF_X509 *cert; - ...; -} PKCS7_SIGNED; - -typedef ... PKCS7_SIGN_ENVELOPE; -typedef ... PKCS7_DIGEST; -typedef ... PKCS7_ENCRYPT; -typedef ... PKCS7_ENVELOPE; -typedef ... PKCS7_SIGNER_INFO; - -typedef struct { - ASN1_OBJECT *type; - union { - char *ptr; - ASN1_OCTET_STRING *data; - PKCS7_SIGNED *sign; - PKCS7_ENVELOPE *enveloped; - PKCS7_SIGN_ENVELOPE *signed_and_enveloped; - PKCS7_DIGEST *digest; - PKCS7_ENCRYPT *encrypted; - ASN1_TYPE *other; - } d; - ...; -} PKCS7; - -static const int PKCS7_TEXT; -""" - -FUNCTIONS = """ -void PKCS7_free(PKCS7 *); -/* 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 **); -""" - -CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_BORINGSSL -static const long Cryptography_HAS_PKCS7_FUNCS = 0; - -int (*PKCS7_verify)(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, - BIO *, int) = NULL; -PKCS7 *(*SMIME_read_PKCS7)(BIO *, BIO **) = NULL; -#else -static const long Cryptography_HAS_PKCS7_FUNCS = 1; -#endif -""" diff --git a/src/_cffi_src/openssl/provider.py b/src/_cffi_src/openssl/provider.py deleted file mode 100644 index 769fded96d23..000000000000 --- a/src/_cffi_src/openssl/provider.py +++ /dev/null @@ -1,43 +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_OPENSSL_300_OR_GREATER -#include -#include -#endif -""" - -TYPES = """ -static const long Cryptography_HAS_PROVIDERS; - -typedef ... OSSL_PROVIDER; -typedef ... OSSL_LIB_CTX; - -static const long PROV_R_BAD_DECRYPT; -static const long PROV_R_XTS_DUPLICATED_KEYS; -static const long PROV_R_WRONG_FINAL_BLOCK_LENGTH; -""" - -FUNCTIONS = """ -OSSL_PROVIDER *OSSL_PROVIDER_load(OSSL_LIB_CTX *, const char *); -int OSSL_PROVIDER_unload(OSSL_PROVIDER *prov); -""" - -CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER -static const long Cryptography_HAS_PROVIDERS = 1; -#else -static const long Cryptography_HAS_PROVIDERS = 0; -typedef void OSSL_PROVIDER; -typedef void OSSL_LIB_CTX; -static const long PROV_R_BAD_DECRYPT = 0; -static const long PROV_R_XTS_DUPLICATED_KEYS = 0; -static const long PROV_R_WRONG_FINAL_BLOCK_LENGTH = 0; -OSSL_PROVIDER *(*OSSL_PROVIDER_load)(OSSL_LIB_CTX *, const char *) = NULL; -int (*OSSL_PROVIDER_unload)(OSSL_PROVIDER *) = NULL; -#endif -""" diff --git a/src/_cffi_src/openssl/rand.py b/src/_cffi_src/openssl/rand.py index ee00fe68d821..50fbeb279e45 100644 --- a/src/_cffi_src/openssl/rand.py +++ b/src/_cffi_src/openssl/rand.py @@ -9,7 +9,6 @@ """ TYPES = """ -typedef ... RAND_METHOD; """ FUNCTIONS = """ diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py index c78d681dca8d..099ec4db13a6 100644 --- a/src/_cffi_src/openssl/ssl.py +++ b/src/_cffi_src/openssl/ssl.py @@ -297,6 +297,9 @@ SSL_SESSION *SSL_get_session(const SSL *); +SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **, const unsigned char **, long); +int i2d_SSL_SESSION(SSL_SESSION *, unsigned char **); + uint64_t SSL_set_options(SSL *, uint64_t); uint64_t SSL_get_options(SSL *); diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index b43593543cee..8527a85eeb9f 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -15,17 +15,14 @@ * Note that the result is an opaque type. */ typedef STACK_OF(X509) Cryptography_STACK_OF_X509; -typedef STACK_OF(X509_CRL) Cryptography_STACK_OF_X509_CRL; typedef STACK_OF(X509_REVOKED) Cryptography_STACK_OF_X509_REVOKED; """ TYPES = """ typedef ... Cryptography_STACK_OF_X509; -typedef ... Cryptography_STACK_OF_X509_CRL; typedef ... Cryptography_STACK_OF_X509_REVOKED; typedef ... X509_ALGOR; -typedef ... X509_ATTRIBUTE; typedef ... X509_EXTENSION; typedef ... X509_EXTENSIONS; typedef ... X509_REQ; @@ -33,10 +30,6 @@ typedef ... X509_CRL; typedef ... X509; -typedef ... NETSCAPE_SPKI; - -typedef ... PKCS8_PRIV_KEY_INFO; - typedef void (*sk_X509_EXTENSION_freefunc)(X509_EXTENSION *); """ @@ -53,8 +46,6 @@ EVP_PKEY *X509_get_pubkey(X509 *); int X509_set_pubkey(X509 *, EVP_PKEY *); -unsigned char *X509_alias_get0(X509 *, int *); -int X509_alias_set1(X509 *, const unsigned char *, int); int X509_sign(X509 *, EVP_PKEY *, const EVP_MD *); int X509_digest(const X509 *, const EVP_MD *, unsigned char *, unsigned int *); @@ -108,14 +99,6 @@ int i2d_X509_CRL_bio(BIO *, X509_CRL *); void X509_CRL_free(X509_CRL *); -int NETSCAPE_SPKI_verify(NETSCAPE_SPKI *, EVP_PKEY *); -int NETSCAPE_SPKI_sign(NETSCAPE_SPKI *, EVP_PKEY *, const EVP_MD *); -char *NETSCAPE_SPKI_b64_encode(NETSCAPE_SPKI *); -EVP_PKEY *NETSCAPE_SPKI_get_pubkey(NETSCAPE_SPKI *); -int NETSCAPE_SPKI_set_pubkey(NETSCAPE_SPKI *, EVP_PKEY *); -NETSCAPE_SPKI *NETSCAPE_SPKI_new(void); -void NETSCAPE_SPKI_free(NETSCAPE_SPKI *); - /* ASN1 serialization */ int i2d_X509_bio(BIO *, X509 *); X509 *d2i_X509_bio(BIO *, X509 **); diff --git a/src/_cffi_src/openssl/x509_vfy.py b/src/_cffi_src/openssl/x509_vfy.py index 26eed9974f82..57c8d870011e 100644 --- a/src/_cffi_src/openssl/x509_vfy.py +++ b/src/_cffi_src/openssl/x509_vfy.py @@ -14,12 +14,10 @@ * together with another opaque typedef for the same name in the TYPES section. * Note that the result is an opaque type. */ -typedef STACK_OF(ASN1_OBJECT) Cryptography_STACK_OF_ASN1_OBJECT; typedef STACK_OF(X509_OBJECT) Cryptography_STACK_OF_X509_OBJECT; """ TYPES = """ -typedef ... Cryptography_STACK_OF_ASN1_OBJECT; typedef ... Cryptography_STACK_OF_X509_OBJECT; typedef ... X509_OBJECT; diff --git a/src/_cffi_src/openssl/x509name.py b/src/_cffi_src/openssl/x509name.py index 5e0349e4846a..8c3c4de758dc 100644 --- a/src/_cffi_src/openssl/x509name.py +++ b/src/_cffi_src/openssl/x509name.py @@ -11,11 +11,9 @@ * See the comment above Cryptography_STACK_OF_X509 in x509.py */ typedef STACK_OF(X509_NAME) Cryptography_STACK_OF_X509_NAME; -typedef STACK_OF(X509_NAME_ENTRY) Cryptography_STACK_OF_X509_NAME_ENTRY; """ TYPES = """ -typedef ... Cryptography_STACK_OF_X509_NAME_ENTRY; typedef ... X509_NAME; typedef ... X509_NAME_ENTRY; typedef ... Cryptography_STACK_OF_X509_NAME; @@ -28,6 +26,7 @@ unsigned long X509_NAME_hash(X509_NAME *); int i2d_X509_NAME(X509_NAME *, unsigned char **); +X509_NAME *d2i_X509_NAME(X509_NAME **, const unsigned char **, long); X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *, int); void X509_NAME_ENTRY_free(X509_NAME_ENTRY *); int X509_NAME_get_index_by_NID(X509_NAME *, int, int); diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 7f04a2cbce35..9905982fff44 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -6,18 +6,9 @@ INCLUDES = """ #include - -/* - * This is part of a work-around for the difficulty cffi has in dealing with - * `STACK_OF(foo)` as the name of a type. We invent a new, simpler name that - * will be an alias for this type and use the alias throughout. This works - * together with another opaque typedef for the same name in the TYPES section. - * Note that the result is an opaque type. - */ """ TYPES = """ -typedef ... EXTENDED_KEY_USAGE; typedef ... CONF; typedef struct { @@ -41,8 +32,6 @@ } d; ...; } GENERAL_NAME; - -static const long X509V3_EXT_ERROR_UNKNOWN; """ diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py index 7d62a32b6fab..d1ca20a6073b 100644 --- a/src/cryptography/__about__.py +++ b/src/cryptography/__about__.py @@ -5,12 +5,12 @@ from __future__ import annotations __all__ = [ - "__version__", "__author__", "__copyright__", + "__version__", ] -__version__ = "42.0.0" +__version__ = "44.0.1" __author__ = "The Python Cryptographic Authority and individual contributors" diff --git a/src/cryptography/__init__.py b/src/cryptography/__init__.py index 86b9a25726d1..f37370e90a71 100644 --- a/src/cryptography/__init__.py +++ b/src/cryptography/__init__.py @@ -4,10 +4,23 @@ from __future__ import annotations +import sys +import warnings + +from cryptography import utils from cryptography.__about__ import __author__, __copyright__, __version__ __all__ = [ - "__version__", "__author__", "__copyright__", + "__version__", ] + +if sys.version_info[:2] == (3, 7): + warnings.warn( + "Python 3.7 is no longer supported by the Python core team " + "and support for it is deprecated in cryptography. A future " + "release of cryptography will remove support for Python 3.7.", + utils.CryptographyDeprecationWarning, + stacklevel=2, + ) diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py index 35ce1131a921..868ecb277789 100644 --- a/src/cryptography/fernet.py +++ b/src/cryptography/fernet.py @@ -213,3 +213,11 @@ def decrypt_at_time( except InvalidToken: pass raise InvalidToken + + def extract_timestamp(self, msg: bytes | str) -> int: + for f in self._fernets: + try: + return f.extract_timestamp(msg) + except InvalidToken: + pass + raise InvalidToken diff --git a/src/cryptography/hazmat/_oid.py b/src/cryptography/hazmat/_oid.py index c5d062c1374a..8bd240d099a9 100644 --- a/src/cryptography/hazmat/_oid.py +++ b/src/cryptography/hazmat/_oid.py @@ -39,6 +39,7 @@ class ExtensionOID: PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3") SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7") + ADMISSIONS = ObjectIdentifier("1.3.36.8.3.3") class OCSPExtensionOID: @@ -154,6 +155,17 @@ class SignatureAlgorithmOID: } +class PublicKeyAlgorithmOID: + DSA = ObjectIdentifier("1.2.840.10040.4.1") + EC_PUBLIC_KEY = ObjectIdentifier("1.2.840.10045.2.1") + RSAES_PKCS1_v1_5 = ObjectIdentifier("1.2.840.113549.1.1.1") + RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") + X25519 = ObjectIdentifier("1.3.101.110") + X448 = ObjectIdentifier("1.3.101.111") + ED25519 = ObjectIdentifier("1.3.101.112") + ED448 = ObjectIdentifier("1.3.101.113") + + class ExtendedKeyUsageOID: SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") @@ -245,6 +257,12 @@ class AttributeOID: SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" ), + PublicKeyAlgorithmOID.DSA: "dsaEncryption", + PublicKeyAlgorithmOID.EC_PUBLIC_KEY: "id-ecPublicKey", + PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5: "rsaEncryption", + PublicKeyAlgorithmOID.RSASSA_PSS: "rsassaPss", + PublicKeyAlgorithmOID.X25519: "X25519", + PublicKeyAlgorithmOID.X448: "X448", ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", @@ -267,6 +285,7 @@ class AttributeOID: ), ExtensionOID.PRECERT_POISON: "ctPoison", ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate", + ExtensionOID.ADMISSIONS: "Admissions", CRLEntryExtensionOID.CRL_REASON: "cRLReason", CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", diff --git a/src/cryptography/hazmat/backends/openssl/aead.py b/src/cryptography/hazmat/backends/openssl/aead.py deleted file mode 100644 index f1d990106474..000000000000 --- a/src/cryptography/hazmat/backends/openssl/aead.py +++ /dev/null @@ -1,272 +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 InvalidTag - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - from cryptography.hazmat.primitives.ciphers.aead import ( - AESCCM, - AESGCM, - ) - - _AEADTypes = typing.Union[AESCCM, AESGCM] - - -def _aead_cipher_supported(backend: Backend, cipher: _AEADTypes) -> bool: - cipher_name = _evp_cipher_cipher_name(cipher) - - return backend._lib.EVP_get_cipherbyname(cipher_name) != backend._ffi.NULL - - -def _encrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: list[bytes], - tag_length: int, -) -> bytes: - return _evp_cipher_encrypt( - backend, cipher, nonce, data, associated_data, tag_length - ) - - -def _decrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: list[bytes], - tag_length: int, -) -> bytes: - return _evp_cipher_decrypt( - backend, cipher, nonce, data, associated_data, tag_length - ) - - -_ENCRYPT = 1 -_DECRYPT = 0 - - -def _evp_cipher_cipher_name(cipher: _AEADTypes) -> bytes: - from cryptography.hazmat.primitives.ciphers.aead import ( - AESCCM, - AESGCM, - ) - - if isinstance(cipher, AESCCM): - return f"aes-{len(cipher._key) * 8}-ccm".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): - evp_cipher = backend._lib.EVP_get_cipherbyname(cipher_name) - backend.openssl_assert(evp_cipher != backend._ffi.NULL) - return evp_cipher - - -def _evp_cipher_aead_setup( - backend: Backend, - cipher_name: bytes, - key: bytes, - nonce: bytes, - tag: bytes | None, - tag_len: int, - operation: int, -): - evp_cipher = _evp_cipher(cipher_name, backend) - ctx = backend._lib.EVP_CIPHER_CTX_new() - ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) - res = backend._lib.EVP_CipherInit_ex( - ctx, - evp_cipher, - backend._ffi.NULL, - backend._ffi.NULL, - backend._ffi.NULL, - int(operation == _ENCRYPT), - ) - backend.openssl_assert(res != 0) - # CCM requires the IVLEN to be set before calling SET_TAG on decrypt - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - backend._lib.EVP_CTRL_AEAD_SET_IVLEN, - len(nonce), - backend._ffi.NULL, - ) - backend.openssl_assert(res != 0) - if operation == _DECRYPT: - assert tag is not None - _evp_cipher_set_tag(backend, ctx, tag) - elif cipher_name.endswith(b"-ccm"): - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - backend._lib.EVP_CTRL_AEAD_SET_TAG, - tag_len, - backend._ffi.NULL, - ) - backend.openssl_assert(res != 0) - - nonce_ptr = backend._ffi.from_buffer(nonce) - key_ptr = backend._ffi.from_buffer(key) - res = backend._lib.EVP_CipherInit_ex( - ctx, - backend._ffi.NULL, - backend._ffi.NULL, - key_ptr, - nonce_ptr, - int(operation == _ENCRYPT), - ) - backend.openssl_assert(res != 0) - return ctx - - -def _evp_cipher_set_tag(backend, ctx, tag: bytes) -> None: - tag_ptr = backend._ffi.from_buffer(tag) - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag_ptr - ) - backend.openssl_assert(res != 0) - - -def _evp_cipher_set_length(backend: Backend, ctx, data_len: int) -> None: - intptr = backend._ffi.new("int *") - res = backend._lib.EVP_CipherUpdate( - ctx, backend._ffi.NULL, intptr, backend._ffi.NULL, data_len - ) - backend.openssl_assert(res != 0) - - -def _evp_cipher_process_aad( - backend: Backend, ctx, associated_data: bytes -) -> None: - outlen = backend._ffi.new("int *") - a_data_ptr = backend._ffi.from_buffer(associated_data) - res = backend._lib.EVP_CipherUpdate( - ctx, backend._ffi.NULL, outlen, a_data_ptr, len(associated_data) - ) - backend.openssl_assert(res != 0) - - -def _evp_cipher_process_data(backend: Backend, ctx, data: bytes) -> bytes: - outlen = backend._ffi.new("int *") - 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)) - backend.openssl_assert(res != 0) - return backend._ffi.buffer(buf, outlen[0])[:] - - -def _evp_cipher_encrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: list[bytes], - tag_length: int, -) -> bytes: - 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. - # However calling this with any other AEAD results in an error - if isinstance(cipher, AESCCM): - _evp_cipher_set_length(backend, ctx, len(data)) - - for ad in associated_data: - _evp_cipher_process_aad(backend, ctx, ad) - processed_data = _evp_cipher_process_data(backend, ctx, data) - outlen = backend._ffi.new("int *") - # All AEADs we support besides OCB are streaming so they return nothing - # in finalization. OCB can return up to (16 byte block - 1) bytes so - # we need a buffer here too. - buf = backend._ffi.new("unsigned char[]", 16) - res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen) - backend.openssl_assert(res != 0) - processed_data += backend._ffi.buffer(buf, outlen[0])[:] - tag_buf = backend._ffi.new("unsigned char[]", tag_length) - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_GET_TAG, tag_length, tag_buf - ) - backend.openssl_assert(res != 0) - tag = backend._ffi.buffer(tag_buf)[:] - - return processed_data + tag - - -def _evp_cipher_decrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: list[bytes], - tag_length: int, -) -> bytes: - from cryptography.hazmat.primitives.ciphers.aead import AESCCM - - if len(data) < tag_length: - raise InvalidTag - - 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. - # However calling this with any other AEAD results in an error - if isinstance(cipher, AESCCM): - _evp_cipher_set_length(backend, ctx, len(data)) - - for ad in associated_data: - _evp_cipher_process_aad(backend, ctx, ad) - # CCM has a different error path if the tag doesn't match. Errors are - # raised in Update and Final is irrelevant. - if isinstance(cipher, AESCCM): - outlen = backend._ffi.new("int *") - buf = backend._ffi.new("unsigned char[]", len(data)) - d_ptr = backend._ffi.from_buffer(data) - res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, d_ptr, len(data)) - if res != 1: - backend._consume_errors() - raise InvalidTag - - processed_data = backend._ffi.buffer(buf, outlen[0])[:] - else: - processed_data = _evp_cipher_process_data(backend, ctx, data) - outlen = backend._ffi.new("int *") - # OCB can return up to 15 bytes (16 byte block - 1) in finalization - buf = backend._ffi.new("unsigned char[]", 16) - res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen) - processed_data += backend._ffi.buffer(buf, outlen[0])[:] - if res == 0: - backend._consume_errors() - raise InvalidTag - - return processed_data diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 5d9eb2768dfb..78996848f391 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -4,18 +4,9 @@ from __future__ import annotations -import collections -import contextlib -import itertools -import typing - -from cryptography import utils, x509 -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends.openssl import aead -from cryptography.hazmat.backends.openssl.ciphers import _CipherContext 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 import hashes from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import utils as asym_utils @@ -25,51 +16,16 @@ PSS, PKCS1v15, ) -from cryptography.hazmat.primitives.asymmetric.types import ( - PrivateKeyTypes, -) from cryptography.hazmat.primitives.ciphers import ( CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, - AES128, - AES256, - ARC4, - SM4, - Camellia, - ChaCha20, - TripleDES, - _BlowfishInternal, - _CAST5Internal, - _IDEAInternal, - _SEEDInternal, ) from cryptography.hazmat.primitives.ciphers.modes import ( CBC, - CFB, - CFB8, - CTR, - ECB, - GCM, - OFB, - XTS, Mode, ) -from cryptography.hazmat.primitives.serialization.pkcs12 import ( - PBES, - PKCS12Certificate, - PKCS12KeyAndCertificates, - PKCS12PrivateKeyTypes, - _PKCS12CATypes, -) - -_MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) - - -# Not actually supported, just used as a marker for some serialization tests. -class _RC2: - pass class Backend: @@ -79,18 +35,6 @@ class Backend: name = "openssl" - # FIPS has opinions about acceptable algorithms and key sizes, but the - # 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: typing.ClassVar[set[bytes]] = { - b"aes-128-ccm", - b"aes-192-ccm", - b"aes-256-ccm", - b"aes-128-gcm", - b"aes-192-gcm", - b"aes-256-gcm", - } # TripleDES encryption is disallowed/deprecated throughout 2023 in # FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA). _fips_ciphers = (AES,) @@ -128,30 +72,20 @@ def __init__(self) -> None: self._lib = self._binding.lib self._fips_enabled = rust_openssl.is_fips_enabled() - self._cipher_registry: dict[ - tuple[type[CipherAlgorithm], type[Mode]], - typing.Callable, - ] = {} - self._register_default_ciphers() - def __repr__(self) -> str: - return "".format( - self.openssl_version_text(), - self._fips_enabled, - self._binding._legacy_provider_loaded, + return ( + f"" ) - def openssl_assert( - self, - ok: bool, - errors: list[rust_openssl.OpenSSLError] | None = None, - ) -> None: - return binding._openssl_assert(ok, errors=errors) + def openssl_assert(self, ok: bool) -> None: + return binding._openssl_assert(ok) def _enable_fips(self) -> None: # This function enables FIPS mode for OpenSSL 3.0.0 on installs that # have the FIPS provider installed properly. - self._binding._enable_fips() + rust_openssl.enable_fips(rust_openssl._providers) assert rust_openssl.is_fips_enabled() self._fips_enabled = rust_openssl.is_fips_enabled() @@ -160,37 +94,18 @@ def openssl_version_text(self) -> str: Friendly string name of the loaded OpenSSL library. This is not necessarily the same version as it was compiled against. - Example: OpenSSL 1.1.1d 10 Sep 2019 + Example: OpenSSL 3.2.1 30 Jan 2024 """ - return self._ffi.string( - self._lib.OpenSSL_version(self._lib.OPENSSL_VERSION) - ).decode("ascii") + return rust_openssl.openssl_version_text() def openssl_version_number(self) -> int: - return self._lib.OpenSSL_version_num() - - def _evp_md_from_algorithm(self, algorithm: hashes.HashAlgorithm): - if algorithm.name in ("blake2b", "blake2s"): - alg = f"{algorithm.name}{algorithm.digest_size * 8}".encode( - "ascii" - ) - else: - alg = algorithm.name.encode("ascii") - - evp_md = self._lib.EVP_get_digestbyname(alg) - return evp_md - - def _evp_md_non_null_from_algorithm(self, algorithm: hashes.HashAlgorithm): - evp_md = self._evp_md_from_algorithm(algorithm) - self.openssl_assert(evp_md != self._ffi.NULL) - return evp_md + return rust_openssl.openssl_version() def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): return False - evp_md = self._evp_md_from_algorithm(algorithm) - return evp_md != self._ffi.NULL + return rust_openssl.hashes.hash_supported(algorithm) def signature_hash_supported( self, algorithm: hashes.HashAlgorithm @@ -205,7 +120,13 @@ def scrypt_supported(self) -> bool: if self._fips_enabled: return False else: - return self._lib.Cryptography_HAS_SCRYPT == 1 + return hasattr(rust_openssl.kdf.Scrypt, "derive") + + def argon2_supported(self) -> bool: + if self._fips_enabled: + return False + else: + return hasattr(rust_openssl.kdf.Argon2id, "derive") def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: # FIPS mode still allows SHA1 for HMAC @@ -221,103 +142,7 @@ def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: if not isinstance(cipher, self._fips_ciphers): return False - try: - adapter = self._cipher_registry[type(cipher), type(mode)] - except KeyError: - return False - evp_cipher = adapter(self, cipher, mode) - return self._ffi.NULL != evp_cipher - - def register_cipher_adapter(self, cipher_cls, mode_cls, adapter) -> None: - if (cipher_cls, mode_cls) in self._cipher_registry: - raise ValueError( - f"Duplicate registration for: {cipher_cls} {mode_cls}." - ) - self._cipher_registry[cipher_cls, mode_cls] = adapter - - def _register_default_ciphers(self) -> None: - for cipher_cls in [AES, AES128, AES256]: - for mode_cls in [CBC, CTR, ECB, OFB, CFB, CFB8, GCM]: - self.register_cipher_adapter( - cipher_cls, - mode_cls, - GetCipherByName( - "{cipher.name}-{cipher.key_size}-{mode.name}" - ), - ) - for mode_cls in [CBC, CTR, ECB, OFB, CFB]: - self.register_cipher_adapter( - Camellia, - mode_cls, - GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}"), - ) - for mode_cls in [CBC, CFB, CFB8, OFB]: - self.register_cipher_adapter( - TripleDES, mode_cls, GetCipherByName("des-ede3-{mode.name}") - ) - 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( - "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, GCM]: - self.register_cipher_adapter( - SM4, mode_cls, GetCipherByName("sm4-{mode.name}") - ) - # Don't register legacy ciphers if they're unavailable. Hypothetically - # this wouldn't be necessary because we test availability by seeing if - # we get an EVP_CIPHER * in the _CipherContext __init__, but OpenSSL 3 - # will return a valid pointer even though the cipher is unavailable. - if ( - self._binding._legacy_provider_loaded - or not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER - ): - for mode_cls in [CBC, CFB, OFB, ECB]: - self.register_cipher_adapter( - _BlowfishInternal, - mode_cls, - GetCipherByName("bf-{mode.name}"), - ) - for mode_cls in [CBC, CFB, OFB, ECB]: - self.register_cipher_adapter( - _SEEDInternal, - mode_cls, - GetCipherByName("seed-{mode.name}"), - ) - for cipher_cls, mode_cls in itertools.product( - [_CAST5Internal, _IDEAInternal], - [CBC, OFB, CFB, ECB], - ): - self.register_cipher_adapter( - cipher_cls, - mode_cls, - GetCipherByName("{cipher.name}-{mode.name}"), - ) - self.register_cipher_adapter( - ARC4, type(None), GetCipherByName("rc4") - ) - # We don't actually support RC2, this is just used by some tests. - self.register_cipher_adapter( - _RC2, type(None), GetCipherByName("rc2") - ) - - def create_symmetric_encryption_ctx( - self, cipher: CipherAlgorithm, mode: Mode - ) -> _CipherContext: - return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) - - def create_symmetric_decryption_ctx( - self, cipher: CipherAlgorithm, mode: Mode - ) -> _CipherContext: - return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + return rust_openssl.ciphers.cipher_supported(cipher, mode) def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: return self.hmac_supported(algorithm) @@ -325,50 +150,6 @@ def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: def _consume_errors(self) -> list[rust_openssl.OpenSSLError]: return rust_openssl.capture_error_stack() - def generate_rsa_parameters_supported( - self, public_exponent: int, key_size: int - ) -> bool: - return ( - public_exponent >= 3 - and public_exponent & 1 != 0 - and key_size >= 512 - ) - - def _bytes_to_bio(self, data: bytes) -> _MemoryBIO: - """ - Return a _MemoryBIO namedtuple of (BIO, char*). - - The char* is the storage for the BIO and it must stay alive until the - BIO is finished with. - """ - data_ptr = self._ffi.from_buffer(data) - bio = self._lib.BIO_new_mem_buf(data_ptr, len(data)) - self.openssl_assert(bio != self._ffi.NULL) - - return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_ptr) - - def _create_mem_bio_gc(self): - """ - Creates an empty memory BIO. - """ - bio_method = self._lib.BIO_s_mem() - self.openssl_assert(bio_method != self._ffi.NULL) - bio = self._lib.BIO_new(bio_method) - self.openssl_assert(bio != self._ffi.NULL) - bio = self._ffi.gc(bio, self._lib.BIO_free) - return bio - - def _read_mem_bio(self, bio) -> bytes: - """ - Reads a memory BIO. This only works on memory BIOs. - """ - buf = self._ffi.new("char **") - buf_len = self._lib.BIO_get_mem_data(bio, buf) - self.openssl_assert(buf_len > 0) - self.openssl_assert(buf[0] != self._ffi.NULL) - bio_data = self._ffi.buffer(buf[0], buf_len)[:] - return bio_data - def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and isinstance(algorithm, hashes.SHA1): return False @@ -411,7 +192,8 @@ def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool: def dsa_supported(self) -> bool: return ( - not self._lib.CRYPTOGRAPHY_IS_BORINGSSL and not self._fips_enabled + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not self._fips_enabled ) def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: @@ -424,81 +206,6 @@ def cmac_algorithm_supported(self, algorithm) -> bool: algorithm, CBC(b"\x00" * algorithm.block_size) ) - def _cert2ossl(self, cert: x509.Certificate) -> typing.Any: - data = cert.public_bytes(serialization.Encoding.DER) - mem_bio = self._bytes_to_bio(data) - x509 = self._lib.d2i_X509_bio(mem_bio.bio, self._ffi.NULL) - self.openssl_assert(x509 != self._ffi.NULL) - x509 = self._ffi.gc(x509, self._lib.X509_free) - return x509 - - def _ossl2cert(self, x509_ptr: typing.Any) -> x509.Certificate: - bio = self._create_mem_bio_gc() - res = self._lib.i2d_X509_bio(bio, x509_ptr) - self.openssl_assert(res == 1) - return x509.load_der_x509_certificate(self._read_mem_bio(bio)) - - def _key2ossl(self, key: PKCS12PrivateKeyTypes) -> typing.Any: - data = key.private_bytes( - serialization.Encoding.DER, - serialization.PrivateFormat.PKCS8, - serialization.NoEncryption(), - ) - mem_bio = self._bytes_to_bio(data) - - evp_pkey = self._lib.d2i_PrivateKey_bio( - mem_bio.bio, - self._ffi.NULL, - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - return self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - def _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 " - "incorrect format or it may be encrypted with an unsupported " - "algorithm." - ) - - elif ( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT - ) - or errors[0]._lib_reason_match( - self._lib.ERR_LIB_PKCS12, - self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, - ) - or ( - self._lib.Cryptography_HAS_PROVIDERS - and errors[0]._lib_reason_match( - self._lib.ERR_LIB_PROV, - self._lib.PROV_R_BAD_DECRYPT, - ) - ) - ): - raise ValueError("Bad decrypt. Incorrect password?") - - elif any( - error._lib_reason_match( - self._lib.ERR_LIB_EVP, - self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM, - ) - for error in errors - ): - raise ValueError("Unsupported public key algorithm.") - - else: - raise ValueError( - "Could not deserialize key data. The data may be in an " - "incorrect format, it may be encrypted with an unsupported " - "algorithm, or it may be an unsupported key type (e.g. EC " - "curves with explicit parameters).", - errors, - ) - def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: if self._fips_enabled and not isinstance( curve, self._fips_ecdh_curves @@ -529,30 +236,22 @@ def elliptic_curve_exchange_algorithm_supported( ) def dh_supported(self) -> bool: - return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL def dh_x942_serialization_supported(self) -> bool: return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 def x25519_supported(self) -> bool: - # Beginning with OpenSSL 3.2.0, X25519 is considered FIPS. - if ( - self._fips_enabled - and not self._lib.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER - ): + if self._fips_enabled: return False return True def x448_supported(self) -> bool: - # Beginning with OpenSSL 3.2.0, X448 is considered FIPS. - if ( - self._fips_enabled - and not self._lib.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER - ): + if self._fips_enabled: return False return ( - not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL ) def ed25519_supported(self) -> bool: @@ -564,334 +263,23 @@ def ed448_supported(self) -> bool: if self._fips_enabled: return False return ( - not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL ) - def aead_cipher_supported(self, cipher) -> bool: - return aead._aead_cipher_supported(self, cipher) - - def _zero_data(self, data, length: int) -> None: - # We clear things this way because at the moment we're not - # sure of a better way that can guarantee it overwrites the - # memory of a bytearray and doesn't just replace the underlying char *. - for i in range(length): - data[i] = 0 - - @contextlib.contextmanager - def _zeroed_null_terminated_buf(self, data): - """ - This method takes bytes, which can be a bytestring or a mutable - buffer like a bytearray, and yields a null-terminated version of that - data. This is required because PKCS12_parse doesn't take a length with - its password char * and ffi.from_buffer doesn't provide null - termination. So, to support zeroing the data via bytearray we - need to build this ridiculous construct that copies the memory, but - zeroes it after use. - """ - if data is None: - yield self._ffi.NULL - else: - data_len = len(data) - buf = self._ffi.new("char[]", data_len + 1) - self._ffi.memmove(buf, data, data_len) - try: - yield buf - finally: - # Cast to a uint8_t * so we can assign by integer - self._zero_data(self._ffi.cast("uint8_t *", buf), data_len) - - def load_key_and_certificates_from_pkcs12( - self, data: bytes, password: bytes | None - ) -> tuple[ - PrivateKeyTypes | None, - x509.Certificate | None, - list[x509.Certificate], - ]: - pkcs12 = self.load_pkcs12(data, password) + def ecdsa_deterministic_supported(self) -> bool: return ( - pkcs12.key, - pkcs12.cert.certificate if pkcs12.cert else None, - [cert.certificate for cert in pkcs12.additional_certs], + rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER + and not self._fips_enabled ) - def load_pkcs12( - self, data: bytes, password: bytes | None - ) -> PKCS12KeyAndCertificates: - if password is not None: - utils._check_byteslike("password", password) - - bio = self._bytes_to_bio(data) - p12 = self._lib.d2i_PKCS12_bio(bio.bio, self._ffi.NULL) - if p12 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Could not deserialize PKCS12 data") - - p12 = self._ffi.gc(p12, self._lib.PKCS12_free) - evp_pkey_ptr = self._ffi.new("EVP_PKEY **") - x509_ptr = self._ffi.new("X509 **") - sk_x509_ptr = self._ffi.new("Cryptography_STACK_OF_X509 **") - with self._zeroed_null_terminated_buf(password) as password_buf: - res = self._lib.PKCS12_parse( - p12, password_buf, evp_pkey_ptr, x509_ptr, sk_x509_ptr - ) - if res == 0: - self._consume_errors() - raise ValueError("Invalid password or PKCS12 data") - - cert = None - key = None - additional_certificates = [] - - if evp_pkey_ptr[0] != self._ffi.NULL: - 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 = 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: - x509 = self._ffi.gc(x509_ptr[0], self._lib.X509_free) - cert_obj = self._ossl2cert(x509) - name = None - maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL) - if maybe_name != self._ffi.NULL: - name = self._ffi.string(maybe_name) - cert = PKCS12Certificate(cert_obj, name) - - if sk_x509_ptr[0] != self._ffi.NULL: - sk_x509 = self._ffi.gc(sk_x509_ptr[0], self._lib.sk_X509_free) - num = self._lib.sk_X509_num(sk_x509_ptr[0]) - - # In OpenSSL < 3.0.0 PKCS12 parsing reverses the order of the - # certificates. - indices: typing.Iterable[int] - if ( - self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER - or self._lib.CRYPTOGRAPHY_IS_BORINGSSL - ): - indices = range(num) - else: - indices = reversed(range(num)) - - for i in indices: - x509 = self._lib.sk_X509_value(sk_x509, i) - self.openssl_assert(x509 != self._ffi.NULL) - x509 = self._ffi.gc(x509, self._lib.X509_free) - addl_cert = self._ossl2cert(x509) - addl_name = None - maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL) - if maybe_name != self._ffi.NULL: - addl_name = self._ffi.string(maybe_name) - additional_certificates.append( - PKCS12Certificate(addl_cert, addl_name) - ) - - return PKCS12KeyAndCertificates(key, cert, additional_certificates) - - def serialize_key_and_certificates_to_pkcs12( - self, - name: bytes | None, - key: PKCS12PrivateKeyTypes | None, - cert: x509.Certificate | None, - cas: list[_PKCS12CATypes] | None, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - password = None - if name is not None: - utils._check_bytes("name", name) - - if isinstance(encryption_algorithm, serialization.NoEncryption): - nid_cert = -1 - nid_key = -1 - pkcs12_iter = 0 - mac_iter = 0 - mac_alg = self._ffi.NULL - elif isinstance( - encryption_algorithm, serialization.BestAvailableEncryption - ): - # PKCS12 encryption is hopeless trash and can never be fixed. - # OpenSSL 3 supports PBESv2, but Libre and Boring do not, so - # we use PBESv1 with 3DES on the older paths. - if self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - nid_cert = self._lib.NID_aes_256_cbc - nid_key = self._lib.NID_aes_256_cbc - else: - nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - # At least we can set this higher than OpenSSL's default - pkcs12_iter = 20000 - # mac_iter chosen for compatibility reasons, see: - # https://www.openssl.org/docs/man1.1.1/man3/PKCS12_create.html - # Did we mention how lousy PKCS12 encryption is? - mac_iter = 1 - # MAC algorithm can only be set on OpenSSL 3.0.0+ - mac_alg = self._ffi.NULL - password = encryption_algorithm.password - elif ( - isinstance( - encryption_algorithm, serialization._KeySerializationEncryption - ) - and encryption_algorithm._format - is serialization.PrivateFormat.PKCS12 - ): - # Default to OpenSSL's defaults. Behavior will vary based on the - # version of OpenSSL cryptography is compiled against. - nid_cert = 0 - nid_key = 0 - # Use the default iters we use in best available - pkcs12_iter = 20000 - # See the Best Available comment for why this is 1 - mac_iter = 1 - password = encryption_algorithm.password - keycertalg = encryption_algorithm._key_cert_algorithm - if keycertalg is PBES.PBESv1SHA1And3KeyTripleDESCBC: - nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - elif keycertalg is PBES.PBESv2SHA256AndAES256CBC: - if not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - raise UnsupportedAlgorithm( - "PBESv2 is not supported by this version of OpenSSL" - ) - nid_cert = self._lib.NID_aes_256_cbc - nid_key = self._lib.NID_aes_256_cbc - else: - assert keycertalg is None - # We use OpenSSL's defaults - - if encryption_algorithm._hmac_hash is not None: - if not self._lib.Cryptography_HAS_PKCS12_SET_MAC: - raise UnsupportedAlgorithm( - "Setting MAC algorithm is not supported by this " - "version of OpenSSL." - ) - mac_alg = self._evp_md_non_null_from_algorithm( - encryption_algorithm._hmac_hash - ) - self.openssl_assert(mac_alg != self._ffi.NULL) - else: - mac_alg = self._ffi.NULL - - if encryption_algorithm._kdf_rounds is not None: - pkcs12_iter = encryption_algorithm._kdf_rounds - - else: - raise ValueError("Unsupported key encryption type") - - if cas is None or len(cas) == 0: - sk_x509 = self._ffi.NULL - else: - sk_x509 = self._lib.sk_X509_new_null() - sk_x509 = self._ffi.gc(sk_x509, self._lib.sk_X509_free) - - # This list is to keep the x509 values alive until end of function - ossl_cas = [] - for ca in cas: - if isinstance(ca, PKCS12Certificate): - ca_alias = ca.friendly_name - ossl_ca = self._cert2ossl(ca.certificate) - if ca_alias is None: - res = self._lib.X509_alias_set1( - ossl_ca, self._ffi.NULL, -1 - ) - else: - res = self._lib.X509_alias_set1( - ossl_ca, ca_alias, len(ca_alias) - ) - self.openssl_assert(res == 1) - else: - ossl_ca = self._cert2ossl(ca) - ossl_cas.append(ossl_ca) - res = self._lib.sk_X509_push(sk_x509, ossl_ca) - backend.openssl_assert(res >= 1) - - with self._zeroed_null_terminated_buf(password) as password_buf: - with self._zeroed_null_terminated_buf(name) as name_buf: - ossl_cert = self._cert2ossl(cert) if cert else self._ffi.NULL - ossl_pkey = ( - self._key2ossl(key) if key is not None else self._ffi.NULL - ) - - p12 = self._lib.PKCS12_create( - password_buf, - name_buf, - ossl_pkey, - ossl_cert, - sk_x509, - nid_key, - nid_cert, - pkcs12_iter, - mac_iter, - 0, - ) - - if ( - self._lib.Cryptography_HAS_PKCS12_SET_MAC - and mac_alg != self._ffi.NULL - ): - self._lib.PKCS12_set_mac( - p12, - password_buf, - -1, - self._ffi.NULL, - 0, - mac_iter, - mac_alg, - ) - - self.openssl_assert(p12 != self._ffi.NULL) - p12 = self._ffi.gc(p12, self._lib.PKCS12_free) - - bio = self._create_mem_bio_gc() - res = self._lib.i2d_PKCS12_bio(bio, p12) - self.openssl_assert(res > 0) - return self._read_mem_bio(bio) - def poly1305_supported(self) -> bool: if self._fips_enabled: return False - elif ( - self._lib.CRYPTOGRAPHY_IS_BORINGSSL - or self._lib.CRYPTOGRAPHY_IS_LIBRESSL - ): - return True - else: - return self._lib.Cryptography_HAS_POLY1305 == 1 + return True def pkcs7_supported(self) -> bool: - return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - - -class GetCipherByName: - def __init__(self, fmt: str): - self._fmt = fmt - - def __call__(self, backend: Backend, cipher: CipherAlgorithm, mode: Mode): - cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower() - evp_cipher = backend._lib.EVP_get_cipherbyname( - cipher_name.encode("ascii") - ) - - # try EVP_CIPHER_fetch if present - if ( - evp_cipher == backend._ffi.NULL - and backend._lib.Cryptography_HAS_300_EVP_CIPHER - ): - evp_cipher = backend._lib.EVP_CIPHER_fetch( - backend._ffi.NULL, - cipher_name.encode("ascii"), - backend._ffi.NULL, - ) - - backend._consume_errors() - return evp_cipher - - -def _get_xts_cipher(backend: Backend, cipher: AES, mode): - cipher_name = f"aes-{cipher.key_size // 2}-xts" - return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) + return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL backend = Backend() diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py deleted file mode 100644 index 3916b1a510ad..000000000000 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ /dev/null @@ -1,282 +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 InvalidTag, UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.primitives import ciphers -from cryptography.hazmat.primitives.ciphers import algorithms, modes - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -class _CipherContext: - _ENCRYPT = 1 - _DECRYPT = 0 - _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: bytes | None = None - - if isinstance(self._cipher, ciphers.BlockCipherAlgorithm): - self._block_size_bytes = self._cipher.block_size // 8 - else: - self._block_size_bytes = 1 - - ctx = self._backend._lib.EVP_CIPHER_CTX_new() - ctx = self._backend._ffi.gc( - ctx, self._backend._lib.EVP_CIPHER_CTX_free - ) - - registry = self._backend._cipher_registry - try: - adapter = registry[type(cipher), type(mode)] - except KeyError: - raise UnsupportedAlgorithm( - "cipher {} in {} mode is not supported " - "by this backend.".format( - cipher.name, mode.name if mode else mode - ), - _Reasons.UNSUPPORTED_CIPHER, - ) - - evp_cipher = adapter(self._backend, cipher, mode) - if evp_cipher == self._backend._ffi.NULL: - msg = f"cipher {cipher.name} " - if mode is not None: - msg += f"in {mode.name} mode " - msg += ( - "is not supported by this backend (Your version of OpenSSL " - "may be too old. Current version: {}.)" - ).format(self._backend.openssl_version_text()) - raise UnsupportedAlgorithm(msg, _Reasons.UNSUPPORTED_CIPHER) - - if isinstance(mode, modes.ModeWithInitializationVector): - iv_nonce = self._backend._ffi.from_buffer( - mode.initialization_vector - ) - elif isinstance(mode, modes.ModeWithTweak): - iv_nonce = self._backend._ffi.from_buffer(mode.tweak) - elif isinstance(mode, modes.ModeWithNonce): - iv_nonce = self._backend._ffi.from_buffer(mode.nonce) - elif isinstance(cipher, algorithms.ChaCha20): - iv_nonce = self._backend._ffi.from_buffer(cipher.nonce) - else: - iv_nonce = self._backend._ffi.NULL - # begin init with cipher and operation type - res = self._backend._lib.EVP_CipherInit_ex( - ctx, - evp_cipher, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - operation, - ) - self._backend.openssl_assert(res != 0) - # set the key length to handle variable key ciphers - res = self._backend._lib.EVP_CIPHER_CTX_set_key_length( - ctx, len(cipher.key) - ) - self._backend.openssl_assert(res != 0) - if isinstance(mode, modes.GCM): - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - self._backend._lib.EVP_CTRL_AEAD_SET_IVLEN, - len(iv_nonce), - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(res != 0) - if mode.tag is not None: - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - self._backend._lib.EVP_CTRL_AEAD_SET_TAG, - len(mode.tag), - mode.tag, - ) - self._backend.openssl_assert(res != 0) - self._tag = mode.tag - - # pass key/iv - res = self._backend._lib.EVP_CipherInit_ex( - ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.from_buffer(cipher.key), - iv_nonce, - operation, - ) - - # Check for XTS mode duplicate keys error - errors = self._backend._consume_errors() - lib = self._backend._lib - if res == 0 and ( - ( - not lib.CRYPTOGRAPHY_IS_LIBRESSL - and errors[0]._lib_reason_match( - lib.ERR_LIB_EVP, lib.EVP_R_XTS_DUPLICATED_KEYS - ) - ) - or ( - lib.Cryptography_HAS_PROVIDERS - and errors[0]._lib_reason_match( - lib.ERR_LIB_PROV, lib.PROV_R_XTS_DUPLICATED_KEYS - ) - ) - ): - raise ValueError("In XTS mode duplicated keys are not allowed") - - self._backend.openssl_assert(res != 0, errors=errors) - - # We purposely disable padding here as it's handled higher up in the - # API. - self._backend._lib.EVP_CIPHER_CTX_set_padding(ctx, 0) - self._ctx = ctx - - def update(self, data: bytes) -> bytes: - buf = bytearray(len(data) + self._block_size_bytes - 1) - n = self.update_into(data, buf) - return bytes(buf[:n]) - - def update_into(self, data: 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 - ) - ) - - data_processed = 0 - total_out = 0 - outlen = self._backend._ffi.new("int *") - baseoutbuf = self._backend._ffi.from_buffer(buf, require_writable=True) - baseinbuf = self._backend._ffi.from_buffer(data) - - while data_processed != total_data_len: - outbuf = baseoutbuf + total_out - inbuf = baseinbuf + data_processed - inlen = min(self._MAX_CHUNK_SIZE, total_data_len - data_processed) - - res = self._backend._lib.EVP_CipherUpdate( - self._ctx, outbuf, outlen, inbuf, inlen - ) - if res == 0 and isinstance(self._mode, modes.XTS): - self._backend._consume_errors() - raise ValueError( - "In XTS mode you must supply at least a full block in the " - "first update call. For AES this is 16 bytes." - ) - else: - self._backend.openssl_assert(res != 0) - data_processed += inlen - total_out += outlen[0] - - return total_out - - def finalize(self) -> bytes: - if ( - self._operation == self._DECRYPT - and isinstance(self._mode, modes.ModeWithAuthenticationTag) - and self.tag is None - ): - raise ValueError( - "Authentication tag must be provided when decrypting." - ) - - buf = self._backend._ffi.new("unsigned char[]", self._block_size_bytes) - outlen = self._backend._ffi.new("int *") - res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen) - if res == 0: - errors = self._backend._consume_errors() - - if not errors and isinstance(self._mode, modes.GCM): - raise InvalidTag - - lib = self._backend._lib - self._backend.openssl_assert( - errors[0]._lib_reason_match( - lib.ERR_LIB_EVP, - lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, - ) - or ( - lib.Cryptography_HAS_PROVIDERS - and errors[0]._lib_reason_match( - lib.ERR_LIB_PROV, - lib.PROV_R_WRONG_FINAL_BLOCK_LENGTH, - ) - ) - or ( - lib.CRYPTOGRAPHY_IS_BORINGSSL - and errors[0].reason - == lib.CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH - ), - errors=errors, - ) - raise ValueError( - "The length of the provided data is not a multiple of " - "the block length." - ) - - if ( - isinstance(self._mode, modes.GCM) - and self._operation == self._ENCRYPT - ): - tag_buf = self._backend._ffi.new( - "unsigned char[]", self._block_size_bytes - ) - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - self._ctx, - self._backend._lib.EVP_CTRL_AEAD_GET_TAG, - self._block_size_bytes, - tag_buf, - ) - self._backend.openssl_assert(res != 0) - self._tag = self._backend._ffi.buffer(tag_buf)[:] - - res = self._backend._lib.EVP_CIPHER_CTX_reset(self._ctx) - self._backend.openssl_assert(res == 1) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def finalize_with_tag(self, tag: bytes) -> bytes: - tag_len = len(tag) - if tag_len < self._mode._min_tag_length: - raise ValueError( - "Authentication tag must be {} bytes or longer.".format( - self._mode._min_tag_length - ) - ) - elif tag_len > self._block_size_bytes: - raise ValueError( - "Authentication tag cannot be more than {} bytes.".format( - self._block_size_bytes - ) - ) - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - self._ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag - ) - self._backend.openssl_assert(res != 0) - self._tag = tag - return self.finalize() - - def authenticate_additional_data(self, data: bytes) -> None: - outlen = self._backend._ffi.new("int *") - res = self._backend._lib.EVP_CipherUpdate( - self._ctx, - self._backend._ffi.NULL, - outlen, - self._backend._ffi.from_buffer(data), - len(data), - ) - self._backend.openssl_assert(res != 0) - - @property - def tag(self) -> bytes | None: - return self._tag diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py deleted file mode 100644 index bf123b6285b6..000000000000 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ /dev/null @@ -1,32 +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 - -from cryptography import x509 - -# CRLReason ::= ENUMERATED { -# unspecified (0), -# keyCompromise (1), -# cACompromise (2), -# affiliationChanged (3), -# superseded (4), -# cessationOfOperation (5), -# certificateHold (6), -# -- value 7 is not used -# removeFromCRL (8), -# privilegeWithdrawn (9), -# aACompromise (10) } -_CRL_ENTRY_REASON_ENUM_TO_CODE = { - x509.ReasonFlags.unspecified: 0, - x509.ReasonFlags.key_compromise: 1, - x509.ReasonFlags.ca_compromise: 2, - x509.ReasonFlags.affiliation_changed: 3, - x509.ReasonFlags.superseded: 4, - x509.ReasonFlags.cessation_of_operation: 5, - x509.ReasonFlags.certificate_hold: 6, - x509.ReasonFlags.remove_from_crl: 8, - x509.ReasonFlags.privilege_withdrawn: 9, - x509.ReasonFlags.aa_compromise: 10, -} diff --git a/src/cryptography/hazmat/bindings/_rust/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/__init__.pyi index 18a6fb87b628..30b67d85597e 100644 --- a/src/cryptography/hazmat/bindings/_rust/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -4,9 +4,20 @@ import typing -def check_pkcs7_padding(data: bytes) -> bool: ... +from cryptography.hazmat.primitives import padding + def check_ansix923_padding(data: bytes) -> bool: ... +class PKCS7PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: bytes) -> bytes: ... + def finalize(self) -> bytes: ... + +class PKCS7UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: bytes) -> bytes: ... + def finalize(self) -> bytes: ... + class ObjectIdentifier: def __init__(self, val: str) -> None: ... @property diff --git a/src/cryptography/hazmat/bindings/_rust/asn1.pyi b/src/cryptography/hazmat/bindings/_rust/asn1.pyi index 35652c6ada1c..3b5f208ecf09 100644 --- a/src/cryptography/hazmat/bindings/_rust/asn1.pyi +++ b/src/cryptography/hazmat/bindings/_rust/asn1.pyi @@ -2,13 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -class TestCertificate: - not_after_tag: int - not_before_tag: int - issuer_value_tags: list[int] - subject_value_tags: list[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 b15628f8d46b..e4321bec2ad2 100644 --- a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi +++ b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -2,22 +2,116 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from cryptography.hazmat.primitives import hashes +import datetime +import typing + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes -from cryptography.x509.ocsp import ( - OCSPRequest, - OCSPRequestBuilder, - OCSPResponse, - OCSPResponseBuilder, - OCSPResponseStatus, -) +from cryptography.x509 import ocsp + +class OCSPRequest: + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def extensions(self) -> x509.Extensions: ... + +class OCSPResponse: + @property + def responses(self) -> typing.Iterator[OCSPSingleResponse]: ... + @property + def response_status(self) -> ocsp.OCSPResponseStatus: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_response_bytes(self) -> bytes: ... + @property + def certificates(self) -> list[x509.Certificate]: ... + @property + def responder_key_hash(self) -> bytes | None: ... + @property + def responder_name(self) -> x509.Name | None: ... + @property + def produced_at(self) -> datetime.datetime: ... + @property + def produced_at_utc(self) -> datetime.datetime: ... + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def single_extensions(self) -> x509.Extensions: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + +class OCSPSingleResponse: + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... -def load_der_ocsp_request(data: bytes) -> OCSPRequest: ... -def load_der_ocsp_response(data: bytes) -> OCSPResponse: ... -def create_ocsp_request(builder: OCSPRequestBuilder) -> OCSPRequest: ... +def load_der_ocsp_request(data: bytes) -> ocsp.OCSPRequest: ... +def load_der_ocsp_response(data: bytes) -> ocsp.OCSPResponse: ... +def create_ocsp_request( + builder: ocsp.OCSPRequestBuilder, +) -> ocsp.OCSPRequest: ... def create_ocsp_response( - status: OCSPResponseStatus, - builder: OCSPResponseBuilder | None, + status: ocsp.OCSPResponseStatus, + builder: ocsp.OCSPResponseBuilder | None, private_key: PrivateKeyTypes | None, hash_algorithm: hashes.HashAlgorithm | None, -) -> OCSPResponse: ... +) -> ocsp.OCSPResponse: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi index 9cdb4d6a5c6e..320cef10250e 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -6,6 +6,7 @@ import typing from cryptography.hazmat.bindings._rust.openssl import ( aead, + ciphers, cmac, dh, dsa, @@ -23,29 +24,44 @@ from cryptography.hazmat.bindings._rust.openssl import ( ) __all__ = [ - "openssl_version", - "raise_openssl_error", "aead", + "ciphers", "cmac", "dh", "dsa", "ec", + "ed448", + "ed25519", "hashes", "hmac", "kdf", "keys", - "ed448", - "ed25519", - "rsa", + "openssl_version", + "openssl_version_text", "poly1305", + "raise_openssl_error", + "rsa", "x448", "x25519", ] +CRYPTOGRAPHY_IS_LIBRESSL: bool +CRYPTOGRAPHY_IS_BORINGSSL: bool +CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_309_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_320_OR_GREATER: bool + +class Providers: ... + +_legacy_provider_loaded: bool +_providers: Providers + def openssl_version() -> int: ... +def openssl_version_text() -> str: ... def raise_openssl_error() -> typing.NoReturn: ... def capture_error_stack() -> list[OpenSSLError]: ... def is_fips_enabled() -> bool: ... +def enable_fips(providers: Providers) -> None: ... class OpenSSLError: @property @@ -54,4 +70,3 @@ class OpenSSLError: def reason(self) -> int: ... @property def reason_text(self) -> bytes: ... - def _lib_reason_match(self, lib: int, reason: int) -> bool: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi index 81e801e30bb5..047f49d819c1 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi @@ -2,6 +2,23 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +class AESGCM: + 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 ChaCha20Poly1305: def __init__(self, key: bytes) -> None: ... @staticmethod @@ -19,6 +36,23 @@ class ChaCha20Poly1305: associated_data: bytes | None, ) -> bytes: ... +class AESCCM: + def __init__(self, key: bytes, tag_length: int = 16) -> 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 AESSIV: def __init__(self, key: bytes) -> None: ... @staticmethod diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi new file mode 100644 index 000000000000..759f3b591cba --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi @@ -0,0 +1,38 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes + +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADEncryptionContext: ... +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode +) -> ciphers.CipherContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADDecryptionContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode +) -> ciphers.CipherContext: ... +def cipher_supported( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode +) -> bool: ... +def _advance( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... +def _advance_aad( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... + +class CipherContext: ... +class AEADEncryptionContext: ... +class AEADDecryptionContext: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi index ca5f42a00615..56f317001629 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi @@ -15,3 +15,5 @@ class Hash(hashes.HashContext): def update(self, data: bytes) -> None: ... def finalize(self) -> bytes: ... def copy(self) -> Hash: ... + +def hash_supported(algorithm: hashes.HashAlgorithm) -> bool: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi index 034a8fed2e78..4b90bb4f7744 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/kdf.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.hashes import HashAlgorithm def derive_pbkdf2_hmac( @@ -11,12 +13,31 @@ def derive_pbkdf2_hmac( iterations: int, length: int, ) -> bytes: ... -def derive_scrypt( - key_material: bytes, - salt: bytes, - n: int, - r: int, - p: int, - max_mem: int, - length: int, -) -> bytes: ... + +class Scrypt: + def __init__( + self, + salt: bytes, + length: int, + n: int, + r: int, + p: int, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class Argon2id: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi index e312d51dc58b..6815b7d9154b 100644 --- a/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi +++ b/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi @@ -9,10 +9,6 @@ from cryptography.hazmat.primitives.asymmetric.types import ( 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, diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi new file mode 100644 index 000000000000..40514c4623d5 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi @@ -0,0 +1,46 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography import x509 +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.hazmat.primitives.serialization import ( + KeySerializationEncryption, +) +from cryptography.hazmat.primitives.serialization.pkcs12 import ( + PKCS12KeyAndCertificates, + PKCS12PrivateKeyTypes, +) + +class PKCS12Certificate: + def __init__( + self, cert: x509.Certificate, friendly_name: bytes | None + ) -> None: ... + @property + def friendly_name(self) -> bytes | None: ... + @property + def certificate(self) -> x509.Certificate: ... + +def load_key_and_certificates( + data: bytes, + password: bytes | None, + backend: typing.Any = None, +) -> tuple[ + PrivateKeyTypes | None, + x509.Certificate | None, + list[x509.Certificate], +]: ... +def load_pkcs12( + data: bytes, + password: bytes | None, + backend: typing.Any = None, +) -> PKCS12KeyAndCertificates: ... +def serialize_key_and_certificates( + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: typing.Iterable[x509.Certificate | PKCS12Certificate] | None, + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi index a84978246572..f9aa81ea0caf 100644 --- a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi +++ b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -1,18 +1,46 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + import typing from cryptography import x509 from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import pkcs7 def serialize_certificates( certs: list[x509.Certificate], encoding: serialization.Encoding, ) -> bytes: ... +def encrypt_and_serialize( + builder: pkcs7.PKCS7EnvelopeBuilder, + encoding: serialization.Encoding, + options: typing.Iterable[pkcs7.PKCS7Options], +) -> bytes: ... def sign_and_serialize( builder: pkcs7.PKCS7SignatureBuilder, encoding: serialization.Encoding, options: typing.Iterable[pkcs7.PKCS7Options], ) -> bytes: ... +def decrypt_der( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: typing.Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_pem( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: typing.Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_smime( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: typing.Iterable[pkcs7.PKCS7Options], +) -> bytes: ... def load_pem_pkcs7_certificates( data: bytes, ) -> list[x509.Certificate]: ... diff --git a/src/cryptography/hazmat/bindings/_rust/test_support.pyi b/src/cryptography/hazmat/bindings/_rust/test_support.pyi new file mode 100644 index 000000000000..ef9f779f2ee9 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/test_support.pyi @@ -0,0 +1,22 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import pkcs7 + +class TestCertificate: + not_after_tag: int + not_before_tag: int + issuer_value_tags: list[int] + subject_value_tags: list[int] + +def test_parse_certificate(data: bytes) -> TestCertificate: ... +def pkcs7_verify( + encoding: serialization.Encoding, + sig: bytes, + msg: bytes | None, + certs: list[x509.Certificate], + options: list[pkcs7.PKCS7Options], +) -> None: ... diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi index 418184f8a6fd..b494fb61de3d 100644 --- a/src/cryptography/hazmat/bindings/_rust/x509.pyi +++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi @@ -6,9 +6,15 @@ import datetime import typing from cryptography import x509 -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 -from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, + PrivateKeyTypes, +) +from cryptography.x509 import certificate_transparency def load_pem_x509_certificate( data: bytes, backend: typing.Any = None @@ -52,20 +58,172 @@ def create_x509_crl( rsa_padding: PKCS1v15 | PSS | None, ) -> x509.CertificateRevocationList: ... -class Sct: ... -class Certificate: ... +class Sct: + @property + def version(self) -> certificate_transparency.Version: ... + @property + def log_id(self) -> bytes: ... + @property + def timestamp(self) -> datetime.datetime: ... + @property + def entry_type(self) -> certificate_transparency.LogEntryType: ... + @property + def signature_hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def signature_algorithm( + self, + ) -> certificate_transparency.SignatureAlgorithm: ... + @property + def signature(self) -> bytes: ... + @property + def extension_bytes(self) -> bytes: ... + +class Certificate: + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + @property + def serial_number(self) -> int: ... + @property + def version(self) -> x509.Version: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def public_key_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def not_valid_before(self) -> datetime.datetime: ... + @property + def not_valid_before_utc(self) -> datetime.datetime: ... + @property + def not_valid_after(self) -> datetime.datetime: ... + @property + def not_valid_after_utc(self) -> datetime.datetime: ... + @property + def issuer(self) -> x509.Name: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> None | PSS | PKCS1v15 | ECDSA: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certificate_bytes(self) -> bytes: ... + @property + def tbs_precertificate_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def verify_directly_issued_by(self, issuer: Certificate) -> None: ... + class RevokedCertificate: ... -class CertificateRevocationList: ... -class CertificateSigningRequest: ... + +class CertificateRevocationList: + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + def get_revoked_certificate_by_serial_number( + self, serial_number: int + ) -> RevokedCertificate | None: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> None | PSS | PKCS1v15 | ECDSA: ... + @property + def issuer(self) -> x509.Name: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def last_update(self) -> datetime.datetime: ... + @property + def last_update_utc(self) -> datetime.datetime: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certlist_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __len__(self) -> int: ... + @typing.overload + def __getitem__(self, idx: int) -> x509.RevokedCertificate: ... + @typing.overload + def __getitem__(self, idx: slice) -> list[x509.RevokedCertificate]: ... + def __iter__(self) -> typing.Iterator[x509.RevokedCertificate]: ... + def is_signature_valid( + self, public_key: CertificateIssuerPublicKeyTypes + ) -> bool: ... + +class CertificateSigningRequest: + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> None | PSS | PKCS1v15 | ECDSA: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def attributes(self) -> x509.Attributes: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certrequest_bytes(self) -> bytes: ... + @property + def is_signature_valid(self) -> bool: ... + def get_attribute_for_oid(self, oid: x509.ObjectIdentifier) -> bytes: ... class PolicyBuilder: def time(self, new_time: datetime.datetime) -> PolicyBuilder: ... def store(self, new_store: Store) -> PolicyBuilder: ... def max_chain_depth(self, new_max_chain_depth: int) -> PolicyBuilder: ... + def build_client_verifier(self) -> ClientVerifier: ... def build_server_verifier( self, subject: x509.verification.Subject ) -> ServerVerifier: ... +class VerifiedClient: + @property + def subjects(self) -> list[x509.GeneralName] | None: ... + @property + def chain(self) -> list[x509.Certificate]: ... + +class ClientVerifier: + @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], + ) -> VerifiedClient: ... + class ServerVerifier: @property def subject(self) -> x509.verification.Subject: ... diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index 30cc3bfa25ef..73c06f7d08ce 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -28,18 +28,6 @@ def cryptography_has_tls_st() -> list[str]: ] -def cryptography_has_mem_functions() -> list[str]: - return [ - "Cryptography_CRYPTO_set_mem_functions", - ] - - -def cryptography_has_ed448() -> list[str]: - return [ - "EVP_PKEY_ED448", - ] - - def cryptography_has_ssl_sigalgs() -> list[str]: return [ "SSL_CTX_set1_sigalgs_list", @@ -121,16 +109,6 @@ def cryptography_has_srtp() -> list[str]: ] -def cryptography_has_providers() -> list[str]: - return [ - "OSSL_PROVIDER_load", - "OSSL_PROVIDER_unload", - "ERR_LIB_PROV", - "PROV_R_WRONG_FINAL_BLOCK_LENGTH", - "PROV_R_BAD_DECRYPT", - ] - - def cryptography_has_op_no_renegotiation() -> list[str]: return [ "SSL_OP_NO_RENEGOTIATION", @@ -143,12 +121,6 @@ def cryptography_has_dtls_get_data_mtu() -> list[str]: ] -def cryptography_has_300_fips() -> list[str]: - return [ - "EVP_default_properties_enable_fips", - ] - - def cryptography_has_ssl_cookie() -> list[str]: return [ "SSL_OP_COOKIE_EXCHANGE", @@ -158,31 +130,16 @@ def cryptography_has_ssl_cookie() -> list[str]: ] -def cryptography_has_pkcs7_funcs() -> list[str]: - return [ - "PKCS7_verify", - "SMIME_read_PKCS7", - ] - - def cryptography_has_prime_checks() -> list[str]: return [ "BN_prime_checks_for_size", ] -def cryptography_has_300_evp_cipher() -> list[str]: - return ["EVP_CIPHER_fetch", "EVP_CIPHER_free"] - - def cryptography_has_unexpected_eof_while_reading() -> list[str]: return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"] -def cryptography_has_pkcs12_set_mac() -> list[str]: - return ["PKCS12_set_mac"] - - def cryptography_has_ssl_op_ignore_unexpected_eof() -> list[str]: return [ "SSL_OP_IGNORE_UNEXPECTED_EOF", @@ -202,8 +159,6 @@ def cryptography_has_get_extms_support() -> 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_MEM_FUNCTIONS": cryptography_has_mem_functions, - "Cryptography_HAS_ED448": cryptography_has_ed448, "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs, "Cryptography_HAS_PSK": cryptography_has_psk, "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, @@ -212,20 +167,15 @@ def cryptography_has_get_extms_support() -> list[str]: "Cryptography_HAS_ENGINE": cryptography_has_engine, "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, - "Cryptography_HAS_PROVIDERS": cryptography_has_providers, "Cryptography_HAS_OP_NO_RENEGOTIATION": ( cryptography_has_op_no_renegotiation ), "Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu, - "Cryptography_HAS_300_FIPS": cryptography_has_300_fips, "Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie, - "Cryptography_HAS_PKCS7_FUNCS": cryptography_has_pkcs7_funcs, "Cryptography_HAS_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 ), - "Cryptography_HAS_PKCS12_SET_MAC": cryptography_has_pkcs12_set_mac, "Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": ( cryptography_has_ssl_op_ignore_unexpected_eof ), diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index 40814f2a58a0..d4dfeef485d1 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -17,13 +17,9 @@ from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES -def _openssl_assert( - ok: bool, - errors: list[openssl.OpenSSLError] | None = None, -) -> None: +def _openssl_assert(ok: bool) -> None: if not ok: - if errors is None: - errors = openssl.capture_error_stack() + errors = openssl.capture_error_stack() raise InternalError( "Unknown OpenSSL error. This error is commonly encountered when " @@ -37,17 +33,6 @@ def _openssl_assert( ) -def _legacy_provider_error(loaded: bool) -> None: - if not loaded: - raise RuntimeError( - "OpenSSL 3.0's legacy provider failed to load. This is a fatal " - "error by default, but cryptography supports running without " - "legacy algorithms by setting the environment variable " - "CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error," - " you have likely made a mistake with your OpenSSL configuration." - ) - - def build_conditional_library( lib: typing.Any, conditional_names: dict[str, typing.Callable[[], list[str]]], @@ -75,29 +60,10 @@ class Binding: ffi = _openssl.ffi _lib_loaded = False _init_lock = threading.Lock() - _legacy_provider: typing.Any = ffi.NULL - _legacy_provider_loaded = False - _default_provider: typing.Any = ffi.NULL def __init__(self) -> None: self._ensure_ffi_initialized() - 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.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER) - self._base_provider = self.lib.OSSL_PROVIDER_load( - self.ffi.NULL, b"base" - ) - _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._fips_provider != self.ffi.NULL) - - res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1) - _openssl_assert(res == 1) - @classmethod def _ensure_ffi_initialized(cls) -> None: with cls._init_lock: @@ -106,25 +72,6 @@ def _ensure_ffi_initialized(cls) -> None: _openssl.lib, CONDITIONAL_NAMES ) cls._lib_loaded = True - # As of OpenSSL 3.0.0 we must register a legacy cipher provider - # to get RC2 (needed for junk asymmetric private key - # serialization), RC4, Blowfish, IDEA, SEED, etc. These things - # are ugly legacy, but we aren't going to get rid of them - # any time soon. - if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - if not os.environ.get("CRYPTOGRAPHY_OPENSSL_NO_LEGACY"): - cls._legacy_provider = cls.lib.OSSL_PROVIDER_load( - cls.ffi.NULL, b"legacy" - ) - cls._legacy_provider_loaded = ( - cls._legacy_provider != cls.ffi.NULL - ) - _legacy_provider_error(cls._legacy_provider_loaded) - - cls._default_provider = cls.lib.OSSL_PROVIDER_load( - cls.ffi.NULL, b"default" - ) - _openssl_assert(cls._default_provider != cls.ffi.NULL) @classmethod def init_static_locks(cls) -> None: @@ -148,9 +95,8 @@ def _verify_package_version(version: str) -> None: "shared object. This can happen if you have multiple copies of " "cryptography installed in your Python path. Please try creating " "a new virtual environment to resolve this issue. " - "Loaded python version: {}, shared object version: {}".format( - version, so_package_version - ) + f"Loaded python version: {version}, " + f"shared object version: {so_package_version}" ) _openssl_assert( diff --git a/src/cryptography/hazmat/decrepit/__init__.py b/src/cryptography/hazmat/decrepit/__init__.py new file mode 100644 index 000000000000..41d731863aa2 --- /dev/null +++ b/src/cryptography/hazmat/decrepit/__init__.py @@ -0,0 +1,5 @@ +# 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 diff --git a/src/cryptography/hazmat/decrepit/ciphers/__init__.py b/src/cryptography/hazmat/decrepit/ciphers/__init__.py new file mode 100644 index 000000000000..41d731863aa2 --- /dev/null +++ b/src/cryptography/hazmat/decrepit/ciphers/__init__.py @@ -0,0 +1,5 @@ +# 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 diff --git a/src/cryptography/hazmat/decrepit/ciphers/algorithms.py b/src/cryptography/hazmat/decrepit/ciphers/algorithms.py new file mode 100644 index 000000000000..a7d4aa3c5d87 --- /dev/null +++ b/src/cryptography/hazmat/decrepit/ciphers/algorithms.py @@ -0,0 +1,107 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, + _verify_key_size, +) + + +class ARC4(CipherAlgorithm): + name = "RC4" + key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class TripleDES(BlockCipherAlgorithm): + name = "3DES" + block_size = 64 + key_sizes = frozenset([64, 128, 192]) + + def __init__(self, key: bytes): + if len(key) == 8: + key += key + key + elif len(key) == 16: + key += key[:8] + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class Blowfish(BlockCipherAlgorithm): + name = "Blowfish" + block_size = 64 + key_sizes = frozenset(range(32, 449, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class CAST5(BlockCipherAlgorithm): + name = "CAST5" + block_size = 64 + key_sizes = frozenset(range(40, 129, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class SEED(BlockCipherAlgorithm): + name = "SEED" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class IDEA(BlockCipherAlgorithm): + name = "IDEA" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +# This class only allows RC2 with a 128-bit key. No support for +# effective key bits or other key sizes is provided. +class RC2(BlockCipherAlgorithm): + name = "RC2" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 diff --git a/src/cryptography/hazmat/primitives/_cipheralgorithm.py b/src/cryptography/hazmat/primitives/_cipheralgorithm.py index 9d7f5bc79c2b..588a61698fdc 100644 --- a/src/cryptography/hazmat/primitives/_cipheralgorithm.py +++ b/src/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -6,6 +6,8 @@ import abc +from cryptography import utils + # This exists to break an import cycle. It is normally accessible from the # ciphers module. @@ -42,3 +44,15 @@ def block_size(self) -> int: """ The size of a block as an integer in bits (e.g. 64, 128). """ + + +def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: + # Verify that the key is instance of bytes + utils._check_byteslike("key", key) + + # Verify that the key size matches the expected key size + if len(key) * 8 not in algorithm.key_sizes: + raise ValueError( + f"Invalid key size ({len(key) * 8}) for {algorithm.name}." + ) + return key diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index b612b40149d4..da1fbea13a6e 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -8,6 +8,7 @@ import typing from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat._oid import ObjectIdentifier from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes @@ -319,8 +320,21 @@ class ECDSA(EllipticCurveSignatureAlgorithm): def __init__( self, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + deterministic_signing: bool = False, ): + from cryptography.hazmat.backends.openssl.backend import backend + + if ( + deterministic_signing + and not backend.ecdsa_deterministic_supported() + ): + raise UnsupportedAlgorithm( + "ECDSA with deterministic signature (RFC 6979) is not " + "supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) self._algorithm = algorithm + self._deterministic_signing = deterministic_signing @property def algorithm( @@ -328,6 +342,12 @@ def algorithm( ) -> asym_utils.Prehashed | hashes.HashAlgorithm: return self._algorithm + @property + def deterministic_signing( + self, + ) -> bool: + return self._deterministic_signing + generate_private_key = rust_openssl.ec.generate_private_key diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index 6420434d82b7..905068e3b8cc 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -5,6 +5,7 @@ from __future__ import annotations import abc +import random import typing from math import gcd @@ -150,8 +151,8 @@ def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: "65537. Almost everyone should choose 65537 here!" ) - if key_size < 512: - raise ValueError("key_size must be at least 512-bits.") + if key_size < 1024: + raise ValueError("key_size must be at least 1024-bits.") def _modinv(e: int, m: int) -> int: @@ -190,10 +191,30 @@ def rsa_crt_dmq1(private_exponent: int, q: int) -> int: return private_exponent % (q - 1) +def rsa_recover_private_exponent(e: int, p: int, q: int) -> int: + """ + Compute the RSA private_exponent (d) given the public exponent (e) + and the RSA primes p and q. + + This uses the Carmichael totient function to generate the + smallest possible working value of the private exponent. + """ + # This lambda_n is the Carmichael totient function. + # The original RSA paper uses the Euler totient function + # here: phi_n = (p - 1) * (q - 1) + # Either version of the private exponent will work, but the + # one generated by the older formulation may be larger + # than necessary. (lambda_n always divides phi_n) + # + # TODO: Replace with lcm(p - 1, q - 1) once the minimum + # supported Python version is >= 3.9. + lambda_n = (p - 1) * (q - 1) // gcd(p - 1, q - 1) + return _modinv(e, lambda_n) + + # Controls the number of iterations rsa_recover_prime_factors will perform -# to obtain the prime factors. Each iteration increments by 2 so the actual -# maximum attempts is half this number. -_MAX_RECOVERY_ATTEMPTS = 1000 +# to obtain the prime factors. +_MAX_RECOVERY_ATTEMPTS = 500 def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: @@ -201,6 +222,9 @@ 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. """ + # reject invalid values early + if 17 != pow(17, e * d, n): + raise ValueError("n, d, e don't match") # See 8.2.2(i) in Handbook of Applied Cryptography. ktot = d * e - 1 # The quantity d*e-1 is a multiple of phi(n), even, @@ -214,8 +238,10 @@ def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: # See "Digitalized Signatures and Public Key Functions as Intractable # as Factorization", M. Rabin, 1979 spotted = False - a = 2 - while not spotted and a < _MAX_RECOVERY_ATTEMPTS: + tries = 0 + while not spotted and tries < _MAX_RECOVERY_ATTEMPTS: + a = random.randint(2, n - 1) + tries += 1 k = t # Cycle through all values a^{t*2^i}=a^k while k < ktot: @@ -228,8 +254,6 @@ def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: spotted = True break k *= 2 - # This value was not any good... let's try another! - a += 2 if not spotted: raise ValueError("Unable to compute factors p and q from exponent d.") # Found ! diff --git a/src/cryptography/hazmat/primitives/ciphers/__init__.py b/src/cryptography/hazmat/primitives/ciphers/__init__.py index cc88fbf2c4c3..10c15d0f5cb3 100644 --- a/src/cryptography/hazmat/primitives/ciphers/__init__.py +++ b/src/cryptography/hazmat/primitives/ciphers/__init__.py @@ -17,11 +17,11 @@ ) __all__ = [ - "Cipher", - "CipherAlgorithm", - "BlockCipherAlgorithm", - "CipherContext", "AEADCipherContext", "AEADDecryptionContext", "AEADEncryptionContext", + "BlockCipherAlgorithm", + "Cipher", + "CipherAlgorithm", + "CipherContext", ] diff --git a/src/cryptography/hazmat/primitives/ciphers/aead.py b/src/cryptography/hazmat/primitives/ciphers/aead.py index 40f1b9b74459..c8a582d7844d 100644 --- a/src/cryptography/hazmat/primitives/ciphers/aead.py +++ b/src/cryptography/hazmat/primitives/ciphers/aead.py @@ -4,171 +4,20 @@ from __future__ import annotations -import os - -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 openssl as rust_openssl __all__ = [ - "ChaCha20Poly1305", "AESCCM", "AESGCM", "AESGCMSIV", "AESOCB3", "AESSIV", + "ChaCha20Poly1305", ] +AESGCM = rust_openssl.aead.AESGCM ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305 +AESCCM = rust_openssl.aead.AESCCM AESSIV = rust_openssl.aead.AESSIV AESOCB3 = rust_openssl.aead.AESOCB3 AESGCMSIV = rust_openssl.aead.AESGCMSIV - - -class AESCCM: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes, tag_length: int = 16): - utils._check_byteslike("key", key) - if len(key) not in (16, 24, 32): - raise ValueError("AESCCM key must be 128, 192, or 256 bits.") - - self._key = key - if not isinstance(tag_length, int): - raise TypeError("tag_length must be an integer") - - if tag_length not in (4, 6, 8, 10, 12, 14, 16): - raise ValueError("Invalid tag_length") - - self._tag_length = tag_length - - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "AESCCM is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - - @classmethod - def generate_key(cls, bit_length: 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: bytes | None, - ) -> 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) - self._validate_lengths(nonce, len(data)) - return aead._encrypt( - backend, self, nonce, data, [associated_data], self._tag_length - ) - - def decrypt( - self, - nonce: bytes, - data: bytes, - associated_data: bytes | None, - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - return aead._decrypt( - backend, self, nonce, data, [associated_data], self._tag_length - ) - - def _validate_lengths(self, nonce: bytes, data_len: int) -> None: - # For information about computing this, see - # https://tools.ietf.org/html/rfc3610#section-2.1 - l_val = 15 - len(nonce) - if 2 ** (8 * l_val) < data_len: - raise ValueError("Data too long for nonce") - - def _check_params( - self, nonce: 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 not 7 <= len(nonce) <= 13: - raise ValueError("Nonce must be between 7 and 13 bytes") - - -class AESGCM: - _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("AESGCM key must be 128, 192, or 256 bits.") - - self._key = key - - @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: bytes | None, - ) -> 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: bytes | None, - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - return aead._decrypt(backend, self, nonce, data, [associated_data], 16) - - def _check_params( - self, - nonce: bytes, - data: bytes, - associated_data: bytes, - ) -> None: - utils._check_byteslike("nonce", nonce) - utils._check_byteslike("data", data) - utils._check_byteslike("associated_data", associated_data) - if len(nonce) < 8 or len(nonce) > 128: - raise ValueError("Nonce must be between 8 and 128 bytes") diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py index 000bdcba97a4..f9fa8a587ea5 100644 --- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -5,24 +5,31 @@ from __future__ import annotations from cryptography import utils +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + ARC4 as ARC4, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + CAST5 as CAST5, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + IDEA as IDEA, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + SEED as SEED, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + Blowfish as Blowfish, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + TripleDES as TripleDES, +) +from cryptography.hazmat.primitives._cipheralgorithm import _verify_key_size from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, CipherAlgorithm, ) -def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: - # Verify that the key is instance of bytes - utils._check_byteslike("key", key) - - # Verify that the key size matches the expected key size - if len(key) * 8 not in algorithm.key_sizes: - raise ValueError( - f"Invalid key size ({len(key) * 8}) for {algorithm.name}." - ) - return key - - class AES(BlockCipherAlgorithm): name = "AES" block_size = 128 @@ -70,122 +77,72 @@ def key_size(self) -> int: return len(self.key) * 8 -class TripleDES(BlockCipherAlgorithm): - name = "3DES" - block_size = 64 - key_sizes = frozenset([64, 128, 192]) - - def __init__(self, key: bytes): - if len(key) == 8: - key += key + key - elif len(key) == 16: - key += key[:8] - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -class Blowfish(BlockCipherAlgorithm): - name = "Blowfish" - block_size = 64 - key_sizes = frozenset(range(32, 449, 8)) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) +utils.deprecated( + ARC4, + __name__, + "ARC4 has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.ARC4 and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="ARC4", +) - @property - def key_size(self) -> int: - return len(self.key) * 8 +utils.deprecated( + TripleDES, + __name__, + "TripleDES has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="TripleDES", +) -_BlowfishInternal = Blowfish utils.deprecated( Blowfish, __name__, - "Blowfish has been deprecated and will be removed in a future release", + "Blowfish has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.Blowfish and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.", utils.DeprecatedIn37, name="Blowfish", ) -class CAST5(BlockCipherAlgorithm): - name = "CAST5" - block_size = 64 - key_sizes = frozenset(range(40, 129, 8)) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -_CAST5Internal = CAST5 utils.deprecated( CAST5, __name__, - "CAST5 has been deprecated and will be removed in a future release", + "CAST5 has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.CAST5 and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.", utils.DeprecatedIn37, name="CAST5", ) -class ARC4(CipherAlgorithm): - name = "RC4" - key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -class IDEA(BlockCipherAlgorithm): - name = "IDEA" - block_size = 64 - key_sizes = frozenset([128]) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -_IDEAInternal = IDEA utils.deprecated( IDEA, __name__, - "IDEA has been deprecated and will be removed in a future release", + "IDEA has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.IDEA and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.", utils.DeprecatedIn37, name="IDEA", ) -class SEED(BlockCipherAlgorithm): - name = "SEED" - block_size = 128 - key_sizes = frozenset([128]) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -_SEEDInternal = SEED utils.deprecated( SEED, __name__, - "SEED has been deprecated and will be removed in a future release", + "SEED has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.SEED and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.", utils.DeprecatedIn37, name="SEED", ) diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index 2082df669a23..ebfa8052c8da 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -7,19 +7,10 @@ import abc import typing -from cryptography.exceptions import ( - AlreadyFinalized, - AlreadyUpdated, - NotYetFinalized, -) +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm from cryptography.hazmat.primitives.ciphers import modes -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.ciphers import ( - _CipherContext as _BackendCipherContext, - ) - class CipherContext(metaclass=abc.ABCMeta): @abc.abstractmethod @@ -42,6 +33,14 @@ def finalize(self) -> bytes: Returns the results of processing the final block as bytes. """ + @abc.abstractmethod + def reset_nonce(self, nonce: bytes) -> None: + """ + Resets the nonce for the cipher context to the provided value. + Raises an exception if it does not support reset or if the + provided nonce does not have a valid length. + """ + class AEADCipherContext(CipherContext, metaclass=abc.ABCMeta): @abc.abstractmethod @@ -97,14 +96,12 @@ def __init__( @typing.overload def encryptor( self: Cipher[modes.ModeWithAuthenticationTag], - ) -> AEADEncryptionContext: - ... + ) -> AEADEncryptionContext: ... @typing.overload def encryptor( self: _CIPHER_TYPE, - ) -> CipherContext: - ... + ) -> CipherContext: ... def encryptor(self): if isinstance(self.mode, modes.ModeWithAuthenticationTag): @@ -112,43 +109,25 @@ def encryptor(self): raise ValueError( "Authentication tag must be None when encrypting." ) - from cryptography.hazmat.backends.openssl.backend import backend - ctx = backend.create_symmetric_encryption_ctx( + return rust_openssl.ciphers.create_encryption_ctx( self.algorithm, self.mode ) - return self._wrap_ctx(ctx, encrypt=True) @typing.overload def decryptor( self: Cipher[modes.ModeWithAuthenticationTag], - ) -> AEADDecryptionContext: - ... + ) -> AEADDecryptionContext: ... @typing.overload def decryptor( self: _CIPHER_TYPE, - ) -> CipherContext: - ... + ) -> CipherContext: ... def decryptor(self): - from cryptography.hazmat.backends.openssl.backend import backend - - ctx = backend.create_symmetric_decryption_ctx( + return rust_openssl.ciphers.create_decryption_ctx( self.algorithm, self.mode ) - return self._wrap_ctx(ctx, encrypt=False) - - def _wrap_ctx( - self, ctx: _BackendCipherContext, encrypt: bool - ) -> AEADEncryptionContext | AEADDecryptionContext | CipherContext: - if isinstance(self.mode, modes.ModeWithAuthenticationTag): - if encrypt: - return _AEADEncryptionContext(ctx) - else: - return _AEADDecryptionContext(ctx) - else: - return _CipherContext(ctx) _CIPHER_TYPE = Cipher[ @@ -161,112 +140,6 @@ def _wrap_ctx( ] ] - -class _CipherContext(CipherContext): - _ctx: _BackendCipherContext | None - - def __init__(self, ctx: _BackendCipherContext) -> None: - self._ctx = ctx - - def update(self, data: bytes) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return self._ctx.update(data) - - def update_into(self, data: bytes, buf: bytes) -> int: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return self._ctx.update_into(data, buf) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - data = self._ctx.finalize() - self._ctx = None - return data - - -class _AEADCipherContext(AEADCipherContext): - _ctx: _BackendCipherContext | None - _tag: bytes | None - - def __init__(self, ctx: _BackendCipherContext) -> None: - self._ctx = ctx - self._bytes_processed = 0 - self._aad_bytes_processed = 0 - self._tag = None - self._updated = False - - def _check_limit(self, data_size: int) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - self._updated = True - self._bytes_processed += data_size - if self._bytes_processed > self._ctx._mode._MAX_ENCRYPTED_BYTES: - raise ValueError( - "{} has a maximum encrypted byte limit of {}".format( - self._ctx._mode.name, self._ctx._mode._MAX_ENCRYPTED_BYTES - ) - ) - - def update(self, data: bytes) -> bytes: - self._check_limit(len(data)) - # mypy needs this assert even though _check_limit already checked - assert self._ctx is not None - return self._ctx.update(data) - - def update_into(self, data: bytes, buf: bytes) -> int: - self._check_limit(len(data)) - # mypy needs this assert even though _check_limit already checked - assert self._ctx is not None - return self._ctx.update_into(data, buf) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - data = self._ctx.finalize() - self._tag = self._ctx.tag - self._ctx = None - return data - - def authenticate_additional_data(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - if self._updated: - raise AlreadyUpdated("Update has been called on this context.") - - self._aad_bytes_processed += len(data) - if self._aad_bytes_processed > self._ctx._mode._MAX_AAD_BYTES: - raise ValueError( - "{} has a maximum AAD byte limit of {}".format( - self._ctx._mode.name, self._ctx._mode._MAX_AAD_BYTES - ) - ) - - self._ctx.authenticate_additional_data(data) - - -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 - return data - - -class _AEADEncryptionContext(_AEADCipherContext, AEADEncryptionContext): - @property - def tag(self) -> bytes: - if self._ctx is not None: - raise NotYetFinalized( - "You must finalize encryption before " "getting the tag." - ) - assert self._tag is not None - return self._tag +CipherContext.register(rust_openssl.ciphers.CipherContext) +AEADEncryptionContext.register(rust_openssl.ciphers.AEADEncryptionContext) +AEADDecryptionContext.register(rust_openssl.ciphers.AEADDecryptionContext) diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index 712ccd3f7945..1dd2cc1e80c3 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -77,12 +77,9 @@ def _check_aes_key_length(self: Mode, algorithm: CipherAlgorithm) -> None: def _check_iv_length( self: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm ) -> None: - if len(self.initialization_vector) * 8 != algorithm.block_size: - raise ValueError( - "Invalid IV size ({}) for {}.".format( - len(self.initialization_vector), self.name - ) - ) + iv_len = len(self.initialization_vector) + if iv_len * 8 != algorithm.block_size: + raise ValueError(f"Invalid IV size ({iv_len}) for {self.name}.") def _check_nonce_length( @@ -242,9 +239,8 @@ def __init__( raise ValueError("min_tag_length must be >= 4") if len(tag) < min_tag_length: raise ValueError( - "Authentication tag must be {} bytes or longer.".format( - min_tag_length - ) + f"Authentication tag must be {min_tag_length} bytes or " + "longer." ) self._tag = tag self._min_tag_length = min_tag_length @@ -267,7 +263,6 @@ def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: block_size_bytes = algorithm.block_size // 8 if self._tag is not None and len(self._tag) > block_size_bytes: raise ValueError( - "Authentication tag cannot be more than {} bytes.".format( - block_size_bytes - ) + f"Authentication tag cannot be more than {block_size_bytes} " + "bytes." ) diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py index c5be0c8eadc0..b819e399287e 100644 --- a/src/cryptography/hazmat/primitives/hashes.py +++ b/src/cryptography/hazmat/primitives/hashes.py @@ -9,27 +9,27 @@ from cryptography.hazmat.bindings._rust import openssl as rust_openssl __all__ = [ - "HashAlgorithm", - "HashContext", - "Hash", - "ExtendableOutputFunction", + "MD5", "SHA1", - "SHA512_224", - "SHA512_256", - "SHA224", - "SHA256", - "SHA384", - "SHA512", "SHA3_224", "SHA3_256", "SHA3_384", "SHA3_512", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA512_224", + "SHA512_256", "SHAKE128", "SHAKE256", - "MD5", + "SM3", "BLAKE2b", "BLAKE2s", - "SM3", + "ExtendableOutputFunction", + "Hash", + "HashAlgorithm", + "HashContext", ] diff --git a/src/cryptography/hazmat/primitives/kdf/argon2.py b/src/cryptography/hazmat/primitives/kdf/argon2.py new file mode 100644 index 000000000000..405fc8dff268 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/argon2.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +Argon2id = rust_openssl.kdf.Argon2id +KeyDerivationFunction.register(Argon2id) + +__all__ = ["Argon2id"] diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py index 2f41db9260ec..802b484c72ae 100644 --- a/src/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -75,7 +75,7 @@ def __init__( if (label or context) and fixed: raise ValueError( - "When supplying fixed data, " "label and context are ignored." + "When supplying fixed data, label and context are ignored." ) if rlen is None or not self._valid_byte_length(rlen): @@ -87,6 +87,9 @@ def __init__( if llen is not None and not isinstance(llen, int): raise TypeError("llen must be an integer") + if llen == 0: + raise ValueError("llen must be non-zero") + if label is None: label = b"" diff --git a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py index 623e1ca7f9eb..82689ebca4ae 100644 --- a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -33,9 +33,7 @@ def __init__( if not ossl.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( - "{} is not supported for PBKDF2 by this backend.".format( - algorithm.name - ), + f"{algorithm.name} is not supported for PBKDF2.", _Reasons.UNSUPPORTED_HASH, ) self._used = False diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py index 05a4f675b6ab..f791ceea371b 100644 --- a/src/cryptography/hazmat/primitives/kdf/scrypt.py +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -5,76 +5,15 @@ from __future__ import annotations import sys -import typing -from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidKey, - UnsupportedAlgorithm, -) from cryptography.hazmat.bindings._rust import openssl as rust_openssl -from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.kdf import KeyDerivationFunction # This is used by the scrypt tests to skip tests that require more memory # than the MEM_LIMIT _MEM_LIMIT = sys.maxsize // 2 +Scrypt = rust_openssl.kdf.Scrypt +KeyDerivationFunction.register(Scrypt) -class Scrypt(KeyDerivationFunction): - def __init__( - self, - salt: bytes, - length: int, - n: int, - r: int, - p: int, - backend: typing.Any = None, - ): - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - if not ossl.scrypt_supported(): - raise UnsupportedAlgorithm( - "This version of OpenSSL does not support scrypt" - ) - self._length = length - utils._check_bytes("salt", salt) - if n < 2 or (n & (n - 1)) != 0: - raise ValueError("n must be greater than 1 and be a power of 2.") - - if r < 1: - raise ValueError("r must be greater than or equal to 1.") - - if p < 1: - raise ValueError("p must be greater than or equal to 1.") - - self._used = False - self._salt = salt - self._n = n - self._r = r - self._p = p - - def derive(self, key_material: bytes) -> bytes: - if self._used: - raise AlreadyFinalized("Scrypt instances can only be used once.") - self._used = True - - utils._check_byteslike("key_material", key_material) - - return rust_openssl.kdf.derive_scrypt( - key_material, - self._salt, - self._n, - self._r, - self._p, - _MEM_LIMIT, - self._length, - ) - - def verify(self, key_material: bytes, expected_key: bytes) -> None: - derived_key = self.derive(key_material) - if not constant_time.bytes_eq(derived_key, expected_key): - raise InvalidKey("Keys do not match.") +__all__ = ["Scrypt"] diff --git a/src/cryptography/hazmat/primitives/keywrap.py b/src/cryptography/hazmat/primitives/keywrap.py index 3ee152b7903a..b93d87d31cff 100644 --- a/src/cryptography/hazmat/primitives/keywrap.py +++ b/src/cryptography/hazmat/primitives/keywrap.py @@ -86,7 +86,7 @@ def aes_key_wrap_with_padding( if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") - aiv = b"\xA6\x59\x59\xA6" + len(key_to_wrap).to_bytes( + aiv = b"\xa6\x59\x59\xa6" + len(key_to_wrap).to_bytes( length=4, byteorder="big" ) # pad the key to wrap if necessary diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index baceaf381880..b2a3f1cfffaa 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -10,8 +10,9 @@ from cryptography import utils from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.bindings._rust import ( + PKCS7PaddingContext, + PKCS7UnpaddingContext, check_ansix923_padding, - check_pkcs7_padding, ) @@ -111,57 +112,14 @@ def __init__(self, block_size: int): self.block_size = block_size def padder(self) -> PaddingContext: - return _PKCS7PaddingContext(self.block_size) + return PKCS7PaddingContext(self.block_size) def unpadder(self) -> PaddingContext: - return _PKCS7UnpaddingContext(self.block_size) + return PKCS7UnpaddingContext(self.block_size) -class _PKCS7PaddingContext(PaddingContext): - _buffer: bytes | None - - def __init__(self, block_size: int): - self.block_size = block_size - # TODO: more copies than necessary, we should use zero-buffer (#193) - self._buffer = b"" - - def update(self, data: bytes) -> bytes: - self._buffer, result = _byte_padding_update( - self._buffer, data, self.block_size - ) - return result - - def _padding(self, size: int) -> bytes: - return bytes([size]) * size - - def finalize(self) -> bytes: - result = _byte_padding_pad( - self._buffer, self.block_size, self._padding - ) - self._buffer = None - return result - - -class _PKCS7UnpaddingContext(PaddingContext): - _buffer: bytes | None - - def __init__(self, block_size: int): - self.block_size = block_size - # TODO: more copies than necessary, we should use zero-buffer (#193) - self._buffer = b"" - - def update(self, data: bytes) -> bytes: - self._buffer, result = _byte_unpadding_update( - self._buffer, data, self.block_size - ) - return result - - def finalize(self) -> bytes: - result = _byte_unpadding_check( - self._buffer, self.block_size, check_pkcs7_padding - ) - self._buffer = None - return result +PaddingContext.register(PKCS7PaddingContext) +PaddingContext.register(PKCS7UnpaddingContext) class ANSIX923: diff --git a/src/cryptography/hazmat/primitives/serialization/__init__.py b/src/cryptography/hazmat/primitives/serialization/__init__.py index b6c9a5cdc520..07b2264b9a51 100644 --- a/src/cryptography/hazmat/primitives/serialization/__init__.py +++ b/src/cryptography/hazmat/primitives/serialization/__init__.py @@ -36,6 +36,21 @@ ) __all__ = [ + "BestAvailableEncryption", + "Encoding", + "KeySerializationEncryption", + "NoEncryption", + "ParameterFormat", + "PrivateFormat", + "PublicFormat", + "SSHCertPrivateKeyTypes", + "SSHCertPublicKeyTypes", + "SSHCertificate", + "SSHCertificateBuilder", + "SSHCertificateType", + "SSHPrivateKeyTypes", + "SSHPublicKeyTypes", + "_KeySerializationEncryption", "load_der_parameters", "load_der_private_key", "load_der_public_key", @@ -45,19 +60,4 @@ "load_ssh_private_key", "load_ssh_public_identity", "load_ssh_public_key", - "Encoding", - "PrivateFormat", - "PublicFormat", - "ParameterFormat", - "KeySerializationEncryption", - "BestAvailableEncryption", - "NoEncryption", - "_KeySerializationEncryption", - "SSHCertificateBuilder", - "SSHCertificate", - "SSHCertificateType", - "SSHCertPublicKeyTypes", - "SSHCertPrivateKeyTypes", - "SSHPrivateKeyTypes", - "SSHPublicKeyTypes", ] diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py index 006a248bd244..549e1f992d39 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -7,6 +7,7 @@ import typing from cryptography import x509 +from cryptography.hazmat.bindings._rust import pkcs12 as rust_pkcs12 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives._serialization import PBES as PBES from cryptography.hazmat.primitives.asymmetric import ( @@ -20,9 +21,9 @@ __all__ = [ "PBES", - "PKCS12PrivateKeyTypes", "PKCS12Certificate", "PKCS12KeyAndCertificates", + "PKCS12PrivateKeyTypes", "load_key_and_certificates", "load_pkcs12", "serialize_key_and_certificates", @@ -37,43 +38,7 @@ ] -class PKCS12Certificate: - def __init__( - self, - cert: x509.Certificate, - friendly_name: bytes | None, - ): - if not isinstance(cert, x509.Certificate): - raise TypeError("Expecting x509.Certificate object") - if friendly_name is not None and not isinstance(friendly_name, bytes): - raise TypeError("friendly_name must be bytes or None") - self._cert = cert - self._friendly_name = friendly_name - - @property - def friendly_name(self) -> bytes | None: - return self._friendly_name - - @property - def certificate(self) -> x509.Certificate: - return self._cert - - def __eq__(self, other: object) -> bool: - if not isinstance(other, PKCS12Certificate): - return NotImplemented - - return ( - self.certificate == other.certificate - and self.friendly_name == other.friendly_name - ) - - def __hash__(self) -> int: - return hash((self.certificate, self.friendly_name)) - - def __repr__(self) -> str: - return "".format( - self.certificate, self.friendly_name - ) +PKCS12Certificate = rust_pkcs12.PKCS12Certificate class PKCS12KeyAndCertificates: @@ -143,28 +108,8 @@ def __repr__(self) -> str: return fmt.format(self.key, self.cert, self.additional_certs) -def load_key_and_certificates( - data: bytes, - password: bytes | None, - backend: typing.Any = None, -) -> tuple[ - PrivateKeyTypes | None, - x509.Certificate | None, - list[x509.Certificate], -]: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_key_and_certificates_from_pkcs12(data, password) - - -def load_pkcs12( - data: bytes, - password: bytes | None, - backend: typing.Any = None, -) -> PKCS12KeyAndCertificates: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pkcs12(data, password) +load_key_and_certificates = rust_pkcs12.load_key_and_certificates +load_pkcs12 = rust_pkcs12.load_pkcs12 _PKCS12CATypes = typing.Union[ @@ -194,22 +139,6 @@ def serialize_key_and_certificates( "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" " private key, or None." ) - if cert is not None and not isinstance(cert, x509.Certificate): - raise TypeError("cert must be a certificate or None") - - if cas is not None: - cas = list(cas) - if not all( - isinstance( - val, - ( - x509.Certificate, - PKCS12Certificate, - ), - ) - for val in cas - ): - raise TypeError("all values in cas must be certificates") if not isinstance( encryption_algorithm, serialization.KeySerializationEncryption @@ -222,8 +151,6 @@ def serialize_key_and_certificates( if key is None and cert is None and not cas: raise ValueError("You must supply at least one of key, cert, or cas") - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.serialize_key_and_certificates_to_pkcs12( + return rust_pkcs12.serialize_key_and_certificates( name, key, cert, cas, encryption_algorithm ) diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py index bae35c5f5988..882e345f2e7f 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -12,6 +12,7 @@ import typing from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa @@ -177,7 +178,97 @@ def sign( return rust_pkcs7.sign_and_serialize(self, encoding, options) -def _smime_encode( +class PKCS7EnvelopeBuilder: + def __init__( + self, + *, + _data: bytes | None = None, + _recipients: list[x509.Certificate] | None = None, + ): + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + if not ossl.rsa_encryption_supported(padding=padding.PKCS1v15()): + raise UnsupportedAlgorithm( + "RSA with PKCS1 v1.5 padding is not supported by this version" + " of OpenSSL.", + _Reasons.UNSUPPORTED_PADDING, + ) + self._data = _data + self._recipients = _recipients if _recipients is not None else [] + + def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder: + _check_byteslike("data", data) + if self._data is not None: + raise ValueError("data may only be set once") + + return PKCS7EnvelopeBuilder(_data=data, _recipients=self._recipients) + + def add_recipient( + self, + certificate: x509.Certificate, + ) -> PKCS7EnvelopeBuilder: + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + if not isinstance(certificate.public_key(), rsa.RSAPublicKey): + raise TypeError("Only RSA keys are supported at this time.") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=[ + *self._recipients, + certificate, + ], + ) + + def encrypt( + self, + encoding: serialization.Encoding, + options: typing.Iterable[PKCS7Options], + ) -> bytes: + if len(self._recipients) == 0: + raise ValueError("Must have at least one recipient") + if self._data is None: + raise ValueError("You must add data to encrypt") + options = list(options) + if not all(isinstance(x, PKCS7Options) for x in options): + raise ValueError("options must be from the PKCS7Options enum") + if encoding not in ( + serialization.Encoding.PEM, + serialization.Encoding.DER, + serialization.Encoding.SMIME, + ): + raise ValueError( + "Must be PEM, DER, or SMIME from the Encoding enum" + ) + + # Only allow options that make sense for encryption + if any( + opt not in [PKCS7Options.Text, PKCS7Options.Binary] + for opt in options + ): + raise ValueError( + "Only the following options are supported for encryption: " + "Text, Binary" + ) + elif PKCS7Options.Text in options and PKCS7Options.Binary in options: + # OpenSSL accepts both options at the same time, but ignores Text. + # We fail defensively to avoid unexpected outputs. + raise ValueError( + "Cannot use Binary and Text options at the same time" + ) + + return rust_pkcs7.encrypt_and_serialize(self, encoding, options) + + +pkcs7_decrypt_der = rust_pkcs7.decrypt_der +pkcs7_decrypt_pem = rust_pkcs7.decrypt_pem +pkcs7_decrypt_smime = rust_pkcs7.decrypt_smime + + +def _smime_signed_encode( data: bytes, signature: bytes, micalg: str, text_mode: bool ) -> bytes: # This function works pretty hard to replicate what OpenSSL does @@ -225,6 +316,51 @@ def _smime_encode( return fp.getvalue() +def _smime_enveloped_encode(data: bytes) -> bytes: + m = email.message.Message() + m.add_header("MIME-Version", "1.0") + m.add_header("Content-Disposition", "attachment", filename="smime.p7m") + m.add_header( + "Content-Type", + "application/pkcs7-mime", + smime_type="enveloped-data", + name="smime.p7m", + ) + m.add_header("Content-Transfer-Encoding", "base64") + + m.set_payload(email.base64mime.body_encode(data, maxlinelen=65)) + + return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0)) + + +def _smime_enveloped_decode(data: bytes) -> bytes: + m = email.message_from_bytes(data) + if m.get_content_type() not in { + "application/x-pkcs7-mime", + "application/pkcs7-mime", + }: + raise ValueError("Not an S/MIME enveloped message") + return bytes(m.get_payload(decode=True)) + + +def _smime_remove_text_headers(data: bytes) -> bytes: + m = email.message_from_bytes(data) + # Using get() instead of get_content_type() since it has None as default, + # where the latter has "text/plain". Both methods are case-insensitive. + content_type = m.get("content-type") + if content_type is None: + raise ValueError( + "Decrypted MIME data has no 'Content-Type' header. " + "Please remove the 'Text' option to parse it manually." + ) + if "text/plain" not in content_type: + raise ValueError( + f"Decrypted MIME data content type is '{content_type}', not " + "'text/plain'. Remove the 'Text' option to parse it manually." + ) + return bytes(m.get_payload(decode=True)) + + class OpenSSLMimePart(email.message.MIMEPart): # A MIMEPart subclass that replicates OpenSSL's behavior of not including # a newline if there are no headers. diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index f33edd55e0ea..c01afb0ccdc9 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -64,6 +64,10 @@ def _bcrypt_kdf( _ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" _CERT_SUFFIX = b"-cert-v01@openssh.com" +# U2F application string suffixed pubkey +_SK_SSH_ED25519 = b"sk-ssh-ed25519@openssh.com" +_SK_SSH_ECDSA_NISTP256 = b"sk-ecdsa-sha2-nistp256@openssh.com" + # These are not key types, only algorithms, so they cannot appear # as a public key type _SSH_RSA_SHA256 = b"rsa-sha2-256" @@ -307,7 +311,9 @@ class _SSHFormatRSA: mpint n, e, d, iqmp, p, q """ - def get_public(self, data: memoryview): + def get_public( + self, data: memoryview + ) -> tuple[tuple[int, int], memoryview]: """RSA public fields""" e, data = _get_mpint(data) n, data = _get_mpint(data) @@ -454,7 +460,9 @@ 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) -> tuple[tuple, memoryview]: + def get_public( + self, data: memoryview + ) -> tuple[tuple[memoryview, memoryview], memoryview]: """ECDSA public fields""" curve, data = _get_sshstr(data) point, data = _get_sshstr(data) @@ -517,7 +525,9 @@ class _SSHFormatEd25519: bytes secret_and_point """ - def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: + def get_public( + self, data: memoryview + ) -> tuple[tuple[memoryview], memoryview]: """Ed25519 public fields""" point, data = _get_sshstr(data) return (point,), data @@ -572,6 +582,56 @@ def encode_private( f_priv.put_sshstr(f_keypair) +def load_application(data) -> tuple[memoryview, memoryview]: + """ + U2F application strings + """ + application, data = _get_sshstr(data) + if not application.tobytes().startswith(b"ssh:"): + raise ValueError( + "U2F application string does not start with b'ssh:' " + f"({application})" + ) + return application, data + + +class _SSHFormatSKEd25519: + """ + The format of a sk-ssh-ed25519@openssh.com public key is: + + string "sk-ssh-ed25519@openssh.com" + string public key + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: + """Make Ed25519 public key from data.""" + public_key, data = _lookup_kformat(_SSH_ED25519).load_public(data) + _, data = load_application(data) + return public_key, data + + +class _SSHFormatSKECDSA: + """ + The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: + + string "sk-ecdsa-sha2-nistp256@openssh.com" + string curve name + ec_point Q + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: + """Make ECDSA public key from data.""" + public_key, data = _lookup_kformat(_ECDSA_NISTP256).load_public(data) + _, data = load_application(data) + return public_key, data + + _KEY_FORMATS = { _SSH_RSA: _SSHFormatRSA(), _SSH_DSA: _SSHFormatDSA(), @@ -579,6 +639,8 @@ def encode_private( _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()), _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()), _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()), + _SK_SSH_ED25519: _SSHFormatSKEd25519(), + _SK_SSH_ECDSA_NISTP256: _SSHFormatSKECDSA(), } diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py index af5ab6efe290..855a5d212ea3 100644 --- a/src/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -67,6 +67,9 @@ def __init__( self._algorithm = algorithm def generate(self, counter: int) -> bytes: + if not isinstance(counter, int): + raise TypeError("Counter parameter must be an integer type.") + truncated_value = self._dynamic_truncate(counter) hotp = truncated_value % (10**self._length) return "{0:0{1}}".format(hotp, self._length).encode() @@ -77,7 +80,12 @@ def verify(self, hotp: bytes, counter: int) -> None: def _dynamic_truncate(self, counter: int) -> int: ctx = hmac.HMAC(self._key, self._algorithm) - ctx.update(counter.to_bytes(length=8, byteorder="big")) + + try: + ctx.update(counter.to_bytes(length=8, byteorder="big")) + except OverflowError: + raise ValueError(f"Counter must be between 0 and {2 ** 64 - 1}.") + hmac_value = ctx.finalize() offset = hmac_value[len(hmac_value) - 1] & 0b1111 diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py index 68a5077468e3..b9ed7349a14e 100644 --- a/src/cryptography/hazmat/primitives/twofactor/totp.py +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -31,6 +31,11 @@ def __init__( ) def generate(self, time: int | float) -> bytes: + if not isinstance(time, (int, float)): + raise TypeError( + "Time parameter must be an integer type or float type." + ) + counter = int(time / self._time_step) return self._hotp.generate(counter) diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index a0ec7a3cd76d..706d0ae4cbd7 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -25,6 +25,7 @@ class CryptographyDeprecationWarning(UserWarning): DeprecatedIn40 = CryptographyDeprecationWarning DeprecatedIn41 = CryptographyDeprecationWarning DeprecatedIn42 = CryptographyDeprecationWarning +DeprecatedIn43 = CryptographyDeprecationWarning def _check_bytes(name: str, value: bytes) -> None: @@ -40,18 +41,13 @@ def _check_byteslike(name: str, value: bytes) -> None: def int_to_bytes(integer: int, length: int | None = None) -> bytes: + if length == 0: + raise ValueError("length argument can't be 0") return integer.to_bytes( length or (integer.bit_length() + 7) // 8 or 1, "big" ) -def _extract_buffer_length(obj: typing.Any) -> tuple[typing.Any, int]: - from cryptography.hazmat.bindings._rust import _openssl - - buf = _openssl.ffi.from_buffer(obj) - return buf, int(_openssl.ffi.cast("uintptr_t", buf)) - - class InterfaceNotImplemented(Exception): pass diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 931618aa49d1..8a89d67f151e 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -30,6 +30,8 @@ ) from cryptography.x509.extensions import ( AccessDescription, + Admission, + Admissions, AuthorityInformationAccess, AuthorityKeyIdentifier, BasicConstraints, @@ -55,6 +57,7 @@ KeyUsage, MSCertificateTemplate, NameConstraints, + NamingAuthority, NoticeReference, OCSPAcceptableResponses, OCSPNoCheck, @@ -63,6 +66,7 @@ PolicyInformation, PrecertificateSignedCertificateTimestamps, PrecertPoison, + ProfessionInfo, ReasonFlags, SignedCertificateTimestamps, SubjectAlternativeName, @@ -97,6 +101,7 @@ ExtensionOID, NameOID, ObjectIdentifier, + PublicKeyAlgorithmOID, SignatureAlgorithmOID, ) @@ -170,88 +175,93 @@ OID_OCSP = AuthorityInformationAccessOID.OCSP __all__ = [ - "certificate_transparency", - "verification", - "load_pem_x509_certificate", - "load_pem_x509_certificates", - "load_der_x509_certificate", - "load_pem_x509_csr", - "load_der_x509_csr", - "load_pem_x509_crl", - "load_der_x509_crl", - "random_serial_number", - "verification", + "OID_CA_ISSUERS", + "OID_OCSP", + "AccessDescription", + "Admission", + "Admissions", "Attribute", "AttributeNotFound", "Attributes", - "InvalidVersion", + "AuthorityInformationAccess", + "AuthorityKeyIdentifier", + "BasicConstraints", + "CRLDistributionPoints", + "CRLNumber", + "CRLReason", + "Certificate", + "CertificateBuilder", + "CertificateIssuer", + "CertificatePolicies", + "CertificateRevocationList", + "CertificateRevocationListBuilder", + "CertificateSigningRequest", + "CertificateSigningRequestBuilder", + "DNSName", "DeltaCRLIndicator", + "DirectoryName", + "DistributionPoint", "DuplicateExtension", + "ExtendedKeyUsage", + "Extension", "ExtensionNotFound", - "UnsupportedGeneralNameType", - "NameAttribute", - "Name", - "RelativeDistinguishedName", - "ObjectIdentifier", "ExtensionType", "Extensions", - "Extension", - "ExtendedKeyUsage", "FreshestCRL", + "GeneralName", + "GeneralNames", + "IPAddress", + "InhibitAnyPolicy", + "InvalidVersion", + "InvalidityDate", + "IssuerAlternativeName", "IssuingDistributionPoint", - "TLSFeature", - "TLSFeatureType", + "KeyUsage", + "MSCertificateTemplate", + "Name", + "NameAttribute", + "NameConstraints", + "NameOID", + "NamingAuthority", + "NoticeReference", "OCSPAcceptableResponses", "OCSPNoCheck", - "BasicConstraints", - "CRLNumber", - "KeyUsage", - "AuthorityInformationAccess", - "SubjectInformationAccess", - "AccessDescription", - "CertificatePolicies", + "OCSPNonce", + "ObjectIdentifier", + "OtherName", + "PolicyConstraints", "PolicyInformation", - "UserNotice", - "NoticeReference", - "SubjectKeyIdentifier", - "NameConstraints", - "CRLDistributionPoints", - "DistributionPoint", - "ReasonFlags", - "InhibitAnyPolicy", - "SubjectAlternativeName", - "IssuerAlternativeName", - "AuthorityKeyIdentifier", - "GeneralNames", - "GeneralName", + "PrecertPoison", + "PrecertificateSignedCertificateTimestamps", + "ProfessionInfo", + "PublicKeyAlgorithmOID", "RFC822Name", - "DNSName", - "UniformResourceIdentifier", + "ReasonFlags", "RegisteredID", - "DirectoryName", - "IPAddress", - "OtherName", - "Certificate", - "CertificateRevocationList", - "CertificateRevocationListBuilder", - "CertificateSigningRequest", + "RelativeDistinguishedName", "RevokedCertificate", "RevokedCertificateBuilder", - "CertificateSigningRequestBuilder", - "CertificateBuilder", - "Version", - "OID_CA_ISSUERS", - "OID_OCSP", - "CertificateIssuer", - "CRLReason", - "InvalidityDate", - "UnrecognizedExtension", - "PolicyConstraints", - "PrecertificateSignedCertificateTimestamps", - "PrecertPoison", - "OCSPNonce", - "SignedCertificateTimestamps", "SignatureAlgorithmOID", - "NameOID", - "MSCertificateTemplate", + "SignedCertificateTimestamps", + "SubjectAlternativeName", + "SubjectInformationAccess", + "SubjectKeyIdentifier", + "TLSFeature", + "TLSFeatureType", + "UniformResourceIdentifier", + "UnrecognizedExtension", + "UnsupportedGeneralNameType", + "UserNotice", + "Version", + "certificate_transparency", + "load_der_x509_certificate", + "load_der_x509_crl", + "load_der_x509_csr", + "load_pem_x509_certificate", + "load_pem_x509_certificates", + "load_pem_x509_crl", + "load_pem_x509_csr", + "random_serial_number", + "verification", + "verification", ] diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 89a75a23ac36..25b317af626f 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -12,7 +12,7 @@ from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, @@ -25,7 +25,6 @@ ) from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPrivateKeyTypes, - CertificateIssuerPublicKeyTypes, CertificatePublicKeyTypes, ) from cryptography.x509.extensions import ( @@ -160,159 +159,7 @@ def __init__(self, msg: str, parsed_version: int) -> None: self.parsed_version = parsed_version -class Certificate(metaclass=abc.ABCMeta): - @abc.abstractmethod - def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: - """ - Returns bytes using digest passed. - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - Returns certificate serial number - """ - - @property - @abc.abstractmethod - def version(self) -> Version: - """ - Returns the certificate version - """ - - @abc.abstractmethod - def public_key(self) -> CertificatePublicKeyTypes: - """ - Returns the public key - """ - - @property - @abc.abstractmethod - 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: - """ - 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: - """ - Returns the issuer name object. - """ - - @property - @abc.abstractmethod - def subject(self) -> Name: - """ - Returns the subject name object. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> hashes.HashAlgorithm | None: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - in the certificate. - """ - - @property - @abc.abstractmethod - 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: - """ - Returns an Extensions object. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature bytes. - """ - - @property - @abc.abstractmethod - def tbs_certificate_bytes(self) -> bytes: - """ - Returns the tbsCertificate payload bytes as defined in RFC 5280. - """ - - @property - @abc.abstractmethod - def tbs_precertificate_bytes(self) -> bytes: - """ - Returns the tbsCertificate payload bytes with the SCT list extension - stripped. - """ - - @abc.abstractmethod - def __eq__(self, other: object) -> bool: - """ - Checks equality. - """ - - @abc.abstractmethod - def __hash__(self) -> int: - """ - Computes a hash. - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the certificate to PEM or DER format. - """ - - @abc.abstractmethod - def verify_directly_issued_by(self, issuer: Certificate) -> None: - """ - This method verifies that certificate issuer name matches the - issuer subject name and that the certificate is signed by the - issuer's private key. No other validation is performed. - """ - - -# Runtime isinstance checks need this since the rust class is not a subclass. -Certificate.register(rust_x509.Certificate) +Certificate = rust_x509.Certificate class RevokedCertificate(metaclass=abc.ABCMeta): @@ -384,261 +231,8 @@ def extensions(self) -> Extensions: return self._extensions -class CertificateRevocationList(metaclass=abc.ABCMeta): - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the CRL to PEM or DER format. - """ - - @abc.abstractmethod - def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: - """ - Returns bytes using digest passed. - """ - - @abc.abstractmethod - def get_revoked_certificate_by_serial_number( - self, serial_number: int - ) -> RevokedCertificate | None: - """ - Returns an instance of RevokedCertificate or None if the serial_number - is not in the CRL. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> hashes.HashAlgorithm | None: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - in the certificate. - """ - - @property - @abc.abstractmethod - 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: - """ - Returns the X509Name with the issuer of this CRL. - """ - - @property - @abc.abstractmethod - 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: - """ - 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: - """ - Returns an Extensions object containing a list of CRL extensions. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature bytes. - """ - - @property - @abc.abstractmethod - def tbs_certlist_bytes(self) -> bytes: - """ - Returns the tbsCertList payload bytes as defined in RFC 5280. - """ - - @abc.abstractmethod - def __eq__(self, other: object) -> bool: - """ - Checks equality. - """ - - @abc.abstractmethod - def __len__(self) -> int: - """ - Number of revoked certificates in the CRL. - """ - - @typing.overload - def __getitem__(self, idx: int) -> RevokedCertificate: - ... - - @typing.overload - def __getitem__(self, idx: slice) -> list[RevokedCertificate]: - ... - - @abc.abstractmethod - def __getitem__( - self, idx: int | slice - ) -> RevokedCertificate | list[RevokedCertificate]: - """ - Returns a revoked certificate (or slice of revoked certificates). - """ - - @abc.abstractmethod - def __iter__(self) -> typing.Iterator[RevokedCertificate]: - """ - Iterator over the revoked certificates - """ - - @abc.abstractmethod - def is_signature_valid( - self, public_key: CertificateIssuerPublicKeyTypes - ) -> bool: - """ - Verifies signature of revocation list against given public key. - """ - - -CertificateRevocationList.register(rust_x509.CertificateRevocationList) - - -class CertificateSigningRequest(metaclass=abc.ABCMeta): - @abc.abstractmethod - def __eq__(self, other: object) -> bool: - """ - Checks equality. - """ - - @abc.abstractmethod - def __hash__(self) -> int: - """ - Computes a hash. - """ - - @abc.abstractmethod - def public_key(self) -> CertificatePublicKeyTypes: - """ - Returns the public key - """ - - @property - @abc.abstractmethod - def subject(self) -> Name: - """ - Returns the subject name object. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> hashes.HashAlgorithm | None: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - in the certificate. - """ - - @property - @abc.abstractmethod - 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: - """ - Returns the extensions in the signing request. - """ - - @property - @abc.abstractmethod - def attributes(self) -> Attributes: - """ - Returns an Attributes object. - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Encodes the request to PEM or DER format. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature bytes. - """ - - @property - @abc.abstractmethod - def tbs_certrequest_bytes(self) -> bytes: - """ - Returns the PKCS#10 CertificationRequestInfo bytes as defined in RFC - 2986. - """ - - @property - @abc.abstractmethod - def is_signature_valid(self) -> bool: - """ - Verifies signature of signing request. - """ - - @abc.abstractmethod - def get_attribute_for_oid(self, oid: ObjectIdentifier) -> bytes: - """ - Get the attribute value for a given OID. - """ - - -# Runtime isinstance checks need this since the rust class is not a subclass. -CertificateSigningRequest.register(rust_x509.CertificateSigningRequest) +CertificateRevocationList = rust_x509.CertificateRevocationList +CertificateSigningRequest = rust_x509.CertificateSigningRequest load_pem_x509_certificate = rust_x509.load_pem_x509_certificate @@ -864,7 +458,7 @@ def serial_number(self, number: int) -> CertificateBuilder: # zero. if number.bit_length() >= 160: # As defined in RFC 5280 raise ValueError( - "The serial number should not be more than 159 " "bits." + "The serial number should not be more than 159 bits." ) return CertificateBuilder( self._issuer_name, @@ -1042,7 +636,7 @@ def last_update( last_update = _convert_to_naive_utc_time(last_update) if last_update < _EARLIEST_UTC_TIME: raise ValueError( - "The last update date must be on or after" " 1950 January 1." + "The last update date must be on or after 1950 January 1." ) if self._next_update is not None and last_update > self._next_update: raise ValueError( @@ -1066,7 +660,7 @@ def next_update( next_update = _convert_to_naive_utc_time(next_update) if next_update < _EARLIEST_UTC_TIME: raise ValueError( - "The last update date must be on or after" " 1950 January 1." + "The last update date must be on or after 1950 January 1." ) if self._last_update is not None and next_update < self._last_update: raise ValueError( @@ -1167,7 +761,7 @@ def serial_number(self, number: int) -> RevokedCertificateBuilder: # zero. if number.bit_length() >= 160: # As defined in RFC 5280 raise ValueError( - "The serial number should not be more than 159 " "bits." + "The serial number should not be more than 159 bits." ) return RevokedCertificateBuilder( number, self._revocation_date, self._extensions @@ -1183,7 +777,7 @@ def revocation_date( time = _convert_to_naive_utc_time(time) if time < _EARLIEST_UTC_TIME: raise ValueError( - "The revocation date must be on or after" " 1950 January 1." + "The revocation date must be on or after 1950 January 1." ) return RevokedCertificateBuilder( self._serial_number, time, self._extensions diff --git a/src/cryptography/x509/certificate_transparency.py b/src/cryptography/x509/certificate_transparency.py index 73647ee716fc..fb66cc604952 100644 --- a/src/cryptography/x509/certificate_transparency.py +++ b/src/cryptography/x509/certificate_transparency.py @@ -4,12 +4,8 @@ from __future__ import annotations -import abc -import datetime - from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 -from cryptography.hazmat.primitives.hashes import HashAlgorithm class LogEntryType(utils.Enum): @@ -36,62 +32,4 @@ class SignatureAlgorithm(utils.Enum): ECDSA = 3 -class SignedCertificateTimestamp(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def version(self) -> Version: - """ - Returns the SCT version. - """ - - @property - @abc.abstractmethod - def log_id(self) -> bytes: - """ - Returns an identifier indicating which log this SCT is for. - """ - - @property - @abc.abstractmethod - def timestamp(self) -> datetime.datetime: - """ - Returns the timestamp for this SCT. - """ - - @property - @abc.abstractmethod - def entry_type(self) -> LogEntryType: - """ - Returns whether this is an SCT for a certificate or pre-certificate. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm(self) -> HashAlgorithm: - """ - Returns the hash algorithm used for the SCT's signature. - """ - - @property - @abc.abstractmethod - def signature_algorithm(self) -> SignatureAlgorithm: - """ - Returns the signing algorithm used for the SCT's signature. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature for this SCT. - """ - - @property - @abc.abstractmethod - def extension_bytes(self) -> bytes: - """ - Returns the raw bytes of any extensions for this SCT. - """ - - -SignedCertificateTimestamp.register(rust_x509.Sct) +SignedCertificateTimestamp = rust_x509.Sct diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index c61c1f4853fd..fc3e7730eca0 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -401,8 +401,8 @@ def __init__( def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -456,8 +456,9 @@ def path_length(self) -> int | None: def __repr__(self) -> str: return ( - "" - ).format(self) + f"" + ) def __eq__(self, other: object) -> bool: if not isinstance(other, BasicConstraints): @@ -729,6 +730,31 @@ class ReasonFlags(utils.Enum): ReasonFlags.aa_compromise: 8, } +# CRLReason ::= ENUMERATED { +# unspecified (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# -- value 7 is not used +# removeFromCRL (8), +# privilegeWithdrawn (9), +# aACompromise (10) } +_CRL_ENTRY_REASON_ENUM_TO_CODE = { + ReasonFlags.unspecified: 0, + ReasonFlags.key_compromise: 1, + ReasonFlags.ca_compromise: 2, + ReasonFlags.affiliation_changed: 3, + ReasonFlags.superseded: 4, + ReasonFlags.cessation_of_operation: 5, + ReasonFlags.certificate_hold: 6, + ReasonFlags.remove_from_crl: 8, + ReasonFlags.privilege_withdrawn: 9, + ReasonFlags.aa_compromise: 10, +} + class PolicyConstraints(ExtensionType): oid = ExtensionOID.POLICY_CONSTRAINTS @@ -851,8 +877,8 @@ def __init__( def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -866,9 +892,7 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: if self.policy_qualifiers is not None: - pq: tuple[str | UserNotice, ...] | None = tuple( - self.policy_qualifiers - ) + pq = tuple(self.policy_qualifiers) else: pq = None @@ -903,8 +927,8 @@ def __init__( def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -943,8 +967,8 @@ def __init__( def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -1431,32 +1455,29 @@ def get_values_for_type( type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], - ) -> list[str]: - ... + ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], - ) -> list[Name]: - ... + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], - ) -> list[ObjectIdentifier]: - ... + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] - ) -> list[_IPAddressTypes]: - ... + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: - ... + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, @@ -1509,32 +1530,29 @@ def get_values_for_type( type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], - ) -> list[str]: - ... + ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], - ) -> list[Name]: - ... + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], - ) -> list[ObjectIdentifier]: - ... + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] - ) -> list[_IPAddressTypes]: - ... + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: - ... + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, @@ -1584,32 +1602,29 @@ def get_values_for_type( type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], - ) -> list[str]: - ... + ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], - ) -> list[Name]: - ... + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], - ) -> list[ObjectIdentifier]: - ... + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] - ) -> list[_IPAddressTypes]: - ... + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: - ... + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, @@ -1659,32 +1674,29 @@ def get_values_for_type( type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], - ) -> list[str]: - ... + ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], - ) -> list[Name]: - ... + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], - ) -> list[ObjectIdentifier]: - ... + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] - ) -> list[_IPAddressTypes]: - ... + ) -> list[_IPAddressTypes]: ... @typing.overload - def get_values_for_type(self, type: type[OtherName]) -> list[OtherName]: - ... + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, @@ -1774,6 +1786,13 @@ def __hash__(self) -> int: def invalidity_date(self) -> datetime.datetime: return self._invalidity_date + @property + def invalidity_date_utc(self) -> datetime.datetime: + if self._invalidity_date.tzinfo is None: + return self._invalidity_date.replace(tzinfo=datetime.timezone.utc) + else: + return self._invalidity_date.astimezone(tz=datetime.timezone.utc) + def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) @@ -1967,10 +1986,12 @@ def __init__( "must all be boolean." ) + # Per RFC5280 Section 5.2.5, the Issuing Distribution Point extension + # in a CRL can have only one of onlyContainsUserCerts, + # onlyContainsCACerts, onlyContainsAttributeCerts set to TRUE. crl_constraints = [ only_contains_user_certs, only_contains_ca_certs, - indirect_crl, only_contains_attribute_certs, ] @@ -1978,7 +1999,7 @@ def __init__( raise ValueError( "Only one of the following can be set to True: " "only_contains_user_certs, only_contains_ca_certs, " - "indirect_crl, only_contains_attribute_certs" + "only_contains_attribute_certs" ) if not any( @@ -2141,6 +2162,287 @@ def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) +class NamingAuthority: + def __init__( + self, + id: ObjectIdentifier | None, + url: str | None, + text: str | None, + ) -> None: + if id is not None and not isinstance(id, ObjectIdentifier): + raise TypeError("id must be an ObjectIdentifier") + + if url is not None and not isinstance(url, str): + raise TypeError("url must be a str") + + if text is not None and not isinstance(text, str): + raise TypeError("text must be a str") + + self._id = id + self._url = url + self._text = text + + @property + def id(self) -> ObjectIdentifier | None: + return self._id + + @property + def url(self) -> str | None: + return self._url + + @property + def text(self) -> str | None: + return self._text + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamingAuthority): + return NotImplemented + + return ( + self.id == other.id + and self.url == other.url + and self.text == other.text + ) + + def __hash__(self) -> int: + return hash( + ( + self.id, + self.url, + self.text, + ) + ) + + +class ProfessionInfo: + def __init__( + self, + naming_authority: NamingAuthority | None, + profession_items: typing.Iterable[str], + profession_oids: typing.Iterable[ObjectIdentifier] | None, + registration_number: str | None, + add_profession_info: bytes | None, + ) -> None: + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_items = list(profession_items) + if not all(isinstance(item, str) for item in profession_items): + raise TypeError( + "Every item in the profession_items list must be a str" + ) + + if profession_oids is not None: + profession_oids = list(profession_oids) + if not all( + isinstance(oid, ObjectIdentifier) for oid in profession_oids + ): + raise TypeError( + "Every item in the profession_oids list must be an " + "ObjectIdentifier" + ) + + if registration_number is not None and not isinstance( + registration_number, str + ): + raise TypeError("registration_number must be a str") + + if add_profession_info is not None and not isinstance( + add_profession_info, bytes + ): + raise TypeError("add_profession_info must be bytes") + + self._naming_authority = naming_authority + self._profession_items = profession_items + self._profession_oids = profession_oids + self._registration_number = registration_number + self._add_profession_info = add_profession_info + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_items(self) -> list[str]: + return self._profession_items + + @property + def profession_oids(self) -> list[ObjectIdentifier] | None: + return self._profession_oids + + @property + def registration_number(self) -> str | None: + return self._registration_number + + @property + def add_profession_info(self) -> bytes | None: + return self._add_profession_info + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ProfessionInfo): + return NotImplemented + + return ( + self.naming_authority == other.naming_authority + and self.profession_items == other.profession_items + and self.profession_oids == other.profession_oids + and self.registration_number == other.registration_number + and self.add_profession_info == other.add_profession_info + ) + + def __hash__(self) -> int: + if self.profession_oids is not None: + profession_oids = tuple(self.profession_oids) + else: + profession_oids = None + return hash( + ( + self.naming_authority, + tuple(self.profession_items), + profession_oids, + self.registration_number, + self.add_profession_info, + ) + ) + + +class Admission: + def __init__( + self, + admission_authority: GeneralName | None, + naming_authority: NamingAuthority | None, + profession_infos: typing.Iterable[ProfessionInfo], + ) -> None: + if admission_authority is not None and not isinstance( + admission_authority, GeneralName + ): + raise TypeError("admission_authority must be a GeneralName") + + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_infos = list(profession_infos) + if not all( + isinstance(info, ProfessionInfo) for info in profession_infos + ): + raise TypeError( + "Every item in the profession_infos list must be a " + "ProfessionInfo" + ) + + self._admission_authority = admission_authority + self._naming_authority = naming_authority + self._profession_infos = profession_infos + + @property + def admission_authority(self) -> GeneralName | None: + return self._admission_authority + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_infos(self) -> list[ProfessionInfo]: + return self._profession_infos + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admission): + return NotImplemented + + return ( + self.admission_authority == other.admission_authority + and self.naming_authority == other.naming_authority + and self.profession_infos == other.profession_infos + ) + + def __hash__(self) -> int: + return hash( + ( + self.admission_authority, + self.naming_authority, + tuple(self.profession_infos), + ) + ) + + +class Admissions(ExtensionType): + oid = ExtensionOID.ADMISSIONS + + def __init__( + self, + authority: GeneralName | None, + admissions: typing.Iterable[Admission], + ) -> None: + if authority is not None and not isinstance(authority, GeneralName): + raise TypeError("authority must be a GeneralName") + + admissions = list(admissions) + if not all( + isinstance(admission, Admission) for admission in admissions + ): + raise TypeError( + "Every item in the contents_of_admissions list must be an " + "Admission" + ) + + self._authority = authority + self._admissions = admissions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_admissions") + + @property + def authority(self) -> GeneralName | None: + return self._authority + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admissions): + return NotImplemented + + return ( + self.authority == other.authority + and self._admissions == other._admissions + ) + + def __hash__(self) -> int: + return hash((self.authority, tuple(self._admissions))) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + class UnrecognizedExtension(ExtensionType): def __init__(self, oid: ObjectIdentifier, value: bytes) -> None: if not isinstance(oid, ObjectIdentifier): diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index 5e8ccfff5994..1b6b89d12a97 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -59,6 +59,12 @@ class _ASN1Type(utils.Enum): } _NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()} +_NAMEOID_LENGTH_LIMIT = { + NameOID.COUNTRY_NAME: (2, 2), + NameOID.JURISDICTION_COUNTRY_NAME: (2, 2), + NameOID.COMMON_NAME: (1, 64), +} + def _escape_dn_value(val: str | bytes) -> str: """Escape special characters in RFC4514 Distinguished Name value.""" @@ -132,19 +138,20 @@ def __init__( if not isinstance(value, str): raise TypeError("value argument must be a str") - if oid in (NameOID.COUNTRY_NAME, NameOID.JURISDICTION_COUNTRY_NAME): + length_limits = _NAMEOID_LENGTH_LIMIT.get(oid) + if length_limits is not None: + min_length, max_length = length_limits assert isinstance(value, str) c_len = len(value.encode("utf8")) - if c_len != 2 and _validate is True: - raise ValueError( - "Country name must be a 2 character country code" - ) - elif c_len != 2: - warnings.warn( - "Country names should be two characters, but the " - f"attribute is {c_len} characters in length.", - stacklevel=2, + if c_len < min_length or c_len > max_length: + msg = ( + f"Attribute's length must be >= {min_length} and " + f"<= {max_length}, but it was {c_len}" ) + if _validate is True: + raise ValueError(msg) + else: + warnings.warn(msg, stacklevel=2) # The appropriate ASN1 string type varies by OID and is defined across # multiple RFCs including 2459, 3280, and 5280. In general UTF8String @@ -263,14 +270,12 @@ def __repr__(self) -> str: class Name: @typing.overload - def __init__(self, attributes: typing.Iterable[NameAttribute]) -> None: - ... + def __init__(self, attributes: typing.Iterable[NameAttribute]) -> None: ... @typing.overload def __init__( self, attributes: typing.Iterable[RelativeDistinguishedName] - ) -> None: - ... + ) -> None: ... def __init__( self, @@ -416,6 +421,10 @@ def parse(self) -> Name: we parse it, we need to reverse again to get the RDNs on the correct order. """ + + if not self._has_data(): + return Name([]) + rdns = [self._parse_rdn()] while self._has_data(): diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py index 9751ceaf9655..5a011c412ad3 100644 --- a/src/cryptography/x509/ocsp.py +++ b/src/cryptography/x509/ocsp.py @@ -4,13 +4,12 @@ from __future__ import annotations -import abc import datetime import typing from cryptography import utils, x509 from cryptography.hazmat.bindings._rust import ocsp -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPrivateKeyTypes, ) @@ -127,279 +126,9 @@ def __init__( self._revocation_reason = revocation_reason -class OCSPRequest(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def issuer_key_hash(self) -> bytes: - """ - The hash of the issuer public key - """ - - @property - @abc.abstractmethod - def issuer_name_hash(self) -> bytes: - """ - The hash of the issuer name - """ - - @property - @abc.abstractmethod - def hash_algorithm(self) -> hashes.HashAlgorithm: - """ - The hash algorithm used in the issuer name and key hashes - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - The serial number of the cert whose status is being checked - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the request to DER - """ - - @property - @abc.abstractmethod - def extensions(self) -> x509.Extensions: - """ - The list of request extensions. Not single request extensions. - """ - - -class OCSPSingleResponse(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def certificate_status(self) -> OCSPCertStatus: - """ - The status of the certificate (an element from the OCSPCertStatus enum) - """ - - @property - @abc.abstractmethod - def revocation_time(self) -> datetime.datetime | None: - """ - The date of when the certificate was revoked or None if not - revoked. - """ - - @property - @abc.abstractmethod - def revocation_reason(self) -> x509.ReasonFlags | None: - """ - The reason the certificate was revoked or None if not specified or - not revoked. - """ - - @property - @abc.abstractmethod - def this_update(self) -> datetime.datetime: - """ - The most recent time at which the status being indicated is known by - the responder to have been correct - """ - - @property - @abc.abstractmethod - def next_update(self) -> datetime.datetime | None: - """ - The time when newer information will be available - """ - - @property - @abc.abstractmethod - def issuer_key_hash(self) -> bytes: - """ - The hash of the issuer public key - """ - - @property - @abc.abstractmethod - def issuer_name_hash(self) -> bytes: - """ - The hash of the issuer name - """ - - @property - @abc.abstractmethod - def hash_algorithm(self) -> hashes.HashAlgorithm: - """ - The hash algorithm used in the issuer name and key hashes - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - The serial number of the cert whose status is being checked - """ - - -class OCSPResponse(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def responses(self) -> typing.Iterator[OCSPSingleResponse]: - """ - An iterator over the individual SINGLERESP structures in the - response - """ - - @property - @abc.abstractmethod - def response_status(self) -> OCSPResponseStatus: - """ - The status of the response. This is a value from the OCSPResponseStatus - enumeration - """ - - @property - @abc.abstractmethod - def signature_algorithm_oid(self) -> x509.ObjectIdentifier: - """ - The ObjectIdentifier of the signature algorithm - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> hashes.HashAlgorithm | None: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - The signature bytes - """ - - @property - @abc.abstractmethod - def tbs_response_bytes(self) -> bytes: - """ - The tbsResponseData bytes - """ - - @property - @abc.abstractmethod - 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 - certificate. - """ - - @property - @abc.abstractmethod - def responder_key_hash(self) -> bytes | None: - """ - The responder's key hash or None - """ - - @property - @abc.abstractmethod - def responder_name(self) -> x509.Name | None: - """ - The responder's Name or None - """ - - @property - @abc.abstractmethod - def produced_at(self) -> datetime.datetime: - """ - The time the response was produced - """ - - @property - @abc.abstractmethod - def certificate_status(self) -> OCSPCertStatus: - """ - The status of the certificate (an element from the OCSPCertStatus enum) - """ - - @property - @abc.abstractmethod - def revocation_time(self) -> datetime.datetime | None: - """ - The date of when the certificate was revoked or None if not - revoked. - """ - - @property - @abc.abstractmethod - def revocation_reason(self) -> x509.ReasonFlags | None: - """ - The reason the certificate was revoked or None if not specified or - not revoked. - """ - - @property - @abc.abstractmethod - def this_update(self) -> datetime.datetime: - """ - The most recent time at which the status being indicated is known by - the responder to have been correct - """ - - @property - @abc.abstractmethod - def next_update(self) -> datetime.datetime | None: - """ - The time when newer information will be available - """ - - @property - @abc.abstractmethod - def issuer_key_hash(self) -> bytes: - """ - The hash of the issuer public key - """ - - @property - @abc.abstractmethod - def issuer_name_hash(self) -> bytes: - """ - The hash of the issuer name - """ - - @property - @abc.abstractmethod - def hash_algorithm(self) -> hashes.HashAlgorithm: - """ - The hash algorithm used in the issuer name and key hashes - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - The serial number of the cert whose status is being checked - """ - - @property - @abc.abstractmethod - def extensions(self) -> x509.Extensions: - """ - The list of response extensions. Not single response extensions. - """ - - @property - @abc.abstractmethod - def single_extensions(self) -> x509.Extensions: - """ - The list of single response extensions. Not response extensions. - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the response to DER - """ +OCSPRequest = ocsp.OCSPRequest +OCSPResponse = ocsp.OCSPResponse +OCSPSingleResponse = ocsp.OCSPSingleResponse class OCSPRequestBuilder: diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index cda50cced5c4..d4e409e0a2a0 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -14,6 +14,7 @@ NameOID, ObjectIdentifier, OCSPExtensionOID, + PublicKeyAlgorithmOID, SignatureAlgorithmOID, SubjectInformationAccessOID, ) @@ -28,6 +29,7 @@ "NameOID", "OCSPExtensionOID", "ObjectIdentifier", + "PublicKeyAlgorithmOID", "SignatureAlgorithmOID", "SubjectInformationAccessOID", ] diff --git a/src/cryptography/x509/verification.py b/src/cryptography/x509/verification.py index ab1a37ae6b01..b83650681237 100644 --- a/src/cryptography/x509/verification.py +++ b/src/cryptography/x509/verification.py @@ -10,15 +10,19 @@ from cryptography.x509.general_name import DNSName, IPAddress __all__ = [ + "ClientVerifier", + "PolicyBuilder", + "ServerVerifier", "Store", "Subject", - "ServerVerifier", - "PolicyBuilder", "VerificationError", + "VerifiedClient", ] Store = rust_x509.Store Subject = typing.Union[DNSName, IPAddress] +VerifiedClient = rust_x509.VerifiedClient +ClientVerifier = rust_x509.ClientVerifier ServerVerifier = rust_x509.ServerVerifier PolicyBuilder = rust_x509.PolicyBuilder VerificationError = rust_x509.VerificationError diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 2322486d0406..9eb165a96f14 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -1,31 +1,28 @@ [package] name = "cryptography-rust" -version = "0.1.0" -authors = ["The cryptography developers "] -edition = "2021" -publish = false -# This specifies the MSRV -rust-version = "1.63.0" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true [dependencies] once_cell = "1" cfg-if = "1" -pyo3 = { version = "0.20", features = ["abi3"] } -asn1 = { version = "0.15.5", default-features = false } +pyo3.workspace = true +asn1.workspace = true cryptography-cffi = { path = "cryptography-cffi" } +cryptography-keepalive = { path = "cryptography-keepalive" } cryptography-key-parsing = { path = "cryptography-key-parsing" } cryptography-x509 = { path = "cryptography-x509" } cryptography-x509-verification = { path = "cryptography-x509-verification" } cryptography-openssl = { path = "cryptography-openssl" } pem = { version = "3", default-features = false } -openssl = "0.10.63" -openssl-sys = "0.9.99" +openssl = "0.10.68" +openssl-sys = "0.9.104" foreign-types-shared = "0.1" self_cell = "1" -[build-dependencies] -cc = "1.0.83" - [features] extension-module = ["pyo3/extension-module"] default = ["extension-module"] @@ -34,14 +31,5 @@ default = ["extension-module"] name = "cryptography_rust" crate-type = ["cdylib"] -[profile.release] -overflow-checks = true - -[workspace] -members = [ - "cryptography-cffi", - "cryptography-key-parsing", - "cryptography-openssl", - "cryptography-x509", - "cryptography-x509-verification", -] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_309_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_OSSLCONF, values("OPENSSL_NO_IDEA", "OPENSSL_NO_CAST", "OPENSSL_NO_BF", "OPENSSL_NO_CAMELLIA", "OPENSSL_NO_SEED", "OPENSSL_NO_SM4"))'] } diff --git a/src/rust/build.rs b/src/rust/build.rs index f247822e0dcd..2d94d8da7ba3 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -12,18 +12,16 @@ fn main() { if version >= 0x3_00_00_00_0 { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_300_OR_GREATER"); } + if version >= 0x3_00_09_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_309_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(); - + if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); - if version >= 0x3_08_00_00_0 { - println!("cargo:rustc-cfg=CRYPTOGRAPHY_LIBRESSL_380_OR_GREATER"); - } } if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { diff --git a/src/rust/cryptography-cffi/Cargo.toml b/src/rust/cryptography-cffi/Cargo.toml index a025e58ceda7..9408de8b4415 100644 --- a/src/rust/cryptography-cffi/Cargo.toml +++ b/src/rust/cryptography-cffi/Cargo.toml @@ -1,15 +1,17 @@ [package] name = "cryptography-cffi" -version = "0.1.0" -authors = ["The cryptography developers "] -edition = "2021" -publish = false -# This specifies the MSRV -rust-version = "1.63.0" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true [dependencies] -pyo3 = { version = "0.20", features = ["abi3"] } -openssl-sys = "0.9.99" +pyo3.workspace = true +openssl-sys = "0.9.104" [build-dependencies] -cc = "1.0.83" +cc = "1.2.1" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(python_implementation, values("CPython", "PyPy"))'] } diff --git a/src/rust/cryptography-cffi/build.rs b/src/rust/cryptography-cffi/build.rs index 5f73714f3415..1243a8187a97 100644 --- a/src/rust/cryptography-cffi/build.rs +++ b/src/rust/cryptography-cffi/build.rs @@ -57,18 +57,23 @@ fn main() { print(os.pathsep.join(b.include_dirs), end='')", ) .unwrap(); - let openssl_include = - std::env::var_os("DEP_OPENSSL_INCLUDE").expect("unable to find openssl include path"); let openssl_c = Path::new(&out_dir).join("_openssl.c"); let mut build = cc::Build::new(); build .file(openssl_c) - .include(openssl_include) + .includes(std::env::var_os("DEP_OPENSSL_INCLUDE")) .flag_if_supported("-Wconversion") .flag_if_supported("-Wno-error=sign-conversion") .flag_if_supported("-Wno-unused-parameter"); + // We use the `-fmacro-prefix-map` option to replace the output directory in macros with a dot. + // This is because we don't want a potentially random build path to end up in the binary because + // CFFI generated code uses the __FILE__ macro in its debug messages. + if let Some(out_dir_str) = Path::new(&out_dir).to_str() { + build.flag_if_supported(format!("-fmacro-prefix-map={}=.", out_dir_str).as_str()); + } + for python_include in env::split_paths(&python_includes) { build.include(python_include); } diff --git a/src/rust/cryptography-cffi/src/lib.rs b/src/rust/cryptography-cffi/src/lib.rs index 110341a1901e..b834f2642473 100644 --- a/src/rust/cryptography-cffi/src/lib.rs +++ b/src/rust/cryptography-cffi/src/lib.rs @@ -4,9 +4,6 @@ #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] -#[cfg(not(python_implementation = "PyPy"))] -use pyo3::FromPyPointer; - #[cfg(python_implementation = "PyPy")] extern "C" { fn Cryptography_make_openssl_module() -> std::os::raw::c_int; @@ -16,18 +13,20 @@ extern "C" { fn PyInit__openssl() -> *mut pyo3::ffi::PyObject; } -pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyModule> { +pub fn create_module( + py: pyo3::Python<'_>, +) -> pyo3::PyResult> { #[cfg(python_implementation = "PyPy")] let openssl_mod = unsafe { let res = Cryptography_make_openssl_module(); assert_eq!(res, 0); - pyo3::types::PyModule::import(py, "_openssl")? + pyo3::types::PyModule::import(py, "_openssl")?.clone() }; #[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) + pyo3::Py::from_owned_ptr_or_err(py, ptr)?.bind(py).clone() }; Ok(openssl_mod) diff --git a/src/rust/cryptography-keepalive/Cargo.toml b/src/rust/cryptography-keepalive/Cargo.toml new file mode 100644 index 000000000000..baf8d9342119 --- /dev/null +++ b/src/rust/cryptography-keepalive/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cryptography-keepalive" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true + +[dependencies] +pyo3.workspace = true diff --git a/src/rust/cryptography-keepalive/src/lib.rs b/src/rust/cryptography-keepalive/src/lib.rs new file mode 100644 index 000000000000..9542f9efc24c --- /dev/null +++ b/src/rust/cryptography-keepalive/src/lib.rs @@ -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. + +#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] + +use pyo3::pybacked::{PyBackedBytes, PyBackedStr}; +use std::cell::UnsafeCell; +use std::ops::Deref; + +pub struct KeepAlive { + values: UnsafeCell>, +} + +/// # Safety +/// Implementers of this trait must ensure that the value returned by +/// `deref()` must remain valid, even if `self` is moved. +pub unsafe trait StableDeref: Deref {} +// SAFETY: `Vec`'s data is on the heap, so as long as it's not mutated, the +// slice returned by `deref` remains valid. +unsafe impl StableDeref for Vec {} +// SAFETY: `PyBackedBytes`'s data is on the heap and `bytes` objects in +// Python are immutable. +unsafe impl StableDeref for PyBackedBytes {} +// SAFETY: `PyBackedStr`'s data is on the heap and `str` objects in +// Python are immutable. +unsafe impl StableDeref for PyBackedStr {} + +#[allow(clippy::new_without_default)] +impl KeepAlive { + pub fn new() -> Self { + KeepAlive { + values: UnsafeCell::new(vec![]), + } + } + + pub fn add(&self, v: T) -> &T::Target { + // SAFETY: We only ever append to `self.values`, which, when combined + // with the invariants of `StableDeref`, means that the result of + // `deref()` will always be valid for the lifetime of `&self`. + unsafe { + let values = &mut *self.values.get(); + values.push(v); + values.last().unwrap().deref() + } + } +} diff --git a/src/rust/cryptography-key-parsing/Cargo.toml b/src/rust/cryptography-key-parsing/Cargo.toml index 3dd0b31fa1a6..9b96b736c405 100644 --- a/src/rust/cryptography-key-parsing/Cargo.toml +++ b/src/rust/cryptography-key-parsing/Cargo.toml @@ -1,15 +1,17 @@ [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" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true [dependencies] -asn1 = { version = "0.15.5", default-features = false } +asn1.workspace = true cfg-if = "1" -openssl = "0.10.63" -openssl-sys = "0.9.99" +openssl = "0.10.68" +openssl-sys = "0.9.104" cryptography-x509 = { path = "../cryptography-x509" } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)'] } diff --git a/src/rust/cryptography-key-parsing/src/lib.rs b/src/rust/cryptography-key-parsing/src/lib.rs index 93c49181c1fe..c97bc3f754c6 100644 --- a/src/rust/cryptography-key-parsing/src/lib.rs +++ b/src/rust/cryptography-key-parsing/src/lib.rs @@ -2,6 +2,10 @@ // 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)] +#![allow(unknown_lints, clippy::result_large_err)] + pub mod rsa; pub mod spki; diff --git a/src/rust/cryptography-key-parsing/src/rsa.rs b/src/rust/cryptography-key-parsing/src/rsa.rs index 066e7053cb52..bf33a492352e 100644 --- a/src/rust/cryptography-key-parsing/src/rsa.rs +++ b/src/rust/cryptography-key-parsing/src/rsa.rs @@ -5,15 +5,15 @@ use crate::KeyParsingResult; #[derive(asn1::Asn1Read)] -struct Pksc1RsaPublicKey<'a> { - n: asn1::BigUint<'a>, +pub struct Pkcs1RsaPublicKey<'a> { + pub n: asn1::BigUint<'a>, e: asn1::BigUint<'a>, } pub fn parse_pkcs1_public_key( data: &[u8], ) -> KeyParsingResult> { - let k = asn1::parse_single::(data)?; + 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())?; diff --git a/src/rust/cryptography-key-parsing/src/spki.rs b/src/rust/cryptography-key-parsing/src/spki.rs index e6e1133c490a..db4f69d94d10 100644 --- a/src/rust/cryptography-key-parsing/src/spki.rs +++ b/src/rust/cryptography-key-parsing/src/spki.rs @@ -9,7 +9,7 @@ use crate::{KeyParsingError, KeyParsingResult}; pub fn parse_public_key( data: &[u8], ) -> KeyParsingResult> { - let k = asn1::parse_single::(data)?; + let k = asn1::parse_single::>(data)?; match k.algorithm.params { AlgorithmParameters::Ec(ec_params) => match ec_params { @@ -114,13 +114,7 @@ pub fn parse_public_key( 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)?) - } - } + Ok(openssl::pkey::PKey::from_dh(dh)?) } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] AlgorithmParameters::DhKeyAgreement(dh_params) => { diff --git a/src/rust/cryptography-openssl/Cargo.toml b/src/rust/cryptography-openssl/Cargo.toml index 3a35c9fcaa2d..3d4c17ebaafd 100644 --- a/src/rust/cryptography-openssl/Cargo.toml +++ b/src/rust/cryptography-openssl/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "cryptography-openssl" -version = "0.1.0" -authors = ["The cryptography developers "] -edition = "2021" -publish = false -# This specifies the MSRV -rust-version = "1.63.0" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true [dependencies] -openssl = "0.10.63" -ffi = { package = "openssl-sys", version = "0.9.99" } +cfg-if = "1" +openssl = "0.10.68" +ffi = { package = "openssl-sys", version = "0.9.101" } foreign-types = "0.3" foreign-types-shared = "0.1" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)'] } diff --git a/src/rust/cryptography-openssl/build.rs b/src/rust/cryptography-openssl/build.rs index a0b4566a753c..bed5a22111f1 100644 --- a/src/rust/cryptography-openssl/build.rs +++ b/src/rust/cryptography-openssl/build.rs @@ -12,6 +12,9 @@ fn main() { 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 env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { @@ -20,5 +23,11 @@ fn main() { if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); + if env::var_os("CARGO_CFG_UNIX").is_some() { + match env::var("CARGO_CFG_TARGET_OS").as_deref() { + Ok("macos") => println!("cargo:rustc-link-lib=c++"), + _ => println!("cargo:rustc-link-lib=stdc++"), + } + } } } diff --git a/src/rust/cryptography-openssl/src/aead.rs b/src/rust/cryptography-openssl/src/aead.rs index 000d5a9c65f9..42f0fd7f8041 100644 --- a/src/rust/cryptography-openssl/src/aead.rs +++ b/src/rust/cryptography-openssl/src/aead.rs @@ -17,15 +17,19 @@ foreign_types::foreign_type! { pub struct AeadCtxRef; } +// SAFETY: Can safely be used from multiple threads concurrently. unsafe impl Sync for AeadCtx {} +// SAFETY: Can safely be sent between threads. unsafe impl Send for AeadCtx {} impl AeadCtx { pub fn new(aead: AeadType, key: &[u8]) -> OpenSSLResult { let aead = match aead { + // SAFETY: No preconditions. AeadType::ChaCha20Poly1305 => unsafe { ffi::EVP_aead_chacha20_poly1305() }, }; + // SAFETY: We're passing a valid key and aead. unsafe { let ctx = cvt_p(ffi::EVP_AEAD_CTX_new( aead, @@ -47,6 +51,7 @@ impl AeadCtxRef { out: &mut [u8], ) -> OpenSSLResult<()> { let mut out_len = out.len(); + // SAFETY: All the lengths and pointers are known valid. unsafe { cvt(ffi::EVP_AEAD_CTX_seal( self.as_ptr(), @@ -72,6 +77,7 @@ impl AeadCtxRef { out: &mut [u8], ) -> OpenSSLResult<()> { let mut out_len = out.len(); + // SAFETY: All the lengths and pointers are known valid. unsafe { cvt(ffi::EVP_AEAD_CTX_open( self.as_ptr(), diff --git a/src/rust/cryptography-openssl/src/fips.rs b/src/rust/cryptography-openssl/src/fips.rs index 9cdbd3f34648..b14d2a5a659d 100644 --- a/src/rust/cryptography-openssl/src/fips.rs +++ b/src/rust/cryptography-openssl/src/fips.rs @@ -2,6 +2,8 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +use crate::{cvt, OpenSSLResult}; #[cfg(all( CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) @@ -9,25 +11,26 @@ use std::ptr; pub fn is_enabled() -> bool { - #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] - { - return false; + cfg_if::cfg_if! { + if #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] { + false + } else if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] { + // SAFETY: No pre-conditions + unsafe { + ffi::EVP_default_properties_is_fips_enabled(ptr::null_mut()) == 1 + } + } else { + openssl::fips::enabled() + } } +} - #[cfg(all( - CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, - not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) - ))] +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +pub fn enable() -> OpenSSLResult<()> { // SAFETY: No pre-conditions unsafe { - ffi::EVP_default_properties_is_fips_enabled(ptr::null_mut()) == 1 + cvt(ffi::EVP_default_properties_enable_fips(ptr::null_mut(), 1))?; } - #[cfg(all( - not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), - not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) - ))] - { - return openssl::fips::enabled(); - } + Ok(()) } diff --git a/src/rust/cryptography-openssl/src/hmac.rs b/src/rust/cryptography-openssl/src/hmac.rs index 84b3a1e3b9b5..64abf83d40ae 100644 --- a/src/rust/cryptography-openssl/src/hmac.rs +++ b/src/rust/cryptography-openssl/src/hmac.rs @@ -22,6 +22,9 @@ unsafe impl Sync for Hmac {} unsafe impl Send for Hmac {} impl Hmac { + // On BoringSSL, the length is a size_t, so the length conversion is a + // no-op. + #[cfg_attr(CRYPTOGRAPHY_IS_BORINGSSL, allow(clippy::useless_conversion))] pub fn new(key: &[u8], md: openssl::hash::MessageDigest) -> OpenSSLResult { // SAFETY: All FFI conditions are handled. unsafe { diff --git a/src/rust/cryptography-openssl/src/poly1305.rs b/src/rust/cryptography-openssl/src/poly1305.rs index 262062eedd3f..e386bc2d7f4a 100644 --- a/src/rust/cryptography-openssl/src/poly1305.rs +++ b/src/rust/cryptography-openssl/src/poly1305.rs @@ -18,9 +18,10 @@ impl Poly1305State { 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. + // SAFETY: 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(); @@ -32,14 +33,17 @@ impl Poly1305State { } } - pub fn update(&mut self, data: &[u8]) -> () { + pub fn update(&mut self, data: &[u8]) { + // SAFETY: context is valid, as is the data ptr. unsafe { ffi::CRYPTO_poly1305_update(self.context.as_mut(), data.as_ptr(), data.len()); }; } - pub fn finalize(&mut self, output: &mut [u8]) -> () { + pub fn finalize(&mut self, output: &mut [u8]) { assert_eq!(output.len(), 16); + // SAFETY: context is valid and we verified that the output is the + // right length. 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 index 1ed759074167..2cc2ff48829c 100644 --- a/src/rust/cryptography-x509-verification/Cargo.toml +++ b/src/rust/cryptography-x509-verification/Cargo.toml @@ -1,15 +1,15 @@ [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" +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true [dependencies] -asn1 = { version = "0.15.5", default-features = false } +asn1.workspace = true cryptography-x509 = { path = "../cryptography-x509" } +cryptography-key-parsing = { path = "../cryptography-key-parsing" } once_cell = "1" [dev-dependencies] diff --git a/src/rust/cryptography-x509-verification/src/certificate.rs b/src/rust/cryptography-x509-verification/src/certificate.rs index 2260fd6d9604..ec1dd33a8085 100644 --- a/src/rust/cryptography-x509-verification/src/certificate.rs +++ b/src/rust/cryptography-x509-verification/src/certificate.rs @@ -68,6 +68,20 @@ Xw4nMqk= ) -> Result<(), Self::Err> { Ok(()) } + + fn clone_public_key(key: &Self::Key) -> Self::Key { + key.clone() + } + + fn clone_extra(extra: &Self::CertificateExtra) -> Self::CertificateExtra { + extra.clone() + } + } + + #[test] + fn test_clone() { + assert_eq!(PublicKeyErrorOps::clone_public_key(&()), ()); + assert_eq!(PublicKeyErrorOps::clone_extra(&()), ()); } #[test] diff --git a/src/rust/cryptography-x509-verification/src/lib.rs b/src/rust/cryptography-x509-verification/src/lib.rs index 6265f75c5502..75ec6ce005da 100644 --- a/src/rust/cryptography-x509-verification/src/lib.rs +++ b/src/rust/cryptography-x509-verification/src/lib.rs @@ -4,6 +4,7 @@ #![forbid(unsafe_code)] #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] +#![allow(unknown_lints, clippy::result_large_err)] pub mod certificate; pub mod ops; @@ -11,14 +12,18 @@ pub mod policy; pub mod trust_store; pub mod types; +use std::fmt::Display; use std::vec; +use asn1::ObjectIdentifier; use cryptography_x509::extensions::{DuplicateExtensionsError, Extensions}; use cryptography_x509::{ + common::Asn1Read, extensions::{NameConstraints, SubjectAlternativeName}, name::GeneralName, oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}, }; +use types::{RFC822Constraint, RFC822Name}; use crate::certificate::cert_is_self_issued; use crate::ops::{CryptoOps, VerificationCertificate}; @@ -28,23 +33,92 @@ use crate::types::DNSName; use crate::types::{DNSConstraint, IPAddress, IPConstraint}; use crate::ApplyNameConstraintStatus::{Applied, Skipped}; -#[derive(Debug, PartialEq, Eq)] -pub enum ValidationError { - CandidatesExhausted(Box), +pub enum ValidationErrorKind<'chain, B: CryptoOps> { + CandidatesExhausted(Box>), Malformed(asn1::ParseError), - DuplicateExtension(DuplicateExtensionsError), + ExtensionError { + oid: ObjectIdentifier, + reason: &'static str, + }, + FatalError(&'static str), Other(String), } -impl From for ValidationError { +pub struct ValidationError<'chain, B: CryptoOps> { + kind: ValidationErrorKind<'chain, B>, + cert: Option>, +} + +impl<'chain, B: CryptoOps> ValidationError<'chain, B> { + pub(crate) fn new(kind: ValidationErrorKind<'chain, B>) -> Self { + ValidationError { kind, cert: None } + } + + pub(crate) fn set_cert(mut self, cert: VerificationCertificate<'chain, B>) -> Self { + self.cert = Some(cert); + self + } + + pub fn certificate(&self) -> Option<&VerificationCertificate<'chain, B>> { + self.cert.as_ref() + } +} + +pub type ValidationResult<'chain, T, B> = Result>; + +impl From for ValidationError<'_, B> { fn from(value: asn1::ParseError) -> Self { - Self::Malformed(value) + Self::new(ValidationErrorKind::Malformed(value)) } } -impl From for ValidationError { +impl From for ValidationError<'_, B> { fn from(value: DuplicateExtensionsError) -> Self { - Self::DuplicateExtension(value) + Self::new(ValidationErrorKind::ExtensionError { + oid: value.0, + reason: "duplicate extension", + }) + } +} + +impl Display for ValidationError<'_, B> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.kind { + ValidationErrorKind::CandidatesExhausted(inner) => { + write!(f, "candidates exhausted: {inner}") + } + ValidationErrorKind::Malformed(err) => err.fmt(f), + ValidationErrorKind::ExtensionError { oid, reason } => { + write!(f, "invalid extension: {oid}: {reason}") + } + ValidationErrorKind::FatalError(err) => write!(f, "fatal error: {err}"), + ValidationErrorKind::Other(err) => write!(f, "{err}"), + } + } +} + +struct Budget { + name_constraint_checks: usize, +} + +impl Budget { + // Same limit as other validators + const DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT: usize = 1 << 20; + + fn new() -> Budget { + Budget { + name_constraint_checks: Self::DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT, + } + } + + fn name_constraint_check<'chain, B: CryptoOps>(&mut self) -> ValidationResult<'chain, (), B> { + self.name_constraint_checks = + self.name_constraint_checks.checked_sub(1).ok_or_else(|| { + ValidationError::new(ValidationErrorKind::FatalError( + "Exceeded maximum name constraint check limit", + )) + })?; + Ok(()) } } @@ -54,11 +128,11 @@ struct NameChain<'a, 'chain> { } impl<'a, 'chain> NameChain<'a, 'chain> { - fn new( + fn new( child: Option<&'a NameChain<'a, 'chain>>, extensions: &Extensions<'chain>, self_issued_intermediate: bool, - ) -> Result { + ) -> ValidationResult<'chain, Self, B> { let sans = match ( self_issued_intermediate, extensions.get_extension(&SUBJECT_ALTERNATIVE_NAME_OID), @@ -72,23 +146,26 @@ impl<'a, 'chain> NameChain<'a, 'chain> { Ok(Self { child, sans }) } - fn evaluate_single_constraint( + fn evaluate_single_constraint( &self, constraint: &GeneralName<'chain>, san: &GeneralName<'chain>, - ) -> Result { + budget: &mut Budget, + ) -> ValidationResult<'chain, ApplyNameConstraintStatus, B> { + budget.name_constraint_check()?; + match (constraint, san) { (GeneralName::DNSName(pattern), GeneralName::DNSName(name)) => { match (DNSConstraint::new(pattern.0), DNSName::new(name.0)) { (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), - (_, None) => Err(ValidationError::Other(format!( + (_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "unsatisfiable DNS name constraint: malformed SAN {}", name.0 - ))), - (None, _) => Err(ValidationError::Other(format!( + )))), + (None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "malformed DNS name constraint: {}", pattern.0 - ))), + )))), } } (GeneralName::IPAddress(pattern), GeneralName::IPAddress(name)) => { @@ -97,34 +174,62 @@ impl<'a, 'chain> NameChain<'a, 'chain> { IPAddress::from_bytes(name), ) { (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), - (_, None) => Err(ValidationError::Other(format!( + (_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "unsatisfiable IP name constraint: malformed SAN {:?}", name, - ))), - (None, _) => Err(ValidationError::Other(format!( + )))), + (None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!( "malformed IP name constraints: {:?}", pattern - ))), + )))), } } + (GeneralName::RFC822Name(pattern), GeneralName::RFC822Name(name)) => { + match (RFC822Constraint::new(pattern.0), RFC822Name::new(name.0)) { + (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), + (_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!( + "unsatisfiable RFC822 name constraint: malformed SAN {:?}", + name.0, + )))), + (None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!( + "malformed RFC822 name constraints: {:?}", + pattern.0 + )))), + } + } + // All other matching pairs of (constraint, name) are currently unsupported. + (GeneralName::OtherName(_), GeneralName::OtherName(_)) + | (GeneralName::X400Address(_), GeneralName::X400Address(_)) + | (GeneralName::DirectoryName(_), GeneralName::DirectoryName(_)) + | (GeneralName::EDIPartyName(_), GeneralName::EDIPartyName(_)) + | ( + GeneralName::UniformResourceIdentifier(_), + GeneralName::UniformResourceIdentifier(_), + ) + | (GeneralName::RegisteredID(_), GeneralName::RegisteredID(_)) => { + Err(ValidationError::new(ValidationErrorKind::Other( + "unsupported name constraint".to_string(), + ))) + } _ => Ok(Skipped), } } - fn evaluate_constraints( + fn evaluate_constraints( &self, - constraints: &NameConstraints<'chain>, - ) -> Result<(), ValidationError> { + constraints: &NameConstraints<'chain, Asn1Read>, + budget: &mut Budget, + ) -> ValidationResult<'chain, (), B> { if let Some(child) = self.child { - child.evaluate_constraints(constraints)?; + child.evaluate_constraints(constraints, budget)?; } 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)?; + for p in permitted_subtrees.clone() { + let status = self.evaluate_single_constraint(&p.base, &san, budget)?; if status.is_applied() { permit = status.is_match(); if permit { @@ -135,18 +240,18 @@ impl<'a, 'chain> NameChain<'a, 'chain> { } if !permit { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::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)?; + for e in excluded_subtrees.clone() { + let status = self.evaluate_single_constraint(&e.base, &san, budget)?; if status.is_match() { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::Other( "excluded name constraint matched SAN".into(), - )); + ))); } } } @@ -160,17 +265,18 @@ pub type Chain<'c, B> = Vec>; pub fn verify<'chain, B: CryptoOps>( leaf: &VerificationCertificate<'chain, B>, - intermediates: impl IntoIterator>, + intermediates: &[VerificationCertificate<'chain, B>], policy: &Policy<'_, B>, store: &Store<'chain, B>, -) -> Result, ValidationError> { - let builder = ChainBuilder::new(intermediates.into_iter().collect(), policy, store); +) -> ValidationResult<'chain, Chain<'chain, B>, B> { + let builder = ChainBuilder::new(intermediates, policy, store); - builder.build_chain(leaf) + let mut budget = Budget::new(); + builder.build_chain(leaf, &mut budget) } struct ChainBuilder<'a, 'chain, B: CryptoOps> { - intermediates: Vec>, + intermediates: &'a [VerificationCertificate<'chain, B>], policy: &'a Policy<'a, B>, store: &'a Store<'chain, B>, } @@ -196,7 +302,7 @@ impl ApplyNameConstraintStatus { impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { fn new( - intermediates: Vec>, + intermediates: &'a [VerificationCertificate<'chain, B>], policy: &'a Policy<'a, B>, store: &'a Store<'chain, B>, ) -> Self { @@ -208,7 +314,7 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { } fn potential_issuers( - &'a self, + &self, cert: &'a VerificationCertificate<'chain, B>, ) -> impl Iterator> + '_ { // TODO: Optimizations: @@ -227,9 +333,10 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { current_depth: u8, working_cert_extensions: &Extensions<'chain>, name_chain: NameChain<'_, 'chain>, - ) -> Result, ValidationError> { + budget: &mut Budget, + ) -> ValidationResult<'chain, Chain<'chain, B>, B> { if let Some(nc) = working_cert_extensions.get_extension(&NAME_CONSTRAINTS_OID) { - name_chain.evaluate_constraints(&nc.value()?)?; + name_chain.evaluate_constraints(&nc.value()?, budget)?; } // Look in the store's root set to see if the working cert is listed. @@ -242,14 +349,14 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { // 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( + return Err(ValidationError::new(ValidationErrorKind::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; + 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 @@ -257,7 +364,7 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { let issuer_extensions = issuing_cert_candidate.certificate().extensions()?; match self.policy.valid_issuer( issuing_cert_candidate, - working_cert.certificate(), + working_cert, current_depth, &issuer_extensions, ) { @@ -280,9 +387,9 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { // See https://gist.github.com/woodruffw/776153088e0df3fc2f0675c5e835f7b8 // for an example of this change. current_depth.checked_add(1).ok_or_else(|| { - ValidationError::Other( + ValidationError::new(ValidationErrorKind::Other( "current depth calculation overflowed".to_string(), - ) + )) })?, &issuer_extensions, NameChain::new( @@ -295,11 +402,19 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { // candidate (which is a non-leaf by definition) isn't self-issued. cert_is_self_issued(issuing_cert_candidate.certificate()), )?, + budget, ) { Ok(mut chain) => { chain.push(working_cert.clone()); return Ok(chain); } + // Immediately return on fatal error. + Err( + e @ ValidationError { + kind: ValidationErrorKind::FatalError(..), + cert: _, + }, + ) => return Err(e), Err(e) => last_err = Some(e), }; } @@ -309,24 +424,30 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { // 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), - }, - ))) + Err(ValidationError::new( + ValidationErrorKind::CandidatesExhausted(last_err.map_or_else( + || { + Box::new(ValidationError::new(ValidationErrorKind::Other( + "all candidates exhausted with no interior errors".to_string(), + ))) + }, + |e| match e { + // Avoid spamming the user with nested `CandidatesExhausted` errors. + ValidationError { + kind: ValidationErrorKind::CandidatesExhausted(e), + cert: _, + } => e, + _ => Box::new(e), + }, + )), + )) } fn build_chain( &self, leaf: &VerificationCertificate<'chain, B>, - ) -> Result, ValidationError> { + budget: &mut Budget, + ) -> ValidationResult<'chain, Chain<'chain, B>, B> { // Before anything else, check whether the given leaf cert // is well-formed according to our policy (and its underlying // certificate profile). @@ -335,16 +456,48 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { let leaf_extensions = leaf.certificate().extensions()?; self.policy - .permits_ee(leaf.certificate(), &leaf_extensions)?; + .permits_ee(leaf.certificate(), &leaf_extensions) + .map_err(|e| e.set_cert(leaf.clone()))?; let mut chain = self.build_chain_inner( leaf, 0, &leaf_extensions, NameChain::new(None, &leaf_extensions, false)?, + budget, )?; // We build the chain in reverse order, fix it now. chain.reverse(); Ok(chain) } } + +#[cfg(test)] +mod tests { + use asn1::ParseError; + use cryptography_x509::oid::SUBJECT_ALTERNATIVE_NAME_OID; + + use crate::certificate::tests::PublicKeyErrorOps; + use crate::{ValidationError, ValidationErrorKind}; + + #[test] + fn test_validationerror_display() { + let err = ValidationError::::new(ValidationErrorKind::Malformed( + ParseError::new(asn1::ParseErrorKind::InvalidLength), + )); + assert_eq!(err.to_string(), "ASN.1 parsing error: invalid length"); + + let err = ValidationError::::new(ValidationErrorKind::ExtensionError { + oid: SUBJECT_ALTERNATIVE_NAME_OID, + reason: "duplicate extension", + }); + assert_eq!( + err.to_string(), + "invalid extension: 2.5.29.17: duplicate extension" + ); + + let err = + ValidationError::::new(ValidationErrorKind::FatalError("oops")); + assert_eq!(err.to_string(), "fatal error: oops"); + } +} diff --git a/src/rust/cryptography-x509-verification/src/ops.rs b/src/rust/cryptography-x509-verification/src/ops.rs index 807bce5dff93..05cca823fdc3 100644 --- a/src/rust/cryptography-x509-verification/src/ops.rs +++ b/src/rust/cryptography-x509-verification/src/ops.rs @@ -5,13 +5,13 @@ use cryptography_x509::certificate::Certificate; pub struct VerificationCertificate<'a, B: CryptoOps> { - cert: Certificate<'a>, + cert: &'a 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 { + pub fn new(cert: &'a Certificate<'a>, extra: B::CertificateExtra) -> Self { VerificationCertificate { cert, extra, @@ -20,7 +20,7 @@ impl<'a, B: CryptoOps> VerificationCertificate<'a, B> { } pub fn certificate(&self) -> &Certificate<'a> { - &self.cert + self.cert } pub fn public_key(&self, ops: &B) -> Result<&B::Key, B::Err> { @@ -33,15 +33,32 @@ impl<'a, B: CryptoOps> VerificationCertificate<'a, B> { } } +impl std::fmt::Debug for VerificationCertificate<'_, B> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VerificationCertificate").finish() + } +} + 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()) + Self { + cert: self.cert, + extra: B::clone_extra(&self.extra), + public_key: { + let cell = once_cell::sync::OnceCell::new(); + if let Some(k) = self.public_key.get() { + cell.set(B::clone_public_key(k)).ok().unwrap(); + } + cell + }, + } } } @@ -53,7 +70,7 @@ pub trait CryptoOps { type Err; /// Extra data that's passed around with the certificate. - type CertificateExtra: Clone; + type CertificateExtra; /// Extracts the public key from the given `Certificate` in /// a `Key` format known by the cryptographic backend, or `None` @@ -63,10 +80,18 @@ pub trait CryptoOps { /// Verifies the signature on `Certificate` using the given /// `Key`. fn verify_signed_by(&self, cert: &Certificate<'_>, key: &Self::Key) -> Result<(), Self::Err>; + + // Makes a `clone` of `Key` + fn clone_public_key(extra: &Self::Key) -> Self::Key; + + // Makes a `clone` of `CertificateExtra` + fn clone_extra(extra: &Self::CertificateExtra) -> Self::CertificateExtra; } #[cfg(test)] pub(crate) mod tests { + use super::VerificationCertificate; + use crate::certificate::tests::PublicKeyErrorOps; use cryptography_x509::certificate::Certificate; pub(crate) fn v1_cert_pem() -> pem::Pem { @@ -89,4 +114,13 @@ zl9HYIMxATFyqSiD9jsx pub(crate) fn cert(cert_pem: &pem::Pem) -> Certificate<'_> { asn1::parse_single(cert_pem.contents()).unwrap() } + + #[test] + fn test_verification_certificate_debug() { + let p = v1_cert_pem(); + let c = cert(&p); + let vc = VerificationCertificate::::new(&c, ()); + + assert_eq!(format!("{:?}", vc), "VerificationCertificate"); + } } diff --git a/src/rust/cryptography-x509-verification/src/policy/extension.rs b/src/rust/cryptography-x509-verification/src/policy/extension.rs index 7006ad5dd110..c5c751a7a96e 100644 --- a/src/rust/cryptography-x509-verification/src/policy/extension.rs +++ b/src/rust/cryptography-x509-verification/src/policy/extension.rs @@ -12,7 +12,9 @@ use cryptography_x509::{ extensions::{Extension, Extensions}, }; -use crate::{ops::CryptoOps, policy::Policy, ValidationError}; +use crate::{ + ops::CryptoOps, policy::Policy, ValidationError, ValidationErrorKind, ValidationResult, +}; pub(crate) struct ExtensionPolicy { pub(crate) authority_information_access: ExtensionValidator, @@ -26,12 +28,12 @@ pub(crate) struct ExtensionPolicy { } impl ExtensionPolicy { - pub(crate) fn permits( + pub(crate) fn permits<'chain>( &self, policy: &Policy<'_, B>, - cert: &Certificate<'_>, + cert: &Certificate<'chain>, extensions: &Extensions<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { let mut authority_information_access_seen = false; let mut authority_key_identifier_seen = false; let mut subject_key_identifier_seen = false; @@ -81,10 +83,10 @@ impl ExtensionPolicy { 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 - ))); + return Err(ValidationError::new(ValidationErrorKind::ExtensionError { + oid: ext.extn_id, + reason: "certificate contains unaccounted-for critical extensions", + })); } _ => {} } @@ -144,11 +146,17 @@ impl Criticality { } } -type PresentExtensionValidatorCallback = - fn(&Policy<'_, B>, &Certificate<'_>, &Extension<'_>) -> Result<(), ValidationError>; +type PresentExtensionValidatorCallback = for<'chain> fn( + &Policy<'_, B>, + &Certificate<'chain>, + &Extension<'_>, +) -> ValidationResult<'chain, (), B>; -type MaybeExtensionValidatorCallback = - fn(&Policy<'_, B>, &Certificate<'_>, Option<&Extension<'_>>) -> Result<(), ValidationError>; +type MaybeExtensionValidatorCallback = for<'chain> fn( + &Policy<'_, B>, + &Certificate<'chain>, + Option<&Extension<'_>>, +) -> ValidationResult<'chain, (), B>; /// Represents different validation states for an extension. pub(crate) enum ExtensionValidator { @@ -195,22 +203,25 @@ impl ExtensionValidator { } } - pub(crate) fn permits( + pub(crate) fn permits<'chain>( &self, policy: &Policy<'_, B>, - cert: &Certificate<'_>, + cert: &Certificate<'chain>, extension: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { 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(), - )), + (ExtensionValidator::NotPresent, Some(extn)) => { + Err(ValidationError::new(ValidationErrorKind::ExtensionError { + oid: extn.extn_id.clone(), + reason: "Certificate contains prohibited extension", + })) + } // Extension MUST be present but is not; NOT OK. - (ExtensionValidator::Present { .. }, None) => Err(ValidationError::Other( - "Certificate is missing required extension".to_string(), + (ExtensionValidator::Present { .. }, None) => Err(ValidationError::new( + ValidationErrorKind::Other("Certificate is missing required extension".to_string()), )), // Extension MUST be present and is; check it. ( @@ -221,9 +232,10 @@ impl ExtensionValidator { Some(extn), ) => { if !criticality.permits(extn.critical) { - return Err(ValidationError::Other( - "Certificate extension has incorrect criticality".to_string(), - )); + return Err(ValidationError::new(ValidationErrorKind::ExtensionError { + oid: extn.extn_id.clone(), + reason: "Certificate extension has incorrect criticality", + })); } // If a custom validator is supplied, apply it. @@ -237,15 +249,17 @@ impl ExtensionValidator { }, 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(), - )); + match extn { + // If the extension is present, apply our criticality check. + Some(extn) if !criticality.permits(extn.critical) => { + Err(ValidationError::new(ValidationErrorKind::ExtensionError { + oid: extn.extn_id.clone(), + reason: "Certificate extension has incorrect criticality", + })) + } + // If a custom validator is supplied, apply it. + _ => validator.map_or(Ok(()), |v| v(policy, cert, extn)), } - - // If a custom validator is supplied, apply it. - validator.map_or(Ok(()), |v| v(policy, cert, extn)) } } } @@ -261,63 +275,69 @@ pub(crate) mod ee { use crate::{ ops::CryptoOps, - policy::{Policy, ValidationError}, + policy::{Policy, ValidationError, ValidationErrorKind, ValidationResult}, }; - pub(crate) fn basic_constraints( + pub(crate) fn basic_constraints<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { let basic_constraints: BasicConstraints = extn.value()?; if basic_constraints.ca { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::Other( "basicConstraints.cA must not be asserted in an EE certificate".to_string(), - )); + ))); } } Ok(()) } - pub(crate) fn subject_alternative_name( + pub(crate) fn subject_alternative_name<'chain, B: CryptoOps>( policy: &Policy<'_, B>, cert: &Certificate<'_>, extn: &Extension<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { match (cert.subject().is_empty(), extn.critical) { // If the subject is empty, the SAN MUST be critical. (true, false) => { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::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( + return Err(ValidationError::new(ValidationErrorKind::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(), - )); + // NOTE: We only verify the SAN against the policy's subject if the + // policy actually contains one. This enables both client and server + // profiles to use this validator, **with the expectation** that + // server profile construction requires a subject to be present. + if let Some(sub) = policy.subject.as_ref() { + let san: SubjectAlternativeName<'_> = extn.value()?; + if !sub.matches(&san) { + return Err(ValidationError::new(ValidationErrorKind::Other( + "leaf certificate has no matching subjectAltName".into(), + ))); + } } Ok(()) } - pub(crate) fn extended_key_usage( + pub(crate) fn extended_key_usage<'chain, B: CryptoOps>( policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { let mut ekus: ExtendedKeyUsage<'_> = extn.value()?; @@ -330,25 +350,27 @@ pub(crate) mod ee { if ekus.any(|eku| eku == policy.extended_key_usage) { Ok(()) } else { - Err(ValidationError::Other("required EKU not found".to_string())) + Err(ValidationError::new(ValidationErrorKind::Other( + "required EKU not found".to_string(), + ))) } } else { Ok(()) } } - pub(crate) fn key_usage( + pub(crate) fn key_usage<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { let key_usage: KeyUsage<'_> = extn.value()?; if key_usage.key_cert_sign() { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::Other( "EE keyUsage must not assert keyCertSign".to_string(), - )); + ))); } } @@ -359,6 +381,7 @@ pub(crate) mod ee { pub(crate) mod ca { use cryptography_x509::{ certificate::Certificate, + common::Asn1Read, extensions::{ AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage, NameConstraints, @@ -368,14 +391,14 @@ pub(crate) mod ca { use crate::{ ops::CryptoOps, - policy::{Policy, ValidationError}, + policy::{Policy, ValidationError, ValidationErrorKind, ValidationResult}, }; - pub(crate) fn authority_key_identifier( + pub(crate) fn authority_key_identifier<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { // 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. @@ -391,61 +414,56 @@ pub(crate) mod ca { // 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()?; + let aki: AuthorityKeyIdentifier<'_, Asn1Read> = 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( + return Err(ValidationError::new(ValidationErrorKind::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(), - )); - } + // NOTE: CABF 7.1.2.1.3 says that Root CAs MUST NOT + // have authorityCertIdentifier or authorityCertSerialNumber, + // but these are present in practice in trust program bundles + // due to older roots that have been grandfathered in. + // Other validators are permissive of these being present, + // so we don't check for them. + // See #11461 for more information. } Ok(()) } - pub(crate) fn key_usage( + pub(crate) fn key_usage<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: &Extension<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { let key_usage: KeyUsage<'_> = extn.value()?; if !key_usage.key_cert_sign() { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::Other( "keyUsage.keyCertSign must be asserted in a CA certificate".to_string(), - )); + ))); } Ok(()) } - pub(crate) fn basic_constraints( + pub(crate) fn basic_constraints<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: &Extension<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { let basic_constraints: BasicConstraints = extn.value()?; if !basic_constraints.ca { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::Other( "basicConstraints.cA must be asserted in a CA certificate".to_string(), - )); + ))); } // NOTE: basicConstraints.pathLength is checked as part of @@ -455,28 +473,28 @@ pub(crate) mod ca { Ok(()) } - pub(crate) fn name_constraints( + pub(crate) fn name_constraints<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { - let name_constraints: NameConstraints<'_> = extn.value()?; + let name_constraints: NameConstraints<'_, Asn1Read> = extn.value()?; let permitted_subtrees_empty = name_constraints .permitted_subtrees .as_ref() - .map_or(true, |pst| pst.unwrap_read().is_empty()); + .map_or(true, |pst| pst.is_empty()); let excluded_subtrees_empty = name_constraints .excluded_subtrees .as_ref() - .map_or(true, |est| est.unwrap_read().is_empty()); + .map_or(true, |est| est.is_empty()); if permitted_subtrees_empty && excluded_subtrees_empty { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::Other( "nameConstraints must have non-empty permittedSubtrees or excludedSubtrees" .to_string(), - )); + ))); } // NOTE: Both RFC 5280 and CABF require each `GeneralSubtree` @@ -487,11 +505,11 @@ pub(crate) mod ca { Ok(()) } - pub(crate) fn extended_key_usage( + pub(crate) fn extended_key_usage<'chain, B: CryptoOps>( policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { if let Some(extn) = extn { let mut ekus: ExtendedKeyUsage<'_> = extn.value()?; @@ -500,7 +518,9 @@ pub(crate) mod ca { 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())) + Err(ValidationError::new(ValidationErrorKind::Other( + "required EKU not found".to_string(), + ))) } } else { Ok(()) @@ -511,23 +531,24 @@ pub(crate) mod ca { pub(crate) mod common { use cryptography_x509::{ certificate::Certificate, + common::Asn1Read, extensions::{Extension, SequenceOfAccessDescriptions}, }; use crate::{ ops::CryptoOps, - policy::{Policy, ValidationError}, + policy::{Policy, ValidationResult}, }; - pub(crate) fn authority_information_access( + pub(crate) fn authority_information_access<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { 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()?; + let _: SequenceOfAccessDescriptions<'_, Asn1Read> = extn.value()?; } Ok(()) @@ -545,7 +566,7 @@ mod tests { 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::policy::{Policy, Subject, ValidationResult}; use crate::types::DNSName; #[test] @@ -572,7 +593,7 @@ mod tests { critical: bool, ext: &T, ) -> Vec { - let ext_value = asn1::write_single(&ext).unwrap(); + let ext_value = asn1::write_single(ext).unwrap(); let ext = Extension { extn_id: oid, critical, @@ -581,11 +602,11 @@ mod tests { asn1::write_single(&ext).unwrap() } - fn present_extension_validator( + fn present_extension_validator<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, _ext: &Extension<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { Ok(()) } @@ -595,7 +616,7 @@ mod tests { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; - let policy = Policy::new( + let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), @@ -621,11 +642,11 @@ mod tests { assert!(extension_validator.permits(&policy, &cert, None).is_err()); } - fn maybe_extension_validator( + fn maybe_extension_validator<'chain, B: CryptoOps>( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, _ext: Option<&Extension<'_>>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { Ok(()) } @@ -635,7 +656,7 @@ mod tests { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; - let policy = Policy::new( + let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), @@ -669,7 +690,7 @@ mod tests { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; - let policy = Policy::new( + let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), @@ -700,7 +721,7 @@ mod tests { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; - let policy = Policy::new( + let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), @@ -729,7 +750,7 @@ mod tests { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; - let policy = Policy::new( + let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), diff --git a/src/rust/cryptography-x509-verification/src/policy/mod.rs b/src/rust/cryptography-x509-verification/src/policy/mod.rs index 6d96e5feaef1..935113fcdf3c 100644 --- a/src/rust/cryptography-x509-verification/src/policy/mod.rs +++ b/src/rust/cryptography-x509-verification/src/policy/mod.rs @@ -9,6 +9,7 @@ use std::ops::Range; use std::sync::Arc; use asn1::ObjectIdentifier; +use cryptography_key_parsing::rsa::Pkcs1RsaPublicKey; use cryptography_x509::certificate::Certificate; use cryptography_x509::common::{ AlgorithmIdentifier, AlgorithmParameters, EcParameters, RsaPssParameters, Time, @@ -18,14 +19,18 @@ use cryptography_x509::common::{ 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, + BASIC_CONSTRAINTS_OID, EC_SECP256R1, EC_SECP384R1, EC_SECP521R1, EKU_CLIENT_AUTH_OID, + 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}; +use crate::{ValidationError, ValidationErrorKind, ValidationResult, VerificationCertificate}; + +// RSA key constraints, as defined in CA/B 6.1.5. +static WEBPKI_MINIMUM_RSA_MODULUS: usize = 2048; // SubjectPublicKeyInfo AlgorithmIdentifier constants, as defined in CA/B 7.1.3.1. @@ -92,7 +97,7 @@ static RSASSA_PSS_SHA256: Lazy> = Lazy::new(|| Algorithm hash_algorithm: PSS_SHA256_HASH_ALG, mask_gen_algorithm: PSS_SHA256_MASK_GEN_ALG, salt_length: 32, - _trailer_field: 1, + _trailer_field: None, }))), }); @@ -103,7 +108,7 @@ static RSASSA_PSS_SHA384: Lazy> = Lazy::new(|| Algorithm hash_algorithm: PSS_SHA384_HASH_ALG, mask_gen_algorithm: PSS_SHA384_MASK_GEN_ALG, salt_length: 48, - _trailer_field: 1, + _trailer_field: None, }))), }); @@ -114,7 +119,7 @@ static RSASSA_PSS_SHA512: Lazy> = Lazy::new(|| Algorithm hash_algorithm: PSS_SHA512_HASH_ALG, mask_gen_algorithm: PSS_SHA512_MASK_GEN_ALG, salt_length: 64, - _trailer_field: 1, + _trailer_field: None, }))), }); @@ -178,7 +183,7 @@ impl Subject<'_> { 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) + IPAddress::from_bytes(addr) == Some(*name) } _ => false, } @@ -204,7 +209,7 @@ pub struct Policy<'a, B: CryptoOps> { /// A subject (i.e. DNS name or other name format) that any EE certificates /// validated by this policy must match. - pub subject: Subject<'a>, + pub subject: Option>, /// The validation time. All certificates validated by this policy must /// be valid at this time. @@ -213,6 +218,10 @@ pub struct Policy<'a, B: CryptoOps> { /// An extended key usage that must appear in EEs validated by this policy. pub extended_key_usage: ObjectIdentifier, + /// The minimum RSA modulus, in bits. + /// This is equivalent to the public key size, e.g. 2048 for an RSA-2048 key. + pub minimum_rsa_modulus: usize, + /// The set of permitted public key algorithms, identified by their /// algorithm identifiers. pub permitted_public_key_algorithms: Arc>>, @@ -226,20 +235,20 @@ pub struct Policy<'a, B: CryptoOps> { } 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( + fn new( ops: B, - subject: Subject<'a>, + subject: Option>, time: asn1::DateTime, max_chain_depth: Option, + extended_key_usage: ObjectIdentifier, ) -> 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(), + extended_key_usage, + minimum_rsa_modulus: WEBPKI_MINIMUM_RSA_MODULUS, permitted_public_key_algorithms: Arc::clone(&*WEBPKI_PERMITTED_SPKI_ALGORITHMS), permitted_signature_algorithms: Arc::clone(&*WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS), ca_extension_policy: ExtensionPolicy { @@ -307,6 +316,9 @@ impl<'a, B: CryptoOps> Policy<'a, B> { Some(ee::key_usage), ), // CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name + // This validator handles both client and server cases by only matching against + // the SAN if the profile contains a subject, which it won't in the client + // validation case. subject_alternative_name: ExtensionValidator::present( Criticality::Agnostic, Some(ee::subject_alternative_name), @@ -328,51 +340,83 @@ impl<'a, B: CryptoOps> Policy<'a, B> { } } - fn permits_basic(&self, cert: &Certificate<'_>) -> Result<(), ValidationError> { + /// Create a new policy with suitable defaults for client certification + /// validation. + /// + /// **IMPORTANT**: This is **not** the appropriate API for verifying + /// website (i.e. server) certificates. For that, you **must** use + /// [`Policy::server`]. + pub fn client(ops: B, time: asn1::DateTime, max_chain_depth: Option) -> Self { + Self::new( + ops, + None, + time, + max_chain_depth, + EKU_CLIENT_AUTH_OID.clone(), + ) + } + + /// Create a new policy with defaults for the server certificate profile + /// defined in the CA/B Forum's Basic Requirements. + pub fn server( + ops: B, + subject: Subject<'a>, + time: asn1::DateTime, + max_chain_depth: Option, + ) -> Self { + Self::new( + ops, + Some(subject), + time, + max_chain_depth, + EKU_SERVER_AUTH_OID.clone(), + ) + } + + fn permits_basic<'chain>(&self, cert: &Certificate<'_>) -> ValidationResult<'chain, (), B> { // CA/B 7.1.1: // Certificates MUST be of type X.509 v3. if cert.tbs_cert.version != 2 { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::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( + return Err(ValidationError::new(ValidationErrorKind::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()) { + let serial = cert.tbs_cert.serial; + if !(1..=21).contains(&serial.as_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( + return Err(ValidationError::new(ValidationErrorKind::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( + ))); + } else if serial.is_negative() { + return Err(ValidationError::new(ValidationErrorKind::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( + return Err(ValidationError::new(ValidationErrorKind::Other( "certificate must have a non-empty Issuer".to_string(), - )); + ))); } // 5280 4.1.2.5: Validity @@ -383,21 +427,21 @@ impl<'a, B: CryptoOps> Policy<'a, B> { 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( + return Err(ValidationError::new(ValidationErrorKind::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( + pub(crate) fn permits_ca<'chain>( &self, - cert: &Certificate<'_>, + cert: &Certificate<'chain>, current_depth: u8, extensions: &Extensions<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { self.permits_basic(cert)?; // 5280 4.1.2.6: Subject @@ -420,9 +464,9 @@ impl<'a, B: CryptoOps> Policy<'a, B> { .path_length .map_or(false, |len| u64::from(current_depth) > len) { - return Err(ValidationError::Other( + return Err(ValidationError::new(ValidationErrorKind::Other( "path length constraint violated".to_string(), - ))?; + ))); } } @@ -432,11 +476,11 @@ impl<'a, B: CryptoOps> Policy<'a, B> { } /// Checks whether the given EE certificate is compatible with this policy. - pub(crate) fn permits_ee( + pub(crate) fn permits_ee<'chain>( &self, - cert: &Certificate<'_>, + cert: &Certificate<'chain>, extensions: &Extensions<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { self.permits_basic(cert)?; self.ee_extension_policy.permits(self, cert, extensions)?; @@ -457,61 +501,94 @@ impl<'a, B: CryptoOps> Policy<'a, B> { /// 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( + pub(crate) fn valid_issuer<'chain>( &self, - issuer: &VerificationCertificate<'_, B>, - child: &Certificate<'_>, + issuer: &VerificationCertificate<'chain, B>, + child: &VerificationCertificate<'chain, B>, current_depth: u8, issuer_extensions: &Extensions<'_>, - ) -> Result<(), ValidationError> { + ) -> ValidationResult<'chain, (), B> { // The issuer needs to be a valid CA at the current depth. - self.permits_ca(issuer.certificate(), current_depth, issuer_extensions)?; + self.permits_ca(issuer.certificate(), current_depth, issuer_extensions) + .map_err(|e| e.set_cert(issuer.clone()))?; // CA/B 7.1.3.1 SubjectPublicKeyInfo + // NOTE: We check the issuer's SPKI here, since the issuer is + // definitionally a CA and thus subject to CABF key requirements. if !self .permitted_public_key_algorithms - .contains(&child.tbs_cert.spki.algorithm) + .contains(&issuer.certificate().tbs_cert.spki.algorithm) { - return Err(ValidationError::Other(format!( + return Err(ValidationError::new(ValidationErrorKind::Other(format!( "Forbidden public key algorithm: {:?}", - &child.tbs_cert.spki.algorithm - ))); + &issuer.certificate().tbs_cert.spki.algorithm + )))); } // CA/B 7.1.3.2 Signature AlgorithmIdentifier + // NOTE: We check the child's signature here, since the issuer's + // signature is not necessarily subject to signature checks (e.g. + // if it's a root). This works out transitively, as any non root-issuer + // will be checked in its recursive step (where it'll be in the child + // position). if !self .permitted_signature_algorithms - .contains(&child.signature_alg) + .contains(&child.certificate().signature_alg) { - return Err(ValidationError::Other(format!( + return Err(ValidationError::new(ValidationErrorKind::Other(format!( "Forbidden signature algorithm: {:?}", - &child.signature_alg - ))); + &child.certificate().signature_alg + )))); + } + + // We do this before checking the RSA key size so that if parsing the + // key fails, we get a nice error message. + let pk = issuer.public_key(&self.ops).map_err(|_| { + ValidationError::new(ValidationErrorKind::Other( + "issuer has malformed public key".to_string(), + )) + })?; + + // CA/B 6.1.5: Key sizes + // NOTE: We don't currently enforce that RSA moduli are divisible by 8, + // since other implementations don't bother. + let issuer_spki = &issuer.certificate().tbs_cert.spki; + if matches!( + issuer_spki.algorithm.params, + AlgorithmParameters::Rsa(_) | AlgorithmParameters::RsaPss(_) + ) { + let rsa_key: Pkcs1RsaPublicKey<'_> = + asn1::parse_single(issuer_spki.subject_public_key.as_bytes())?; + + if rsa_key.n.as_bytes().len() * 8 < self.minimum_rsa_modulus { + return Err(ValidationError::new(ValidationErrorKind::Other( + "RSA key is too weak".into(), + ))); + } } - 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( + if self.ops.verify_signed_by(child.certificate(), pk).is_err() { + return Err(ValidationError::new(ValidationErrorKind::Other( "signature does not match".to_string(), - )); + ))); } Ok(()) } } -fn permits_validity_date(validity_date: &Time) -> Result<(), ValidationError> { +fn permits_validity_date<'chain, B: CryptoOps>( + validity_date: &Time, +) -> ValidationResult<'chain, (), B> { 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( + return Err(ValidationError::new(ValidationErrorKind::Other( "validity dates between 1950 and 2049 must be UtcTime".to_string(), - )); + ))); } } @@ -534,6 +611,7 @@ mod tests { RSASSA_PKCS1V15_SHA384, RSASSA_PKCS1V15_SHA512, RSASSA_PSS_SHA256, RSASSA_PSS_SHA384, RSASSA_PSS_SHA512, WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS, }; + use crate::certificate::tests::PublicKeyErrorOps; use crate::{ policy::{ Subject, SPKI_RSA, SPKI_SECP256R1, SPKI_SECP384R1, SPKI_SECP521R1, @@ -602,7 +680,7 @@ mod tests { 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(), + asn1::write_single(RSASSA_PSS_SHA256.deref()).unwrap(), exp_encoding ); } @@ -611,7 +689,7 @@ mod tests { 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(), + asn1::write_single(RSASSA_PSS_SHA384.deref()).unwrap(), exp_encoding ); } @@ -620,7 +698,7 @@ mod tests { 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(), + asn1::write_single(RSASSA_PSS_SHA512.deref()).unwrap(), exp_encoding ); } @@ -702,9 +780,9 @@ mod tests { 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()); + Time::GeneralizedTime(asn1::X509GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date::(&utc_validity).is_ok()); + assert!(permits_validity_date::(&generalized_validity).is_err()); } { // 2049 date. @@ -712,9 +790,9 @@ mod tests { 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()); + Time::GeneralizedTime(asn1::X509GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date::(&utc_validity).is_ok()); + assert!(permits_validity_date::(&generalized_validity).is_err()); } { // 2050 date. @@ -722,8 +800,8 @@ mod tests { 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()); + Time::GeneralizedTime(asn1::X509GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date::(&generalized_validity).is_ok()); } { // 2051 date. @@ -732,8 +810,8 @@ mod tests { // 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()); + Time::GeneralizedTime(asn1::X509GeneralizedTime::new(generalized_dt).unwrap()); + assert!(permits_validity_date::(&generalized_validity).is_ok()); } { // Post-2050 date. @@ -742,8 +820,8 @@ mod tests { // 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()); + Time::GeneralizedTime(asn1::X509GeneralizedTime::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 index 462b81965df4..c3b525930d9f 100644 --- a/src/rust/cryptography-x509-verification/src/trust_store.rs +++ b/src/rust/cryptography-x509-verification/src/trust_store.rs @@ -22,7 +22,7 @@ impl<'a, B: CryptoOps> Store<'a, B> { by_subject .entry(cert.certificate().tbs_cert.subject.clone()) .or_default() - .push(cert.clone()); + .push(cert); } Store { by_subject } } @@ -51,9 +51,12 @@ mod tests { #[test] fn test_store() { let cert_pem = v1_cert_pem(); - let cert = VerificationCertificate::new(cert(&cert_pem), ()); - let store = Store::<'_, PublicKeyErrorOps>::new([cert.clone()]); + let c1 = cert(&cert_pem); + let c2 = cert(&cert_pem); + let cert1 = VerificationCertificate::new(&c1, ()); + let cert2 = VerificationCertificate::new(&c2, ()); + let store = Store::<'_, PublicKeyErrorOps>::new([cert1]); - assert!(store.contains(&cert)); + assert!(store.contains(&cert2)); } } diff --git a/src/rust/cryptography-x509-verification/src/types.rs b/src/rust/cryptography-x509-verification/src/types.rs index f564715219cd..0cd84489e089 100644 --- a/src/rust/cryptography-x509-verification/src/types.rs +++ b/src/rust/cryptography-x509-verification/src/types.rs @@ -5,6 +5,13 @@ use std::net::IpAddr; use std::str::FromStr; +use asn1::IA5String; + +// RFC 2822 3.2.4 +static ATEXT_CHARS: &str = "!#$%&'*+-/=?^_`{|}~"; + +/// Represents a DNS name can be used in X.509 name matching. +/// /// 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]. @@ -75,6 +82,17 @@ impl<'a> DNSName<'a> { fn rlabels(&self) -> impl Iterator { self.as_str().rsplit('.') } + + /// Returns true if this domain is a subdomain of the other domain. + fn is_subdomain_of(&self, other: &DNSName<'_>) -> bool { + // NOTE: This is nearly identical to `DNSConstraint::matches`, + // except that the subdomain must be strictly longer than the parent domain. + self.as_str().len() > other.as_str().len() + && self + .rlabels() + .zip(other.rlabels()) + .all(|(a, o)| a.eq_ignore_ascii_case(o)) + } } impl PartialEq for DNSName<'_> { @@ -84,6 +102,9 @@ impl PartialEq for DNSName<'_> { } } +/// Represents either a DNS name or a DNS wildcard for use in X.509 name +/// matching. +/// /// 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 @@ -298,9 +319,90 @@ impl IPConstraint { } } +/// An `RFC822Name` represents an email address, as defined in [RFC 822 6.1] +/// and as amended by [RFC 2821 4.1.2]. In particular, it represents the `Mailbox` +/// rule from RFC 2821's grammar. +/// +/// This type does not currently support the quoted local-part form; email +/// addresses that use this form will be rejected. +/// +/// [RFC 822 6.1]: https://datatracker.ietf.org/doc/html/rfc822#section-6.1 +/// [RFC 2821 4.1.2]: https://datatracker.ietf.org/doc/html/rfc2821#section-4.1.2 +#[derive(PartialEq)] +pub struct RFC822Name<'a> { + pub mailbox: IA5String<'a>, + pub domain: DNSName<'a>, +} + +impl<'a> RFC822Name<'a> { + pub fn new(value: &'a str) -> Option { + // Mailbox = Local-part "@" Domain + // Both must be present. + let (local_part, domain) = value.split_once('@')?; + let local_part = IA5String::new(local_part)?; + + // Local-part = Dot-string / Quoted-string + // NOTE(ww): We do not support the Quoted-string form, for now. + // + // Dot-string: Atom *("." Atom) + // Atom = 1*atext + // + // NOTE(ww): `atext`'s production is in RFC 2822 3.2.4. + for component in local_part.as_str().split('.') { + if component.is_empty() + || !component + .chars() + .all(|c| c.is_ascii_alphanumeric() || ATEXT_CHARS.contains(c)) + { + return None; + } + } + + Some(Self { + mailbox: local_part, + domain: DNSName::new(domain)?, + }) + } +} + +/// An `RFC822Constraint` represents a Name Constraint on email addresses. +pub enum RFC822Constraint<'a> { + /// A constraint for an exact match on a specific email address. + Exact(RFC822Name<'a>), + /// A constraint for any mailbox on a particular domain. + OnDomain(DNSName<'a>), + /// A constraint for any mailbox *within* a particular domain. + /// For example, `InDomain("example.com")` will match `foo@bar.example.com` + /// but not `foo@example.com`, since `bar.example.com` is in `example.com` + /// but `example.com` is not within itself. + InDomain(DNSName<'a>), +} + +impl<'a> RFC822Constraint<'a> { + pub fn new(constraint: &'a str) -> Option { + if let Some(constraint) = constraint.strip_prefix('.') { + Some(Self::InDomain(DNSName::new(constraint)?)) + } else if let Some(email) = RFC822Name::new(constraint) { + Some(Self::Exact(email)) + } else { + Some(Self::OnDomain(DNSName::new(constraint)?)) + } + } + + pub fn matches(&self, email: &RFC822Name<'_>) -> bool { + match self { + Self::Exact(pat) => pat == email, + Self::OnDomain(pat) => &email.domain == pat, + Self::InDomain(pat) => email.domain.is_subdomain_of(pat), + } + } +} + #[cfg(test)] mod tests { - use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint}; + use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint, RFC822Name}; + + use super::RFC822Constraint; #[test] fn test_dnsname_debug_trait() { @@ -392,6 +494,33 @@ mod tests { ); } + #[test] + fn test_dnsname_is_subdomain_of() { + for (sup, sub, check) in &[ + // good cases + ("example.com", "sub.example.com", true), + ("example.com", "a.b.example.com", true), + ("sub.example.com", "sub.sub.example.com", true), + ("sub.example.com", "sub.sub.sub.example.com", true), + ("com", "example.com", true), + ("example.com", "com.example.com", true), + ("example.com", "com.example.example.com", true), + // bad cases + ("example.com", "example.com", false), + ("example.com", "com", false), + ("sub.example.com", "example.com", false), + ("sub.sub.example.com", "sub.sub.example.com", false), + ("sub.sub.example.com", "example.com", false), + ("com.example.com", "com.example.com", false), + ("com.example.example.com", "com.example.example.com", false), + ] { + let sup = DNSName::new(sup).unwrap(); + let sub = DNSName::new(sub).unwrap(); + + assert_eq!(sub.is_subdomain_of(&sup), *check); + } + } + #[test] fn test_dnspattern_new() { assert_eq!(DNSPattern::new("*"), None); @@ -587,4 +716,153 @@ mod tests { assert!(!ipv6_128.matches(&IPAddress::from_str("2600::ff00:dede").unwrap())); assert!(!ipv6_128.matches(&IPAddress::from_str("2600:db8::ff00:0").unwrap())); } + + #[test] + fn test_rfc822name() { + for bad_case in &[ + "", + // Missing local-part. + "@example.com", + " @example.com", + " @example.com", + // Missing domain cases. + "foo", + "foo@", + "foo@ ", + "foo@ ", + // Invalid domains. + "foo@!!!", + "foo@white space", + "foo@🙈", + // Invalid local part (empty mailbox sections). + ".@example.com", + "foo.@example.com", + ".foo@example.com", + ".foo.@example.com", + ".f.o.o.@example.com", + // Invalid local part (@ in mailbox). + "lol@lol@example.com", + "lol\\@lol@example.com", + "example@example.com@example.com", + "@@example.com", + // Invalid local part (invalid characters). + "lol\"lol@example.com", + "lol;lol@example.com", + "🙈@example.com", + // Intentionally unsupported quoted local parts. + "\"validbutunsupported\"@example.com", + ] { + assert!(RFC822Name::new(bad_case).is_none()); + } + + // Each good case is (address, (mailbox, domain)). + for (address, (mailbox, domain)) in &[ + // Normal mailboxes. + ("foo@example.com", ("foo", "example.com")), + ("foo.bar@example.com", ("foo.bar", "example.com")), + ("foo.bar.baz@example.com", ("foo.bar.baz", "example.com")), + ("1.2.3.4.5@example.com", ("1.2.3.4.5", "example.com")), + // Mailboxes with special but valid characters. + ("{legal}@example.com", ("{legal}", "example.com")), + ("{&*.legal}@example.com", ("{&*.legal}", "example.com")), + ("``````````@example.com", ("``````````", "example.com")), + ("hello?@sub.example.com", ("hello?", "sub.example.com")), + ] { + let parsed = RFC822Name::new(&address).unwrap(); + assert_eq!(&parsed.mailbox.as_str(), mailbox); + assert_eq!(&parsed.domain.as_str(), domain); + } + } + + #[test] + fn test_rfc822constraint_new() { + for (case, valid) in &[ + // good cases + ("foo@example.com", true), + ("foo.bar@example.com", true), + ("foo!bar@example.com", true), + ("example.com", true), + ("sub.example.com", true), + ("foo@sub.example.com", true), + ("foo.bar@sub.example.com", true), + ("foo!bar@sub.example.com", true), + (".example.com", true), + (".sub.example.com", true), + // bad cases + ("@example.com", false), + ("@@example.com", false), + ("foo@.example.com", false), + (".foo@example.com", false), + (".foo.@example.com", false), + ("foo.@example.com", false), + ("invaliddomain!", false), + ("..example.com", false), + ("foo..example.com", false), + (".foo..example.com", false), + ("..foo..example.com", false), + ] { + assert_eq!(RFC822Constraint::new(case).is_some(), *valid); + } + } + + #[test] + fn test_rfc822constraint_matches() { + { + let exact = RFC822Constraint::new("foo@example.com").unwrap(); + + // Ordinary exact match. + assert!(exact.matches(&RFC822Name::new("foo@example.com").unwrap())); + // Case changes are okay in the domain. + assert!(exact.matches(&RFC822Name::new("foo@EXAMPLE.com").unwrap())); + + // Case changes are not okay in the mailbox. + assert!(!exact.matches(&RFC822Name::new("Foo@example.com").unwrap())); + assert!(!exact.matches(&RFC822Name::new("FOO@example.com").unwrap())); + + // Different mailboxes and domains do not match. + assert!(!exact.matches(&RFC822Name::new("foo.bar@example.com").unwrap())); + assert!(!exact.matches(&RFC822Name::new("foo@sub.example.com").unwrap())); + } + + { + let on_domain = RFC822Constraint::new("example.com").unwrap(); + + // Ordinary domain matches. + assert!(on_domain.matches(&RFC822Name::new("foo@example.com").unwrap())); + assert!(on_domain.matches(&RFC822Name::new("bar@example.com").unwrap())); + assert!(on_domain.matches(&RFC822Name::new("foo.bar@example.com").unwrap())); + assert!(on_domain.matches(&RFC822Name::new("foo!bar@example.com").unwrap())); + // Case changes are okay in the domain and in the mailbox, + // since any mailbox on the domain is okay. + assert!(on_domain.matches(&RFC822Name::new("foo@EXAMPLE.com").unwrap())); + assert!(on_domain.matches(&RFC822Name::new("FOO@example.com").unwrap())); + + // Subdomains and other domains do not match. + assert!(!on_domain.matches(&RFC822Name::new("foo@sub.example.com").unwrap())); + assert!(!on_domain.matches(&RFC822Name::new("foo@localhost").unwrap())); + } + + { + let in_domain = RFC822Constraint::new(".example.com").unwrap(); + + // Any subdomain and mailbox matches. + assert!(in_domain.matches(&RFC822Name::new("foo@sub.example.com").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("foo@sub.sub.example.com").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("foo@com.example.example.com").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("foo.bar@com.example.example.com").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("foo!bar@com.example.example.com").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("bar@com.example.example.com").unwrap())); + // Case changes are okay in the subdomains and in the mailbox, since any mailbox + // in the domain is okay. + assert!(in_domain.matches(&RFC822Name::new("foo@SUB.example.com").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("foo@sub.EXAMPLE.com").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("foo@sub.example.COM").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("FOO@sub.example.COM").unwrap())); + assert!(in_domain.matches(&RFC822Name::new("FOO@sub.example.com").unwrap())); + + // Superdomains and other domains do not match. + assert!(!in_domain.matches(&RFC822Name::new("foo@example.com").unwrap())); + assert!(!in_domain.matches(&RFC822Name::new("foo@com").unwrap())); + } + } } diff --git a/src/rust/cryptography-x509/Cargo.toml b/src/rust/cryptography-x509/Cargo.toml index 9a877fd13cb6..03f2c260890e 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.63.0" +rust-version = "1.65.0" [dependencies] -asn1 = { version = "0.15.5", default-features = false } +asn1.workspace = true diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs index 77cebc30464e..77ccd011a85e 100644 --- a/src/rust/cryptography-x509/src/common.rs +++ b/src/rust/cryptography-x509/src/common.rs @@ -125,6 +125,33 @@ pub enum AlgorithmParameters<'a> { #[defined_by(oid::DH_KEY_AGREEMENT_OID)] DhKeyAgreement(BasicDHParams<'a>), + #[defined_by(oid::PBES2_OID)] + Pbes2(PBES2Params<'a>), + + #[defined_by(oid::PBKDF2_OID)] + Pbkdf2(PBKDF2Params<'a>), + + #[defined_by(oid::HMAC_WITH_SHA1_OID)] + HmacWithSha1(Option), + #[defined_by(oid::HMAC_WITH_SHA256_OID)] + HmacWithSha256(Option), + + // Used only in PKCS#7 AlgorithmIdentifiers + // https://datatracker.ietf.org/doc/html/rfc3565#section-4.1 + // + // From RFC 3565 section 4.1: + // The AlgorithmIdentifier parameters field MUST be present, and the + // parameters field MUST contain a AES-IV: + // + // AES-IV ::= OCTET STRING (SIZE(16)) + #[defined_by(oid::AES_128_CBC_OID)] + Aes128Cbc([u8; 16]), + #[defined_by(oid::AES_256_CBC_OID)] + Aes256Cbc([u8; 16]), + + #[defined_by(oid::PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC)] + Pbes1WithShaAnd3KeyTripleDesCbc(PBES1Params), + #[default] Other(asn1::ObjectIdentifier, Option>), } @@ -171,7 +198,7 @@ impl<'a> asn1::Asn1Readable<'a> for RawTlv<'a> { true } } -impl<'a> asn1::Asn1Writable for RawTlv<'a> { +impl asn1::Asn1Writable for RawTlv<'_> { fn write(&self, w: &mut asn1::Writer<'_>) -> asn1::WriteResult { w.write_tlv(self.tag, move |dest| dest.push_slice(self.value)) } @@ -180,7 +207,7 @@ impl<'a> asn1::Asn1Writable for RawTlv<'a> { #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub enum Time { UtcTime(asn1::UtcTime), - GeneralizedTime(asn1::GeneralizedTime), + GeneralizedTime(asn1::X509GeneralizedTime), } impl Time { @@ -236,6 +263,42 @@ impl asn1::SimpleAsn1W } } +pub trait Asn1Operation { + type SequenceOfVec<'a, T> + where + T: 'a; + type SetOfVec<'a, T> + where + T: 'a; + type OwnedBitString<'a>; +} + +pub struct Asn1Read; +pub struct Asn1Write; + +impl Asn1Operation for Asn1Read { + type SequenceOfVec<'a, T> + = asn1::SequenceOf<'a, T> + where + T: 'a; + type SetOfVec<'a, T> + = asn1::SetOf<'a, T> + where + T: 'a; + type OwnedBitString<'a> = asn1::BitString<'a>; +} +impl Asn1Operation for Asn1Write { + type SequenceOfVec<'a, T> + = asn1::SequenceOfWriter<'a, T, Vec> + where + T: 'a; + type SetOfVec<'a, T> + = asn1::SetOfWriter<'a, T, Vec> + where + T: 'a; + type OwnedBitString<'a> = asn1::OwnedBitString; +} + #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct DssSignature<'a> { pub r: asn1::BigUint<'a>, @@ -367,9 +430,18 @@ pub struct RsaPssParameters<'a> { #[explicit(2)] #[default(20u16)] pub salt_length: u16, + // While the RFC describes this field as `DEFAULT 1`, it also states that + // parsers must accept this field being encoded with a value of 1, in + // conflict with DER's requirement that field DEFAULT values not be + // encoded. Thus we just treat this as an optional field. + // + // Users of this struct should supply `None` to indicate the DEFAULT value + // of 1, or `Some` to indicate a different value. Note that if you supply + // `Some(1)` this will result in encoding a violation of the DER rules, + // thus this should never be done except to round-trip an existing + // structure. #[explicit(3)] - #[default(1u8)] - pub _trailer_field: u8, + pub _trailer_field: Option, } // https://datatracker.ietf.org/doc/html/rfc3279#section-2.3.2 @@ -386,6 +458,34 @@ pub struct DssParams<'a> { pub g: asn1::BigUint<'a>, } +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] +pub struct PBES2Params<'a> { + pub key_derivation_func: Box>, + pub encryption_scheme: Box>, +} + +const HMAC_SHA1_ALG: AlgorithmIdentifier<'static> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::HmacWithSha1(Some(())), +}; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] +pub struct PBKDF2Params<'a> { + // This is technically a CHOICE that can be an otherSource. We don't + // support that. + pub salt: &'a [u8], + pub iteration_count: u64, + pub key_length: Option, + #[default(HMAC_SHA1_ALG)] + pub prf: Box>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] +pub struct PBES1Params { + pub salt: [u8; 8], + pub iterations: u64, +} + /// 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. @@ -407,13 +507,32 @@ impl<'a> asn1::SimpleAsn1Readable<'a> for UnvalidatedVisibleString<'a> { } } -impl<'a> asn1::SimpleAsn1Writable for UnvalidatedVisibleString<'a> { +impl asn1::SimpleAsn1Writable for UnvalidatedVisibleString<'_> { const TAG: asn1::Tag = asn1::VisibleString::TAG; fn write_data(&self, _: &mut asn1::WriteBuf) -> asn1::WriteResult { unimplemented!(); } } +/// A BMPString ASN.1 element, where it is stored as a UTF-8 string in memory. +pub struct Utf8StoredBMPString<'a>(pub &'a str); + +impl<'a> Utf8StoredBMPString<'a> { + pub fn new(s: &'a str) -> Self { + Utf8StoredBMPString(s) + } +} + +impl asn1::SimpleAsn1Writable for Utf8StoredBMPString<'_> { + const TAG: asn1::Tag = asn1::BMPString::TAG; + fn write_data(&self, writer: &mut asn1::WriteBuf) -> asn1::WriteResult { + for ch in self.0.encode_utf16() { + writer.push_slice(&ch.to_be_bytes())?; + } + Ok(()) + } +} + #[derive(Clone)] pub struct WithTlv<'a, T> { tlv: asn1::Tlv<'a>, @@ -448,7 +567,7 @@ impl<'a, T: asn1::Asn1Readable<'a>> asn1::Asn1Readable<'a> for WithTlv<'a, T> { } } -impl<'a, T: asn1::Asn1Writable> asn1::Asn1Writable for WithTlv<'a, T> { +impl asn1::Asn1Writable for WithTlv<'_, T> { fn write(&self, w: &mut asn1::Writer<'_>) -> asn1::WriteResult<()> { self.value.write(w) } diff --git a/src/rust/cryptography-x509/src/crl.rs b/src/rust/cryptography-x509/src/crl.rs index acd4adb64eb0..ced8fb8e26b2 100644 --- a/src/rust/cryptography-x509/src/crl.rs +++ b/src/rust/cryptography-x509/src/crl.rs @@ -2,10 +2,10 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use crate::common::Asn1Operation; use crate::{common, extensions, name}; -pub type ReasonFlags<'a> = - Option, asn1::OwnedBitString>>; +pub type ReasonFlags<'a, Op> = Option<::OwnedBitString<'a>>; #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash)] pub struct CertificateRevocationList<'a> { @@ -41,9 +41,9 @@ pub struct RevokedCertificate<'a> { } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct IssuingDistributionPoint<'a> { +pub struct IssuingDistributionPoint<'a, Op: Asn1Operation> { #[explicit(0)] - pub distribution_point: Option>, + pub distribution_point: Option>, #[implicit(1)] #[default(false)] @@ -54,7 +54,7 @@ pub struct IssuingDistributionPoint<'a> { pub only_contains_ca_certs: bool, #[implicit(3)] - pub only_some_reasons: ReasonFlags<'a>, + pub only_some_reasons: ReasonFlags<'a, Op>, #[implicit(4)] #[default(false)] diff --git a/src/rust/cryptography-x509/src/csr.rs b/src/rust/cryptography-x509/src/csr.rs index 790134bacce0..95745db9380e 100644 --- a/src/rust/cryptography-x509/src/csr.rs +++ b/src/rust/cryptography-x509/src/csr.rs @@ -44,7 +44,7 @@ impl CertificationRequestInfo<'_> { pub fn check_attribute_length<'a>( values: asn1::SetOf<'a, asn1::Tlv<'a>>, ) -> Result<(), asn1::ParseError> { - if values.count() > 1 { + if values.count() != 1 { // TODO: We should raise a more specific error here // Only single-valued attributes are supported Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue)) diff --git a/src/rust/cryptography-x509/src/extensions.rs b/src/rust/cryptography-x509/src/extensions.rs index bbd0f2377896..2ffa8781d1a0 100644 --- a/src/rust/cryptography-x509/src/extensions.rs +++ b/src/rust/cryptography-x509/src/extensions.rs @@ -5,10 +5,10 @@ use std::collections::HashSet; use crate::common; +use crate::common::Asn1Operation; use crate::crl; use crate::name; -#[derive(Debug, PartialEq, Eq)] pub struct DuplicateExtensionsError(pub asn1::ObjectIdentifier); pub type RawExtensions<'a> = common::Asn1ReadableOrWritable< @@ -94,48 +94,41 @@ pub struct AccessDescription<'a> { pub access_location: name::GeneralName<'a>, } -pub type SequenceOfAccessDescriptions<'a> = common::Asn1ReadableOrWritable< - asn1::SequenceOf<'a, AccessDescription<'a>>, - asn1::SequenceOfWriter<'a, AccessDescription<'a>, Vec>>, ->; +pub type SequenceOfAccessDescriptions<'a, Op> = + ::SequenceOfVec<'a, AccessDescription<'a>>; // Needed due to clippy type complexity warning. -type SequenceOfPolicyQualifiers<'a> = common::Asn1ReadableOrWritable< - asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, - asn1::SequenceOfWriter<'a, PolicyQualifierInfo<'a>, Vec>>, ->; +type SequenceOfPolicyQualifiers<'a, Op> = + ::SequenceOfVec<'a, PolicyQualifierInfo<'a, Op>>; #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct PolicyInformation<'a> { +pub struct PolicyInformation<'a, Op: Asn1Operation + 'a> { pub policy_identifier: asn1::ObjectIdentifier, - pub policy_qualifiers: Option>, + pub policy_qualifiers: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct PolicyQualifierInfo<'a> { +pub struct PolicyQualifierInfo<'a, Op: Asn1Operation> { pub policy_qualifier_id: asn1::ObjectIdentifier, - pub qualifier: Qualifier<'a>, + pub qualifier: Qualifier<'a, Op>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub enum Qualifier<'a> { +pub enum Qualifier<'a, Op: Asn1Operation> { CpsUri(asn1::IA5String<'a>), - UserNotice(UserNotice<'a>), + UserNotice(UserNotice<'a, Op>), } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct UserNotice<'a> { - pub notice_ref: Option>, +pub struct UserNotice<'a, Op: Asn1Operation> { + pub notice_ref: Option>, pub explicit_text: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct NoticeReference<'a> { +pub struct NoticeReference<'a, Op: Asn1Operation> { pub organization: DisplayText<'a>, - pub notice_numbers: common::Asn1ReadableOrWritable< - asn1::SequenceOf<'a, asn1::BigUint<'a>>, - asn1::SequenceOfWriter<'a, asn1::BigUint<'a>, Vec>>, - >, + pub notice_numbers: Op::SequenceOfVec<'a, asn1::BigUint<'a>>, } // DisplayText also allows BMPString, which we currently do not support. @@ -149,19 +142,15 @@ pub enum DisplayText<'a> { BmpString(asn1::BMPString<'a>), } -// Needed due to clippy type complexity warning. -pub type SequenceOfSubtrees<'a> = common::Asn1ReadableOrWritable< - asn1::SequenceOf<'a, GeneralSubtree<'a>>, - asn1::SequenceOfWriter<'a, GeneralSubtree<'a>, Vec>>, ->; +pub type SequenceOfSubtrees<'a, Op> = ::SequenceOfVec<'a, GeneralSubtree<'a>>; #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct NameConstraints<'a> { +pub struct NameConstraints<'a, Op: Asn1Operation> { #[implicit(0)] - pub permitted_subtrees: Option>, + pub permitted_subtrees: Option>, #[implicit(1)] - pub excluded_subtrees: Option>, + pub excluded_subtrees: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] @@ -184,41 +173,32 @@ pub struct MSCertificateTemplate { } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct DistributionPoint<'a> { +pub struct DistributionPoint<'a, Op: Asn1Operation> { #[explicit(0)] - pub distribution_point: Option>, + pub distribution_point: Option>, #[implicit(1)] - pub reasons: crl::ReasonFlags<'a>, + pub reasons: crl::ReasonFlags<'a, Op>, #[implicit(2)] - pub crl_issuer: Option>, + pub crl_issuer: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub enum DistributionPointName<'a> { +pub enum DistributionPointName<'a, Op: Asn1Operation> { #[implicit(0)] - FullName(name::SequenceOfGeneralName<'a>), + FullName(name::SequenceOfGeneralName<'a, Op>), #[implicit(1)] - NameRelativeToCRLIssuer( - common::Asn1ReadableOrWritable< - asn1::SetOf<'a, common::AttributeTypeValue<'a>>, - asn1::SetOfWriter< - 'a, - common::AttributeTypeValue<'a>, - Vec>, - >, - >, - ), + NameRelativeToCRLIssuer(Op::SetOfVec<'a, common::AttributeTypeValue<'a>>), } #[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub struct AuthorityKeyIdentifier<'a> { +pub struct AuthorityKeyIdentifier<'a, Op: Asn1Operation> { #[implicit(0)] pub key_identifier: Option<&'a [u8]>, #[implicit(1)] - pub authority_cert_issuer: Option>, + pub authority_cert_issuer: Option>, #[implicit(2)] pub authority_cert_serial_number: Option>, } @@ -232,7 +212,7 @@ pub struct BasicConstraints { 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 type ExtendedKeyUsage<'a> = asn1::SequenceOf<'a, asn1::ObjectIdentifier, 1>; pub struct KeyUsage<'a>(asn1::BitString<'a>); @@ -253,7 +233,7 @@ impl KeyUsage<'_> { self.0.has_bit_set(0) } - pub fn content_comitment(&self) -> bool { + pub fn content_commitment(&self) -> bool { self.0.has_bit_set(1) } @@ -286,6 +266,43 @@ impl KeyUsage<'_> { } } +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct NamingAuthority<'a> { + pub id: Option, + pub url: Option>, + pub text: Option>, +} + +type SequenceOfDisplayTexts<'a, Op> = ::SequenceOfVec<'a, DisplayText<'a>>; + +type SequenceOfObjectIdentifiers<'a, Op> = + ::SequenceOfVec<'a, asn1::ObjectIdentifier>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct ProfessionInfo<'a, Op: Asn1Operation> { + #[explicit(0)] + pub naming_authority: Option>, + pub profession_items: SequenceOfDisplayTexts<'a, Op>, + pub profession_oids: Option>, + pub registration_number: Option>, + pub add_profession_info: Option<&'a [u8]>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct Admission<'a, Op: Asn1Operation + 'a> { + #[explicit(0)] + pub admission_authority: Option>, + #[explicit(1)] + pub naming_authority: Option>, + pub profession_infos: Op::SequenceOfVec<'a, ProfessionInfo<'a, Op>>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write)] +pub struct Admissions<'a, Op: Asn1Operation> { + pub admission_authority: Option>, + pub contents_of_admissions: Op::SequenceOfVec<'a, Admission<'a, Op>>, +} + #[cfg(test)] mod tests { use super::{BasicConstraints, Extension, Extensions, KeyUsage}; @@ -364,7 +381,7 @@ mod tests { let ku: KeyUsage<'_> = asn1::parse_single(&asn1).unwrap(); assert!(!ku.is_zeroed()); assert!(ku.digital_signature()); - assert!(ku.content_comitment()); + assert!(ku.content_commitment()); assert!(ku.key_encipherment()); assert!(ku.data_encipherment()); assert!(ku.key_agreement()); diff --git a/src/rust/cryptography-x509/src/lib.rs b/src/rust/cryptography-x509/src/lib.rs index c74424acfa34..54c3b12aa942 100644 --- a/src/rust/cryptography-x509/src/lib.rs +++ b/src/rust/cryptography-x509/src/lib.rs @@ -4,6 +4,7 @@ #![forbid(unsafe_code)] #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] +#![allow(unknown_lints, clippy::result_large_err)] pub mod certificate; pub mod common; @@ -14,4 +15,5 @@ pub mod name; pub mod ocsp_req; pub mod ocsp_resp; pub mod oid; +pub mod pkcs12; pub mod pkcs7; diff --git a/src/rust/cryptography-x509/src/name.rs b/src/rust/cryptography-x509/src/name.rs index 21b6cc8fca9a..078bca19446e 100644 --- a/src/rust/cryptography-x509/src/name.rs +++ b/src/rust/cryptography-x509/src/name.rs @@ -2,7 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::common; +use crate::common::{self, Asn1Operation}; pub type NameReadable<'a> = asn1::SequenceOf<'a, asn1::SetOf<'a, common::AttributeTypeValue<'a>>>; @@ -35,7 +35,7 @@ impl<'a> asn1::SimpleAsn1Readable<'a> for UnvalidatedIA5String<'a> { } } -impl<'a> asn1::SimpleAsn1Writable for UnvalidatedIA5String<'a> { +impl asn1::SimpleAsn1Writable for UnvalidatedIA5String<'_> { const TAG: asn1::Tag = asn1::IA5String::TAG; fn write_data(&self, dest: &mut asn1::WriteBuf) -> asn1::WriteResult { dest.push_slice(self.0.as_bytes()) @@ -82,7 +82,5 @@ pub enum GeneralName<'a> { RegisteredID(asn1::ObjectIdentifier), } -pub(crate) type SequenceOfGeneralName<'a> = common::Asn1ReadableOrWritable< - asn1::SequenceOf<'a, GeneralName<'a>>, - asn1::SequenceOfWriter<'a, GeneralName<'a>, Vec>>, ->; +pub(crate) type SequenceOfGeneralName<'a, Op> = + ::SequenceOfVec<'a, GeneralName<'a>>; diff --git a/src/rust/cryptography-x509/src/ocsp_resp.rs b/src/rust/cryptography-x509/src/ocsp_resp.rs index f40707ed2f75..5b0338b5028e 100644 --- a/src/rust/cryptography-x509/src/ocsp_resp.rs +++ b/src/rust/cryptography-x509/src/ocsp_resp.rs @@ -39,7 +39,7 @@ pub struct ResponseData<'a> { #[default(0)] pub version: u8, pub responder_id: ResponderId<'a>, - pub produced_at: asn1::GeneralizedTime, + pub produced_at: asn1::X509GeneralizedTime, pub responses: common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, SingleResponse<'a>>, asn1::SequenceOfWriter<'a, SingleResponse<'a>, Vec>>, @@ -60,9 +60,9 @@ pub enum ResponderId<'a> { pub struct SingleResponse<'a> { pub cert_id: ocsp_req::CertID<'a>, pub cert_status: CertStatus, - pub this_update: asn1::GeneralizedTime, + pub this_update: asn1::X509GeneralizedTime, #[explicit(0)] - pub next_update: Option, + pub next_update: Option, #[explicit(1)] pub raw_single_extensions: Option>, } @@ -79,7 +79,7 @@ pub enum CertStatus { #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct RevokedInfo { - pub revocation_time: asn1::GeneralizedTime, + pub revocation_time: asn1::X509GeneralizedTime, #[explicit(0)] pub revocation_reason: Option, } diff --git a/src/rust/cryptography-x509/src/oid.rs b/src/rust/cryptography-x509/src/oid.rs index bf5d0ba29689..ee148a7896ee 100644 --- a/src/rust/cryptography-x509/src/oid.rs +++ b/src/rust/cryptography-x509/src/oid.rs @@ -44,6 +44,7 @@ pub const FRESHEST_CRL_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 46); pub const INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 54); pub const ACCEPTABLE_RESPONSES_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 4); +pub const ADMISSIONS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 8, 3, 3); // Public key identifiers pub const EC_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 2, 1); @@ -147,3 +148,16 @@ pub const EKU_OCSP_SIGNING_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 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); + +pub const PBES2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 13); +pub const PBKDF2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 12); + +pub const PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC: asn1::ObjectIdentifier = + asn1::oid!(1, 2, 840, 113549, 1, 12, 1, 3); + +pub const AES_256_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 42); +pub const AES_192_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 22); +pub const AES_128_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 2); + +pub const HMAC_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 7); +pub const HMAC_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 9); diff --git a/src/rust/cryptography-x509/src/pkcs12.rs b/src/rust/cryptography-x509/src/pkcs12.rs new file mode 100644 index 000000000000..f8f518a4b615 --- /dev/null +++ b/src/rust/cryptography-x509/src/pkcs12.rs @@ -0,0 +1,84 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::common::{AlgorithmIdentifier, Utf8StoredBMPString}; +use crate::pkcs7; + +pub const CERT_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 3); +pub const KEY_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 1); +pub const SHROUDED_KEY_BAG_OID: asn1::ObjectIdentifier = + asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 2); +pub const X509_CERTIFICATE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 22, 1); +pub const FRIENDLY_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 20); +pub const LOCAL_KEY_ID_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 21); + +#[derive(asn1::Asn1Write)] +pub struct Pfx<'a> { + pub version: u8, + pub auth_safe: pkcs7::ContentInfo<'a>, + pub mac_data: Option>, +} + +#[derive(asn1::Asn1Write)] +pub struct MacData<'a> { + pub mac: pkcs7::DigestInfo<'a>, + pub salt: &'a [u8], + #[default(1u64)] + pub iterations: u64, +} + +#[derive(asn1::Asn1Write)] +pub struct SafeBag<'a> { + pub _bag_id: asn1::DefinedByMarker, + #[defined_by(_bag_id)] + pub bag_value: asn1::Explicit, 0>, + pub attributes: Option, Vec>>>, +} + +#[derive(asn1::Asn1Write)] +pub struct Attribute<'a> { + pub _attr_id: asn1::DefinedByMarker, + #[defined_by(_attr_id)] + pub attr_values: AttributeSet<'a>, +} + +#[derive(asn1::Asn1DefinedByWrite)] +pub enum AttributeSet<'a> { + #[defined_by(FRIENDLY_NAME_OID)] + FriendlyName(asn1::SetOfWriter<'a, Utf8StoredBMPString<'a>, [Utf8StoredBMPString<'a>; 1]>), + + #[defined_by(LOCAL_KEY_ID_OID)] + LocalKeyId(asn1::SetOfWriter<'a, &'a [u8], [&'a [u8]; 1]>), +} + +#[derive(asn1::Asn1DefinedByWrite)] +pub enum BagValue<'a> { + #[defined_by(CERT_BAG_OID)] + CertBag(CertBag<'a>), + + #[defined_by(KEY_BAG_OID)] + KeyBag(asn1::Tlv<'a>), + + #[defined_by(SHROUDED_KEY_BAG_OID)] + ShroudedKeyBag(EncryptedPrivateKeyInfo<'a>), +} + +#[derive(asn1::Asn1Write)] +pub struct CertBag<'a> { + pub _cert_id: asn1::DefinedByMarker, + #[defined_by(_cert_id)] + pub cert_value: asn1::Explicit, 0>, +} + +#[derive(asn1::Asn1DefinedByWrite)] +pub enum CertType<'a> { + #[defined_by(X509_CERTIFICATE_OID)] + X509(asn1::OctetStringEncoded>), +} + +#[derive(asn1::Asn1Write)] +pub struct EncryptedPrivateKeyInfo<'a> { + pub encryption_algorithm: AlgorithmIdentifier<'a>, + pub encrypted_data: &'a [u8], +} diff --git a/src/rust/cryptography-x509/src/pkcs7.rs b/src/rust/cryptography-x509/src/pkcs7.rs index c5b7a9e3f650..7a55d48b473b 100644 --- a/src/rust/cryptography-x509/src/pkcs7.rs +++ b/src/rust/cryptography-x509/src/pkcs7.rs @@ -6,8 +6,10 @@ use crate::{certificate, common, csr, name}; pub const PKCS7_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 1); pub const PKCS7_SIGNED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 2); +pub const PKCS7_ENVELOPED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 3); +pub const PKCS7_ENCRYPTED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 6); -#[derive(asn1::Asn1Write)] +#[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct ContentInfo<'a> { pub _content_type: asn1::DefinedByMarker, @@ -15,30 +17,50 @@ pub struct ContentInfo<'a> { pub content: Content<'a>, } -#[derive(asn1::Asn1DefinedByWrite)] +#[derive(asn1::Asn1DefinedByWrite, asn1::Asn1DefinedByRead)] pub enum Content<'a> { + #[defined_by(PKCS7_ENVELOPED_DATA_OID)] + EnvelopedData(asn1::Explicit>, 0>), #[defined_by(PKCS7_SIGNED_DATA_OID)] - SignedData(asn1::Explicit<'a, Box>, 0>), + SignedData(asn1::Explicit>, 0>), #[defined_by(PKCS7_DATA_OID)] - Data(Option>), + Data(Option>), + #[defined_by(PKCS7_ENCRYPTED_DATA_OID)] + EncryptedData(asn1::Explicit, 0>), } -#[derive(asn1::Asn1Write)] +#[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct SignedData<'a> { pub version: u8, - pub digest_algorithms: asn1::SetOfWriter<'a, common::AlgorithmIdentifier<'a>>, + pub digest_algorithms: common::Asn1ReadableOrWritable< + asn1::SetOf<'a, common::AlgorithmIdentifier<'a>>, + asn1::SetOfWriter<'a, common::AlgorithmIdentifier<'a>>, + >, pub content_info: ContentInfo<'a>, #[implicit(0)] - pub certificates: Option>>, + pub certificates: Option< + common::Asn1ReadableOrWritable< + asn1::SetOf<'a, certificate::Certificate<'a>>, + asn1::SetOfWriter<'a, certificate::Certificate<'a>>, + >, + >, // We don't ever supply any of these, so for now, don't fill out the fields. #[implicit(1)] - pub crls: Option>>, + pub crls: Option< + common::Asn1ReadableOrWritable< + asn1::SetOf<'a, asn1::Sequence<'a>>, + asn1::SetOfWriter<'a, asn1::Sequence<'a>>, + >, + >, - pub signer_infos: asn1::SetOfWriter<'a, SignerInfo<'a>>, + pub signer_infos: common::Asn1ReadableOrWritable< + asn1::SetOf<'a, SignerInfo<'a>>, + asn1::SetOfWriter<'a, SignerInfo<'a>>, + >, } -#[derive(asn1::Asn1Write)] +#[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct SignerInfo<'a> { pub version: u8, pub issuer_and_serial_number: IssuerAndSerialNumber<'a>, @@ -53,8 +75,46 @@ pub struct SignerInfo<'a> { pub unauthenticated_attributes: Option>, } -#[derive(asn1::Asn1Write)] +#[derive(asn1::Asn1Write, asn1::Asn1Read)] +pub struct EnvelopedData<'a> { + pub version: u8, + pub recipient_infos: common::Asn1ReadableOrWritable< + asn1::SetOf<'a, RecipientInfo<'a>>, + asn1::SetOfWriter<'a, RecipientInfo<'a>>, + >, + pub encrypted_content_info: EncryptedContentInfo<'a>, +} + +#[derive(asn1::Asn1Write, asn1::Asn1Read)] +pub struct RecipientInfo<'a> { + pub version: u8, + pub issuer_and_serial_number: IssuerAndSerialNumber<'a>, + pub key_encryption_algorithm: common::AlgorithmIdentifier<'a>, + pub encrypted_key: &'a [u8], +} + +#[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct IssuerAndSerialNumber<'a> { pub issuer: name::Name<'a>, pub serial_number: asn1::BigInt<'a>, } + +#[derive(asn1::Asn1Write, asn1::Asn1Read)] +pub struct EncryptedData<'a> { + pub version: u8, + pub encrypted_content_info: EncryptedContentInfo<'a>, +} + +#[derive(asn1::Asn1Write, asn1::Asn1Read)] +pub struct EncryptedContentInfo<'a> { + pub content_type: asn1::ObjectIdentifier, + pub content_encryption_algorithm: common::AlgorithmIdentifier<'a>, + #[implicit(0)] + pub encrypted_content: Option<&'a [u8]>, +} + +#[derive(asn1::Asn1Write, asn1::Asn1Read)] +pub struct DigestInfo<'a> { + pub algorithm: common::AlgorithmIdentifier<'a>, + pub digest: &'a [u8], +} diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs index 641417545fce..26ee176bb935 100644 --- a/src/rust/src/asn1.rs +++ b/src/rust/src/asn1.rs @@ -2,20 +2,21 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use asn1::SimpleAsn1Readable; -use cryptography_x509::certificate::Certificate; -use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo, Time}; -use cryptography_x509::name::Name; +use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo}; +use pyo3::pybacked::PyBackedBytes; use pyo3::types::IntoPyDict; -use pyo3::ToPyObject; +use pyo3::types::PyAnyMethods; +use pyo3::IntoPyObject; use crate::error::{CryptographyError, CryptographyResult}; use crate::types; -pub(crate) fn py_oid_to_oid(py_oid: &pyo3::PyAny) -> pyo3::PyResult { +pub(crate) fn py_oid_to_oid( + py_oid: pyo3::Bound<'_, pyo3::PyAny>, +) -> pyo3::PyResult { Ok(py_oid - .downcast::>()? - .borrow() + .downcast::()? + .get() .oid .clone()) } @@ -23,52 +24,55 @@ pub(crate) fn py_oid_to_oid(py_oid: &pyo3::PyAny) -> pyo3::PyResult( py: pyo3::Python<'p>, oid: &asn1::ObjectIdentifier, -) -> pyo3::PyResult<&'p pyo3::PyAny> { - Ok(pyo3::Py::new(py, crate::oid::ObjectIdentifier { oid: oid.clone() })?.into_ref(py)) +) -> pyo3::PyResult> { + Ok(pyo3::Bound::new(py, crate::oid::ObjectIdentifier { oid: oid.clone() })?.into_any()) } -#[pyo3::prelude::pyfunction] -fn parse_spki_for_data( - py: pyo3::Python<'_>, +#[pyo3::pyfunction] +fn parse_spki_for_data<'p>( + py: pyo3::Python<'p>, data: &[u8], -) -> Result { +) -> Result, CryptographyError> { let spki = asn1::parse_single::>(data)?; if spki.subject_public_key.padding_bits() != 0 { return Err(pyo3::exceptions::PyValueError::new_err("Invalid public key encoding").into()); } - Ok(pyo3::types::PyBytes::new(py, spki.subject_public_key.as_bytes()).to_object(py)) + Ok(pyo3::types::PyBytes::new( + py, + spki.subject_public_key.as_bytes(), + )) } pub(crate) fn big_byte_slice_to_py_int<'p>( py: pyo3::Python<'p>, v: &'_ [u8], -) -> pyo3::PyResult<&'p pyo3::PyAny> { - let int_type = py.get_type::(); - let kwargs = [("signed", true)].into_py_dict(py); - int_type.call_method(pyo3::intern!(py, "from_bytes"), (v, "big"), Some(kwargs)) +) -> pyo3::PyResult> { + let int_type = py.get_type::(); + let kwargs = [("signed", true)].into_py_dict(py)?; + int_type.call_method(pyo3::intern!(py, "from_bytes"), (v, "big"), Some(&kwargs)) } -#[pyo3::prelude::pyfunction] -fn decode_dss_signature( - py: pyo3::Python<'_>, +#[pyo3::pyfunction] +fn decode_dss_signature<'p>( + py: pyo3::Python<'p>, data: &[u8], -) -> Result { +) -> CryptographyResult> { let sig = asn1::parse_single::>(data)?; Ok(( big_byte_slice_to_py_int(py, sig.r.as_bytes())?, big_byte_slice_to_py_int(py, sig.s.as_bytes())?, ) - .to_object(py)) + .into_pyobject(py)? + .into_any()) } pub(crate) fn py_uint_to_big_endian_bytes<'p>( py: pyo3::Python<'p>, - v: &'p pyo3::types::PyLong, -) -> pyo3::PyResult<&'p [u8]> { - let zero = (0).to_object(py); - if v.lt(zero)? { + v: pyo3::Bound<'p, pyo3::types::PyInt>, +) -> pyo3::PyResult { + if v.lt(0)? { return Err(pyo3::exceptions::PyValueError::new_err( "Negative integers are not supported", )); @@ -90,11 +94,11 @@ pub(crate) fn encode_der_data<'p>( py: pyo3::Python<'p>, pem_tag: String, data: Vec, - encoding: &'p pyo3::PyAny, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if encoding.is(types::ENCODING_DER.get(py)?) { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, +) -> CryptographyResult> { + if encoding.is(&types::ENCODING_DER.get(py)?) { Ok(pyo3::types::PyBytes::new(py, &data)) - } else if encoding.is(types::ENCODING_PEM.get(py)?) { + } else if encoding.is(&types::ENCODING_PEM.get(py)?) { Ok(pyo3::types::PyBytes::new( py, &pem::encode_config( @@ -111,70 +115,25 @@ pub(crate) fn encode_der_data<'p>( } } -#[pyo3::prelude::pyfunction] -fn encode_dss_signature( - py: pyo3::Python<'_>, - r: &pyo3::types::PyLong, - s: &pyo3::types::PyLong, -) -> CryptographyResult { +#[pyo3::pyfunction] +fn encode_dss_signature<'p>( + py: pyo3::Python<'p>, + r: pyo3::Bound<'_, pyo3::types::PyInt>, + s: pyo3::Bound<'_, pyo3::types::PyInt>, +) -> CryptographyResult> { + let r_bytes = py_uint_to_big_endian_bytes(py, r)?; + let s_bytes = py_uint_to_big_endian_bytes(py, s)?; let sig = DssSignature { - r: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, r)?).unwrap(), - s: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, s)?).unwrap(), + r: asn1::BigUint::new(&r_bytes).unwrap(), + s: asn1::BigUint::new(&s_bytes).unwrap(), }; let result = asn1::write_single(&sig)?; - Ok(pyo3::types::PyBytes::new(py, &result).to_object(py)) -} - -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.asn1")] -struct TestCertificate { - #[pyo3(get)] - not_before_tag: u8, - #[pyo3(get)] - not_after_tag: u8, - #[pyo3(get)] - issuer_value_tags: Vec, - #[pyo3(get)] - subject_value_tags: Vec, -} - -fn parse_name_value_tags(rdns: &Name<'_>) -> Vec { - let mut tags = vec![]; - for rdn in rdns.unwrap_read().clone() { - let mut attributes = rdn.collect::>(); - assert_eq!(attributes.len(), 1); - - tags.push(attributes.pop().unwrap().value.tag().as_u8().unwrap()); - } - tags + Ok(pyo3::types::PyBytes::new(py, &result)) } -fn time_tag(t: &Time) -> u8 { - match t { - Time::UtcTime(_) => asn1::UtcTime::TAG.as_u8().unwrap(), - Time::GeneralizedTime(_) => asn1::GeneralizedTime::TAG.as_u8().unwrap(), - } -} - -#[pyo3::prelude::pyfunction] -fn test_parse_certificate(data: &[u8]) -> Result { - let cert = asn1::parse_single::>(data)?; - - Ok(TestCertificate { - not_before_tag: time_tag(&cert.tbs_cert.validity.not_before), - not_after_tag: time_tag(&cert.tbs_cert.validity.not_after), - issuer_value_tags: parse_name_value_tags(&cert.tbs_cert.issuer), - subject_value_tags: parse_name_value_tags(&cert.tbs_cert.subject), - }) -} - -pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { - let submod = pyo3::prelude::PyModule::new(py, "asn1")?; - submod.add_function(pyo3::wrap_pyfunction!(parse_spki_for_data, submod)?)?; - - submod.add_function(pyo3::wrap_pyfunction!(decode_dss_signature, submod)?)?; - submod.add_function(pyo3::wrap_pyfunction!(encode_dss_signature, submod)?)?; - - submod.add_function(pyo3::wrap_pyfunction!(test_parse_certificate, submod)?)?; - - Ok(submod) +#[pyo3::pymodule] +#[pyo3(name = "asn1")] +pub(crate) mod asn1_mod { + #[pymodule_export] + use super::{decode_dss_signature, encode_dss_signature, parse_spki_for_data}; } diff --git a/src/rust/src/backend/aead.rs b/src/rust/src/backend/aead.rs index 7c364dede81e..fc56b64d6553 100644 --- a/src/rust/src/backend/aead.rs +++ b/src/rust/src/backend/aead.rs @@ -5,6 +5,7 @@ use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; +use pyo3::types::{PyAnyMethods, PyListMethods}; fn check_length(data: &[u8]) -> CryptographyResult<()> { if data.len() > (i32::MAX as usize) { @@ -21,7 +22,7 @@ fn check_length(data: &[u8]) -> CryptographyResult<()> { enum Aad<'a> { Single(CffiBuf<'a>), - List(&'a pyo3::types::PyList), + List(pyo3::Bound<'a, pyo3::types::PyList>), } struct EvpCipherAead { @@ -77,6 +78,7 @@ impl EvpCipherAead { ctx: &mut openssl::cipher_ctx::CipherCtx, data: &[u8], out: &mut [u8], + is_ccm: bool, ) -> CryptographyResult<()> { let bs = ctx.block_size(); @@ -87,9 +89,11 @@ impl EvpCipherAead { 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); + if !is_ccm { + 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 @@ -128,12 +132,22 @@ impl EvpCipherAead { plaintext: &[u8], aad: Option>, nonce: Option<&[u8]>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { 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) + Self::encrypt_with_context( + py, + ctx, + plaintext, + aad, + nonce, + self.tag_len, + self.tag_first, + false, + ) } + #[allow(clippy::too_many_arguments)] fn encrypt_with_context<'p>( py: pyo3::Python<'p>, mut ctx: openssl::cipher_ctx::CipherCtx, @@ -142,13 +156,19 @@ impl EvpCipherAead { nonce: Option<&[u8]>, tag_len: usize, tag_first: bool, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + is_ccm: bool, + ) -> CryptographyResult> { check_length(plaintext)?; - if let Some(nonce) = nonce { - ctx.set_iv_length(nonce.len())?; + if !is_ccm { + if let Some(nonce) = nonce { + ctx.set_iv_length(nonce.len())?; + } + ctx.encrypt_init(None, None, nonce)?; + } + if is_ccm { + ctx.set_data_len(plaintext.len())?; } - ctx.encrypt_init(None, None, nonce)?; Self::process_aad(&mut ctx, aad)?; @@ -164,7 +184,7 @@ impl EvpCipherAead { (ciphertext, tag) = b.split_at_mut(plaintext.len()); } - Self::process_data(&mut ctx, plaintext, ciphertext)?; + Self::process_data(&mut ctx, plaintext, ciphertext, is_ccm)?; ctx.tag(tag).map_err(CryptographyError::from)?; @@ -179,7 +199,7 @@ impl EvpCipherAead { ciphertext: &[u8], aad: Option>, nonce: Option<&[u8]>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let mut ctx = openssl::cipher_ctx::CipherCtx::new()?; ctx.copy(&self.base_decryption_ctx)?; Self::decrypt_with_context( @@ -190,9 +210,11 @@ impl EvpCipherAead { nonce, self.tag_len, self.tag_first, + false, ) } + #[allow(clippy::too_many_arguments)] fn decrypt_with_context<'p>( py: pyo3::Python<'p>, mut ctx: openssl::cipher_ctx::CipherCtx, @@ -201,16 +223,12 @@ impl EvpCipherAead { nonce: Option<&[u8]>, tag_len: usize, tag_first: bool, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + is_ccm: bool, + ) -> CryptographyResult> { 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 { @@ -221,7 +239,18 @@ impl EvpCipherAead { } else { (ciphertext_data, tag) = ciphertext.split_at(ciphertext.len() - tag_len); } - ctx.set_tag(tag)?; + + if !is_ccm { + if let Some(nonce) = nonce { + ctx.set_iv_length(nonce.len())?; + } + + ctx.decrypt_init(None, None, nonce)?; + ctx.set_tag(tag)?; + } + if is_ccm { + ctx.set_data_len(ciphertext_data.len())?; + } Self::process_aad(&mut ctx, aad)?; @@ -229,7 +258,7 @@ impl EvpCipherAead { py, ciphertext_data.len(), |b| { - Self::process_data(&mut ctx, ciphertext_data, b) + Self::process_data(&mut ctx, ciphertext_data, b, is_ccm) .map_err(|_| exceptions::InvalidTag::new_err(()))?; Ok(()) @@ -238,38 +267,30 @@ impl EvpCipherAead { } } -#[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, + is_ccm: bool, } -#[cfg(not(any( - CRYPTOGRAPHY_IS_LIBRESSL, - CRYPTOGRAPHY_IS_BORINGSSL, - not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), - CRYPTOGRAPHY_OPENSSL_320_OR_GREATER -)))] impl LazyEvpCipherAead { + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn new( cipher: &'static openssl::cipher::CipherRef, key: pyo3::Py, tag_len: usize, tag_first: bool, + is_ccm: bool, ) -> LazyEvpCipherAead { LazyEvpCipherAead { cipher, key, tag_len, tag_first, + is_ccm, } } @@ -279,11 +300,19 @@ impl LazyEvpCipherAead { plaintext: &[u8], aad: Option>, nonce: Option<&[u8]>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let key_buf = self.key.as_ref(py).extract::>()?; + ) -> CryptographyResult> { + let key_buf = self.key.bind(py).extract::>()?; let mut encryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; - encryption_ctx.encrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; + if self.is_ccm { + encryption_ctx.encrypt_init(Some(self.cipher), None, None)?; + encryption_ctx.set_iv_length(nonce.as_ref().unwrap().len())?; + encryption_ctx.set_tag_length(self.tag_len)?; + encryption_ctx.encrypt_init(None, Some(key_buf.as_bytes()), nonce)?; + } else { + encryption_ctx.encrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; + } + EvpCipherAead::encrypt_with_context( py, encryption_ctx, @@ -292,6 +321,7 @@ impl LazyEvpCipherAead { nonce, self.tag_len, self.tag_first, + self.is_ccm, ) } @@ -301,11 +331,26 @@ impl LazyEvpCipherAead { ciphertext: &[u8], aad: Option>, nonce: Option<&[u8]>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let key_buf = self.key.as_ref(py).extract::>()?; + ) -> CryptographyResult> { + let key_buf = self.key.bind(py).extract::>()?; let mut decryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; - decryption_ctx.decrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; + if self.is_ccm { + decryption_ctx.decrypt_init(Some(self.cipher), None, None)?; + decryption_ctx.set_iv_length(nonce.as_ref().unwrap().len())?; + + if ciphertext.len() < self.tag_len { + return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); + } + + let (_, tag) = ciphertext.split_at(ciphertext.len() - self.tag_len); + decryption_ctx.set_tag(tag)?; + + decryption_ctx.decrypt_init(None, Some(key_buf.as_bytes()), nonce)?; + } else { + decryption_ctx.decrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; + } + EvpCipherAead::decrypt_with_context( py, decryption_ctx, @@ -314,6 +359,7 @@ impl LazyEvpCipherAead { nonce, self.tag_len, self.tag_first, + self.is_ccm, ) } } @@ -343,7 +389,7 @@ impl EvpAead { plaintext: &[u8], aad: Option>, nonce: Option<&[u8]>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { check_length(plaintext)?; let ad = if let Some(Aad::Single(ad)) = &aad { @@ -371,7 +417,7 @@ impl EvpAead { ciphertext: &[u8], aad: Option>, nonce: Option<&[u8]>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { if ciphertext.len() < self.tag_len { return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); } @@ -398,7 +444,7 @@ impl EvpAead { } } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead")] struct ChaCha20Poly1305 { #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] ctx: EvpAead, @@ -420,7 +466,7 @@ struct ChaCha20Poly1305 { ctx: LazyEvpCipherAead, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl ChaCha20Poly1305 { #[new] fn new(py: pyo3::Python<'_>, key: pyo3::Py) -> CryptographyResult { @@ -443,8 +489,8 @@ impl ChaCha20Poly1305 { } else if #[cfg(any( CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, - not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER - )))] { + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + ))] { if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( @@ -478,6 +524,7 @@ impl ChaCha20Poly1305 { key, 16, false, + false, ) }) } @@ -485,19 +532,18 @@ impl ChaCha20Poly1305 { } #[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 generate_key(py: pyo3::Python<'_>) -> CryptographyResult> { + Ok(types::OS_URANDOM.get(py)?.call1((32,))?) } + #[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> { + ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); @@ -511,13 +557,14 @@ impl ChaCha20Poly1305 { .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> { + ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); @@ -532,7 +579,265 @@ impl ChaCha20Poly1305 { } } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( + frozen, + module = "cryptography.hazmat.bindings._rust.openssl.aead", + name = "AESGCM" +)] +struct AesGcm { + #[cfg(any( + CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + ))] + ctx: EvpCipherAead, + + #[cfg(not(any( + CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, + CRYPTOGRAPHY_IS_LIBRESSL, + CRYPTOGRAPHY_IS_BORINGSSL, + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + )))] + ctx: LazyEvpCipherAead, +} + +#[pyo3::pymethods] +impl AesGcm { + #[new] + fn new(py: pyo3::Python<'_>, key: pyo3::Py) -> CryptographyResult { + let key_buf = key.extract::>(py)?; + let cipher = match key_buf.as_bytes().len() { + 16 => openssl::cipher::Cipher::aes_128_gcm(), + 24 => openssl::cipher::Cipher::aes_192_gcm(), + 32 => openssl::cipher::Cipher::aes_256_gcm(), + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "AESGCM key must be 128, 192, or 256 bits.", + ), + )) + } + }; + + cfg_if::cfg_if! { + if #[cfg(any( + CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, + CRYPTOGRAPHY_IS_BORINGSSL, + CRYPTOGRAPHY_IS_LIBRESSL, + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + ))] { + Ok(AesGcm { + ctx: EvpCipherAead::new(cipher, key_buf.as_bytes(), 16, false)?, + }) + } else { + Ok(AesGcm { + ctx: LazyEvpCipherAead::new(cipher, key, 16, false, false), + }) + + } + } + } + + #[staticmethod] + fn generate_key( + py: pyo3::Python<'_>, + bit_length: usize, + ) -> CryptographyResult> { + 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> { + let nonce_bytes = nonce.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() < 8 || nonce_bytes.len() > 128 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be between 8 and 128 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> { + let nonce_bytes = nonce.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() < 8 || nonce_bytes.len() > 128 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be between 8 and 128 bytes"), + )); + } + + self.ctx + .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) + } +} + +#[pyo3::pyclass( + frozen, + module = "cryptography.hazmat.bindings._rust.openssl.aead", + name = "AESCCM" +)] +struct AesCcm { + ctx: LazyEvpCipherAead, + tag_length: usize, +} + +#[pyo3::pymethods] +impl AesCcm { + #[new] + #[pyo3(signature = (key, tag_length=None))] + fn new( + py: pyo3::Python<'_>, + key: pyo3::Py, + tag_length: Option, + ) -> CryptographyResult { + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] { + let _ = py; + let _ = key; + let _ = tag_length; + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "AES-CCM is not supported by this version of OpenSSL", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )) + } else { + let key_buf = key.extract::>(py)?; + let cipher = match key_buf.as_bytes().len() { + 16 => openssl::cipher::Cipher::aes_128_ccm(), + 24 => openssl::cipher::Cipher::aes_192_ccm(), + 32 => openssl::cipher::Cipher::aes_256_ccm(), + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "AESCCM key must be 128, 192, or 256 bits.", + ), + )) + } + }; + let tag_length = tag_length.unwrap_or(16); + if ![4, 6, 8, 10, 12, 14, 16].contains(&tag_length) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid tag_length"), + )); + } + + Ok(AesCcm { + ctx: LazyEvpCipherAead::new(cipher, key, tag_length, false, true), + tag_length + }) + } + } + } + + #[staticmethod] + fn generate_key( + py: pyo3::Python<'_>, + bit_length: usize, + ) -> CryptographyResult> { + 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> { + let nonce_bytes = nonce.as_bytes(); + let data_bytes = data.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() < 7 || nonce_bytes.len() > 13 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be between 7 and 13 bytes"), + )); + } + + check_length(data_bytes)?; + // For information about computing this, see + // https://tools.ietf.org/html/rfc3610#section-2.1 + let l_val = 15 - nonce_bytes.len(); + let max_length = 1usize.checked_shl(8 * l_val as u32); + // If `max_length` overflowed, then it's not possible for data to be + // longer than it. + if max_length.map(|v| v < data_bytes.len()).unwrap_or(false) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Data too long for nonce"), + )); + } + + 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> { + let nonce_bytes = nonce.as_bytes(); + let data_bytes = data.as_bytes(); + let aad = associated_data.map(Aad::Single); + + if nonce_bytes.len() < 7 || nonce_bytes.len() > 13 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Nonce must be between 7 and 13 bytes"), + )); + } + // For information about computing this, see + // https://tools.ietf.org/html/rfc3610#section-2.1 + let l_val = 15 - nonce_bytes.len(); + let max_length = 1usize.checked_shl(8 * l_val as u32); + // If `max_length` overflowed, then it's not possible for data to be + // longer than it. + let pt_length = data_bytes.len().saturating_sub(self.tag_length); + if max_length.map(|v| v < pt_length).unwrap_or(false) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Data too long for nonce"), + )); + } + + self.ctx.decrypt(py, data_bytes, aad, Some(nonce_bytes)) + } +} + +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESSIV" @@ -541,7 +846,7 @@ struct AesSiv { ctx: EvpCipherAead, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl AesSiv { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { @@ -574,19 +879,23 @@ impl AesSiv { ctx: EvpCipherAead::new(&cipher, key.as_bytes(), 16, true)?, }) } else { - return Err(CryptographyError::from( + _ = cipher_name; + + 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> { + fn generate_key( + py: pyo3::Python<'_>, + bit_length: usize, + ) -> CryptographyResult> { 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"), @@ -601,8 +910,8 @@ impl AesSiv { &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, - associated_data: Option<&pyo3::types::PyList>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + associated_data: Option>, + ) -> CryptographyResult> { let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::List); @@ -619,14 +928,14 @@ impl AesSiv { &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, - associated_data: Option<&pyo3::types::PyList>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + associated_data: Option>, + ) -> CryptographyResult> { let aad = associated_data.map(Aad::List); self.ctx.decrypt(py, data.as_bytes(), aad, None) } } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESOCB3" @@ -635,18 +944,20 @@ struct AesOcb3 { ctx: EvpCipherAead, } -#[pyo3::prelude::pymethods] +#[pyo3::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( + _ = key; + + 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( @@ -678,7 +989,10 @@ impl AesOcb3 { } #[staticmethod] - fn generate_key(py: pyo3::Python<'_>, bit_length: usize) -> CryptographyResult<&pyo3::PyAny> { + fn generate_key( + py: pyo3::Python<'_>, + bit_length: usize, + ) -> CryptographyResult> { 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"), @@ -695,7 +1009,7 @@ impl AesOcb3 { nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); @@ -716,7 +1030,7 @@ impl AesOcb3 { nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); @@ -731,7 +1045,7 @@ impl AesOcb3 { } } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESGCMSIV" @@ -740,7 +1054,7 @@ struct AesGcmSiv { ctx: EvpCipherAead, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl AesGcmSiv { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { @@ -784,7 +1098,10 @@ impl AesGcmSiv { } #[staticmethod] - fn generate_key(py: pyo3::Python<'_>, bit_length: usize) -> CryptographyResult<&pyo3::PyAny> { + fn generate_key( + py: pyo3::Python<'_>, + bit_length: usize, + ) -> CryptographyResult> { 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"), @@ -801,7 +1118,7 @@ impl AesGcmSiv { nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::Single); @@ -826,7 +1143,7 @@ impl AesGcmSiv { nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() != 12 { @@ -839,13 +1156,8 @@ impl AesGcmSiv { } } -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) +#[pyo3::pymodule] +pub(crate) mod aead { + #[pymodule_export] + use super::{AesCcm, AesGcm, AesGcmSiv, AesOcb3, AesSiv, ChaCha20Poly1305}; } diff --git a/src/rust/src/backend/cipher_registry.rs b/src/rust/src/backend/cipher_registry.rs index 128f087ff498..6157010c0652 100644 --- a/src/rust/src/backend/cipher_registry.rs +++ b/src/rust/src/backend/cipher_registry.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use openssl::cipher::Cipher; +use pyo3::types::PyAnyMethods; use crate::error::CryptographyResult; use crate::types; @@ -29,8 +30,8 @@ impl RegistryKey { 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()?, + algorithm_hash: algorithm.bind(py).hash()?, + mode_hash: mode.bind(py).hash()?, }) } } @@ -56,6 +57,7 @@ impl std::hash::Hash for RegistryKey { enum RegistryCipher { Ref(&'static openssl::cipher::CipherRef), + Owned(Cipher), } impl From<&'static openssl::cipher::CipherRef> for RegistryCipher { @@ -64,6 +66,12 @@ impl From<&'static openssl::cipher::CipherRef> for RegistryCipher { } } +impl From for RegistryCipher { + fn from(c: Cipher) -> RegistryCipher { + RegistryCipher::Owned(c) + } +} + struct RegistryBuilder<'p> { py: pyo3::Python<'p>, m: HashMap, @@ -79,13 +87,18 @@ impl<'p> RegistryBuilder<'p> { fn add( &mut self, - algorithm: &pyo3::PyAny, - mode: &pyo3::PyAny, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + mode: &pyo3::Bound<'_, pyo3::PyAny>, key_size: Option, cipher: impl Into, ) -> CryptographyResult<()> { self.m.insert( - RegistryKey::new(self.py, algorithm.into(), mode.into(), key_size)?, + RegistryKey::new( + self.py, + algorithm.clone().unbind(), + mode.clone().unbind(), + key_size, + )?, cipher.into(), ); @@ -122,49 +135,185 @@ fn get_cipher_registry( let sm4 = types::SM4.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] let seed = types::SEED.get(py)?; + let arc4 = types::ARC4.get(py)?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + let chacha20 = types::CHACHA20.get(py)?; + let rc2 = types::RC2.get(py)?; let cbc = types::CBC.get(py)?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + let cfb = types::CFB.get(py)?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + let cfb8 = types::CFB8.get(py)?; + let ofb = types::OFB.get(py)?; + let ecb = types::ECB.get(py)?; + let ctr = types::CTR.get(py)?; + let gcm = types::GCM.get(py)?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + let xts = types::XTS.get(py)?; + + let none = py.None(); + let none_type = none.bind(py).get_type(); + + 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(&aes, &ofb, Some(128), Cipher::aes_128_ofb())?; + m.add(&aes, &ofb, Some(192), Cipher::aes_192_ofb())?; + m.add(&aes, &ofb, Some(256), Cipher::aes_256_ofb())?; + + m.add(&aes, &gcm, Some(128), Cipher::aes_128_gcm())?; + m.add(&aes, &gcm, Some(192), Cipher::aes_192_gcm())?; + m.add(&aes, &gcm, Some(256), Cipher::aes_256_gcm())?; + + m.add(&aes, &ctr, Some(128), Cipher::aes_128_ctr())?; + m.add(&aes, &ctr, Some(192), Cipher::aes_192_ctr())?; + m.add(&aes, &ctr, Some(256), Cipher::aes_256_ctr())?; + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + { + m.add(&aes, &cfb8, Some(128), Cipher::aes_128_cfb8())?; + m.add(&aes, &cfb8, Some(192), Cipher::aes_192_cfb8())?; + m.add(&aes, &cfb8, Some(256), Cipher::aes_256_cfb8())?; + + m.add(&aes, &cfb, Some(128), Cipher::aes_128_cfb128())?; + m.add(&aes, &cfb, Some(192), Cipher::aes_192_cfb128())?; + m.add(&aes, &cfb, Some(256), Cipher::aes_256_cfb128())?; + } - 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(&aes, &ecb, Some(128), Cipher::aes_128_ecb())?; + m.add(&aes, &ecb, Some(192), Cipher::aes_192_ecb())?; + m.add(&aes, &ecb, Some(256), Cipher::aes_256_ecb())?; - m.add(aes128, cbc, Some(128), Cipher::aes_128_cbc())?; - m.add(aes256, cbc, Some(256), Cipher::aes_256_cbc())?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + { + m.add(&aes, &xts, Some(256), Cipher::aes_128_xts())?; + m.add(&aes, &xts, Some(512), Cipher::aes_256_xts())?; + } - m.add(triple_des, cbc, Some(192), Cipher::des_ede3_cbc())?; + m.add(&aes128, &cbc, Some(128), Cipher::aes_128_cbc())?; + m.add(&aes256, &cbc, Some(256), Cipher::aes_256_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())?; + m.add(&aes128, &ofb, Some(128), Cipher::aes_128_ofb())?; + m.add(&aes256, &ofb, Some(256), Cipher::aes_256_ofb())?; - #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] - m.add(sm4, cbc, Some(128), Cipher::sm4_cbc())?; + m.add(&aes128, &gcm, Some(128), Cipher::aes_128_gcm())?; + m.add(&aes256, &gcm, Some(256), Cipher::aes_256_gcm())?; - #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] - m.add(seed, cbc, Some(128), Cipher::seed_cbc())?; + m.add(&aes128, &ctr, Some(128), Cipher::aes_128_ctr())?; + m.add(&aes256, &ctr, Some(256), Cipher::aes_256_ctr())?; - #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] - m.add(blowfish, cbc, None, Cipher::bf_cbc())?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + { + m.add(&aes128, &cfb8, Some(128), Cipher::aes_128_cfb8())?; + m.add(&aes256, &cfb8, Some(256), Cipher::aes_256_cfb8())?; - #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] - m.add(cast5, cbc, None, Cipher::cast5_cbc())?; + m.add(&aes128, &cfb, Some(128), Cipher::aes_128_cfb128())?; + m.add(&aes256, &cfb, Some(256), Cipher::aes_256_cfb128())?; + } - #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] - m.add(idea, cbc, Some(128), Cipher::idea_cbc())?; + m.add(&aes128, &ecb, Some(128), Cipher::aes_128_ecb())?; + m.add(&aes256, &ecb, Some(256), Cipher::aes_256_ecb())?; + + m.add(&triple_des, &cbc, Some(192), Cipher::des_ede3_cbc())?; + m.add(&triple_des, &ecb, Some(192), Cipher::des_ede3_ecb())?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + { + m.add(&triple_des, &cfb8, Some(192), Cipher::des_ede3_cfb8())?; + m.add(&triple_des, &cfb, Some(192), Cipher::des_ede3_cfb64())?; + m.add(&triple_des, &ofb, Some(192), Cipher::des_ede3_ofb())?; + } + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] + { + m.add(&camellia, &cbc, Some(128), Cipher::camellia128_cbc())?; + m.add(&camellia, &cbc, Some(192), Cipher::camellia192_cbc())?; + m.add(&camellia, &cbc, Some(256), Cipher::camellia256_cbc())?; + + m.add(&camellia, &ecb, Some(128), Cipher::camellia128_ecb())?; + m.add(&camellia, &ecb, Some(192), Cipher::camellia192_ecb())?; + m.add(&camellia, &ecb, Some(256), Cipher::camellia256_ecb())?; + + m.add(&camellia, &ofb, Some(128), Cipher::camellia128_ofb())?; + m.add(&camellia, &ofb, Some(192), Cipher::camellia192_ofb())?; + m.add(&camellia, &ofb, Some(256), Cipher::camellia256_ofb())?; + + m.add(&camellia, &cfb, Some(128), Cipher::camellia128_cfb128())?; + m.add(&camellia, &cfb, Some(192), Cipher::camellia192_cfb128())?; + m.add(&camellia, &cfb, Some(256), Cipher::camellia256_cfb128())?; + } + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] + { + m.add(&sm4, &cbc, Some(128), Cipher::sm4_cbc())?; + m.add(&sm4, &ctr, Some(128), Cipher::sm4_ctr())?; + m.add(&sm4, &cfb, Some(128), Cipher::sm4_cfb128())?; + m.add(&sm4, &ofb, Some(128), Cipher::sm4_ofb())?; + m.add(&sm4, &ecb, Some(128), Cipher::sm4_ecb())?; + + #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] + if let Ok(c) = Cipher::fetch(None, "sm4-gcm", None) { + m.add(&sm4, &gcm, Some(128), c)?; + } + } + + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + m.add(&chacha20, none_type.as_any(), None, Cipher::chacha20())?; + + // Don't register legacy ciphers if they're unavailable. In theory + // this shouldn't be necessary but OpenSSL 3 will return an EVP_CIPHER + // even when the cipher is unavailable. + if cfg!(not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)) + || types::LEGACY_PROVIDER_LOADED.get(py)?.is_truthy()? + { + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] + { + m.add(&blowfish, &cbc, None, Cipher::bf_cbc())?; + m.add(&blowfish, &cfb, None, Cipher::bf_cfb64())?; + m.add(&blowfish, &ofb, None, Cipher::bf_ofb())?; + m.add(&blowfish, &ecb, None, Cipher::bf_ecb())?; + } + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] + { + m.add(&seed, &cbc, Some(128), Cipher::seed_cbc())?; + m.add(&seed, &cfb, Some(128), Cipher::seed_cfb128())?; + m.add(&seed, &ofb, Some(128), Cipher::seed_ofb())?; + m.add(&seed, &ecb, Some(128), Cipher::seed_ecb())?; + } + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] + { + m.add(&cast5, &cbc, None, Cipher::cast5_cbc())?; + m.add(&cast5, &ecb, None, Cipher::cast5_ecb())?; + m.add(&cast5, &ofb, None, Cipher::cast5_ofb())?; + m.add(&cast5, &cfb, None, Cipher::cast5_cfb64())?; + } + + #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] + { + m.add(&idea, &cbc, Some(128), Cipher::idea_cbc())?; + m.add(&idea, &ecb, Some(128), Cipher::idea_ecb())?; + m.add(&idea, &ofb, Some(128), Cipher::idea_ofb())?; + m.add(&idea, &cfb, Some(128), Cipher::idea_cfb64())?; + } + + m.add(&arc4, none_type.as_any(), None, Cipher::rc4())?; + + if let Some(rc2_cbc) = Cipher::from_nid(openssl::nid::Nid::RC2_CBC) { + m.add(&rc2, &cbc, Some(128), rc2_cbc)?; + } + } Ok(m.build()) }) } -pub(crate) fn get_cipher<'a>( - py: pyo3::Python<'_>, - algorithm: &pyo3::PyAny, - mode_cls: &pyo3::PyAny, -) -> CryptographyResult> { +pub(crate) fn get_cipher<'py>( + py: pyo3::Python<'py>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + mode_cls: pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { let registry = get_cipher_registry(py)?; let key_size = algorithm @@ -174,6 +323,7 @@ pub(crate) fn get_cipher<'a>( match registry.get(&key) { Some(RegistryCipher::Ref(c)) => Ok(Some(c)), + Some(RegistryCipher::Owned(c)) => Ok(Some(c)), None => Ok(None), } } diff --git a/src/rust/src/backend/ciphers.rs b/src/rust/src/backend/ciphers.rs new file mode 100644 index 000000000000..a469d7824eda --- /dev/null +++ b/src/rust/src/backend/ciphers.rs @@ -0,0 +1,620 @@ +// 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::buf::{CffiBuf, CffiMutBuf}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use crate::types; +use pyo3::types::PyAnyMethods; +use pyo3::IntoPyObject; + +pub(crate) struct CipherContext { + ctx: openssl::cipher_ctx::CipherCtx, + py_mode: pyo3::PyObject, + py_algorithm: pyo3::PyObject, + side: openssl::symm::Mode, +} + +impl CipherContext { + pub(crate) fn new( + py: pyo3::Python<'_>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + mode: pyo3::Bound<'_, pyo3::PyAny>, + side: openssl::symm::Mode, + ) -> CryptographyResult { + let cipher = + match cipher_registry::get_cipher(py, algorithm.clone(), mode.get_type().into_any())? { + Some(c) => c, + None => { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!( + "cipher {} in {} mode is not supported ", + algorithm.getattr(pyo3::intern!(py, "name"))?, + if mode.is_truthy()? { + mode.getattr(pyo3::intern!(py, "name"))? + } else { + mode + } + ), + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )) + } + }; + + let iv_nonce = if mode.is_instance(&types::MODE_WITH_INITIALIZATION_VECTOR.get(py)?)? { + Some( + mode.getattr(pyo3::intern!(py, "initialization_vector"))? + .extract::>()?, + ) + } else if mode.is_instance(&types::MODE_WITH_TWEAK.get(py)?)? { + Some( + mode.getattr(pyo3::intern!(py, "tweak"))? + .extract::>()?, + ) + } else if mode.is_instance(&types::MODE_WITH_NONCE.get(py)?)? { + Some( + mode.getattr(pyo3::intern!(py, "nonce"))? + .extract::>()?, + ) + } else if algorithm.is_instance(&types::CHACHA20.get(py)?)? { + Some( + algorithm + .getattr(pyo3::intern!(py, "nonce"))? + .extract::>()?, + ) + } else { + None + }; + + let key = algorithm + .getattr(pyo3::intern!(py, "key"))? + .extract::>()?; + + let init_op = match side { + openssl::symm::Mode::Encrypt => openssl::cipher_ctx::CipherCtxRef::encrypt_init, + openssl::symm::Mode::Decrypt => openssl::cipher_ctx::CipherCtxRef::decrypt_init, + }; + + let mut ctx = openssl::cipher_ctx::CipherCtx::new()?; + init_op(&mut ctx, Some(cipher), None, None)?; + ctx.set_key_length(key.as_bytes().len())?; + + if let Some(iv) = iv_nonce.as_ref() { + if cipher.iv_length() != 0 && cipher.iv_length() != iv.as_bytes().len() { + ctx.set_iv_length(iv.as_bytes().len())?; + } + } + + if mode.is_instance(&types::XTS.get(py)?)? { + init_op( + &mut ctx, + None, + Some(key.as_bytes()), + iv_nonce.as_ref().map(|b| b.as_bytes()), + ) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "In XTS mode duplicated keys are not allowed", + ) + })?; + } else { + init_op( + &mut ctx, + None, + Some(key.as_bytes()), + iv_nonce.as_ref().map(|b| b.as_bytes()), + )?; + }; + + ctx.set_padding(false); + + Ok(CipherContext { + ctx, + py_mode: mode.into(), + py_algorithm: algorithm.into(), + side, + }) + } + + fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { + if !self + .py_mode + .bind(py) + .is_instance(&types::MODE_WITH_NONCE.get(py)?)? + && !self + .py_algorithm + .bind(py) + .is_instance(&types::CHACHA20.get(py)?)? + { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "This algorithm or mode does not support resetting the nonce.", + exceptions::Reasons::UNSUPPORTED_CIPHER, + )), + )); + } + if nonce.as_bytes().len() != self.ctx.iv_length() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "Nonce must be {} bytes long", + self.ctx.iv_length() + )), + )); + } + let init_op = match self.side { + openssl::symm::Mode::Encrypt => openssl::cipher_ctx::CipherCtxRef::encrypt_init, + openssl::symm::Mode::Decrypt => openssl::cipher_ctx::CipherCtxRef::decrypt_init, + }; + init_op(&mut self.ctx, None, None, Some(nonce.as_bytes()))?; + Ok(()) + } + + fn update<'p>( + &mut self, + py: pyo3::Python<'p>, + data: &[u8], + ) -> CryptographyResult> { + let mut buf = vec![0; data.len() + self.ctx.block_size()]; + let n = self.update_into(py, data, &mut buf)?; + Ok(pyo3::types::PyBytes::new(py, &buf[..n])) + } + + pub(crate) fn update_into( + &mut self, + py: pyo3::Python<'_>, + data: &[u8], + buf: &mut [u8], + ) -> CryptographyResult { + if buf.len() < (data.len() + self.ctx.block_size() - 1) { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "buffer must be at least {} bytes for this payload", + data.len() + self.ctx.block_size() - 1 + )), + )); + } + + let mut total_written = 0; + for chunk in data.chunks(1 << 29) { + // SAFETY: We ensure that outbuf is sufficiently large above. + unsafe { + let n = if self.py_mode.bind(py).is_instance(&types::XTS.get(py)?)? { + self.ctx.cipher_update_unchecked(chunk, Some(&mut buf[total_written..])).map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "In XTS mode you must supply at least a full block in the first update call. For AES this is 16 bytes." + ) + })? + } else { + self.ctx + .cipher_update_unchecked(chunk, Some(&mut buf[total_written..]))? + }; + total_written += n; + } + } + + Ok(total_written) + } + + fn authenticate_additional_data(&mut self, data: &[u8]) -> CryptographyResult<()> { + self.ctx.cipher_update(data, None)?; + Ok(()) + } + + pub(crate) fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + let mut out_buf = vec![0; self.ctx.block_size()]; + let n = self.ctx.cipher_final(&mut out_buf).or_else(|e| { + if e.errors().is_empty() + && self + .py_mode + .bind(py) + .is_instance(&types::MODE_WITH_AUTHENTICATION_TAG.get(py)?)? + { + return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); + } + Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The length of the provided data is not a multiple of the block length.", + ), + )) + })?; + Ok(pyo3::types::PyBytes::new(py, &out_buf[..n])) + } +} + +#[pyo3::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.ciphers", + name = "CipherContext" +)] +struct PyCipherContext { + ctx: Option, +} + +#[pyo3::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.ciphers", + name = "AEADEncryptionContext" +)] +struct PyAEADEncryptionContext { + ctx: Option, + tag: Option>, + updated: bool, + bytes_remaining: u64, + aad_bytes_remaining: u64, +} + +#[pyo3::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.ciphers", + name = "AEADDecryptionContext" +)] +struct PyAEADDecryptionContext { + ctx: Option, + updated: bool, + bytes_remaining: u64, + aad_bytes_remaining: u64, +} + +fn get_mut_ctx(ctx: Option<&mut CipherContext>) -> CryptographyResult<&mut CipherContext> { + ctx.ok_or_else(exceptions::already_finalized_error) +} + +#[pyo3::pymethods] +impl PyCipherContext { + fn update<'p>( + &mut self, + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + ) -> CryptographyResult> { + get_mut_ctx(self.ctx.as_mut())?.update(py, data.as_bytes()) + } + + fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { + get_mut_ctx(self.ctx.as_mut())?.reset_nonce(py, nonce) + } + + fn update_into( + &mut self, + py: pyo3::Python<'_>, + data: CffiBuf<'_>, + mut buf: CffiMutBuf<'_>, + ) -> CryptographyResult { + get_mut_ctx(self.ctx.as_mut())?.update_into(py, data.as_bytes(), buf.as_mut_bytes()) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + let result = get_mut_ctx(self.ctx.as_mut())?.finalize(py)?; + self.ctx = None; + Ok(result) + } +} + +#[pyo3::pymethods] +impl PyAEADEncryptionContext { + fn update<'p>( + &mut self, + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + ) -> CryptographyResult> { + let data = data.as_bytes(); + + self.updated = true; + self.bytes_remaining = self + .bytes_remaining + .checked_sub(data.len().try_into().unwrap()) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") + })?; + get_mut_ctx(self.ctx.as_mut())?.update(py, data) + } + + fn update_into( + &mut self, + py: pyo3::Python<'_>, + data: CffiBuf<'_>, + mut buf: CffiMutBuf<'_>, + ) -> CryptographyResult { + let data = data.as_bytes(); + + self.updated = true; + self.bytes_remaining = self + .bytes_remaining + .checked_sub(data.len().try_into().unwrap()) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") + })?; + get_mut_ctx(self.ctx.as_mut())?.update_into(py, data, buf.as_mut_bytes()) + } + + fn authenticate_additional_data(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + let ctx = get_mut_ctx(self.ctx.as_mut())?; + if self.updated { + return Err(CryptographyError::from( + exceptions::AlreadyUpdated::new_err("Update has been called on this context."), + )); + } + + let data = data.as_bytes(); + self.aad_bytes_remaining = self + .aad_bytes_remaining + .checked_sub(data.len().try_into().unwrap()) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Exceeded maximum AAD byte limit") + })?; + ctx.authenticate_additional_data(data) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + let ctx = get_mut_ctx(self.ctx.as_mut())?; + let result = ctx.finalize(py)?; + + // XXX: do not hard code 16 + let tag = pyo3::types::PyBytes::new_with(py, 16, |t| { + ctx.ctx.tag(t).map_err(CryptographyError::from)?; + Ok(()) + })?; + self.tag = Some(tag.unbind()); + self.ctx = None; + + Ok(result) + } + + #[getter] + fn tag(&self, py: pyo3::Python<'_>) -> CryptographyResult> { + Ok(self + .tag + .as_ref() + .ok_or_else(|| { + exceptions::NotYetFinalized::new_err( + "You must finalize encryption before getting the tag.", + ) + })? + .clone_ref(py)) + } + + fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { + get_mut_ctx(self.ctx.as_mut())?.reset_nonce(py, nonce) + } +} + +#[pyo3::pymethods] +impl PyAEADDecryptionContext { + fn update<'p>( + &mut self, + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + ) -> CryptographyResult> { + let data = data.as_bytes(); + + self.updated = true; + self.bytes_remaining = self + .bytes_remaining + .checked_sub(data.len().try_into().unwrap()) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") + })?; + get_mut_ctx(self.ctx.as_mut())?.update(py, data) + } + + fn update_into( + &mut self, + py: pyo3::Python<'_>, + data: CffiBuf<'_>, + mut buf: CffiMutBuf<'_>, + ) -> CryptographyResult { + let data = data.as_bytes(); + + self.updated = true; + self.bytes_remaining = self + .bytes_remaining + .checked_sub(data.len().try_into().unwrap()) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") + })?; + get_mut_ctx(self.ctx.as_mut())?.update_into(py, data, buf.as_mut_bytes()) + } + + fn authenticate_additional_data(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { + let ctx = get_mut_ctx(self.ctx.as_mut())?; + if self.updated { + return Err(CryptographyError::from( + exceptions::AlreadyUpdated::new_err("Update has been called on this context."), + )); + } + + let data = data.as_bytes(); + self.aad_bytes_remaining = self + .aad_bytes_remaining + .checked_sub(data.len().try_into().unwrap()) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err("Exceeded maximum AAD byte limit") + })?; + ctx.authenticate_additional_data(data) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + let ctx = get_mut_ctx(self.ctx.as_mut())?; + + if ctx + .py_mode + .bind(py) + .getattr(pyo3::intern!(py, "tag"))? + .is_none() + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Authentication tag must be provided when decrypting.", + ), + )); + } + + let result = ctx.finalize(py)?; + self.ctx = None; + Ok(result) + } + + fn finalize_with_tag<'p>( + &mut self, + py: pyo3::Python<'p>, + tag: &[u8], + ) -> CryptographyResult> { + let ctx = get_mut_ctx(self.ctx.as_mut())?; + + if !ctx + .py_mode + .bind(py) + .getattr(pyo3::intern!(py, "tag"))? + .is_none() + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Authentication tag must be provided only once.", + ), + )); + } + + let min_tag_length = ctx + .py_mode + .bind(py) + .getattr(pyo3::intern!(py, "_min_tag_length"))? + .extract()?; + // XXX: Do not hard code 16 + if tag.len() < min_tag_length { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "Authentication tag must be {} bytes or longer.", + min_tag_length + )), + )); + } else if tag.len() > 16 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "Authentication tag cannot be more than {} bytes.", + 16 + )), + )); + } + + ctx.ctx.set_tag(tag)?; + let result = ctx.finalize(py)?; + self.ctx = None; + Ok(result) + } + + fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { + get_mut_ctx(self.ctx.as_mut())?.reset_nonce(py, nonce) + } +} + +#[pyo3::pyfunction] +fn create_encryption_ctx<'p>( + py: pyo3::Python<'p>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + mode: pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { + let ctx = CipherContext::new(py, algorithm, mode.clone(), openssl::symm::Mode::Encrypt)?; + + if mode.is_instance(&types::MODE_WITH_AUTHENTICATION_TAG.get(py)?)? { + Ok(PyAEADEncryptionContext { + ctx: Some(ctx), + tag: None, + updated: false, + bytes_remaining: mode + .getattr(pyo3::intern!(py, "_MAX_ENCRYPTED_BYTES"))? + .extract()?, + aad_bytes_remaining: mode + .getattr(pyo3::intern!(py, "_MAX_AAD_BYTES"))? + .extract()?, + } + .into_pyobject(py)? + .into_any()) + } else { + Ok(PyCipherContext { ctx: Some(ctx) } + .into_pyobject(py)? + .into_any()) + } +} + +#[pyo3::pyfunction] +fn create_decryption_ctx<'p>( + py: pyo3::Python<'p>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + mode: pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { + let mut ctx = CipherContext::new(py, algorithm, mode.clone(), openssl::symm::Mode::Decrypt)?; + + if mode.is_instance(&types::MODE_WITH_AUTHENTICATION_TAG.get(py)?)? { + if let Some(tag) = mode + .getattr(pyo3::intern!(py, "tag"))? + .extract::>()? + { + ctx.ctx.set_tag(&tag)?; + } + + Ok(PyAEADDecryptionContext { + ctx: Some(ctx), + updated: false, + bytes_remaining: mode + .getattr(pyo3::intern!(py, "_MAX_ENCRYPTED_BYTES"))? + .extract()?, + aad_bytes_remaining: mode + .getattr(pyo3::intern!(py, "_MAX_AAD_BYTES"))? + .extract()?, + } + .into_pyobject(py)? + .into_any()) + } else { + Ok(PyCipherContext { ctx: Some(ctx) } + .into_pyobject(py)? + .into_any()) + } +} + +#[pyo3::pyfunction] +fn cipher_supported( + py: pyo3::Python<'_>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + mode: pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult { + Ok(cipher_registry::get_cipher(py, algorithm, mode.get_type().into_any())?.is_some()) +} + +#[pyo3::pyfunction] +fn _advance(ctx: pyo3::Bound<'_, pyo3::PyAny>, n: u64) { + if let Ok(c) = ctx.downcast::() { + c.borrow_mut().bytes_remaining -= n; + } else if let Ok(c) = ctx.downcast::() { + c.borrow_mut().bytes_remaining -= n; + } +} + +#[pyo3::pyfunction] +fn _advance_aad(ctx: pyo3::Bound<'_, pyo3::PyAny>, n: u64) { + if let Ok(c) = ctx.downcast::() { + c.borrow_mut().aad_bytes_remaining -= n; + } else if let Ok(c) = ctx.downcast::() { + c.borrow_mut().aad_bytes_remaining -= n; + } +} + +#[pyo3::pymodule] +pub(crate) mod ciphers { + #[pymodule_export] + use super::{ + _advance, _advance_aad, cipher_supported, create_decryption_ctx, create_encryption_ctx, + PyAEADDecryptionContext, PyAEADEncryptionContext, PyCipherContext, + }; +} diff --git a/src/rust/src/backend/cmac.rs b/src/rust/src/backend/cmac.rs index acacbf02f6ad..7519c1b88603 100644 --- a/src/rust/src/backend/cmac.rs +++ b/src/rust/src/backend/cmac.rs @@ -3,12 +3,12 @@ // 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}; +use pyo3::types::{PyAnyMethods, PyBytesMethods}; -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.cmac", name = "CMAC" )] @@ -21,28 +21,29 @@ impl Cmac { if let Some(ctx) = self.ctx.as_ref() { return Ok(ctx); }; - Err(already_finalized_error()) + Err(exceptions::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()) + Err(exceptions::already_finalized_error()) } } #[pyo3::pymethods] impl Cmac { #[new] + #[pyo3(signature = (algorithm, backend=None))] fn new( py: pyo3::Python<'_>, - algorithm: &pyo3::PyAny, - backend: Option<&pyo3::PyAny>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; - if !algorithm.is_instance(types::BLOCK_CIPHER_ALGORITHM.get(py)?)? { + if !algorithm.is_instance(&types::BLOCK_CIPHER_ALGORITHM.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Expected instance of BlockCipherAlgorithm.", @@ -50,8 +51,8 @@ impl Cmac { )); } - let cipher = - cipher_registry::get_cipher(py, algorithm, types::CBC.get(py)?)?.ok_or_else(|| { + let cipher = cipher_registry::get_cipher(py, algorithm.clone(), types::CBC.get(py)?)? + .ok_or_else(|| { exceptions::UnsupportedAlgorithm::new_err(( "CMAC is not supported with this algorithm", exceptions::Reasons::UNSUPPORTED_CIPHER, @@ -73,14 +74,15 @@ impl Cmac { fn finalize<'p>( &mut self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { 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(); + let actual = self.finalize(py)?; + let actual = actual.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."), @@ -97,10 +99,8 @@ impl Cmac { } } -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) +#[pyo3::pymodule] +pub(crate) mod cmac { + #[pymodule_export] + use super::Cmac; } diff --git a/src/rust/src/backend/dh.rs b/src/rust/src/backend/dh.rs index 5ec1804e0df8..a19ab6342e90 100644 --- a/src/rust/src/backend/dh.rs +++ b/src/rust/src/backend/dh.rs @@ -8,29 +8,31 @@ use crate::asn1::encode_der_data; use crate::backend::utils; use crate::error::{CryptographyError, CryptographyResult}; use crate::{types, x509}; +use pyo3::types::PyAnyMethods; const MIN_MODULUS_SIZE: u32 = 512; -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] pub(crate) struct DHPrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] pub(crate) struct DHPublicKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] struct DHParameters { dh: openssl::dh::Dh, } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] +#[pyo3(signature = (generator, key_size, backend=None))] fn generate_parameters( generator: u32, key_size: u32, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -68,27 +70,11 @@ pub(crate) fn public_key_from_pkey( } } -#[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] +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] fn from_der_parameters( data: &[u8], - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; let asn1_params = asn1::parse_single::>(data)?; @@ -105,10 +91,11 @@ fn from_der_parameters( }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] fn from_pem_parameters( data: &[u8], - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; let parsed = x509::find_in_pem( @@ -124,13 +111,13 @@ fn dh_parameters_from_numbers( py: pyo3::Python<'_>, numbers: &DHParameterNumbers, ) -> CryptographyResult> { - let p = utils::py_int_to_bn(py, numbers.p.as_ref(py))?; + let p = utils::py_int_to_bn(py, numbers.p.bind(py))?; let q = numbers .q .as_ref() - .map(|v| utils::py_int_to_bn(py, v.as_ref(py))) + .map(|v| utils::py_int_to_bn(py, v.bind(py))) .transpose()?; - let g = utils::py_int_to_bn(py, numbers.g.as_ref(py))?; + let g = utils::py_int_to_bn(py, numbers.g.bind(py))?; Ok(openssl::dh::Dh::from_pqg(p, q, g)?) } @@ -144,7 +131,7 @@ fn clone_dh( Ok(openssl::dh::Dh::from_pqg(p, q, g)?) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DHPrivateKey { #[getter] fn key_size(&self) -> i32 { @@ -154,14 +141,15 @@ impl DHPrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, - public_key: &DHPublicKey, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + peer_public_key: &DHPublicKey, + ) -> CryptographyResult> { 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| { + let len = deriver.len()?; + Ok(pyo3::types::PyBytes::new_with(py, len, |b| { let n = deriver.derive(b).unwrap(); let pad = b.len() - n; @@ -209,7 +197,8 @@ impl DHPrivateKey { let orig_dh = self.pkey.dh().unwrap(); let dh = clone_dh(&orig_dh)?; - let pkey = pkey_from_dh(dh.set_public_key(orig_dh.public_key().to_owned()?)?)?; + let pkey = + openssl::pkey::PKey::from_dh(dh.set_public_key(orig_dh.public_key().to_owned()?)?)?; Ok(DHPublicKey { pkey }) } @@ -221,13 +210,13 @@ impl DHPrivateKey { } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !format.is(types::PRIVATE_FORMAT_PKCS8.get(py)?) { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { + 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", @@ -248,7 +237,7 @@ impl DHPrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DHPublicKey { #[getter] fn key_size(&self) -> i32 { @@ -256,12 +245,12 @@ impl DHPublicKey { } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !format.is(types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { + 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", @@ -311,13 +300,13 @@ impl DHPublicKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::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: pkey_from_dh(dh)?, + pkey: openssl::pkey::PKey::from_dh(dh)?, }) } @@ -340,10 +329,10 @@ impl DHParameters { fn parameter_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &'p pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !format.is(types::PARAMETER_FORMAT_PKCS3.get(py)?) { + encoding: pyo3::Bound<'p, pyo3::PyAny>, + format: pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { + if !format.is(&types::PARAMETER_FORMAT_PKCS3.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Only PKCS3 serialization is supported"), )); @@ -367,58 +356,59 @@ impl DHParameters { } else { "X9.42 DH PARAMETERS" }; - encode_der_data(py, tag.to_string(), data, encoding) + encode_der_data(py, tag.to_string(), data, &encoding) } } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] struct DHPrivateNumbers { #[pyo3(get)] - x: pyo3::Py, + x: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] struct DHPublicNumbers { #[pyo3(get)] - y: pyo3::Py, + y: pyo3::Py, #[pyo3(get)] parameter_numbers: pyo3::Py, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] struct DHParameterNumbers { #[pyo3(get)] - p: pyo3::Py, + p: pyo3::Py, #[pyo3(get)] - g: pyo3::Py, + g: pyo3::Py, #[pyo3(get)] - q: Option>, + q: Option>, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DHPrivateNumbers { #[new] fn new( - x: pyo3::Py, + x: pyo3::Py, public_numbers: pyo3::Py, ) -> DHPrivateNumbers { DHPrivateNumbers { x, public_numbers } } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[pyo3(signature = (backend=None))] fn private_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> 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 pub_key = utils::py_int_to_bn(py, self.public_numbers.get().y.bind(py))?; + let priv_key = utils::py_int_to_bn(py, self.x.bind(py))?; let dh = dh.set_key(pub_key, priv_key)?; if !dh.check_key()? { @@ -429,7 +419,7 @@ impl DHPrivateNumbers { )); } - let pkey = pkey_from_dh(dh)?; + let pkey = openssl::pkey::PKey::from_dh(dh)?; Ok(DHPrivateKey { pkey }) } @@ -438,19 +428,19 @@ impl DHPrivateNumbers { py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { - Ok(self.x.as_ref(py).eq(other.x.as_ref(py))? + Ok((**self.x.bind(py)).eq(other.x.bind(py))? && self .public_numbers - .as_ref(py) - .eq(other.public_numbers.as_ref(py))?) + .bind(py) + .eq(other.public_numbers.bind(py))?) } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DHPublicNumbers { #[new] fn new( - y: pyo3::Py, + y: pyo3::Py, parameter_numbers: pyo3::Py, ) -> DHPublicNumbers { DHPublicNumbers { @@ -460,18 +450,19 @@ impl DHPublicNumbers { } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> 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 pub_key = utils::py_int_to_bn(py, self.y.bind(py))?; - let pkey = pkey_from_dh(dh.set_public_key(pub_key)?)?; + let pkey = openssl::pkey::PKey::from_dh(dh.set_public_key(pub_key)?)?; Ok(DHPublicKey { pkey }) } @@ -481,30 +472,31 @@ impl DHPublicNumbers { py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { - Ok(self.y.as_ref(py).eq(other.y.as_ref(py))? + Ok((**self.y.bind(py)).eq(other.y.bind(py))? && self .parameter_numbers - .as_ref(py) - .eq(other.parameter_numbers.as_ref(py))?) + .bind(py) + .eq(other.parameter_numbers.bind(py))?) } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DHParameterNumbers { #[new] + #[pyo3(signature = (p, g, q=None))] fn new( py: pyo3::Python<'_>, - p: pyo3::Py, - g: pyo3::Py, - q: Option>, + p: pyo3::Py, + g: pyo3::Py, + q: Option>, ) -> CryptographyResult { - if g.as_ref(py).lt(2)? { + if g.bind(py).lt(2)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("DH generator must be 2 or greater"), )); } - if p.as_ref(py) + if p.bind(py) .call_method0("bit_length")? .lt(MIN_MODULUS_SIZE)? { @@ -518,10 +510,11 @@ impl DHParameterNumbers { Ok(DHParameterNumbers { p, g, q }) } + #[pyo3(signature = (backend=None))] fn parameters( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -535,28 +528,21 @@ impl DHParameterNumbers { 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))?, + (Some(self_q), Some(other_q)) => (**self_q.bind(py)).eq(other_q.bind(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))? + Ok((**self.p.bind(py)).eq(other.p.bind(py))? + && (**self.g.bind(py)).eq(other.g.bind(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!(from_der_parameters, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_pem_parameters, m)?)?; - - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod dh { + #[pymodule_export] + use super::{ + from_der_parameters, from_pem_parameters, generate_parameters, DHParameterNumbers, + DHParameters, DHPrivateKey, DHPrivateNumbers, DHPublicKey, DHPublicNumbers, + }; } diff --git a/src/rust/src/backend/dsa.rs b/src/rust/src/backend/dsa.rs index cf0824613fdb..86ddac9c88d0 100644 --- a/src/rust/src/backend/dsa.rs +++ b/src/rust/src/backend/dsa.rs @@ -3,10 +3,12 @@ // for complete details. use crate::backend::utils; +use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; -use crate::exceptions; +use crate::{error, exceptions}; +use pyo3::types::PyAnyMethods; -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAPrivateKey" @@ -15,7 +17,7 @@ pub(crate) struct DsaPrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAPublicKey" @@ -24,7 +26,7 @@ pub(crate) struct DsaPublicKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAParameters" @@ -49,7 +51,7 @@ pub(crate) fn public_key_from_pkey( } } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn generate_parameters(key_size: u32) -> CryptographyResult { let dsa = openssl::dsa::Dsa::generate_params(key_size)?; Ok(DsaParameters { dsa }) @@ -61,20 +63,25 @@ fn clone_dsa_params( openssl::dsa::Dsa::from_pqg(d.p().to_owned()?, d.q().to_owned()?, d.g().to_owned()?) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DsaPrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &[u8], - algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let (data, _) = utils::calculate_digest_and_algorithm(py, data, algorithm)?; + data: CffiBuf<'_>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + ) -> CryptographyResult> { + 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()?; let mut sig = vec![]; - signer.sign_to_vec(data, &mut sig)?; + signer.sign_to_vec(data.as_bytes(), &mut sig).map_err(|e| { + pyo3::exceptions::PyValueError::new_err(( + "DSA signing failed. This generally indicates an invalid key.", + error::list_from_openssl_error(py, &e).unbind(), + )) + })?; Ok(pyo3::types::PyBytes::new(py, &sig)) } @@ -127,12 +134,12 @@ impl DsaPrivateKey { } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, @@ -146,20 +153,22 @@ impl DsaPrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DsaPublicKey { fn verify( &self, py: pyo3::Python<'_>, - signature: &[u8], - data: &[u8], - algorithm: &pyo3::PyAny, + signature: CffiBuf<'_>, + data: CffiBuf<'_>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { - let (data, _) = utils::calculate_digest_and_algorithm(py, data, algorithm)?; + 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.as_bytes(), signature.as_bytes()) + .unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), @@ -200,11 +209,11 @@ impl DsaPublicKey { } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } @@ -217,7 +226,7 @@ impl DsaPublicKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DsaParameters { fn generate_private_key(&self) -> CryptographyResult { let dsa = clone_dsa_params(&self.dsa)?.generate_key()?; @@ -245,7 +254,7 @@ fn check_dsa_parameters( if ![1024, 2048, 3072, 4096].contains( ¶meters .p - .as_ref(py) + .bind(py) .call_method0("bit_length")? .extract::()?, ) { @@ -259,7 +268,7 @@ fn check_dsa_parameters( if ![160, 224, 256].contains( ¶meters .q - .as_ref(py) + .bind(py) .call_method0("bit_length")? .extract::()?, ) { @@ -268,7 +277,7 @@ fn check_dsa_parameters( )); } - if parameters.g.as_ref(py).le(1)? || parameters.g.as_ref(py).ge(parameters.p.as_ref(py))? { + if parameters.g.bind(py).le(1)? || parameters.g.bind(py).ge(parameters.p.bind(py))? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("g, p don't satisfy 1 < g < p."), )); @@ -284,21 +293,16 @@ fn check_dsa_private_numbers( 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))? { + if numbers.x.bind(py).le(0)? || numbers.x.bind(py).ge(params.q.bind(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)), - )?)? + if (**numbers.public_numbers.get().y.bind(py)).ne(params + .g + .bind(py) + .pow(numbers.x.bind(py), Some(params.p.bind(py)))?)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("y must be equal to (g ** x % p)."), @@ -308,58 +312,59 @@ fn check_dsa_private_numbers( Ok(()) } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.dsa", name = "DSAPrivateNumbers" )] struct DsaPrivateNumbers { #[pyo3(get)] - x: pyo3::Py, + x: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.dsa", name = "DSAPublicNumbers" )] struct DsaPublicNumbers { #[pyo3(get)] - y: pyo3::Py, + y: pyo3::Py, #[pyo3(get)] parameter_numbers: pyo3::Py, } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.dsa", name = "DSAParameterNumbers" )] struct DsaParameterNumbers { #[pyo3(get)] - p: pyo3::Py, + p: pyo3::Py, #[pyo3(get)] - q: pyo3::Py, + q: pyo3::Py, #[pyo3(get)] - g: pyo3::Py, + g: pyo3::Py, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DsaPrivateNumbers { #[new] fn new( - x: pyo3::Py, + x: pyo3::Py, public_numbers: pyo3::Py, ) -> DsaPrivateNumbers { DsaPrivateNumbers { x, public_numbers } } + #[pyo3(signature = (backend=None))] fn private_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -369,11 +374,11 @@ impl DsaPrivateNumbers { 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))?, + utils::py_int_to_bn(py, parameter_numbers.p.bind(py))?, + utils::py_int_to_bn(py, parameter_numbers.q.bind(py))?, + utils::py_int_to_bn(py, parameter_numbers.g.bind(py))?, + utils::py_int_to_bn(py, self.x.bind(py))?, + utils::py_int_to_bn(py, public_numbers.y.bind(py))?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_dsa(dsa)?; @@ -385,19 +390,19 @@ impl DsaPrivateNumbers { py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { - Ok(self.x.as_ref(py).eq(other.x.as_ref(py))? + Ok((**self.x.bind(py)).eq(other.x.bind(py))? && self .public_numbers - .as_ref(py) - .eq(other.public_numbers.as_ref(py))?) + .bind(py) + .eq(other.public_numbers.bind(py))?) } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DsaPublicNumbers { #[new] fn new( - y: pyo3::Py, + y: pyo3::Py, parameter_numbers: pyo3::Py, ) -> DsaPublicNumbers { DsaPublicNumbers { @@ -406,10 +411,11 @@ impl DsaPublicNumbers { } } + #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -418,10 +424,10 @@ impl DsaPublicNumbers { 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))?, + utils::py_int_to_bn(py, parameter_numbers.p.bind(py))?, + utils::py_int_to_bn(py, parameter_numbers.q.bind(py))?, + utils::py_int_to_bn(py, parameter_numbers.g.bind(py))?, + utils::py_int_to_bn(py, self.y.bind(py))?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_dsa(dsa)?; @@ -433,46 +439,47 @@ impl DsaPublicNumbers { py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { - Ok(self.y.as_ref(py).eq(other.y.as_ref(py))? + Ok((**self.y.bind(py)).eq(other.y.bind(py))? && self .parameter_numbers - .as_ref(py) - .eq(other.parameter_numbers.as_ref(py))?) + .bind(py) + .eq(other.parameter_numbers.bind(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()?; + let y = self.y.bind(py); + let parameter_numbers = self.parameter_numbers.bind(py).repr()?; Ok(format!( "" )) } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl DsaParameterNumbers { #[new] fn new( - p: pyo3::Py, - q: pyo3::Py, - g: pyo3::Py, + p: pyo3::Py, + q: pyo3::Py, + g: pyo3::Py, ) -> DsaParameterNumbers { DsaParameterNumbers { p, q, g } } + #[pyo3(signature = (backend=None))] fn parameters( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> 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))?, + utils::py_int_to_bn(py, self.p.bind(py))?, + utils::py_int_to_bn(py, self.q.bind(py))?, + utils::py_int_to_bn(py, self.g.bind(py))?, ) .unwrap(); Ok(DsaParameters { dsa }) @@ -483,29 +490,24 @@ impl DsaParameterNumbers { 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((**self.p.bind(py)).eq(other.p.bind(py))? + && (**self.q.bind(py)).eq(other.q.bind(py))? + && (**self.g.bind(py)).eq(other.g.bind(py))?) } 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); + let p = self.p.bind(py); + let q = self.q.bind(py); + let g = self.g.bind(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!(generate_parameters, m)?)?; - - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod dsa { + #[pymodule_export] + use super::{ + generate_parameters, DsaParameterNumbers, DsaParameters, DsaPrivateKey, DsaPrivateNumbers, + DsaPublicKey, DsaPublicNumbers, + }; } diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs index e221b025cbb9..37bfc9123dbd 100644 --- a/src/rust/src/backend/ec.rs +++ b/src/rust/src/backend/ec.rs @@ -5,20 +5,22 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -use pyo3::ToPyObject; +use pyo3::types::{PyAnyMethods, PyDictMethods}; use crate::backend::utils; +use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::common::cstr_from_literal; use crate::{exceptions, types}; -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] +#[pyo3::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")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] pub(crate) struct ECPublicKey { pkey: openssl::pkey::PKey, #[pyo3(get)] @@ -27,14 +29,14 @@ pub(crate) struct ECPublicKey { fn curve_from_py_curve( py: pyo3::Python<'_>, - py_curve: &pyo3::PyAny, + py_curve: pyo3::Bound<'_, pyo3::PyAny>, allow_curve_class: bool, ) -> CryptographyResult { - if !py_curve.is_instance(types::ELLIPTIC_CURVE.get(py)?)? { + 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)?; + let message = cstr_from_literal!("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, message, 1)?; } else { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err("curve must be an EllipticCurve instance"), @@ -42,8 +44,8 @@ fn curve_from_py_curve( } } - let curve_name = py_curve.getattr(pyo3::intern!(py, "name"))?.extract()?; - let nid = match curve_name { + let py_curve_name = py_curve.getattr(pyo3::intern!(py, "name"))?; + let nid = match &*py_curve_name.extract::()? { "secp192r1" => openssl::nid::Nid::X9_62_PRIME192V1, "secp224r1" => openssl::nid::Nid::SECP224R1, "secp256r1" => openssl::nid::Nid::X9_62_PRIME256V1, @@ -72,7 +74,7 @@ fn curve_from_py_curve( #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] "brainpoolP512r1" => openssl::nid::Nid::BRAINPOOL_P512R1, - _ => { + curve_name => { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!("Curve {curve_name} is not supported"), @@ -88,16 +90,7 @@ fn curve_from_py_curve( 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()?; - +) -> CryptographyResult> { if curve.asn1_flag() == openssl::ec::Asn1Flag::EXPLICIT_CURVE { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -106,9 +99,11 @@ fn py_curve_from_curve<'p>( )); } + let name = curve.curve_name().unwrap().short_name()?; + types::CURVE_TYPES .get(py)? - .extract::<&pyo3::types::PyDict>()? + .extract::>()? .get_item(name)? .ok_or_else(|| { CryptographyError::from(exceptions::UnsupportedAlgorithm::new_err(( @@ -131,8 +126,8 @@ fn check_key_infinity( Ok(()) } -#[pyo3::prelude::pyfunction] -fn curve_supported(py: pyo3::Python<'_>, py_curve: &pyo3::PyAny) -> bool { +#[pyo3::pyfunction] +fn curve_supported(py: pyo3::Python<'_>, py_curve: pyo3::Bound<'_, pyo3::PyAny>) -> bool { curve_from_py_curve(py, py_curve, false).is_ok() } @@ -160,11 +155,12 @@ pub(crate) fn public_key_from_pkey( curve: curve.into(), }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] +#[pyo3(signature = (curve, backend=None))] fn generate_private_key( py: pyo3::Python<'_>, - curve: &pyo3::PyAny, - backend: Option<&pyo3::PyAny>, + curve: pyo3::Bound<'_, pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -177,13 +173,13 @@ fn generate_private_key( }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn derive_private_key( py: pyo3::Python<'_>, - py_private_value: &pyo3::types::PyLong, - py_curve: &pyo3::PyAny, + py_private_value: &pyo3::Bound<'_, pyo3::types::PyInt>, + py_curve: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { - let curve = curve_from_py_curve(py, py_curve, false)?; + let curve = curve_from_py_curve(py, py_curve.clone(), false)?; let private_value = utils::py_int_to_bn(py, py_private_value)?; let mut point = openssl::ec::EcPoint::new(&curve)?; @@ -191,7 +187,9 @@ fn derive_private_key( 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)?; + ec.check_key().map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Invalid EC key (key out of range, infinity, etc.)") + })?; let pkey = openssl::pkey::PKey::from_ec_key(ec)?; Ok(ECPrivateKey { @@ -200,13 +198,13 @@ fn derive_private_key( }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_public_bytes( py: pyo3::Python<'_>, - py_curve: &pyo3::PyAny, + py_curve: pyo3::Bound<'_, pyo3::PyAny>, data: &[u8], ) -> CryptographyResult { - let curve = curve_from_py_curve(py, py_curve, false)?; + let curve = curve_from_py_curve(py, py_curve.clone(), false)?; let mut bn_ctx = openssl::bn::BigNumContext::new()?; let point = openssl::ec::EcPoint::from_bytes(&curve, data, &mut bn_ctx) @@ -220,20 +218,23 @@ fn from_public_bytes( }) } -#[pyo3::prelude::pymethods] +#[pyo3::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 key_size<'p>( + &'p self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + self.curve.bind(py).getattr(pyo3::intern!(py, "key_size")) } fn exchange<'p>( &self, py: pyo3::Python<'p>, - algorithm: &pyo3::PyAny, - public_key: &ECPublicKey, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !algorithm.is_instance(types::ECDH.get(py)?)? { + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + peer_public_key: &ECPublicKey, + ) -> CryptographyResult> { + if !algorithm.is_instance(&types::ECDH.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "Unsupported EC exchange algorithm", @@ -243,20 +244,21 @@ impl ECPrivateKey { } let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; - // If `set_peer_ex` is available, we don't valid the key. This is + // If `set_peer_ex` is available, we don't validate 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(&public_key.pkey, false) + .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(&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| { + let len = deriver.len()?; + Ok(pyo3::types::PyBytes::new_with(py, len, |b| { let n = deriver.derive(b).map_err(|_| { pyo3::exceptions::PyValueError::new_err("Error computing shared key.") })?; @@ -268,10 +270,10 @@ impl ECPrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &[u8], - algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !algorithm.is_instance(types::ECDSA.get(py)?)? { + data: CffiBuf<'_>, + signature_algorithm: pyo3::Bound<'_, 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", @@ -279,21 +281,40 @@ impl ECPrivateKey { )), )); } - - let (data, _) = utils::calculate_digest_and_algorithm( - py, - data, - algorithm.getattr(pyo3::intern!(py, "algorithm"))?, - )?; + let bound_algorithm = signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?; + let (data, algo) = + utils::calculate_digest_and_algorithm(py, data.as_bytes(), &bound_algorithm)?; let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; signer.sign_init()?; + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)]{ + let deterministic: bool = signature_algorithm + .getattr(pyo3::intern!(py, "deterministic_signing"))? + .extract()?; + if deterministic { + let hash_function_name = algo + .getattr(pyo3::intern!(py, "name"))? + .extract::()?; + let hash_function = openssl::md::Md::fetch(None, &hash_function_name, None)?; + // Setting a deterministic nonce type requires to explicitly set the hash function. + // See https://github.com/openssl/openssl/issues/23205 + signer.set_signature_md(&hash_function)?; + signer.set_nonce_type(openssl::pkey_ctx::NonceType::DETERMINISTIC_K)?; + } else { + signer.set_nonce_type(openssl::pkey_ctx::NonceType::RANDOM_K)?; + } + } else { + let _ = algo; + } + } + // 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)?; + signer.sign_to_vec(data.as_bytes(), &mut sig)?; Ok(pyo3::types::PyBytes::new(py, &sig)) } @@ -337,12 +358,12 @@ impl ECPrivateKey { } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, @@ -356,21 +377,24 @@ impl ECPrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::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 key_size<'p>( + &'p self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + self.curve.bind(py).getattr(pyo3::intern!(py, "key_size")) } fn verify( &self, py: pyo3::Python<'_>, - signature: &[u8], - data: &[u8], - signature_algorithm: &pyo3::PyAny, + signature: CffiBuf<'_>, + data: CffiBuf<'_>, + signature_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { - if !signature_algorithm.is_instance(types::ECDSA.get(py)?)? { + if !signature_algorithm.is_instance(&types::ECDSA.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "Unsupported elliptic curve signature algorithm", @@ -381,13 +405,15 @@ impl ECPublicKey { let (data, _) = utils::calculate_digest_and_algorithm( py, - data, - signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?, + 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).unwrap_or(false); + let valid = verifier + .verify(data.as_bytes(), signature.as_bytes()) + .unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), @@ -419,11 +445,11 @@ impl ECPublicKey { } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } @@ -436,20 +462,20 @@ impl ECPublicKey { } } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] struct EllipticCurvePrivateNumbers { #[pyo3(get)] - private_value: pyo3::Py, + private_value: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] struct EllipticCurvePublicNumbers { #[pyo3(get)] - x: pyo3::Py, + x: pyo3::Py, #[pyo3(get)] - y: pyo3::Py, + y: pyo3::Py, #[pyo3(get)] curve: pyo3::Py, } @@ -459,8 +485,7 @@ fn public_key_from_numbers( 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)? { + if numbers.x.bind(py).lt(0)? || numbers.y.bind(py).lt(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Invalid EC key. Both x and y must be non-negative.", @@ -468,8 +493,8 @@ fn public_key_from_numbers( )); } - 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 x = utils::py_int_to_bn(py, numbers.x.bind(py))?; + let y = utils::py_int_to_bn(py, numbers.y.bind(py))?; let mut point = openssl::ec::EcPoint::new(curve)?; let mut bn_ctx = openssl::bn::BigNumContext::new()?; @@ -484,11 +509,11 @@ fn public_key_from_numbers( Ok(openssl::ec::EcKey::from_public_key(curve, &point)?) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl EllipticCurvePrivateNumbers { #[new] fn new( - private_value: pyo3::Py, + private_value: pyo3::Py, public_numbers: pyo3::Py, ) -> EllipticCurvePrivateNumbers { EllipticCurvePrivateNumbers { @@ -497,16 +522,18 @@ impl EllipticCurvePrivateNumbers { } } + #[pyo3(signature = (backend=None))] fn private_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; - let curve = curve_from_py_curve(py, self.public_numbers.get().curve.as_ref(py), false)?; + let curve = + curve_from_py_curve(py, self.public_numbers.get().curve.bind(py).clone(), 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 private_value = utils::py_int_to_bn(py, self.private_value.bind(py))?; let mut bn_ctx = openssl::bn::BigNumContext::new()?; let mut expected_pub = openssl::ec::EcPoint::new(&curve)?; @@ -537,36 +564,35 @@ impl EllipticCurvePrivateNumbers { 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))?) + Ok( + (**self.private_value.bind(py)).eq(other.private_value.bind(py))? + && self + .public_numbers + .bind(py) + .eq(other.public_numbers.bind(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); + self.private_value.bind(py).hash()?.hash(&mut hasher); + self.public_numbers.bind(py).hash()?.hash(&mut hasher); Ok(hasher.finish()) } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl EllipticCurvePublicNumbers { #[new] fn new( py: pyo3::Python<'_>, - x: pyo3::Py, - y: pyo3::Py, + x: pyo3::Py, + y: pyo3::Py, curve: pyo3::Py, ) -> CryptographyResult { if !curve - .as_ref(py) - .is_instance(types::ELLIPTIC_CURVE.get(py)?)? + .bind(py) + .is_instance(&types::ELLIPTIC_CURVE.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( @@ -578,14 +604,15 @@ impl EllipticCurvePublicNumbers { Ok(EllipticCurvePublicNumbers { x, y, curve }) } + #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; - let curve = curve_from_py_curve(py, self.curve.as_ref(py), false)?; + let curve = curve_from_py_curve(py, self.curve.bind(py).clone(), false)?; let public_key = public_key_from_numbers(py, self, &curve)?; let pkey = openssl::pkey::PKey::from_ec_key(public_key)?; @@ -601,34 +628,34 @@ impl EllipticCurvePublicNumbers { 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))? + Ok((**self.x.bind(py)).eq(other.x.bind(py))? + && (**self.y.bind(py)).eq(other.y.bind(py))? && self .curve - .as_ref(py) + .bind(py) .getattr(pyo3::intern!(py, "name"))? - .eq(other.curve.as_ref(py).getattr(pyo3::intern!(py, "name"))?)? + .eq(other.curve.bind(py).getattr(pyo3::intern!(py, "name"))?)? && self .curve - .as_ref(py) + .bind(py) .getattr(pyo3::intern!(py, "key_size"))? .eq(other .curve - .as_ref(py) + .bind(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.x.bind(py).hash()?.hash(&mut hasher); + self.y.bind(py).hash()?.hash(&mut hasher); self.curve - .as_ref(py) + .bind(py) .getattr(pyo3::intern!(py, "name"))? .hash()? .hash(&mut hasher); self.curve - .as_ref(py) + .bind(py) .getattr(pyo3::intern!(py, "key_size"))? .hash()? .hash(&mut hasher); @@ -636,26 +663,20 @@ impl EllipticCurvePublicNumbers { } 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"))?; + let x = self.x.bind(py); + let y = self.y.bind(py); + let curve_name = self.curve.bind(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) +#[pyo3::pymodule] +pub(crate) mod ec { + #[pymodule_export] + use super::{ + curve_supported, derive_private_key, from_public_bytes, generate_private_key, ECPrivateKey, + ECPublicKey, EllipticCurvePrivateNumbers, EllipticCurvePublicNumbers, + }; } diff --git a/src/rust/src/backend/ed25519.rs b/src/rust/src/backend/ed25519.rs index f68da83bfb47..721bac816882 100644 --- a/src/rust/src/backend/ed25519.rs +++ b/src/rust/src/backend/ed25519.rs @@ -7,17 +7,17 @@ use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] pub(crate) struct Ed25519PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] pub(crate) struct Ed25519PublicKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(Ed25519PrivateKey { pkey: openssl::pkey::PKey::generate_ed25519()?, @@ -40,7 +40,7 @@ pub(crate) fn public_key_from_pkey( } } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes( data.as_bytes(), @@ -52,7 +52,7 @@ fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { Ok(Ed25519PrivateKey { pkey }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::ED25519) .map_err(|_| { @@ -61,17 +61,18 @@ fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { Ok(Ed25519PublicKey { pkey }) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl Ed25519PrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &[u8], - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + data: CffiBuf<'_>, + ) -> CryptographyResult> { let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; - Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { + let len = signer.len()?; + Ok(pyo3::types::PyBytes::new_with(py, 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(()) @@ -91,18 +92,18 @@ impl Ed25519PrivateKey { fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, @@ -116,11 +117,11 @@ impl Ed25519PrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::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 { @@ -135,17 +136,17 @@ impl Ed25519PublicKey { fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) } @@ -158,14 +159,10 @@ impl Ed25519PublicKey { } } -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!(from_private_bytes, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; - - m.add_class::()?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod ed25519 { + #[pymodule_export] + use super::{ + from_private_bytes, from_public_bytes, generate_key, Ed25519PrivateKey, Ed25519PublicKey, + }; } diff --git a/src/rust/src/backend/ed448.rs b/src/rust/src/backend/ed448.rs index eeed28e92f6e..ba743d02c1ef 100644 --- a/src/rust/src/backend/ed448.rs +++ b/src/rust/src/backend/ed448.rs @@ -7,17 +7,17 @@ use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] pub(crate) struct Ed448PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] pub(crate) struct Ed448PublicKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(Ed448PrivateKey { pkey: openssl::pkey::PKey::generate_ed448()?, @@ -40,17 +40,17 @@ pub(crate) fn public_key_from_pkey( } } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::ED448) .map_err(|_| { - pyo3::exceptions::PyValueError::new_err("An Ed448 private key is 56 bytes long") + pyo3::exceptions::PyValueError::new_err("An Ed448 private key is 57 bytes long") })?; Ok(Ed448PrivateKey { pkey }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::ED448) .map_err(|_| { @@ -59,17 +59,18 @@ fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { Ok(Ed448PublicKey { pkey }) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl Ed448PrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &[u8], - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + data: CffiBuf<'_>, + ) -> CryptographyResult> { let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; - Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { + let len = signer.len()?; + Ok(pyo3::types::PyBytes::new_with(py, 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(()) @@ -89,18 +90,18 @@ impl Ed448PrivateKey { fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, @@ -114,11 +115,11 @@ impl Ed448PrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::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( @@ -132,17 +133,17 @@ impl Ed448PublicKey { fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) } @@ -155,14 +156,10 @@ impl Ed448PublicKey { } } -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!(from_private_bytes, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; - - m.add_class::()?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod ed448 { + #[pymodule_export] + use super::{ + from_private_bytes, from_public_bytes, generate_key, Ed448PrivateKey, Ed448PublicKey, + }; } diff --git a/src/rust/src/backend/hashes.rs b/src/rust/src/backend/hashes.rs index ac5de597c354..09c75f336ec2 100644 --- a/src/rust/src/backend/hashes.rs +++ b/src/rust/src/backend/hashes.rs @@ -2,46 +2,41 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::PyAnyMethods; use std::borrow::Cow; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")] +#[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")] pub(crate) struct Hash { #[pyo3(get)] algorithm: pyo3::Py, ctx: Option, } -pub(crate) fn already_finalized_error() -> CryptographyError { - CryptographyError::from(exceptions::AlreadyFinalized::new_err( - "Context was already finalized.", - )) -} - impl Hash { fn get_ctx(&self) -> CryptographyResult<&openssl::hash::Hasher> { if let Some(ctx) = self.ctx.as_ref() { return Ok(ctx); }; - Err(already_finalized_error()) + Err(exceptions::already_finalized_error()) } fn get_mut_ctx(&mut self) -> CryptographyResult<&mut openssl::hash::Hasher> { if let Some(ctx) = self.ctx.as_mut() { return Ok(ctx); } - Err(already_finalized_error()) + Err(exceptions::already_finalized_error()) } } pub(crate) fn message_digest_from_algorithm( py: pyo3::Python<'_>, - algorithm: &pyo3::PyAny, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { - if !algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? { + if !algorithm.is_instance(&types::HASH_ALGORITHM.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err("Expected instance of hashes.HashAlgorithm."), )); @@ -49,14 +44,14 @@ pub(crate) fn message_digest_from_algorithm( let name = algorithm .getattr(pyo3::intern!(py, "name"))? - .extract::<&str>()?; + .extract::()?; let openssl_name = if name == "blake2b" || name == "blake2s" { let digest_size = algorithm .getattr(pyo3::intern!(py, "digest_size"))? .extract::()?; Cow::Owned(format!("{}{}", name, digest_size * 8)) } else { - Cow::Borrowed(name) + Cow::Borrowed(name.as_ref()) }; match openssl::hash::MessageDigest::from_name(&openssl_name) { @@ -70,6 +65,11 @@ pub(crate) fn message_digest_from_algorithm( } } +#[pyo3::pyfunction] +fn hash_supported(py: pyo3::Python<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>) -> bool { + message_digest_from_algorithm(py, &algorithm).is_ok() +} + impl Hash { pub(crate) fn update_bytes(&mut self, data: &[u8]) -> CryptographyResult<()> { self.get_mut_ctx()?.update(data)?; @@ -83,8 +83,8 @@ impl Hash { #[pyo3(signature = (algorithm, backend=None))] pub(crate) fn new( py: pyo3::Python<'_>, - algorithm: &pyo3::PyAny, - backend: Option<&pyo3::PyAny>, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + backend: Option<&pyo3::Bound<'_, pyo3::PyAny>>, ) -> CryptographyResult { let _ = backend; @@ -92,7 +92,7 @@ impl Hash { let ctx = openssl::hash::Hasher::new(md)?; Ok(Hash { - algorithm: algorithm.into(), + algorithm: algorithm.clone().unbind(), ctx: Some(ctx), }) } @@ -104,12 +104,12 @@ impl Hash { pub(crate) fn finalize<'p>( &mut self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] { let algorithm = self.algorithm.clone_ref(py); - let algorithm = algorithm.as_ref(py); - if algorithm.is_instance(types::EXTENDABLE_OUTPUT_FUNCTION.get(py)?)? { + let algorithm = algorithm.bind(py); + 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"))? @@ -136,9 +136,8 @@ impl Hash { } } -pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { - let m = pyo3::prelude::PyModule::new(py, "hashes")?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod hashes { + #[pymodule_export] + use super::{hash_supported, Hash}; } diff --git a/src/rust/src/backend/hmac.rs b/src/rust/src/backend/hmac.rs index d035a6156c3d..4e2d06943377 100644 --- a/src/rust/src/backend/hmac.rs +++ b/src/rust/src/backend/hmac.rs @@ -2,34 +2,59 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::backend::hashes::{already_finalized_error, message_digest_from_algorithm}; +use crate::backend::hashes::message_digest_from_algorithm; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; +use pyo3::types::PyBytesMethods; -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.hmac", name = "HMAC" )] -struct Hmac { +pub(crate) struct Hmac { #[pyo3(get)] algorithm: pyo3::Py, ctx: Option, } impl Hmac { + pub(crate) fn new_bytes( + py: pyo3::Python<'_>, + key: &[u8], + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + ) -> CryptographyResult { + let md = message_digest_from_algorithm(py, algorithm)?; + let ctx = cryptography_openssl::hmac::Hmac::new(key, md).map_err(|_| { + exceptions::UnsupportedAlgorithm::new_err(( + "Digest is not supported for HMAC", + exceptions::Reasons::UNSUPPORTED_HASH, + )) + })?; + + Ok(Hmac { + ctx: Some(ctx), + algorithm: algorithm.clone().unbind(), + }) + } + + pub(crate) fn update_bytes(&mut self, data: &[u8]) -> CryptographyResult<()> { + self.get_mut_ctx()?.update(data)?; + Ok(()) + } + fn get_ctx(&self) -> CryptographyResult<&cryptography_openssl::hmac::Hmac> { if let Some(ctx) = self.ctx.as_ref() { return Ok(ctx); }; - Err(already_finalized_error()) + Err(exceptions::already_finalized_error()) } fn get_mut_ctx(&mut self) -> CryptographyResult<&mut cryptography_openssl::hmac::Hmac> { if let Some(ctx) = self.ctx.as_mut() { return Ok(ctx); } - Err(already_finalized_error()) + Err(exceptions::already_finalized_error()) } } @@ -40,41 +65,30 @@ impl Hmac { fn new( py: pyo3::Python<'_>, key: CffiBuf<'_>, - algorithm: &pyo3::PyAny, - backend: Option<&pyo3::PyAny>, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; - let md = message_digest_from_algorithm(py, algorithm)?; - 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), - algorithm: algorithm.into(), - }) + Hmac::new_bytes(py, key.as_bytes(), algorithm) } 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> { + ) -> CryptographyResult> { 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(); + let actual_bound = self.finalize(py)?; + let actual = actual_bound.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."), @@ -92,9 +106,8 @@ impl Hmac { } } -pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { - let m = pyo3::prelude::PyModule::new(py, "hmac")?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod hmac { + #[pymodule_export] + use super::Hmac; } diff --git a/src/rust/src/backend/kdf.rs b/src/rust/src/backend/kdf.rs index 35cf0eb266a3..2144caf1ea9a 100644 --- a/src/rust/src/backend/kdf.rs +++ b/src/rust/src/backend/kdf.rs @@ -2,19 +2,23 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +#[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] +use pyo3::types::PyBytesMethods; + use crate::backend::hashes; use crate::buf::CffiBuf; -use crate::error::CryptographyResult; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; -#[pyo3::prelude::pyfunction] -fn derive_pbkdf2_hmac<'p>( +#[pyo3::pyfunction] +pub(crate) fn derive_pbkdf2_hmac<'p>( py: pyo3::Python<'p>, key_material: CffiBuf<'_>, - algorithm: &pyo3::PyAny, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, salt: &[u8], iterations: usize, length: usize, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { +) -> CryptographyResult> { let md = hashes::message_digest_from_algorithm(py, algorithm)?; Ok(pyo3::types::PyBytes::new_with(py, length, |b| { @@ -23,37 +27,307 @@ fn derive_pbkdf2_hmac<'p>( })?) } -#[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] -#[pyo3::prelude::pyfunction] -#[allow(clippy::too_many_arguments)] -fn derive_scrypt<'p>( - py: pyo3::Python<'p>, - key_material: CffiBuf<'_>, - salt: &[u8], +#[pyo3::pyclass(module = "cryptography.hazmat.primitives.kdf.scrypt")] +struct Scrypt { + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] + salt: pyo3::Py, + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] + length: usize, + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] n: u64, + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] r: u64, + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] p: u64, - max_mem: u64, - length: usize, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { - Ok(pyo3::types::PyBytes::new_with(py, length, |b| { - openssl::pkcs5::scrypt(key_material.as_bytes(), salt, n, r, p, max_mem, b).map_err(|_| { - // memory required formula explained here: - // https://blog.filippo.io/the-scrypt-parameters/ - let min_memory = 128 * n * r / (1024 * 1024); - pyo3::exceptions::PyMemoryError::new_err(format!( - "Not enough memory to derive key. These parameters require {min_memory}MB of memory." - )) - }) - })?) + + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] + used: bool, } -pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { - let m = pyo3::prelude::PyModule::new(py, "kdf")?; +#[pyo3::pymethods] +impl Scrypt { + #[new] + #[pyo3(signature = (salt, length, n, r, p, backend=None))] + fn new( + salt: pyo3::Py, + length: usize, + n: u64, + r: u64, + p: u64, + backend: Option>, + ) -> CryptographyResult { + _ = backend; + + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_IS_LIBRESSL)] { + _ = salt; + _ = length; + _ = n; + _ = r; + _ = p; + + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err( + "This version of OpenSSL does not support scrypt" + ), + )) + } else { + if cryptography_openssl::fips::is_enabled() { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err( + "This version of OpenSSL does not support scrypt" + ), + )); + } + + if n < 2 || (n & (n - 1)) != 0 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "n must be greater than 1 and be a power of 2." + ), + )); + } + if r < 1 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "r must be greater than or equal to 1." + ), + )); + } + if p < 1 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "p must be greater than or equal to 1." + ), + )); + } + + Ok(Scrypt{ + salt, + length, + n, + r, + p, + used: false, + }) + } + } + } - m.add_function(pyo3::wrap_pyfunction!(derive_pbkdf2_hmac, m)?)?; #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] - m.add_function(pyo3::wrap_pyfunction!(derive_scrypt, m)?)?; + fn derive<'p>( + &mut self, + py: pyo3::Python<'p>, + key_material: CffiBuf<'_>, + ) -> CryptographyResult> { + if self.used { + return Err(exceptions::already_finalized_error()); + } + self.used = true; + + Ok(pyo3::types::PyBytes::new_with(py, self.length, |b| { + openssl::pkcs5::scrypt(key_material.as_bytes(), self.salt.as_bytes(py), self.n, self.r, self.p, (usize::MAX / 2).try_into().unwrap(), b).map_err(|_| { + // memory required formula explained here: + // https://blog.filippo.io/the-scrypt-parameters/ + let min_memory = 128 * self.n * self.r / (1024 * 1024); + pyo3::exceptions::PyMemoryError::new_err(format!( + "Not enough memory to derive key. These parameters require {min_memory}MB of memory." + )) + }) + })?) + } + + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] + fn verify( + &mut self, + py: pyo3::Python<'_>, + key_material: CffiBuf<'_>, + expected_key: CffiBuf<'_>, + ) -> CryptographyResult<()> { + let actual = self.derive(py, key_material)?; + let actual_bytes = actual.as_bytes(); + let expected_bytes = expected_key.as_bytes(); + + if actual_bytes.len() != expected_bytes.len() + || !openssl::memcmp::eq(actual_bytes, expected_bytes) + { + return Err(CryptographyError::from(exceptions::InvalidKey::new_err( + "Keys do not match.", + ))); + } + + Ok(()) + } +} + +#[pyo3::pyclass(module = "cryptography.hazmat.primitives.kdf.argon2")] +struct Argon2id { + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + salt: pyo3::Py, + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + length: usize, + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + iterations: u32, + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + lanes: u32, + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + memory_cost: u32, + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + ad: Option>, + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + secret: Option>, + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + used: bool, +} + +#[pyo3::pymethods] +impl Argon2id { + #[new] + #[pyo3(signature = (salt, length, iterations, lanes, memory_cost, ad=None, secret=None))] + #[allow(clippy::too_many_arguments)] + fn new( + py: pyo3::Python<'_>, + salt: pyo3::Py, + length: usize, + iterations: u32, + lanes: u32, + memory_cost: u32, + ad: Option>, + secret: Option>, + ) -> CryptographyResult { + cfg_if::cfg_if! { + if #[cfg(not(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER))] { + _ = py; + _ = salt; + _ = length; + _ = iterations; + _ = lanes; + _ = memory_cost; + _ = ad; + _ = secret; + + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err( + "This version of OpenSSL does not support argon2id" + ), + )) + } else { + if cryptography_openssl::fips::is_enabled() { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err( + "This version of OpenSSL does not support argon2id" + ), + )); + } + + if salt.as_bytes(py).len() < 8 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "salt must be at least 8 bytes" + ), + )); + } + if length < 4 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "length must be greater than or equal to 4." + ), + )); + } + if iterations < 1 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "iterations must be greater than or equal to 1." + ), + )); + } + if lanes < 1 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "lanes must be greater than or equal to 1." + ), + )); + } + + if memory_cost / 8 < lanes { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "memory_cost must be an integer >= 8 * lanes." + ), + )); + } + + + Ok(Argon2id{ + salt, + length, + iterations, + lanes, + memory_cost, + ad, + secret, + used: false, + }) + } + } + } + + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + fn derive<'p>( + &mut self, + py: pyo3::Python<'p>, + key_material: CffiBuf<'_>, + ) -> CryptographyResult> { + if self.used { + return Err(exceptions::already_finalized_error()); + } + self.used = true; + Ok(pyo3::types::PyBytes::new_with(py, self.length, |b| { + openssl::kdf::argon2id( + None, + key_material.as_bytes(), + self.salt.as_bytes(py), + self.ad.as_ref().map(|ad| ad.as_bytes(py)), + self.secret.as_ref().map(|secret| secret.as_bytes(py)), + self.iterations, + self.lanes, + self.memory_cost, + b, + ) + .map_err(CryptographyError::from)?; + Ok(()) + })?) + } + + #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] + fn verify( + &mut self, + py: pyo3::Python<'_>, + key_material: CffiBuf<'_>, + expected_key: CffiBuf<'_>, + ) -> CryptographyResult<()> { + let actual = self.derive(py, key_material)?; + let actual_bytes = actual.as_bytes(); + let expected_bytes = expected_key.as_bytes(); + + if actual_bytes.len() != expected_bytes.len() + || !openssl::memcmp::eq(actual_bytes, expected_bytes) + { + return Err(CryptographyError::from(exceptions::InvalidKey::new_err( + "Keys do not match.", + ))); + } + + Ok(()) + } +} - Ok(m) +#[pyo3::pymodule] +pub(crate) mod kdf { + #[pymodule_export] + use super::derive_pbkdf2_hmac; + #[pymodule_export] + use super::Argon2id; + #[pymodule_export] + use super::Scrypt; } diff --git a/src/rust/src/backend/keys.rs b/src/rust/src/backend/keys.rs index bd3e8eb28e3b..4a323adedc4c 100644 --- a/src/rust/src/backend/keys.rs +++ b/src/rust/src/backend/keys.rs @@ -2,23 +2,22 @@ // 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 pyo3::IntoPyObject; use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] #[pyo3(signature = (data, password, backend=None, *, unsafe_skip_rsa_key_validation=false))] -fn load_der_private_key( - py: pyo3::Python<'_>, +fn load_der_private_key<'p>( + py: pyo3::Python<'p>, data: CffiBuf<'_>, password: Option>, - backend: Option<&pyo3::PyAny>, + backend: Option>, unsafe_skip_rsa_key_validation: bool, -) -> CryptographyResult { +) -> CryptographyResult> { let _ = backend; if let Ok(pkey) = openssl::pkey::PKey::private_key_from_der(data.as_bytes()) { if password.is_some() { @@ -41,15 +40,15 @@ fn load_der_private_key( private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] #[pyo3(signature = (data, password, backend=None, *, unsafe_skip_rsa_key_validation=false))] -fn load_pem_private_key( - py: pyo3::Python<'_>, +fn load_pem_private_key<'p>( + py: pyo3::Python<'p>, data: CffiBuf<'_>, password: Option>, - backend: Option<&pyo3::PyAny>, + backend: Option>, unsafe_skip_rsa_key_validation: bool, -) -> CryptographyResult { +) -> CryptographyResult> { let _ = backend; let password = password.as_ref().map(CffiBuf::as_bytes); let mut status = utils::PasswordCallbackStatus::Unused; @@ -61,29 +60,18 @@ fn load_pem_private_key( 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<'_>, +pub(crate) fn private_key_from_pkey<'p>( + py: pyo3::Python<'p>, pkey: &openssl::pkey::PKeyRef, unsafe_skip_rsa_key_validation: bool, -) -> CryptographyResult { +) -> 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))] + .into_pyobject(py)? + .into_any()), openssl::pkey::Id::RSA_PSS => { // At the moment the way we handle RSA PSS keys is to strip the // PSS constraints from them and treat them as normal RSA keys @@ -94,54 +82,62 @@ fn private_key_from_pkey( 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), + .into_pyobject(py)? + .into_any(), ) } - 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)) - } + openssl::pkey::Id::EC => Ok(crate::backend::ec::private_key_from_pkey(py, pkey)? + .into_pyobject(py)? + .into_any()), + openssl::pkey::Id::X25519 => Ok(crate::backend::x25519::private_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), #[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::X448 => Ok(crate::backend::x448::private_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), - openssl::pkey::Id::ED25519 => { - Ok(crate::backend::ed25519::private_key_from_pkey(pkey).into_py(py)) - } + openssl::pkey::Id::ED25519 => Ok(crate::backend::ed25519::private_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), #[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)), + openssl::pkey::Id::ED448 => Ok(crate::backend::ed448::private_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), + openssl::pkey::Id::DSA => Ok(crate::backend::dsa::private_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), + openssl::pkey::Id::DH => Ok(crate::backend::dh::private_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), #[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)), + openssl::pkey::Id::DHX => Ok(crate::backend::dh::private_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), _ => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err("Unsupported key type."), )), } } -#[pyo3::prelude::pyfunction] -fn load_der_public_key( - py: pyo3::Python<'_>, +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] +fn load_der_public_key<'p>( + py: pyo3::Python<'p>, data: CffiBuf<'_>, - backend: Option<&pyo3::PyAny>, -) -> CryptographyResult { + backend: Option>, +) -> CryptographyResult> { let _ = backend; load_der_public_key_bytes(py, data.as_bytes()) } -pub(crate) fn load_der_public_key_bytes( - py: pyo3::Python<'_>, +pub(crate) fn load_der_public_key_bytes<'p>( + py: pyo3::Python<'p>, data: &[u8], -) -> CryptographyResult { +) -> 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 @@ -156,53 +152,85 @@ pub(crate) fn load_der_public_key_bytes( } } -#[pyo3::prelude::pyfunction] -fn load_pem_public_key( - py: pyo3::Python<'_>, +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] +fn load_pem_public_key<'p>( + py: pyo3::Python<'p>, data: CffiBuf<'_>, - backend: Option<&pyo3::PyAny>, -) -> CryptographyResult { + backend: Option>, +) -> CryptographyResult> { let _ = backend; let p = pem::parse(data.as_bytes())?; let pkey = match p.tag() { - "RSA PUBLIC KEY" => cryptography_key_parsing::rsa::parse_pkcs1_public_key(p.contents())?, + "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)), + _ => return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + "Valid PEM but no BEGIN PUBLIC KEY/END PUBLIC KEY delimiters. Are you sure this is a public key?" + ))), }; public_key_from_pkey(py, &pkey, pkey.id()) } -fn public_key_from_pkey( - py: pyo3::Python<'_>, +fn public_key_from_pkey<'p>( + py: pyo3::Python<'p>, pkey: &openssl::pkey::PKeyRef, id: openssl::pkey::Id, -) -> CryptographyResult { +) -> 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)) - } + openssl::pkey::Id::RSA => Ok(crate::backend::rsa::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), + openssl::pkey::Id::EC => Ok(crate::backend::ec::public_key_from_pkey(py, pkey)? + .into_pyobject(py)? + .into_any()), + openssl::pkey::Id::X25519 => Ok(crate::backend::x25519::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] - openssl::pkey::Id::X448 => Ok(crate::backend::x448::public_key_from_pkey(pkey).into_py(py)), + openssl::pkey::Id::X448 => Ok(crate::backend::x448::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), - openssl::pkey::Id::ED25519 => { - Ok(crate::backend::ed25519::public_key_from_pkey(pkey).into_py(py)) - } + openssl::pkey::Id::ED25519 => Ok(crate::backend::ed25519::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] - openssl::pkey::Id::ED448 => { - Ok(crate::backend::ed448::public_key_from_pkey(pkey).into_py(py)) - } + openssl::pkey::Id::ED448 => Ok(crate::backend::ed448::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), - 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)), + openssl::pkey::Id::DSA => Ok(crate::backend::dsa::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), + openssl::pkey::Id::DH => Ok(crate::backend::dh::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), #[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)), + openssl::pkey::Id::DHX => Ok(crate::backend::dh::public_key_from_pkey(pkey) + .into_pyobject(py)? + .into_any()), _ => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err("Unsupported key type."), @@ -210,24 +238,21 @@ fn public_key_from_pkey( } } -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) +#[pyo3::pymodule] +pub(crate) mod keys { + #[pymodule_export] + use super::{ + load_der_private_key, load_der_public_key, load_pem_private_key, load_pem_public_key, + }; } #[cfg(test)] mod tests { - use super::public_key_from_pkey; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + use super::{private_key_from_pkey, public_key_from_pkey}; #[test] + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn test_public_key_from_pkey_unknown_key() { pyo3::prepare_freethreaded_python(); @@ -240,4 +265,15 @@ mod tests { assert!(public_key_from_pkey(py, &pkey, openssl::pkey::Id::CMAC).is_err()); }); } + + #[test] + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + fn test_private_key_from_pkey_unknown_key() { + pyo3::prepare_freethreaded_python(); + + pyo3::Python::with_gil(|py| { + let pkey = openssl::pkey::PKey::hmac(&[0; 32]).unwrap(); + assert!(private_key_from_pkey(py, &pkey, false).is_err()); + }); + } } diff --git a/src/rust/src/backend/mod.rs b/src/rust/src/backend/mod.rs index 7e085d623b40..a447565d7229 100644 --- a/src/rust/src/backend/mod.rs +++ b/src/rust/src/backend/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod aead; pub(crate) mod cipher_registry; +pub(crate) mod ciphers; pub(crate) mod cmac; pub(crate) mod dh; pub(crate) mod dsa; @@ -21,29 +22,3 @@ pub(crate) mod utils; 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())?)?; - - 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())?)?; - - module.add_submodule(x25519::create_module(module.py())?)?; - #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] - module.add_submodule(x448::create_module(module.py())?)?; - - module.add_submodule(poly1305::create_module(module.py())?)?; - - module.add_submodule(hashes::create_module(module.py())?)?; - module.add_submodule(hmac::create_module(module.py())?)?; - module.add_submodule(kdf::create_module(module.py())?)?; - 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 66fc6239fa02..9b1d8165f8dc 100644 --- a/src/rust/src/backend/poly1305.rs +++ b/src/rust/src/backend/poly1305.rs @@ -2,10 +2,10 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::backend::hashes::already_finalized_error; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; +use pyo3::types::PyBytesMethods; #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] struct Poly1305Boring { @@ -31,7 +31,7 @@ impl Poly1305Boring { fn finalize<'p>( &mut self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let result = pyo3::types::PyBytes::new_with(py, 16usize, |b| { self.context.finalize(b.as_mut()); Ok(()) @@ -77,7 +77,7 @@ impl Poly1305Open { fn finalize<'p>( &mut self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let result = pyo3::types::PyBytes::new_with(py, self.signer.len()?, |b| { let n = self.signer.sign(b).unwrap(); assert_eq!(n, b.len()); @@ -87,7 +87,7 @@ impl Poly1305Open { } } -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")] +#[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")] struct Poly1305 { #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] inner: Option, @@ -114,7 +114,7 @@ impl Poly1305 { py: pyo3::Python<'p>, key: CffiBuf<'_>, data: CffiBuf<'_>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let mut p = Poly1305::new(key)?; p.update(data)?; p.finalize(py) @@ -135,24 +135,29 @@ impl Poly1305 { fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { self.inner .as_mut() - .map_or(Err(already_finalized_error()), |b| b.update(data)) + .map_or(Err(exceptions::already_finalized_error()), |b| { + b.update(data) + }) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let res = self .inner .as_mut() - .map_or(Err(already_finalized_error()), |b| b.finalize(py)); + .map_or(Err(exceptions::already_finalized_error()), |b| { + b.finalize(py) + }); self.inner = None; res } fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { - let actual = self.finalize(py)?.as_bytes(); + let actual_bound = self.finalize(py)?; + let actual = actual_bound.as_bytes(); if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err("Value did not match computed tag."), @@ -163,10 +168,8 @@ impl Poly1305 { } } -pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { - let m = pyo3::prelude::PyModule::new(py, "poly1305")?; - - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod poly1305 { + #[pymodule_export] + use super::Poly1305; } diff --git a/src/rust/src/backend/rsa.rs b/src/rust/src/backend/rsa.rs index 35dd1053fdfc..79b385ffb73f 100644 --- a/src/rust/src/backend/rsa.rs +++ b/src/rust/src/backend/rsa.rs @@ -6,10 +6,12 @@ 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}; +use pyo3::types::PyAnyMethods; -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.rsa", name = "RSAPrivateKey" @@ -18,7 +20,7 @@ pub(crate) struct RsaPrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.rsa", name = "RSAPublicKey" @@ -59,7 +61,7 @@ pub(crate) fn public_key_from_pkey( } } -#[pyo3::prelude::pyfunction] +#[pyo3::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)?; @@ -68,7 +70,7 @@ fn generate_private_key(public_exponent: u32, key_size: u32) -> CryptographyResu } fn oaep_hash_supported(md: &openssl::hash::MessageDigest) -> bool { - (!cryptography_openssl::fips::is_enabled() && md == &openssl::hash::MessageDigest::sha1()) + md == &openssl::hash::MessageDigest::sha1() || md == &openssl::hash::MessageDigest::sha224() || md == &openssl::hash::MessageDigest::sha256() || md == &openssl::hash::MessageDigest::sha384() @@ -78,9 +80,9 @@ fn oaep_hash_supported(md: &openssl::hash::MessageDigest) -> bool { fn setup_encryption_ctx( py: pyo3::Python<'_>, ctx: &mut openssl::pkey_ctx::PkeyCtx, - padding: &pyo3::PyAny, + padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { - if !padding.is_instance(types::ASYMMETRIC_PADDING.get(py)?)? { + 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.", @@ -88,12 +90,12 @@ fn setup_encryption_ctx( )); } - let padding_enum = if padding.is_instance(types::PKCS1V15.get(py)?)? { + let padding_enum = if padding.is_instance(&types::PKCS1V15.get(py)?)? { openssl::rsa::Padding::PKCS1 - } else if padding.is_instance(types::OAEP.get(py)?)? { + } else if padding.is_instance(&types::OAEP.get(py)?)? { if !padding .getattr(pyo3::intern!(py, "_mgf"))? - .is_instance(types::MGF1.get(py)?)? + .is_instance(&types::MGF1.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( @@ -121,13 +123,13 @@ fn setup_encryption_ctx( if padding_enum == openssl::rsa::Padding::PKCS1_OAEP { let mgf1_md = hashes::message_digest_from_algorithm( py, - padding + &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"))?, + &padding.getattr(pyo3::intern!(py, "_algorithm"))?, )?; if !oaep_hash_supported(&mgf1_md) || !oaep_hash_supported(&oaep_md) { @@ -144,10 +146,10 @@ fn setup_encryption_ctx( if let Some(label) = padding .getattr(pyo3::intern!(py, "_label"))? - .extract::>()? + .extract::>()? { if !label.is_empty() { - ctx.set_rsa_oaep_label(label)?; + ctx.set_rsa_oaep_label(&label)?; } } } @@ -158,12 +160,12 @@ fn setup_encryption_ctx( fn setup_signature_ctx( py: pyo3::Python<'_>, ctx: &mut openssl::pkey_ctx::PkeyCtx, - padding: &pyo3::PyAny, - algorithm: &pyo3::PyAny, + padding: &pyo3::Bound<'_, pyo3::PyAny>, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, key_size: usize, is_signing: bool, ) -> CryptographyResult<()> { - if !padding.is_instance(types::ASYMMETRIC_PADDING.get(py)?)? { + 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.", @@ -171,12 +173,12 @@ fn setup_signature_ctx( )); } - let padding_enum = if padding.is_instance(types::PKCS1V15.get(py)?)? { + let padding_enum = if padding.is_instance(&types::PKCS1V15.get(py)?)? { openssl::rsa::Padding::PKCS1 - } else if padding.is_instance(types::PSS.get(py)?)? { + } else if padding.is_instance(&types::PSS.get(py)?)? { if !padding .getattr(pyo3::intern!(py, "_mgf"))? - .is_instance(types::MGF1.get(py)?)? + .is_instance(&types::MGF1.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( @@ -187,7 +189,7 @@ fn setup_signature_ctx( } // PSS padding requires a hash algorithm - if !algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? { + if !algorithm.is_instance(&types::HASH_ALGORITHM.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Expected instance of hashes.HashAlgorithm.", @@ -248,11 +250,11 @@ fn setup_signature_ctx( 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)?)? { + 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)?)? { + } 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)?)? { + } else if salt.is_instance(&types::PADDING_AUTO.get(py)?)? { if is_signing { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -266,7 +268,7 @@ fn setup_signature_ctx( let mgf1_md = hashes::message_digest_from_algorithm( py, - padding + &padding .getattr(pyo3::intern!(py, "_mgf"))? .getattr(pyo3::intern!(py, "_algorithm"))?, )?; @@ -276,41 +278,42 @@ fn setup_signature_ctx( Ok(()) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl RsaPrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, - data: &[u8], - padding: &pyo3::PyAny, - algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::PyAny> { - let (data, algorithm) = utils::calculate_digest_and_algorithm(py, data, algorithm)?; + data: CffiBuf<'_>, + padding: &pyo3::Bound<'p, pyo3::PyAny>, + algorithm: &pyo3::Bound<'p, 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.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)?; + setup_signature_ctx(py, &mut ctx, padding, &algorithm, self.pkey.size(), true)?; - let length = ctx.sign(data, None)?; + let length = ctx.sign(data.as_bytes(), None)?; Ok(pyo3::types::PyBytes::new_with(py, length, |b| { - let length = ctx.sign(data, Some(b)).map_err(|_| { + let length = ctx.sign(data.as_bytes(), 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(()) - })?) + })?.into_any()) } fn decrypt<'p>( &self, py: pyo3::Python<'p>, ciphertext: &[u8], - padding: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + padding: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { let key_size_bytes = usize::try_from((self.pkey.rsa().unwrap().n().num_bits() + 7) / 8).unwrap(); if key_size_bytes != ciphertext.len() { @@ -395,12 +398,12 @@ impl RsaPrivateKey { } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, @@ -414,23 +417,26 @@ impl RsaPrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl RsaPublicKey { fn verify( &self, py: pyo3::Python<'_>, - signature: &[u8], - data: &[u8], - padding: &pyo3::PyAny, - algorithm: &pyo3::PyAny, + signature: CffiBuf<'_>, + data: CffiBuf<'_>, + padding: &pyo3::Bound<'_, pyo3::PyAny>, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { - let (data, algorithm) = utils::calculate_digest_and_algorithm(py, data, algorithm)?; + 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)?; + setup_signature_ctx(py, &mut ctx, padding, &algorithm, self.pkey.size(), false)?; - let valid = ctx.verify(data, signature).unwrap_or(false); + let valid = ctx + .verify(data.as_bytes(), signature.as_bytes()) + .unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), @@ -444,8 +450,8 @@ impl RsaPublicKey { &self, py: pyo3::Python<'p>, plaintext: &[u8], - padding: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + padding: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.encrypt_init()?; @@ -465,10 +471,10 @@ impl RsaPublicKey { &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)?)? { + padding: &pyo3::Bound<'_, pyo3::PyAny>, + algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + ) -> CryptographyResult> { + 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.", @@ -507,11 +513,11 @@ impl RsaPublicKey { } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } @@ -524,50 +530,50 @@ impl RsaPublicKey { } } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.rsa", name = "RSAPrivateNumbers" )] struct RsaPrivateNumbers { #[pyo3(get)] - p: pyo3::Py, + p: pyo3::Py, #[pyo3(get)] - q: pyo3::Py, + q: pyo3::Py, #[pyo3(get)] - d: pyo3::Py, + d: pyo3::Py, #[pyo3(get)] - dmp1: pyo3::Py, + dmp1: pyo3::Py, #[pyo3(get)] - dmq1: pyo3::Py, + dmq1: pyo3::Py, #[pyo3(get)] - iqmp: pyo3::Py, + iqmp: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } -#[pyo3::prelude::pyclass( +#[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.rsa", name = "RSAPublicNumbers" )] struct RsaPublicNumbers { #[pyo3(get)] - e: pyo3::Py, + e: pyo3::Py, #[pyo3(get)] - n: pyo3::Py, + 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, + p: &pyo3::Bound<'_, pyo3::types::PyInt>, + q: &pyo3::Bound<'_, pyo3::types::PyInt>, + private_exponent: &pyo3::Bound<'_, pyo3::types::PyInt>, + dmp1: &pyo3::Bound<'_, pyo3::types::PyInt>, + dmq1: &pyo3::Bound<'_, pyo3::types::PyInt>, + iqmp: &pyo3::Bound<'_, pyo3::types::PyInt>, + public_exponent: &pyo3::Bound<'_, pyo3::types::PyInt>, + modulus: &pyo3::Bound<'_, pyo3::types::PyInt>, ) -> CryptographyResult<()> { if modulus.lt(3)? { return Err(CryptographyError::from( @@ -617,26 +623,25 @@ fn check_private_key_components( )); } - // No `bitand` method. - if public_exponent.call_method1("__and__", (1,))?.eq(0)? { + if public_exponent.bitand(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)? { + if dmp1.bitand(1)?.eq(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("dmp1 must be odd."), )); } - if dmq1.call_method1("__and__", (1,))?.eq(0)? { + if dmq1.bitand(1)?.eq(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("dmq1 must be odd."), )); } - if p.call_method1("__mul__", (q,))?.ne(modulus)? { + if p.mul(q)?.ne(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("p*q must equal modulus."), )); @@ -645,16 +650,16 @@ fn check_private_key_components( Ok(()) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl RsaPrivateNumbers { #[new] fn new( - p: pyo3::Py, - q: pyo3::Py, - d: pyo3::Py, - dmp1: pyo3::Py, - dmq1: pyo3::Py, - iqmp: pyo3::Py, + p: pyo3::Py, + q: pyo3::Py, + d: pyo3::Py, + dmp1: pyo3::Py, + dmq1: pyo3::Py, + iqmp: pyo3::Py, public_numbers: pyo3::Py, ) -> RsaPrivateNumbers { Self { @@ -672,31 +677,31 @@ impl RsaPrivateNumbers { fn private_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option<&pyo3::Bound<'_, 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), + self.p.bind(py), + self.q.bind(py), + self.d.bind(py), + self.dmp1.bind(py), + self.dmq1.bind(py), + self.iqmp.bind(py), + self.public_numbers.get().e.bind(py), + self.public_numbers.get().n.bind(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))?, + utils::py_int_to_bn(py, public_numbers.n.bind(py))?, + utils::py_int_to_bn(py, public_numbers.e.bind(py))?, + utils::py_int_to_bn(py, self.d.bind(py))?, + utils::py_int_to_bn(py, self.p.bind(py))?, + utils::py_int_to_bn(py, self.q.bind(py))?, + utils::py_int_to_bn(py, self.dmp1.bind(py))?, + utils::py_int_to_bn(py, self.dmq1.bind(py))?, + utils::py_int_to_bn(py, self.iqmp.bind(py))?, ) .unwrap(); if !unsafe_skip_rsa_key_validation { @@ -711,34 +716,34 @@ impl RsaPrivateNumbers { 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))? + Ok((**self.p.bind(py)).eq(other.p.bind(py))? + && (**self.q.bind(py)).eq(other.q.bind(py))? + && (**self.d.bind(py)).eq(other.d.bind(py))? + && (**self.dmp1.bind(py)).eq(other.dmp1.bind(py))? + && (**self.dmq1.bind(py)).eq(other.dmq1.bind(py))? + && (**self.iqmp.bind(py)).eq(other.iqmp.bind(py))? && self .public_numbers - .as_ref(py) - .eq(other.public_numbers.as_ref(py))?) + .bind(py) + .eq(other.public_numbers.bind(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); + self.p.bind(py).hash()?.hash(&mut hasher); + self.q.bind(py).hash()?.hash(&mut hasher); + self.d.bind(py).hash()?.hash(&mut hasher); + self.dmp1.bind(py).hash()?.hash(&mut hasher); + self.dmq1.bind(py).hash()?.hash(&mut hasher); + self.iqmp.bind(py).hash()?.hash(&mut hasher); + self.public_numbers.bind(py).hash()?.hash(&mut hasher); Ok(hasher.finish()) } } fn check_public_key_components( - e: &pyo3::types::PyLong, - n: &pyo3::types::PyLong, + e: &pyo3::Bound<'_, pyo3::types::PyInt>, + n: &pyo3::Bound<'_, pyo3::types::PyInt>, ) -> CryptographyResult<()> { if n.lt(3)? { return Err(CryptographyError::from( @@ -752,8 +757,7 @@ fn check_public_key_components( )); } - // No `bitand` method. - if e.call_method1("__and__", (1,))?.eq(0)? { + if e.bitand(1)?.eq(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("e must be odd."), )); @@ -762,25 +766,26 @@ fn check_public_key_components( Ok(()) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl RsaPublicNumbers { #[new] - fn new(e: pyo3::Py, n: pyo3::Py) -> RsaPublicNumbers { + fn new(e: pyo3::Py, n: pyo3::Py) -> RsaPublicNumbers { RsaPublicNumbers { e, n } } + #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, - backend: Option<&pyo3::PyAny>, + backend: Option<&pyo3::Bound<'_, pyo3::PyAny>>, ) -> CryptographyResult { let _ = backend; - check_public_key_components(self.e.as_ref(py), self.n.as_ref(py))?; + check_public_key_components(self.e.bind(py), self.n.bind(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))?, + utils::py_int_to_bn(py, self.n.bind(py))?, + utils::py_int_to_bn(py, self.e.bind(py))?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_rsa(rsa)?; @@ -793,33 +798,29 @@ impl RsaPublicNumbers { 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))?, + (**self.e.bind(py)).eq(other.e.bind(py))? + && (**self.n.bind(py)).eq(other.n.bind(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); + self.e.bind(py).hash()?.hash(&mut hasher); + self.n.bind(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); + let e = self.e.bind(py); + let n = self.n.bind(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) +#[pyo3::pymodule] +pub(crate) mod rsa { + #[pymodule_export] + use super::{ + generate_private_key, RsaPrivateKey, RsaPrivateNumbers, RsaPublicKey, RsaPublicNumbers, + }; } diff --git a/src/rust/src/backend/utils.rs b/src/rust/src/backend/utils.rs index 3373a565cf2c..832fdf3542f5 100644 --- a/src/rust/src/backend/utils.rs +++ b/src/rust/src/backend/utils.rs @@ -5,30 +5,31 @@ use crate::backend::hashes::Hash; use crate::error::{CryptographyError, CryptographyResult}; use crate::{error, types}; +use pyo3::types::{PyAnyMethods, PyBytesMethods}; pub(crate) fn py_int_to_bn( py: pyo3::Python<'_>, - v: &pyo3::PyAny, + v: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let n = v .call_method0(pyo3::intern!(py, "bit_length"))? .extract::()? / 8 + 1; - let bytes: &[u8] = v + let bytes = v .call_method1(pyo3::intern!(py, "to_bytes"), (n, pyo3::intern!(py, "big")))? - .extract()?; + .extract::()?; - Ok(openssl::bn::BigNum::from_slice(bytes)?) + Ok(openssl::bn::BigNum::from_slice(&bytes)?) } pub(crate) fn bn_to_py_int<'p>( py: pyo3::Python<'p>, b: &openssl::bn::BigNumRef, -) -> CryptographyResult<&'p pyo3::PyAny> { +) -> CryptographyResult> { assert!(!b.is_negative()); - let int_type = py.get_type::(); + let int_type = py.get_type::(); Ok(int_type.call_method1( pyo3::intern!(py, "from_bytes"), (b.to_vec(), pyo3::intern!(py, "big")), @@ -42,29 +43,29 @@ pub(crate) fn bn_to_big_endian_bytes(b: &openssl::bn::BigNumRef) -> Cryptography #[allow(clippy::too_many_arguments)] pub(crate) fn pkey_private_bytes<'p>( py: pyo3::Python<'p>, - key_obj: &pyo3::PyAny, + key_obj: &pyo3::Bound<'p, pyo3::PyAny>, pkey: &openssl::pkey::PKey, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, openssh_allowed: bool, raw_allowed: bool, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !encoding.is_instance(types::ENCODING.get(py)?)? { +) -> CryptographyResult> { + 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(types::PRIVATE_FORMAT.get(py)?)? { + 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(types::KEY_SERIALIZATION_ENCRYPTION.get(py)?)? { + 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", @@ -73,12 +74,12 @@ pub(crate) fn pkey_private_bytes<'p>( } if raw_allowed - && (encoding.is(types::ENCODING_RAW.get(py)?) - || format.is(types::PRIVATE_FORMAT_RAW.get(py)?)) + && (encoding.is(&types::ENCODING_RAW.get(py)?) + || format.is(&types::PRIVATE_FORMAT_RAW.get(py)?)) { - 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)?)? + 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()" @@ -88,17 +89,19 @@ pub(crate) fn pkey_private_bytes<'p>( return Ok(pyo3::types::PyBytes::new(py, &raw_bytes)); } - let password = if encryption_algorithm.is_instance(types::NO_ENCRYPTION.get(py)?)? { - b"" - } else if encryption_algorithm.is_instance(types::BEST_AVAILABLE_ENCRYPTION.get(py)?)? - || (encryption_algorithm.is_instance(types::ENCRYPTION_BUILDER.get(py)?)? + let py_password; + let password = if encryption_algorithm.is_instance(&types::NO_ENCRYPTION.get(py)?)? { + b"" as &[u8] + } 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 + py_password = encryption_algorithm .getattr(pyo3::intern!(py, "password"))? - .extract::<&[u8]>()? + .extract::()?; + &py_password } else { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Unsupported encryption type"), @@ -113,8 +116,8 @@ pub(crate) fn pkey_private_bytes<'p>( )); } - if format.is(types::PRIVATE_FORMAT_PKCS8.get(py)?) { - if encoding.is(types::ENCODING_PEM.get(py)?) { + 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 { @@ -124,7 +127,7 @@ pub(crate) fn pkey_private_bytes<'p>( )? }; return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); - } else if encoding.is(types::ENCODING_DER.get(py)?) { + } else if encoding.is(&types::ENCODING_DER.get(py)?) { let der_bytes = if password.is_empty() { pkey.private_key_to_pkcs8()? } else { @@ -140,9 +143,16 @@ pub(crate) fn pkey_private_bytes<'p>( )); } - if format.is(types::PRIVATE_FORMAT_TRADITIONAL_OPENSSL.get(py)?) { + if format.is(&types::PRIVATE_FORMAT_TRADITIONAL_OPENSSL.get(py)?) { + if cryptography_openssl::fips::is_enabled() && !password.is_empty() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Encrypted traditional OpenSSL format is not supported in FIPS mode", + ), + )); + } if let Ok(rsa) = pkey.rsa() { - if encoding.is(types::ENCODING_PEM.get(py)?) { + if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { rsa.private_key_to_pem()? } else { @@ -152,7 +162,7 @@ pub(crate) fn pkey_private_bytes<'p>( )? }; return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); - } else if encoding.is(types::ENCODING_DER.get(py)?) { + } else if encoding.is(&types::ENCODING_DER.get(py)?) { if !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -165,7 +175,7 @@ pub(crate) fn pkey_private_bytes<'p>( return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); } } else if let Ok(dsa) = pkey.dsa() { - if encoding.is(types::ENCODING_PEM.get(py)?) { + if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { dsa.private_key_to_pem()? } else { @@ -175,7 +185,7 @@ pub(crate) fn pkey_private_bytes<'p>( )? }; return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); - } else if encoding.is(types::ENCODING_DER.get(py)?) { + } else if encoding.is(&types::ENCODING_DER.get(py)?) { if !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -188,7 +198,7 @@ pub(crate) fn pkey_private_bytes<'p>( return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); } } else if let Ok(ec) = pkey.ec_key() { - if encoding.is(types::ENCODING_PEM.get(py)?) { + if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { ec.private_key_to_pem()? } else { @@ -198,7 +208,7 @@ pub(crate) fn pkey_private_bytes<'p>( )? }; return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); - } else if encoding.is(types::ENCODING_DER.get(py)?) { + } else if encoding.is(&types::ENCODING_DER.get(py)?) { if !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -214,8 +224,8 @@ pub(crate) fn pkey_private_bytes<'p>( } // OpenSSH + PEM - if openssh_allowed && format.is(types::PRIVATE_FORMAT_OPENSSH.get(py)?) { - if encoding.is(types::ENCODING_PEM.get(py)?) { + 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))? @@ -236,21 +246,21 @@ pub(crate) fn pkey_private_bytes<'p>( pub(crate) fn pkey_public_bytes<'p>( py: pyo3::Python<'p>, - key_obj: &pyo3::PyAny, + key_obj: &pyo3::Bound<'p, pyo3::PyAny>, pkey: &openssl::pkey::PKey, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, openssh_allowed: bool, raw_allowed: bool, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !encoding.is_instance(types::ENCODING.get(py)?)? { +) -> CryptographyResult> { + 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(types::PUBLIC_FORMAT.get(py)?)? { + 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", @@ -259,11 +269,11 @@ pub(crate) fn pkey_public_bytes<'p>( } if raw_allowed - && (encoding.is(types::ENCODING_RAW.get(py)?) - || format.is(types::PUBLIC_FORMAT_RAW.get(py)?)) + && (encoding.is(&types::ENCODING_RAW.get(py)?) + || format.is(&types::PUBLIC_FORMAT_RAW.get(py)?)) { - if !encoding.is(types::ENCODING_RAW.get(py)?) - || !format.is(types::PUBLIC_FORMAT_RAW.get(py)?) + 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( @@ -276,11 +286,11 @@ pub(crate) fn pkey_public_bytes<'p>( } // SubjectPublicKeyInfo + PEM/DER - if format.is(types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) { - if encoding.is(types::ENCODING_PEM.get(py)?) { + 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(types::ENCODING_DER.get(py)?) { + } 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)); } @@ -292,10 +302,10 @@ 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)?) { + 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)?) { + } else if format.is(&types::PUBLIC_FORMAT_COMPRESSED_POINT.get(py)?) { openssl::ec::PointConversionForm::COMPRESSED } else { return Err(CryptographyError::from( @@ -313,11 +323,11 @@ pub(crate) fn pkey_public_bytes<'p>( } if let Ok(rsa) = pkey.rsa() { - if format.is(types::PUBLIC_FORMAT_PKCS1.get(py)?) { - if encoding.is(types::ENCODING_PEM.get(py)?) { + 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)?) { + } 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)); } @@ -330,8 +340,8 @@ pub(crate) fn pkey_public_bytes<'p>( } // OpenSSH + OpenSSH - if openssh_allowed && format.is(types::PUBLIC_FORMAT_OPENSSH.get(py)?) { - if encoding.is(types::ENCODING_OPENSSH.get(py)?) { + 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,))? @@ -350,22 +360,39 @@ pub(crate) fn pkey_public_bytes<'p>( )) } +pub(crate) enum BytesOrPyBytes<'a> { + Bytes(&'a [u8]), + PyBytes(pyo3::Bound<'a, pyo3::types::PyBytes>), +} + +impl BytesOrPyBytes<'_> { + pub(crate) fn as_bytes(&self) -> &[u8] { + match self { + BytesOrPyBytes::Bytes(v) => v, + BytesOrPyBytes::PyBytes(v) => v.as_bytes(), + } + } +} + 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")?; + data: &'p [u8], + algorithm: &pyo3::Bound<'p, pyo3::PyAny>, +) -> CryptographyResult<(BytesOrPyBytes<'p>, pyo3::Bound<'p, pyo3::PyAny>)> { + let (algorithm, data) = if algorithm.is_instance(&types::PREHASHED.get(py)?)? { + ( + algorithm.getattr("_algorithm")?, + BytesOrPyBytes::Bytes(data), + ) } 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(); - } + (algorithm.clone(), BytesOrPyBytes::PyBytes(h.finalize(py)?)) + }; - if data.len() != algorithm.getattr("digest_size")?.extract()? { + if data.as_bytes().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.", @@ -429,12 +456,12 @@ pub(crate) fn handle_key_load_result( )), )), (Err(e), _, _) => { - let errors = error::list_from_openssl_error(py, 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(), + pyo3::exceptions::PyValueError::new_err(( + "Could not deserialize key data. The data may be in an incorrect format, the provided password may be incorrect, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).", + errors.unbind(), + )) )) } } diff --git a/src/rust/src/backend/x25519.rs b/src/rust/src/backend/x25519.rs index 00e2866cfc39..4cc6124aefc5 100644 --- a/src/rust/src/backend/x25519.rs +++ b/src/rust/src/backend/x25519.rs @@ -6,17 +6,17 @@ use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::CryptographyResult; -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] pub(crate) struct X25519PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] pub(crate) struct X25519PublicKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(X25519PrivateKey { pkey: openssl::pkey::PKey::generate_x25519()?, @@ -39,7 +39,7 @@ pub(crate) fn public_key_from_pkey( } } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X25519) @@ -51,7 +51,7 @@ fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { Ok(X25519PrivateKey { pkey }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::X25519) .map_err(|_| { @@ -60,15 +60,15 @@ fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { Ok(X25519PublicKey { pkey }) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl X25519PrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, - public_key: &X25519PublicKey, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + peer_public_key: &X25519PublicKey, + ) -> CryptographyResult> { 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(|_| { @@ -92,18 +92,18 @@ impl X25519PrivateKey { fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, @@ -117,22 +117,22 @@ impl X25519PrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl X25519PublicKey { fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) } @@ -145,14 +145,10 @@ impl X25519PublicKey { } } -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!(from_private_bytes, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; - - m.add_class::()?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod x25519 { + #[pymodule_export] + use super::{ + from_private_bytes, from_public_bytes, generate_key, X25519PrivateKey, X25519PublicKey, + }; } diff --git a/src/rust/src/backend/x448.rs b/src/rust/src/backend/x448.rs index 07c84bc36aca..953302dd63d1 100644 --- a/src/rust/src/backend/x448.rs +++ b/src/rust/src/backend/x448.rs @@ -6,17 +6,17 @@ use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::CryptographyResult; -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] pub(crate) struct X448PrivateKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] pub(crate) struct X448PublicKey { pkey: openssl::pkey::PKey, } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(X448PrivateKey { pkey: openssl::pkey::PKey::generate_x448()?, @@ -39,7 +39,7 @@ pub(crate) fn public_key_from_pkey( } } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X448) @@ -50,7 +50,7 @@ fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { })?; Ok(X448PrivateKey { pkey }) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::X448) .map_err(|_| { @@ -59,15 +59,15 @@ fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { Ok(X448PublicKey { pkey }) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl X448PrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, - public_key: &X448PublicKey, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + peer_public_key: &X448PublicKey, + ) -> CryptographyResult> { 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(|_| { @@ -91,18 +91,18 @@ impl X448PrivateKey { fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn private_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - encryption_algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, @@ -116,22 +116,22 @@ impl X448PrivateKey { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl X448PublicKey { fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) } fn public_bytes<'p>( - slf: &pyo3::PyCell, + slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - format: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + format: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) } @@ -144,14 +144,10 @@ impl X448PublicKey { } } -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!(from_private_bytes, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; - - m.add_class::()?; - m.add_class::()?; - - Ok(m) +#[pyo3::pymodule] +pub(crate) mod x448 { + #[pymodule_export] + use super::{ + from_private_bytes, from_public_bytes, generate_key, X448PrivateKey, X448PublicKey, + }; } diff --git a/src/rust/src/buf.rs b/src/rust/src/buf.rs index c1f2cc8253c7..e55bf12a45be 100644 --- a/src/rust/src/buf.rs +++ b/src/rust/src/buf.rs @@ -2,31 +2,59 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use std::slice; - use crate::types; +use pyo3::types::IntoPyDict; +use pyo3::types::PyAnyMethods; +use std::slice; pub(crate) struct CffiBuf<'p> { - _pyobj: &'p pyo3::PyAny, - _bufobj: &'p pyo3::PyAny, + pyobj: pyo3::Bound<'p, pyo3::PyAny>, + _bufobj: pyo3::Bound<'p, pyo3::PyAny>, buf: &'p [u8], } -impl CffiBuf<'_> { +fn _extract_buffer_length<'p>( + pyobj: &pyo3::Bound<'p, pyo3::PyAny>, + mutable: bool, +) -> pyo3::PyResult<(pyo3::Bound<'p, pyo3::PyAny>, usize)> { + let py = pyobj.py(); + let bufobj = if mutable { + let kwargs = [(pyo3::intern!(py, "require_writable"), true)].into_py_dict(py)?; + types::FFI_FROM_BUFFER + .get(py)? + .call((pyobj,), Some(&kwargs))? + } else { + types::FFI_FROM_BUFFER.get(py)?.call1((pyobj,))? + }; + let ptrval = types::FFI_CAST + .get(py)? + .call1((pyo3::intern!(py, "uintptr_t"), bufobj.clone()))? + .call_method0(pyo3::intern!(py, "__int__"))? + .extract::()?; + Ok((bufobj, ptrval)) +} + +impl<'a> CffiBuf<'a> { + pub(crate) fn from_bytes(py: pyo3::Python<'a>, buf: &'a [u8]) -> Self { + CffiBuf { + pyobj: py.None().into_bound(py), + _bufobj: py.None().into_bound(py), + buf, + } + } + pub(crate) fn as_bytes(&self) -> &[u8] { self.buf } + + pub(crate) fn into_pyobj(self) -> pyo3::Bound<'a, pyo3::PyAny> { + self.pyobj + } } 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) = types::EXTRACT_BUFFER_LENGTH - .get(py)? - .call1((pyobj,))? - .extract()?; - + fn extract_bound(pyobj: &pyo3::Bound<'a, pyo3::PyAny>) -> pyo3::PyResult { + let (bufobj, ptrval) = _extract_buffer_length(pyobj, false)?; let len = bufobj.len()?; let buf = if len == 0 { &[] @@ -43,7 +71,46 @@ impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { }; Ok(CffiBuf { - _pyobj: pyobj, + pyobj: pyobj.clone(), + _bufobj: bufobj, + buf, + }) + } +} + +pub(crate) struct CffiMutBuf<'p> { + _pyobj: pyo3::Bound<'p, pyo3::PyAny>, + _bufobj: pyo3::Bound<'p, pyo3::PyAny>, + buf: &'p mut [u8], +} + +impl CffiMutBuf<'_> { + pub(crate) fn as_mut_bytes(&mut self) -> &mut [u8] { + self.buf + } +} + +impl<'a> pyo3::conversion::FromPyObject<'a> for CffiMutBuf<'a> { + fn extract_bound(pyobj: &pyo3::Bound<'a, pyo3::PyAny>) -> pyo3::PyResult { + let (bufobj, ptrval) = _extract_buffer_length(pyobj, true)?; + + let len = bufobj.len()?; + let buf = if len == 0 { + &mut [] + } else { + // SAFETY: _extract_buffer_length ensures that we have a valid ptr + // and length (and we ensure we meet slice's requirements for + // 0-length slices above), we're keeping pyobj alive which ensures + // the buffer is valid. But! There is no actually guarantee + // against concurrent mutation. See + // https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ + // for details. This is the same as our cffi status quo ante, so + // we're doing an unsound thing and living with it. + unsafe { slice::from_raw_parts_mut(ptrval as *mut u8, len) } + }; + + Ok(CffiMutBuf { + _pyobj: pyobj.clone(), _bufobj: bufobj, buf, }) diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs index a4461d05a87a..165b2b782483 100644 --- a/src/rust/src/error.rs +++ b/src/rust/src/error.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 pyo3::ToPyObject; +use std::fmt; + +use pyo3::types::PyListMethods; use crate::exceptions; @@ -32,8 +34,8 @@ impl From for CryptographyError { } } -impl From> for CryptographyError { - fn from(e: pyo3::PyDowncastError<'_>) -> CryptographyError { +impl From> for CryptographyError { + fn from(e: pyo3::DowncastError<'_, '_>) -> CryptographyError { CryptographyError::Py(e.into()) } } @@ -80,15 +82,15 @@ impl From for CryptographyError { } } -pub(crate) fn list_from_openssl_error( - py: pyo3::Python<'_>, - error_stack: openssl::error::ErrorStack, -) -> &pyo3::types::PyList { +pub(crate) fn list_from_openssl_error<'p>( + py: pyo3::Python<'p>, + error_stack: &openssl::error::ErrorStack, +) -> pyo3::Bound<'p, 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() }) + pyo3::Bound::new(py, OpenSSLError { e: e.clone() }) .expect("Failed to create OpenSSLError"), ) .expect("Failed to append to list"); @@ -96,35 +98,54 @@ pub(crate) fn list_from_openssl_error( errors } +impl fmt::Display for CryptographyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CryptographyError::Asn1Parse(asn1_error) => { + write!(f, "error parsing asn1 value: {asn1_error:?}") + } + CryptographyError::Asn1Write(asn1::WriteError::AllocationError) => { + write!( + f, + "failed to allocate memory while performing ASN.1 serialization" + ) + } + CryptographyError::KeyParsing(asn1_error) => { + write!( + f, + "Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters). Details: {asn1_error}", + ) + } + CryptographyError::Py(py_error) => write!(f, "{}", py_error), + CryptographyError::OpenSSL(error_stack) => { + write!( + f, + "Unknown OpenSSL error. This error is commonly encountered + when another library is not cleaning up the OpenSSL error + stack. If you are using cryptography with another library + that uses OpenSSL try disabling it before reporting a bug. + Otherwise please file an issue at + https://github.com/pyca/cryptography/issues with + information on how to reproduce this. ({error_stack})" + ) + } + } + } +} + impl From for pyo3::PyErr { fn from(e: CryptographyError) -> pyo3::PyErr { match e { - CryptographyError::Asn1Parse(asn1_error) => pyo3::exceptions::PyValueError::new_err( - format!("error parsing asn1 value: {asn1_error:?}"), - ), + CryptographyError::Asn1Parse(_) | CryptographyError::KeyParsing(_) => { + pyo3::exceptions::PyValueError::new_err(e.to_string()) + } CryptographyError::Asn1Write(asn1::WriteError::AllocationError) => { - pyo3::exceptions::PyMemoryError::new_err( - "failed to allocate memory while performing ASN.1 serialization", - ) + pyo3::exceptions::PyMemoryError::new_err(e.to_string()) } - 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| { + CryptographyError::OpenSSL(ref error_stack) => pyo3::Python::with_gil(|py| { let errors = list_from_openssl_error(py, error_stack); - exceptions::InternalError::new_err(( - format!( - "Unknown OpenSSL error. This error is commonly encountered - when another library is not cleaning up the OpenSSL error - stack. If you are using cryptography with another library - that uses OpenSSL try disabling it before reporting a bug. - Otherwise please file an issue at - https://github.com/pyca/cryptography/issues with - information on how to reproduce this. ({errors:?})" - ), - errors.to_object(py), - )) + exceptions::InternalError::new_err((e.to_string(), errors.unbind())) }), } } @@ -145,14 +166,14 @@ impl CryptographyError { // The primary purpose of this alias is for brevity to keep function signatures // to a single-line as a work around for coverage issues. See // https://github.com/pyca/cryptography/pull/6173 -pub(crate) type CryptographyResult = Result; +pub(crate) type CryptographyResult = Result; -#[pyo3::prelude::pyfunction] +#[pyo3::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")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl")] pub(crate) struct OpenSSLError { e: openssl::error::Error, } @@ -174,10 +195,6 @@ impl OpenSSLError { 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!( "", @@ -189,11 +206,13 @@ impl OpenSSLError { } } -#[pyo3::prelude::pyfunction] -pub(crate) fn capture_error_stack(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyList> { +#[pyo3::pyfunction] +pub(crate) fn capture_error_stack( + py: pyo3::Python<'_>, +) -> pyo3::PyResult> { 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() })?)?; + errs.append(pyo3::Bound::new(py, OpenSSLError { e: e.clone() })?)?; } Ok(errs) } @@ -202,6 +221,16 @@ pub(crate) fn capture_error_stack(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3: mod tests { use super::CryptographyError; + #[test] + fn test_cryptographyerror_display() { + pyo3::prepare_freethreaded_python(); + pyo3::Python::with_gil(|py| { + let py_error = pyo3::exceptions::PyRuntimeError::new_err("abc"); + let e: CryptographyError = py_error.clone_ref(py).into(); + assert!(e.to_string() == py_error.to_string()); + }) + } + #[test] fn test_cryptographyerror_from() { pyo3::prepare_freethreaded_python(); @@ -214,8 +243,7 @@ mod tests { let py_e: pyo3::PyErr = e.into(); assert!(py_e.is_instance_of::(py)); - let e: CryptographyError = - pyo3::PyDowncastError::new(py.None().as_ref(py), "abc").into(); + let e: CryptographyError = pyo3::DowncastError::new(py.None().bind(py), "abc").into(); assert!(matches!(e, CryptographyError::Py(_))); let e = cryptography_key_parsing::KeyParsingError::OpenSSL( diff --git a/src/rust/src/exceptions.rs b/src/rust/src/exceptions.rs index c9456513993d..cfcedd2eb474 100644 --- a/src/rust/src/exceptions.rs +++ b/src/rust/src/exceptions.rs @@ -2,12 +2,16 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -#[pyo3::prelude::pyclass( +use crate::error::CryptographyError; + +#[pyo3::pyclass( frozen, + eq, module = "cryptography.hazmat.bindings._rust.exceptions", name = "_Reasons" )] #[allow(non_camel_case_types)] +#[derive(PartialEq)] pub(crate) enum Reasons { BACKEND_MISSING_INTERFACE, UNSUPPORTED_HASH, @@ -23,20 +27,25 @@ pub(crate) enum Reasons { UNSUPPORTED_MAC, } -pyo3::import_exception!(cryptography.exceptions, AlreadyFinalized); -pyo3::import_exception!(cryptography.exceptions, InternalError); -pyo3::import_exception!(cryptography.exceptions, InvalidSignature); -pyo3::import_exception!(cryptography.exceptions, InvalidTag); -pyo3::import_exception!(cryptography.exceptions, UnsupportedAlgorithm); -pyo3::import_exception!(cryptography.x509, AttributeNotFound); -pyo3::import_exception!(cryptography.x509, DuplicateExtension); -pyo3::import_exception!(cryptography.x509, UnsupportedGeneralNameType); -pyo3::import_exception!(cryptography.x509, InvalidVersion); - -pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { - let submod = pyo3::prelude::PyModule::new(py, "exceptions")?; +pyo3::import_exception_bound!(cryptography.exceptions, AlreadyUpdated); +pyo3::import_exception_bound!(cryptography.exceptions, AlreadyFinalized); +pyo3::import_exception_bound!(cryptography.exceptions, InternalError); +pyo3::import_exception_bound!(cryptography.exceptions, InvalidKey); +pyo3::import_exception_bound!(cryptography.exceptions, InvalidSignature); +pyo3::import_exception_bound!(cryptography.exceptions, InvalidTag); +pyo3::import_exception_bound!(cryptography.exceptions, NotYetFinalized); +pyo3::import_exception_bound!(cryptography.exceptions, UnsupportedAlgorithm); +pyo3::import_exception_bound!(cryptography.x509, AttributeNotFound); +pyo3::import_exception_bound!(cryptography.x509, DuplicateExtension); +pyo3::import_exception_bound!(cryptography.x509, UnsupportedGeneralNameType); +pyo3::import_exception_bound!(cryptography.x509, InvalidVersion); - submod.add_class::()?; +pub(crate) fn already_finalized_error() -> CryptographyError { + CryptographyError::from(AlreadyFinalized::new_err("Context was already finalized.")) +} - Ok(submod) +#[pyo3::pymodule] +pub(crate) mod exceptions { + #[pymodule_export] + use super::Reasons; } diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 9dd54f4b901d..b2642c5ce999 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -3,6 +3,14 @@ // for complete details. #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] +#![allow(unknown_lints, non_local_definitions, clippy::result_large_err)] + +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +use crate::error::CryptographyResult; +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +use openssl::provider; +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +use std::env; mod asn1; mod backend; @@ -11,54 +19,246 @@ mod error; mod exceptions; pub(crate) mod oid; mod padding; +mod pkcs12; mod pkcs7; +mod test_support; pub(crate) mod types; mod x509; -#[pyo3::prelude::pyfunction] +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +#[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust")] +struct LoadedProviders { + legacy: Option, + _default: provider::Provider, + + fips: Option, +} + +#[pyo3::pyfunction] fn openssl_version() -> i64 { openssl::version::number() } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] +fn openssl_version_text() -> &'static str { + openssl::version::version() +} + +#[pyo3::pyfunction] fn is_fips_enabled() -> bool { cryptography_openssl::fips::is_enabled() } -#[pyo3::prelude::pymodule] -fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> { - m.add_function(pyo3::wrap_pyfunction!(padding::check_pkcs7_padding, m)?)?; - m.add_function(pyo3::wrap_pyfunction!(padding::check_ansix923_padding, m)?)?; - m.add_class::()?; - - m.add_submodule(asn1::create_submodule(py)?)?; - m.add_submodule(pkcs7::create_submodule(py)?)?; - m.add_submodule(exceptions::create_submodule(py)?)?; - - let x509_mod = pyo3::prelude::PyModule::new(py, "x509")?; - crate::x509::certificate::add_to_module(x509_mod)?; - crate::x509::common::add_to_module(x509_mod)?; - crate::x509::crl::add_to_module(x509_mod)?; - crate::x509::csr::add_to_module(x509_mod)?; - crate::x509::sct::add_to_module(x509_mod)?; - crate::x509::verify::add_to_module(x509_mod)?; - m.add_submodule(x509_mod)?; - - let ocsp_mod = pyo3::prelude::PyModule::new(py, "ocsp")?; - crate::x509::ocsp_req::add_to_module(ocsp_mod)?; - crate::x509::ocsp_resp::add_to_module(ocsp_mod)?; - m.add_submodule(ocsp_mod)?; - - m.add_submodule(cryptography_cffi::create_module(py)?)?; - - let openssl_mod = pyo3::prelude::PyModule::new(py, "openssl")?; - openssl_mod.add_function(pyo3::wrap_pyfunction!(openssl_version, m)?)?; - openssl_mod.add_function(pyo3::wrap_pyfunction!(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::()?; - crate::backend::add_to_module(openssl_mod)?; - m.add_submodule(openssl_mod)?; +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +fn _initialize_providers() -> CryptographyResult { + // As of OpenSSL 3.0.0 we must register a legacy cipher provider + // to get RC2 (needed for junk asymmetric private key + // serialization), RC4, Blowfish, IDEA, SEED, etc. These things + // are ugly legacy, but we aren't going to get rid of them + // any time soon. + let load_legacy = env::var("CRYPTOGRAPHY_OPENSSL_NO_LEGACY") + .map(|v| v.is_empty() || v == "0") + .unwrap_or(true); + let legacy = if load_legacy { + let legacy_result = provider::Provider::load(None, "legacy"); + _legacy_provider_error(legacy_result.is_ok())?; + Some(legacy_result?) + } else { + None + }; + let _default = provider::Provider::load(None, "default")?; + Ok(LoadedProviders { + legacy, + _default, + fips: None, + }) +} + +fn _legacy_provider_error(success: bool) -> pyo3::PyResult<()> { + if !success { + return Err(pyo3::exceptions::PyRuntimeError::new_err( + "OpenSSL 3.0's legacy provider failed to load. This is a fatal error by default, but cryptography supports running without legacy algorithms by setting the environment variable CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error, you have likely made a mistake with your OpenSSL configuration." + )); + } + Ok(()) +} +#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] +#[pyo3::pyfunction] +fn enable_fips(providers: &mut LoadedProviders) -> CryptographyResult<()> { + providers.fips = Some(provider::Provider::load(None, "fips")?); + cryptography_openssl::fips::enable()?; Ok(()) } + +#[pyo3::pymodule] +mod _rust { + use pyo3::types::PyModuleMethods; + + #[pymodule_export] + use crate::asn1::asn1_mod; + #[pymodule_export] + use crate::exceptions::exceptions; + #[pymodule_export] + use crate::oid::ObjectIdentifier; + #[pymodule_export] + use crate::padding::{check_ansix923_padding, PKCS7PaddingContext, PKCS7UnpaddingContext}; + #[pymodule_export] + use crate::pkcs12::pkcs12; + #[pymodule_export] + use crate::pkcs7::pkcs7_mod; + #[pymodule_export] + use crate::test_support::test_support; + + #[pyo3::pymodule] + mod x509 { + #[pymodule_export] + use crate::x509::certificate::{ + create_x509_certificate, load_der_x509_certificate, load_pem_x509_certificate, + load_pem_x509_certificates, Certificate, + }; + #[pymodule_export] + use crate::x509::common::{encode_extension_value, encode_name_bytes}; + #[pymodule_export] + use crate::x509::crl::{ + create_x509_crl, load_der_x509_crl, load_pem_x509_crl, CertificateRevocationList, + RevokedCertificate, + }; + #[pymodule_export] + use crate::x509::csr::{ + create_x509_csr, load_der_x509_csr, load_pem_x509_csr, CertificateSigningRequest, + }; + #[pymodule_export] + use crate::x509::sct::Sct; + #[pymodule_export] + use crate::x509::verify::{ + PolicyBuilder, PyClientVerifier, PyServerVerifier, PyStore, PyVerifiedClient, + VerificationError, + }; + } + + #[pyo3::pymodule] + mod ocsp { + #[pymodule_export] + use crate::x509::ocsp_req::{create_ocsp_request, load_der_ocsp_request, OCSPRequest}; + #[pymodule_export] + use crate::x509::ocsp_resp::{ + create_ocsp_response, load_der_ocsp_response, OCSPResponse, OCSPSingleResponse, + }; + } + + #[pyo3::pymodule] + mod openssl { + use pyo3::prelude::PyModuleMethods; + + #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] + #[pymodule_export] + use super::super::enable_fips; + #[pymodule_export] + use super::super::{is_fips_enabled, openssl_version, openssl_version_text}; + #[pymodule_export] + use crate::backend::aead::aead; + #[pymodule_export] + use crate::backend::ciphers::ciphers; + #[pymodule_export] + use crate::backend::cmac::cmac; + #[pymodule_export] + use crate::backend::dh::dh; + #[pymodule_export] + use crate::backend::dsa::dsa; + #[pymodule_export] + use crate::backend::ec::ec; + #[pymodule_export] + use crate::backend::ed25519::ed25519; + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[pymodule_export] + use crate::backend::ed448::ed448; + #[pymodule_export] + use crate::backend::hashes::hashes; + #[pymodule_export] + use crate::backend::hmac::hmac; + #[pymodule_export] + use crate::backend::kdf::kdf; + #[pymodule_export] + use crate::backend::keys::keys; + #[pymodule_export] + use crate::backend::poly1305::poly1305; + #[pymodule_export] + use crate::backend::rsa::rsa; + #[pymodule_export] + use crate::backend::x25519::x25519; + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + #[pymodule_export] + use crate::backend::x448::x448; + #[pymodule_export] + use crate::error::{capture_error_stack, raise_openssl_error, OpenSSLError}; + + #[pymodule_init] + fn init(openssl_mod: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { + openssl_mod.add( + "CRYPTOGRAPHY_OPENSSL_300_OR_GREATER", + cfg!(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + )?; + openssl_mod.add( + "CRYPTOGRAPHY_OPENSSL_309_OR_GREATER", + cfg!(CRYPTOGRAPHY_OPENSSL_309_OR_GREATER), + )?; + openssl_mod.add( + "CRYPTOGRAPHY_OPENSSL_320_OR_GREATER", + cfg!(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER), + )?; + + openssl_mod.add("CRYPTOGRAPHY_IS_LIBRESSL", cfg!(CRYPTOGRAPHY_IS_LIBRESSL))?; + openssl_mod.add("CRYPTOGRAPHY_IS_BORINGSSL", cfg!(CRYPTOGRAPHY_IS_BORINGSSL))?; + + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] { + let providers = super::super::_initialize_providers()?; + if providers.legacy.is_some() { + openssl_mod.add("_legacy_provider_loaded", true)?; + } else { + openssl_mod.add("_legacy_provider_loaded", false)?; + } + openssl_mod.add("_providers", providers)?; + } else { + // default value for non-openssl 3+ + openssl_mod.add("_legacy_provider_loaded", false)?; + } + } + cfg_if::cfg_if! { + if #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)] { + use std::ptr; + use std::cmp::max; + + let available = std::thread::available_parallelism().map_or(0, |v| v.get() as u64); + // SAFETY: This sets a libctx provider limit, but we always use the same libctx by passing NULL. + unsafe { + let current = openssl_sys::OSSL_get_max_threads(ptr::null_mut()); + // Set the thread limit to the max of available parallelism or current limit. + openssl_sys::OSSL_set_max_threads(ptr::null_mut(), max(available, current)); + } + } + } + + Ok(()) + } + } + + #[pymodule_init] + fn init(m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { + m.add_submodule(&cryptography_cffi::create_module(m.py())?)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::_legacy_provider_error; + + #[test] + fn test_legacy_provider_error() { + assert!(_legacy_provider_error(true).is_ok()); + assert!(_legacy_provider_error(false).is_err()); + } +} diff --git a/src/rust/src/oid.rs b/src/rust/src/oid.rs index 4bf764eee408..c034c3dcb601 100644 --- a/src/rust/src/oid.rs +++ b/src/rust/src/oid.rs @@ -2,13 +2,13 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; - use crate::error::CryptographyResult; use crate::types; +use pyo3::types::PyAnyMethods; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust")] pub(crate) struct ObjectIdentifier { pub(crate) oid: asn1::ObjectIdentifier, } @@ -23,15 +23,15 @@ impl ObjectIdentifier { } #[getter] - fn dotted_string<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyString { - pyo3::types::PyString::new(py, &self.oid.to_string()) + fn dotted_string(&self) -> String { + self.oid.to_string() } #[getter] fn _name<'p>( - slf: pyo3::PyRef<'_, Self>, + slf: pyo3::PyRef<'p, Self>, py: pyo3::Python<'p>, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { + ) -> pyo3::PyResult> { types::OID_NAMES .get(py)? .call_method1(pyo3::intern!(py, "get"), (slf, "Unknown OID")) @@ -41,17 +41,12 @@ impl ObjectIdentifier { slf } - fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let self_clone = pyo3::PyCell::new( - py, - ObjectIdentifier { - oid: self.oid.clone(), - }, - )?; - let name = ObjectIdentifier::_name(self_clone.borrow(), py)?.extract::<&str>()?; + fn __repr__(slf: &pyo3::Bound<'_, Self>, py: pyo3::Python<'_>) -> pyo3::PyResult { + let name = Self::_name(slf.borrow(), py)?; Ok(format!( "", - self.oid, name + slf.get().oid, + name.extract::()? )) } diff --git a/src/rust/src/padding.rs b/src/rust/src/padding.rs index 523fe85a5718..eb16cfaaad41 100644 --- a/src/rust/src/padding.rs +++ b/src/rust/src/padding.rs @@ -2,6 +2,10 @@ // 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::CryptographyResult; +use crate::exceptions; + /// 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 { @@ -16,7 +20,6 @@ fn constant_time_lt(a: u8, b: u8) -> u8 { 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(); @@ -39,7 +42,7 @@ pub(crate) fn check_pkcs7_padding(data: &[u8]) -> bool { (mismatch & 1) == 0 } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] pub(crate) fn check_ansix923_padding(data: &[u8]) -> bool { let mut mismatch = 0; let pad_size = *data.last().unwrap(); @@ -63,6 +66,109 @@ pub(crate) fn check_ansix923_padding(data: &[u8]) -> bool { (mismatch & 1) == 0 } +#[pyo3::pyclass] +pub(crate) struct PKCS7PaddingContext { + block_size: usize, + length_seen: Option, +} + +#[pyo3::pymethods] +impl PKCS7PaddingContext { + #[new] + pub(crate) fn new(block_size: usize) -> PKCS7PaddingContext { + PKCS7PaddingContext { + block_size: block_size / 8, + length_seen: Some(0), + } + } + + pub(crate) fn update<'a>( + &mut self, + buf: CffiBuf<'a>, + ) -> CryptographyResult> { + match self.length_seen.as_mut() { + Some(v) => { + *v += buf.as_bytes().len(); + Ok(buf.into_pyobj()) + } + None => Err(exceptions::already_finalized_error()), + } + } + + pub(crate) fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + match self.length_seen.take() { + Some(v) => { + let pad_size = self.block_size - (v % self.block_size); + let pad = vec![pad_size as u8; pad_size]; + Ok(pyo3::types::PyBytes::new(py, &pad)) + } + None => Err(exceptions::already_finalized_error()), + } + } +} + +#[pyo3::pyclass] +pub(crate) struct PKCS7UnpaddingContext { + block_size: usize, + buffer: Option>, +} + +#[pyo3::pymethods] +impl PKCS7UnpaddingContext { + #[new] + pub(crate) fn new(block_size: usize) -> PKCS7UnpaddingContext { + PKCS7UnpaddingContext { + block_size: block_size / 8, + buffer: Some(Vec::new()), + } + } + + pub(crate) fn update<'a>( + &mut self, + py: pyo3::Python<'a>, + buf: CffiBuf<'a>, + ) -> CryptographyResult> { + match self.buffer.as_mut() { + Some(v) => { + v.extend_from_slice(buf.as_bytes()); + let finished_blocks = (v.len() / self.block_size).saturating_sub(1); + let result_size = finished_blocks * self.block_size; + let result = v.drain(..result_size); + Ok(pyo3::types::PyBytes::new(py, result.as_slice())) + } + None => Err(exceptions::already_finalized_error()), + } + } + + pub(crate) fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { + match self.buffer.take() { + Some(v) => { + if v.len() != self.block_size { + return Err( + pyo3::exceptions::PyValueError::new_err("Invalid padding bytes.").into(), + ); + } + if !check_pkcs7_padding(&v) { + return Err( + pyo3::exceptions::PyValueError::new_err("Invalid padding bytes.").into(), + ); + } + + let pad_size = *v.last().unwrap(); + let result = &v[..v.len() - pad_size as usize]; + Ok(pyo3::types::PyBytes::new(py, result)) + } + None => Err(exceptions::already_finalized_error()), + } + } +} + #[cfg(test)] mod tests { use super::constant_time_lt; diff --git a/src/rust/src/pkcs12.rs b/src/rust/src/pkcs12.rs new file mode 100644 index 000000000000..3de031a22b38 --- /dev/null +++ b/src/rust/src/pkcs12.rs @@ -0,0 +1,909 @@ +// 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::{ciphers, hashes, hmac, kdf, keys}; +use crate::buf::CffiBuf; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::padding::PKCS7PaddingContext; +use crate::x509::certificate::Certificate; +use crate::{types, x509}; +use cryptography_x509::common::Utf8StoredBMPString; +use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; +use pyo3::IntoPyObject; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +#[pyo3::pyclass(frozen)] +struct PKCS12Certificate { + #[pyo3(get)] + certificate: pyo3::Py, + #[pyo3(get)] + friendly_name: Option>, +} + +#[pyo3::pymethods] +impl PKCS12Certificate { + #[new] + #[pyo3(signature = (cert, friendly_name=None))] + fn new( + cert: pyo3::Py, + friendly_name: Option>, + ) -> PKCS12Certificate { + PKCS12Certificate { + certificate: cert, + friendly_name, + } + } + + fn __eq__( + &self, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, Self>, + ) -> CryptographyResult { + let friendly_name_eq = match (&self.friendly_name, &other.friendly_name) { + (Some(a), Some(b)) => a.bind(py).as_bytes() == b.bind(py).as_bytes(), + (None, None) => true, + _ => false, + }; + Ok(friendly_name_eq && self.certificate.bind(py).eq(other.certificate.bind(py))?) + } + + fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { + let mut hasher = DefaultHasher::new(); + self.certificate.bind(py).hash()?.hash(&mut hasher); + match &self.friendly_name { + Some(v) => v.bind(py).hash()?.hash(&mut hasher), + None => None::.hash(&mut hasher), + }; + Ok(hasher.finish()) + } + + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + let py_friendly_name_repr; + let friendly_name_repr = match &self.friendly_name { + Some(v) => { + py_friendly_name_repr = v + .bind(py) + .repr()? + .extract::()?; + &*py_friendly_name_repr + } + None => "None", + }; + Ok(format!( + "", + self.certificate.bind(py).str()?, + friendly_name_repr + )) + } +} + +pub(crate) fn symmetric_encrypt( + py: pyo3::Python<'_>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + mode: pyo3::Bound<'_, pyo3::PyAny>, + data: &[u8], +) -> CryptographyResult> { + let block_size = algorithm + .getattr(pyo3::intern!(py, "block_size"))? + .extract()?; + + let mut cipher = + ciphers::CipherContext::new(py, algorithm, mode, openssl::symm::Mode::Encrypt)?; + + let mut ciphertext = vec![0; data.len() + (block_size / 8 * 2)]; + let n = cipher.update_into(py, data, &mut ciphertext)?; + + let mut padder = PKCS7PaddingContext::new(block_size); + assert!(padder.update(CffiBuf::from_bytes(py, data))?.is_none()); + let padding = padder.finalize(py)?; + + let pad_n = cipher.update_into(py, padding.as_bytes(), &mut ciphertext[n..])?; + let final_block = cipher.finalize(py)?; + assert!(final_block.as_bytes().is_empty()); + ciphertext.truncate(n + pad_n); + + Ok(ciphertext) +} + +enum EncryptionAlgorithm { + PBESv1SHA1And3KeyTripleDESCBC, + PBESv2SHA256AndAES256CBC, +} + +impl EncryptionAlgorithm { + fn salt_length(&self) -> usize { + match self { + EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => 8, + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => 16, + } + } + + fn algorithm_identifier<'a>( + &self, + cipher_kdf_iter: u64, + salt: &'a [u8], + iv: &'a [u8], + ) -> cryptography_x509::common::AlgorithmIdentifier<'a> { + match self { + EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => { + cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::Pbes1WithShaAnd3KeyTripleDesCbc(cryptography_x509::common::PBES1Params{ + salt: salt[..8].try_into().unwrap(), + iterations: cipher_kdf_iter, + }), + } + } + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { + let kdf_algorithm_identifier = cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::Pbkdf2( + cryptography_x509::common::PBKDF2Params { + salt, + iteration_count: cipher_kdf_iter, + key_length: None, + prf: Box::new(cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: + cryptography_x509::common::AlgorithmParameters::HmacWithSha256( + Some(()), + ), + }), + }, + ), + }; + let encryption_algorithm_identifier = + cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::Aes256Cbc( + iv[..16].try_into().unwrap(), + ), + }; + + cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::Pbes2( + cryptography_x509::common::PBES2Params { + key_derivation_func: Box::new(kdf_algorithm_identifier), + encryption_scheme: Box::new(encryption_algorithm_identifier), + }, + ), + } + } + } + } + + fn encrypt( + &self, + py: pyo3::Python<'_>, + password: &[u8], + cipher_kdf_iter: u64, + salt: &[u8], + iv: &[u8], + data: &[u8], + ) -> CryptographyResult> { + match self { + EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => { + let key = pkcs12_kdf( + password, + salt, + KDF_ENCRYPTION_KEY_ID, + cipher_kdf_iter, + 24, + openssl::hash::MessageDigest::sha1(), + )?; + let iv = pkcs12_kdf( + password, + salt, + KDF_IV_ID, + cipher_kdf_iter, + 8, + openssl::hash::MessageDigest::sha1(), + )?; + + let triple_des = types::TRIPLE_DES + .get(py)? + .call1((pyo3::types::PyBytes::new(py, &key),))?; + let cbc = types::CBC + .get(py)? + .call1((pyo3::types::PyBytes::new(py, &iv),))?; + + symmetric_encrypt(py, triple_des, cbc, data) + } + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { + let pass_buf = CffiBuf::from_bytes(py, password); + let sha256 = types::SHA256.get(py)?.call0()?; + + let key = kdf::derive_pbkdf2_hmac( + py, + pass_buf, + &sha256, + salt, + cipher_kdf_iter.try_into().unwrap(), + 32, + )?; + + let aes256 = types::AES256.get(py)?.call1((key,))?; + let cbc = types::CBC.get(py)?.call1((iv,))?; + + symmetric_encrypt(py, aes256, cbc, data) + } + } + } +} + +const KDF_ENCRYPTION_KEY_ID: u8 = 1; +const KDF_IV_ID: u8 = 2; +const KDF_MAC_KEY_ID: u8 = 3; + +fn pkcs12_kdf( + pass: &[u8], + salt: &[u8], + id: u8, + rounds: u64, + key_len: usize, + hash_alg: openssl::hash::MessageDigest, +) -> CryptographyResult> { + // Encode the password as big-endian UTF-16 with NUL trailer + let pass = std::str::from_utf8(pass) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("key must be valid UTF-8"))? + .encode_utf16() + .chain([0]) + .flat_map(|v| v.to_be_bytes()) + .collect::>(); + + // Comments are borrowed from BoringSSL. + // In the spec, |block_size| is called "v", but measured in bits. + let block_size = hash_alg.block_size(); + + // 1. Construct a string, D (the "diversifier"), by concatenating v/8 copies + // of ID. + let d = vec![id; block_size]; + + // 2. Concatenate copies of the salt together to create a string S of length + // v(ceiling(s/v)) bits (the final copy of the salt may be truncated to + // create S). Note that if the salt is the empty string, then so is S. + // + // 3. Concatenate copies of the password together to create a string P of + // length v(ceiling(p/v)) bits (the final copy of the password may be + // truncated to create P). Note that if the password is the empty string, + // then so is P. + // + // 4. Set I=S||P to be the concatenation of S and P. + let s_len = block_size * ((salt.len() + block_size - 1) / block_size); + let p_len = block_size * ((pass.len() + block_size - 1) / block_size); + + let mut init_key = vec![0; s_len + p_len]; + for i in 0..s_len { + init_key[i] = salt[i % salt.len()]; + } + for i in 0..p_len { + init_key[i + s_len] = pass[i % pass.len()]; + } + + let mut result = vec![0; key_len]; + let mut pos = 0; + loop { + // A. Set A_i=H^r(D||I). (i.e., the r-th hash of D||I, + // H(H(H(... H(D||I)))) + + let mut h = openssl::hash::Hasher::new(hash_alg)?; + h.update(&d)?; + h.update(&init_key)?; + let mut a = h.finish()?; + + for _ in 1..rounds { + let mut h = openssl::hash::Hasher::new(hash_alg)?; + h.update(&a)?; + a = h.finish()?; + } + + let to_add = a.len().min(result.len() - pos); + result[pos..pos + to_add].copy_from_slice(&a[..to_add]); + pos += to_add; + if pos == result.len() { + break; + } + + // B. Concatenate copies of A_i to create a string B of length v bits (the + // final copy of A_i may be truncated to create B). + let mut b = vec![0; block_size]; + for i in 0..block_size { + b[i] = a[i % a.len()]; + } + + // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit blocks, + // where k=ceiling(s/v)+ceiling(p/v), modify I by setting I_j=(I_j+B+1) mod + // 2^v for each j. + assert!(init_key.len() % block_size == 0); + let mut j = 0; + while j < init_key.len() { + let mut carry = 1u16; + let mut k = block_size - 1; + loop { + carry += init_key[k + j] as u16 + b[k] as u16; + init_key[j + k] = carry as u8; + carry >>= 8; + if k == 0 { + break; + } + k -= 1; + } + j += block_size; + } + } + + Ok(result) +} + +fn pkcs12_attributes<'a>( + friendly_name: Option<&'a [u8]>, + local_key_id: Option<&'a [u8]>, +) -> CryptographyResult< + Option< + asn1::SetOfWriter< + 'a, + cryptography_x509::pkcs12::Attribute<'a>, + Vec>, + >, + >, +> { + let mut attrs = vec![]; + if let Some(name) = friendly_name { + let name_str = std::str::from_utf8(name).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("friendly_name must be valid UTF-8") + })?; + + attrs.push(cryptography_x509::pkcs12::Attribute { + _attr_id: asn1::DefinedByMarker::marker(), + attr_values: cryptography_x509::pkcs12::AttributeSet::FriendlyName( + asn1::SetOfWriter::new([Utf8StoredBMPString::new(name_str)]), + ), + }); + } + if let Some(key_id) = local_key_id { + attrs.push(cryptography_x509::pkcs12::Attribute { + _attr_id: asn1::DefinedByMarker::marker(), + attr_values: cryptography_x509::pkcs12::AttributeSet::LocalKeyId( + asn1::SetOfWriter::new([key_id]), + ), + }); + } + + if attrs.is_empty() { + Ok(None) + } else { + Ok(Some(asn1::SetOfWriter::new(attrs))) + } +} + +fn cert_to_bag<'a>( + cert: &'a Certificate, + friendly_name: Option<&'a [u8]>, + local_key_id: Option<&'a [u8]>, +) -> CryptographyResult> { + Ok(cryptography_x509::pkcs12::SafeBag { + _bag_id: asn1::DefinedByMarker::marker(), + bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::CertBag( + cryptography_x509::pkcs12::CertBag { + _cert_id: asn1::DefinedByMarker::marker(), + cert_value: asn1::Explicit::new(cryptography_x509::pkcs12::CertType::X509( + asn1::OctetStringEncoded::new(cert.raw.borrow_dependent().clone()), + )), + }, + )), + attributes: pkcs12_attributes(friendly_name, local_key_id)?, + }) +} + +#[allow(clippy::type_complexity)] +fn decode_encryption_algorithm<'a>( + py: pyo3::Python<'a>, + encryption_algorithm: pyo3::Bound<'a, pyo3::PyAny>, +) -> CryptographyResult<( + pyo3::pybacked::PyBackedBytes, + pyo3::Bound<'a, pyo3::PyAny>, + u64, + u64, + Option, +)> { + let default_hmac_alg = types::SHA256.get(py)?.call0()?; + let default_hmac_kdf_iter = 2048; + let default_cipher_kdf_iter = 20000; + + if encryption_algorithm.is_instance(&types::NO_ENCRYPTION.get(py)?)? { + Ok(( + pyo3::types::PyBytes::new(py, b"").extract()?, + default_hmac_alg, + default_hmac_kdf_iter, + default_cipher_kdf_iter, + None, + )) + } else if encryption_algorithm.is_instance(&types::ENCRYPTION_BUILDER.get(py)?)? + && encryption_algorithm + .getattr(pyo3::intern!(py, "_format"))? + .is(&types::PRIVATE_FORMAT_PKCS12.get(py)?) + { + let key_cert_alg = + encryption_algorithm.getattr(pyo3::intern!(py, "_key_cert_algorithm"))?; + let cipher = if key_cert_alg.is(&types::PBES_PBESV1SHA1AND3KEYTRIPLEDESCBC.get(py)?) { + EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC + } else if key_cert_alg.is(&types::PBES_PBESV2SHA256ANDAES256CBC.get(py)?) { + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC + } else { + assert!(key_cert_alg.is_none()); + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC + }; + + let hmac_alg = if let Some(v) = encryption_algorithm + .getattr(pyo3::intern!(py, "_hmac_hash"))? + .extract()? + { + v + } else { + default_hmac_alg + }; + + let cipher_kdf_iter = if let Some(v) = encryption_algorithm + .getattr(pyo3::intern!(py, "_kdf_rounds"))? + .extract()? + { + v + } else { + default_cipher_kdf_iter + }; + + Ok(( + encryption_algorithm + .getattr(pyo3::intern!(py, "password"))? + .extract()?, + hmac_alg, + default_hmac_kdf_iter, + cipher_kdf_iter, + Some(cipher), + )) + } else if encryption_algorithm.is_instance(&types::BEST_AVAILABLE_ENCRYPTION.get(py)?)? { + Ok(( + encryption_algorithm + .getattr(pyo3::intern!(py, "password"))? + .extract()?, + default_hmac_alg, + default_hmac_kdf_iter, + default_cipher_kdf_iter, + Some(EncryptionAlgorithm::PBESv2SHA256AndAES256CBC), + )) + } else { + Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Unsupported key encryption type"), + )) + } +} + +#[derive(pyo3::FromPyObject)] +enum CertificateOrPKCS12Certificate { + Certificate(pyo3::Py), + PKCS12Certificate(pyo3::Py), +} + +#[pyo3::pyfunction] +#[pyo3(signature = (name, key, cert, cas, encryption_algorithm))] +fn serialize_key_and_certificates<'p>( + py: pyo3::Python<'p>, + name: Option<&[u8]>, + key: Option>, + cert: Option<&Certificate>, + cas: Option>, + encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { + let (password, mac_algorithm, mac_kdf_iter, cipher_kdf_iter, encryption_algorithm) = + decode_encryption_algorithm(py, encryption_algorithm)?; + + let mut auth_safe_contents = vec![]; + let ( + cert_bag_contents, + cert_salt, + cert_iv, + cert_ciphertext, + key_bag_contents, + key_salt, + key_iv, + key_ciphertext, + ); + let mut ca_certs = vec![]; + let mut key_id = None; + if cert.is_some() || cas.is_some() { + let mut cert_bags = vec![]; + + if let Some(cert) = cert { + if let Some(ref key) = key { + if !cert + .public_key(py)? + .eq(key.call_method0(pyo3::intern!(py, "public_key"))?)? + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Certificate public key and provided private key do not match", + ), + )); + } + key_id = Some(cert.fingerprint(py, &types::SHA1.get(py)?.call0()?)?); + } + + cert_bags.push(cert_to_bag( + cert, + name, + key_id.as_ref().map(|v| v.as_bytes()), + )?); + } + + if let Some(cas) = cas { + for cert in cas.try_iter()? { + ca_certs.push(cert?.extract::()?); + } + + for cert in &ca_certs { + let bag = match cert { + CertificateOrPKCS12Certificate::Certificate(c) => { + cert_to_bag(c.get(), None, None)? + } + CertificateOrPKCS12Certificate::PKCS12Certificate(c) => cert_to_bag( + c.get().certificate.get(), + c.get().friendly_name.as_ref().map(|v| v.as_bytes(py)), + None, + )?, + }; + cert_bags.push(bag); + } + } + + cert_bag_contents = asn1::write_single(&asn1::SequenceOfWriter::new(cert_bags))?; + if let Some(e) = &encryption_algorithm { + cert_salt = types::OS_URANDOM + .get(py)? + .call1((e.salt_length(),))? + .extract::()?; + cert_iv = types::OS_URANDOM + .get(py)? + .call1((16,))? + .extract::()?; + cert_ciphertext = e.encrypt( + py, + &password, + cipher_kdf_iter, + &cert_salt, + &cert_iv, + &cert_bag_contents, + )?; + + auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::EncryptedData(asn1::Explicit::new( + cryptography_x509::pkcs7::EncryptedData { + version: 0, + encrypted_content_info: cryptography_x509::pkcs7::EncryptedContentInfo { + content_type: cryptography_x509::pkcs7::PKCS7_DATA_OID, + content_encryption_algorithm: e.algorithm_identifier( + cipher_kdf_iter, + &cert_salt, + &cert_iv, + ), + encrypted_content: Some(&cert_ciphertext), + }, + }, + )), + }) + } else { + auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( + &cert_bag_contents, + ))), + }); + } + } + + if let Some(key) = key { + let der = types::ENCODING_DER.get(py)?; + let pkcs8 = types::PRIVATE_FORMAT_PKCS8.get(py)?; + let no_encryption = types::NO_ENCRYPTION.get(py)?.call0()?; + + let pkcs8_bytes = key + .call_method1( + pyo3::intern!(py, "private_bytes"), + (der, pkcs8, no_encryption), + )? + .extract::()?; + + let key_bag = if let Some(e) = encryption_algorithm { + key_salt = types::OS_URANDOM + .get(py)? + .call1((e.salt_length(),))? + .extract::()?; + key_iv = types::OS_URANDOM + .get(py)? + .call1((16,))? + .extract::()?; + key_ciphertext = e.encrypt( + py, + &password, + cipher_kdf_iter, + &key_salt, + &key_iv, + &pkcs8_bytes, + )?; + + cryptography_x509::pkcs12::SafeBag { + _bag_id: asn1::DefinedByMarker::marker(), + bag_value: asn1::Explicit::new( + cryptography_x509::pkcs12::BagValue::ShroudedKeyBag( + cryptography_x509::pkcs12::EncryptedPrivateKeyInfo { + encryption_algorithm: e.algorithm_identifier( + cipher_kdf_iter, + &key_salt, + &key_iv, + ), + encrypted_data: &key_ciphertext, + }, + ), + ), + attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()))?, + } + } else { + let pkcs8_tlv = asn1::parse_single(&pkcs8_bytes)?; + + cryptography_x509::pkcs12::SafeBag { + _bag_id: asn1::DefinedByMarker::marker(), + bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::KeyBag( + pkcs8_tlv, + )), + attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()))?, + } + }; + + key_bag_contents = asn1::write_single(&asn1::SequenceOfWriter::new([key_bag]))?; + auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( + &key_bag_contents, + ))), + }); + } + + let auth_safe_content = asn1::write_single(&asn1::SequenceOfWriter::new(auth_safe_contents))?; + + let salt = types::OS_URANDOM + .get(py)? + .call1((8,))? + .extract::()?; + let mac_algorithm_md = hashes::message_digest_from_algorithm(py, &mac_algorithm)?; + let mac_key = pkcs12_kdf( + &password, + &salt, + KDF_MAC_KEY_ID, + mac_kdf_iter, + mac_algorithm_md.size(), + mac_algorithm_md, + )?; + let mac_digest = { + let mut h = hmac::Hmac::new_bytes(py, &mac_key, &mac_algorithm)?; + h.update_bytes(&auth_safe_content)?; + h.finalize(py)? + }; + let mac_algorithm_identifier = crate::x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS + [&*mac_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::()?] + .clone(); + + let p12 = cryptography_x509::pkcs12::Pfx { + version: 3, + auth_safe: cryptography_x509::pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( + &auth_safe_content, + ))), + }, + mac_data: Some(cryptography_x509::pkcs12::MacData { + mac: cryptography_x509::pkcs7::DigestInfo { + algorithm: mac_algorithm_identifier, + digest: mac_digest.as_bytes(), + }, + salt: &salt, + iterations: mac_kdf_iter, + }), + }; + Ok(pyo3::types::PyBytes::new(py, &asn1::write_single(&p12)?)) +} + +fn decode_p12( + data: CffiBuf<'_>, + password: Option>, +) -> CryptographyResult { + let p12 = openssl::pkcs12::Pkcs12::from_der(data.as_bytes()).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Could not deserialize PKCS12 data") + })?; + + let password = if let Some(p) = password.as_ref() { + std::str::from_utf8(p.as_bytes()) + .map_err(|_| pyo3::exceptions::PyUnicodeDecodeError::new_err(()))? + } else { + // Treat `password=None` the same as empty string. They're actually + // not the same in PKCS#12, but OpenSSL transparently handles them the + // same. + "" + }; + let parsed = p12 + .parse2(password) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid password or PKCS12 data"))?; + + Ok(parsed) +} + +#[pyo3::pyfunction] +#[pyo3(signature = (data, password, backend=None))] +fn load_key_and_certificates<'p>( + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + password: Option>, + backend: Option>, +) -> CryptographyResult<( + pyo3::Bound<'p, pyo3::PyAny>, + Option, + pyo3::Bound<'p, pyo3::types::PyList>, +)> { + let _ = backend; + + let p12 = decode_p12(data, password)?; + + let private_key = if let Some(pkey) = p12.pkey { + keys::private_key_from_pkey(py, &pkey, false)? + } else { + py.None().into_bound(py) + }; + let cert = if let Some(ossl_cert) = p12.cert { + let cert_der = pyo3::types::PyBytes::new(py, &ossl_cert.to_der()?).unbind(); + Some(x509::certificate::load_der_x509_certificate( + py, cert_der, None, + )?) + } else { + None + }; + let additional_certs = pyo3::types::PyList::empty(py); + if let Some(ossl_certs) = p12.ca { + cfg_if::cfg_if! { + if #[cfg(any( + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL + ))] { + let it = ossl_certs.iter(); + } else { + let it = ossl_certs.iter().rev(); + } + }; + + for ossl_cert in it { + let cert_der = pyo3::types::PyBytes::new(py, &ossl_cert.to_der()?).unbind(); + let cert = x509::certificate::load_der_x509_certificate(py, cert_der, None)?; + additional_certs.append(cert)?; + } + } + + Ok((private_key, cert, additional_certs)) +} + +#[pyo3::pyfunction] +#[pyo3(signature = (data, password, backend=None))] +fn load_pkcs12<'p>( + py: pyo3::Python<'p>, + data: CffiBuf<'_>, + password: Option>, + backend: Option>, +) -> CryptographyResult> { + let _ = backend; + + let p12 = decode_p12(data, password)?; + + let private_key = if let Some(pkey) = p12.pkey { + keys::private_key_from_pkey(py, &pkey, false)? + } else { + py.None().into_bound(py) + }; + let cert = if let Some(ossl_cert) = p12.cert { + let cert_der = pyo3::types::PyBytes::new(py, &ossl_cert.to_der()?).unbind(); + let cert = x509::certificate::load_der_x509_certificate(py, cert_der, None)?; + let alias = ossl_cert + .alias() + .map(|a| pyo3::types::PyBytes::new(py, a).unbind()); + + PKCS12Certificate::new(pyo3::Py::new(py, cert)?, alias) + .into_pyobject(py)? + .into_any() + .unbind() + } else { + py.None() + }; + let additional_certs = pyo3::types::PyList::empty(py); + if let Some(ossl_certs) = p12.ca { + cfg_if::cfg_if! { + if #[cfg(any( + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL + ))] { + let it = ossl_certs.iter(); + } else { + let it = ossl_certs.iter().rev(); + } + }; + + for ossl_cert in it { + let cert_der = pyo3::types::PyBytes::new(py, &ossl_cert.to_der()?).unbind(); + let cert = x509::certificate::load_der_x509_certificate(py, cert_der, None)?; + let alias = ossl_cert + .alias() + .map(|a| pyo3::types::PyBytes::new(py, a).unbind()); + + let p12_cert = PKCS12Certificate::new(pyo3::Py::new(py, cert)?, alias); + additional_certs.append(p12_cert)?; + } + } + + Ok(types::PKCS12KEYANDCERTIFICATES + .get(py)? + .call1((private_key, cert, additional_certs))?) +} + +#[pyo3::pymodule] +pub(crate) mod pkcs12 { + #[pymodule_export] + use super::{ + load_key_and_certificates, load_pkcs12, serialize_key_and_certificates, PKCS12Certificate, + }; +} + +#[cfg(test)] +mod tests { + use super::{pkcs12_kdf, KDF_ENCRYPTION_KEY_ID, KDF_IV_ID, KDF_MAC_KEY_ID}; + + #[test] + fn test_pkcs12_kdf() { + for (password, salt, id, rounds, key_len, hash, expected_key) in [ + // From https://github.com/RustCrypto/formats/blob/master/pkcs12/tests/kdf.rs + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7F\xa3\x17~[\x07h\xa3\x11\x8b\xf8c" as &[u8]), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97sI\xdbn&\xcc\xc9\x98\xd9\xe8\xf8=l"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84G\x02\xc2\xc1\xf3\xb4c!\xe2RJM"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97s"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9d"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05'"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"dr\xc0\xeb\xad?\xabA#\xe8\xb5\xedx4\xde!\xee\xb2\x01\x87\xb3\xef\xf7\x8a}\x1c\xdf\xfa@4\x85\x1d"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"?\x91\x13\xf0\\0\xa9\x96\xc4\xa5\x16@\x9b\xda\xc9\xd0e\xf4B\x96\xcc\xd5+\xb7]\xe3\xfc\xfd\xbe+\xf10"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 100, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r"), + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 200, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r\x9c\xea`\x14\xd7\xfeb\xa2\xed\x92m\xc3ka0\x7f\x11\x9dd\xed\xbc\xebZ\x9cX\x13;\xbfu\xba\x0b\xef\x00\n\x1aQ\x80\xe4\xb1\xde}\x89\xc8\x95(\xbc\xb7\x89\x9a\x1eF\xfdM\xa0\xd9\xde\x8f\x8ee\xe8\xd0\xd7u\xe3=\x12G\xe7mYj401a\xb2\x19\xf3\x9a\xfd\xa4H\xbfQ\x8a(5\xfc^(\xf0\xb5Z\x1ba7\xa2\xc7\x0c\xf7"), + + ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha512(), b"\xb1J\x9f\x01\xbf\xd9\xdc\xe4\xc9\xd6m/\xe9\x93~_\xd9\xf1\xaf\xa5\x9e7\no\xa4\xfc\x81\xc1\xcc\x8e\xc8\xee"), + + // From https://cs.opensource.google/go/x/crypto/+/master:pkcs12/pbkdf_test.go + (b"sesame", b"\xff\xff\xff\xff\xff\xff\xff\xff", KDF_ENCRYPTION_KEY_ID, 2048, 24, openssl::hash::MessageDigest::sha1(), b"\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1"), + ] { + let result = pkcs12_kdf(password, salt, id, rounds, key_len, hash).map_err(|_| ()).unwrap(); + assert_eq!(result, expected_key); + } + } + + #[test] + fn test_pkcs12_kdf_error() { + // Key is not valid UTF-8 + let result = pkcs12_kdf( + b"\x91\x82%\xa1", + b"\x01\x02\x03\x04", + KDF_ENCRYPTION_KEY_ID, + 100, + 8, + openssl::hash::MessageDigest::sha256(), + ); + assert!(matches!(result, Err(_))); + } +} diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs index f307cf483ad7..90cd063f8b6a 100644 --- a/src/rust/src/pkcs7.rs +++ b/src/rust/src/pkcs7.rs @@ -6,17 +6,21 @@ use std::borrow::Cow; use std::collections::HashMap; use std::ops::Deref; +use cryptography_x509::common::{AlgorithmIdentifier, AlgorithmParameters}; use cryptography_x509::csr::Attribute; +use cryptography_x509::pkcs7::PKCS7_DATA_OID; use cryptography_x509::{common, oid, pkcs7}; use once_cell::sync::Lazy; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use openssl::pkcs7::Pkcs7; -#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] -use pyo3::IntoPy; +use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use crate::asn1::encode_der_data; +use crate::backend::ciphers; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; +use crate::padding::PKCS7UnpaddingContext; +use crate::pkcs12::symmetric_encrypt; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use crate::x509::certificate::load_der_x509_certificate; use crate::{exceptions, types, x509}; @@ -26,10 +30,6 @@ const PKCS7_MESSAGE_DIGEST_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 1 const PKCS7_SIGNING_TIME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 5); const PKCS7_SMIME_CAP_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 15); -const AES_256_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 42); -const AES_192_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 22); -const AES_128_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 2); - static OIDS_TO_MIC_NAME: Lazy> = Lazy::new(|| { let mut h = HashMap::new(); h.insert(&oid::SHA224_OID, "sha-224"); @@ -39,12 +39,12 @@ static OIDS_TO_MIC_NAME: Lazy> = Lazy::ne h }); -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn serialize_certificates<'p>( py: pyo3::Python<'p>, py_certs: Vec>, - encoding: &'p pyo3::PyAny, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, +) -> CryptographyResult> { if py_certs.is_empty() { return Err(pyo3::exceptions::PyTypeError::new_err( "certs must be a list of certs with length >= 1", @@ -54,19 +54,21 @@ fn serialize_certificates<'p>( let raw_certs = py_certs .iter() - .map(|c| c.raw.borrow_dependent()) + .map(|c| c.raw.borrow_dependent().clone()) .collect::>(); let signed_data = pkcs7::SignedData { version: 1, - digest_algorithms: asn1::SetOfWriter::new(&[]), + digest_algorithms: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(&[])), content_info: pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), - content: pkcs7::Content::Data(Some(asn1::Explicit::new(b""))), + content: pkcs7::Content::Data(None), }, - certificates: Some(asn1::SetOfWriter::new(&raw_certs)), + certificates: Some(common::Asn1ReadableOrWritable::new_write( + asn1::SetOfWriter::new(&raw_certs), + )), crls: None, - signer_infos: asn1::SetOfWriter::new(&[]), + signer_infos: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(&[])), }; let content_info = pkcs7::ContentInfo { @@ -78,13 +80,358 @@ fn serialize_certificates<'p>( encode_der_data(py, "PKCS7".to_string(), content_info_bytes, encoding) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] +fn encrypt_and_serialize<'p>( + py: pyo3::Python<'p>, + builder: &pyo3::Bound<'p, pyo3::PyAny>, + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + options: &pyo3::Bound<'p, pyo3::types::PyList>, +) -> CryptographyResult> { + let raw_data: CffiBuf<'p> = builder.getattr(pyo3::intern!(py, "_data"))?.extract()?; + let text_mode = options.contains(types::PKCS7_TEXT.get(py)?)?; + let data_with_header = if options.contains(types::PKCS7_BINARY.get(py)?)? { + Cow::Borrowed(raw_data.as_bytes()) + } else { + smime_canonicalize(raw_data.as_bytes(), text_mode).0 + }; + + // The message is encrypted with AES-128-CBC, which the S/MIME v3.2 RFC + // specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7) + let key = types::OS_URANDOM.get(py)?.call1((16,))?; + let aes128_algorithm = types::AES128.get(py)?.call1((&key,))?; + let iv = types::OS_URANDOM.get(py)?.call1((16,))?; + let cbc_mode = types::CBC.get(py)?.call1((&iv,))?; + + let encrypted_content = symmetric_encrypt(py, aes128_algorithm, cbc_mode, &data_with_header)?; + + let py_recipients: Vec> = builder + .getattr(pyo3::intern!(py, "_recipients"))? + .extract()?; + + let mut recipient_infos = vec![]; + let padding = types::PKCS1V15.get(py)?.call0()?; + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + for cert in py_recipients.iter() { + // Currently, keys are encrypted with RSA (PKCS #1 v1.5), which the S/MIME v3.2 RFC + // specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.3) + let encrypted_key = cert + .call_method0(pyo3::intern!(py, "public_key"))? + .call_method1(pyo3::intern!(py, "encrypt"), (&key, &padding))? + .extract::()?; + + recipient_infos.push(pkcs7::RecipientInfo { + version: 0, + issuer_and_serial_number: pkcs7::IssuerAndSerialNumber { + issuer: cert.get().raw.borrow_dependent().tbs_cert.issuer.clone(), + serial_number: cert.get().raw.borrow_dependent().tbs_cert.serial, + }, + key_encryption_algorithm: AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Rsa(Some(())), + }, + encrypted_key: ka_bytes.add(encrypted_key), + }); + } + + let enveloped_data = pkcs7::EnvelopedData { + version: 0, + recipient_infos: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( + &recipient_infos, + )), + + encrypted_content_info: pkcs7::EncryptedContentInfo { + content_type: PKCS7_DATA_OID, + content_encryption_algorithm: AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Aes128Cbc(iv.extract()?), + }, + encrypted_content: Some(&encrypted_content), + }, + }; + + let content_info = pkcs7::ContentInfo { + _content_type: asn1::DefinedByMarker::marker(), + content: pkcs7::Content::EnvelopedData(asn1::Explicit::new(Box::new(enveloped_data))), + }; + let ci_bytes = asn1::write_single(&content_info)?; + + if encoding.is(&types::ENCODING_SMIME.get(py)?) { + Ok(types::SMIME_ENVELOPED_ENCODE + .get(py)? + .call1((&*ci_bytes,))? + .extract()?) + } else { + // Handles the DER, PEM, and error cases + encode_der_data(py, "PKCS7".to_string(), ci_bytes, encoding) + } +} + +#[pyo3::pyfunction] +fn decrypt_smime<'p>( + py: pyo3::Python<'p>, + data: CffiBuf<'p>, + certificate: pyo3::Bound<'p, x509::certificate::Certificate>, + private_key: pyo3::Bound<'p, pyo3::types::PyAny>, + options: &pyo3::Bound<'p, pyo3::types::PyList>, +) -> CryptographyResult> { + let decoded_smime_data = types::SMIME_ENVELOPED_DECODE + .get(py)? + .call1((data.as_bytes(),))?; + let data = decoded_smime_data.extract()?; + + decrypt_der(py, data, certificate, private_key, options) +} +#[pyo3::pyfunction] +fn decrypt_pem<'p>( + py: pyo3::Python<'p>, + data: &[u8], + certificate: pyo3::Bound<'p, x509::certificate::Certificate>, + private_key: pyo3::Bound<'p, pyo3::types::PyAny>, + options: &pyo3::Bound<'p, pyo3::types::PyList>, +) -> CryptographyResult> { + let pem_str = std::str::from_utf8(data) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid PEM data"))?; + let pem = pem::parse(pem_str) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Failed to parse PEM data"))?; + + // Raise error if the PEM tag is not PKCS7 + if pem.tag() != "PKCS7" { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The provided PEM data does not have the PKCS7 tag.", + ), + )); + } + + decrypt_der(py, &pem.into_contents(), certificate, private_key, options) +} + +#[pyo3::pyfunction] +fn decrypt_der<'p>( + py: pyo3::Python<'p>, + data: &[u8], + certificate: pyo3::Bound<'p, x509::certificate::Certificate>, + private_key: pyo3::Bound<'p, pyo3::types::PyAny>, + options: &pyo3::Bound<'p, pyo3::types::PyList>, +) -> CryptographyResult> { + // Check the decrypt parameters + check_decrypt_parameters(py, &certificate, &private_key, options)?; + + // Decrypt the data + let content_info = asn1::parse_single::>(data)?; + let plain_content = match content_info.content { + pkcs7::Content::EnvelopedData(data) => { + // Extract enveloped data + let enveloped_data = data.into_inner(); + + // Get recipients, and the one matching with the given certificate (if any) + let mut recipient_infos = enveloped_data.recipient_infos.unwrap_read().clone(); + let recipient_certificate = certificate.get().raw.borrow_dependent(); + let recipient_serial_number = recipient_certificate.tbs_cert.serial; + let recipient_issuer = recipient_certificate.tbs_cert.issuer.clone(); + let found_recipient_info = recipient_infos.find(|info| { + info.issuer_and_serial_number.serial_number == recipient_serial_number + && info.issuer_and_serial_number.issuer == recipient_issuer + }); + + // Raise error when no recipient is found + let recipient_info = match found_recipient_info { + Some(info) => info, + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "No recipient found that matches the given certificate.", + ), + )); + } + }; + + // Raise error when the key encryption algorithm is not RSA + let key = match recipient_info.key_encryption_algorithm.oid() { + &oid::RSA_OID => { + let padding = types::PKCS1V15.get(py)?.call0()?; + private_key + .call_method1( + pyo3::intern!(py, "decrypt"), + (recipient_info.encrypted_key, &padding), + )? + .extract::()? + } + _ => { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Only RSA with PKCS #1 v1.5 padding is currently supported for key decryption.", + exceptions::Reasons::UNSUPPORTED_SERIALIZATION, + )), + )); + } + }; + + // Get algorithm + // TODO: implement all the possible algorithms + let algorithm_identifier = enveloped_data + .encrypted_content_info + .content_encryption_algorithm; + let (algorithm, mode) = match algorithm_identifier.params { + AlgorithmParameters::Aes128Cbc(iv) => ( + types::AES128.get(py)?.call1((key,))?, + types::CBC + .get(py)? + .call1((pyo3::types::PyBytes::new(py, &iv),))?, + ), + _ => { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "Only AES-128-CBC is currently supported for content decryption.", + exceptions::Reasons::UNSUPPORTED_SERIALIZATION, + )), + )); + } + }; + + // Decrypt the content using the key and proper algorithm + let encrypted_content = match enveloped_data.encrypted_content_info.encrypted_content { + Some(content) => content, + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The EnvelopedData structure does not contain encrypted content.", + ), + )); + } + }; + let decrypted_content = symmetric_decrypt(py, algorithm, mode, encrypted_content)?; + pyo3::types::PyBytes::new(py, decrypted_content.as_slice()) + } + _ => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The PKCS7 data is not an EnvelopedData structure.", + ), + )); + } + }; + + // If text_mode, remove the headers after checking the content type + let plain_data = if options.contains(types::PKCS7_TEXT.get(py)?)? { + let stripped_data = types::SMIME_REMOVE_TEXT_HEADERS + .get(py)? + .call1((plain_content.as_bytes(),))?; + pyo3::types::PyBytes::new(py, stripped_data.extract()?) + } else { + pyo3::types::PyBytes::new(py, plain_content.as_bytes()) + }; + + Ok(plain_data) +} + +fn check_decrypt_parameters<'p>( + py: pyo3::Python<'p>, + certificate: &pyo3::Bound<'p, x509::certificate::Certificate>, + private_key: &pyo3::Bound<'p, pyo3::PyAny>, + options: &pyo3::Bound<'p, pyo3::types::PyList>, +) -> Result<(), CryptographyError> { + // Check if RSA encryption with PKCS1 v1.5 padding is supported (dependent of FIPS mode) + if cryptography_openssl::fips::is_enabled() { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "RSA with PKCS1 v1.5 padding is not supported by this version of OpenSSL.", + exceptions::Reasons::UNSUPPORTED_PADDING, + )), + )); + } + + // Check if all options are from the PKCS7Options enum + let pkcs7_options = types::PKCS7_OPTIONS.get(py)?; + for opt in options.iter() { + if !opt.is_instance(&pkcs7_options)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "options must be from the PKCS7Options enum", + ), + )); + } + } + + // Check if any option is not PKCS7Options::Text + let text_option = types::PKCS7_TEXT.get(py)?; + for opt in options.iter() { + if !opt.eq(text_option.clone())? { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Only the following options are supported for decryption: Text", + ), + )); + } + } + + // Check if certificate's public key is an RSA public key + let public_key_type = types::RSA_PUBLIC_KEY.get(py)?; + if !certificate + .call_method0(pyo3::intern!(py, "public_key"))? + .is_instance(&public_key_type)? + { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Only certificate with RSA public keys are supported at this time.", + ), + )); + } + + // Check if private_key is an instance of RSA private key + let private_key_type = types::RSA_PRIVATE_KEY.get(py)?; + if !private_key.is_instance(&private_key_type)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Only RSA private keys are supported at this time.", + ), + )); + } + + Ok(()) +} + +pub(crate) fn symmetric_decrypt( + py: pyo3::Python<'_>, + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + mode: pyo3::Bound<'_, pyo3::PyAny>, + data: &[u8], +) -> CryptographyResult> { + let block_size = algorithm + .getattr(pyo3::intern!(py, "block_size"))? + .extract()?; + + let mut cipher = + ciphers::CipherContext::new(py, algorithm, mode, openssl::symm::Mode::Decrypt)?; + + // Decrypt the data + let mut decrypted_data = vec![0; data.len() + (block_size / 8)]; + let count = cipher.update_into(py, data, &mut decrypted_data)?; + let final_block = cipher.finalize(py)?; + assert!(final_block.as_bytes().is_empty()); + decrypted_data.truncate(count); + + // Unpad the data + let mut unpadder = PKCS7UnpaddingContext::new(block_size); + let unpadded_first_blocks = unpadder.update(py, CffiBuf::from_bytes(py, &decrypted_data))?; + let unpadded_last_block = unpadder.finalize(py)?; + + let unpadded_data = [ + unpadded_first_blocks.as_bytes(), + unpadded_last_block.as_bytes(), + ] + .concat(); + + Ok(unpadded_data) +} + +#[pyo3::pyfunction] fn sign_and_serialize<'p>( py: pyo3::Python<'p>, - builder: &'p pyo3::PyAny, - encoding: &'p pyo3::PyAny, - options: &'p pyo3::types::PyList, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { + builder: &pyo3::Bound<'p, pyo3::PyAny>, + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + options: &pyo3::Bound<'p, pyo3::types::PyList>, +) -> CryptographyResult> { let raw_data: CffiBuf<'p> = builder.getattr(pyo3::intern!(py, "_data"))?.extract()?; let text_mode = options.contains(types::PKCS7_TEXT.get(py)?)?; let (data_with_header, data_without_header) = @@ -104,16 +451,17 @@ fn sign_and_serialize<'p>( // Subset of values OpenSSL provides: // https://github.com/openssl/openssl/blob/667a8501f0b6e5705fd611d5bb3ca24848b07154/crypto/pkcs7/pk7_smime.c#L150 // removing all the ones that are bad cryptography - AES_256_CBC_OID, - AES_192_CBC_OID, - AES_128_CBC_OID, + asn1::SequenceOfWriter::new([oid::AES_256_CBC_OID]), + asn1::SequenceOfWriter::new([oid::AES_192_CBC_OID]), + asn1::SequenceOfWriter::new([oid::AES_128_CBC_OID]), ]))?; + #[allow(clippy::type_complexity)] let py_signers: Vec<( pyo3::PyRef<'p, x509::certificate::Certificate>, - &pyo3::PyAny, - &pyo3::PyAny, - &pyo3::PyAny, + pyo3::Bound<'_, pyo3::PyAny>, + pyo3::Bound<'_, pyo3::PyAny>, + pyo3::Bound<'_, pyo3::PyAny>, )> = builder.getattr(pyo3::intern!(py, "_signers"))?.extract()?; let py_certs: Vec> = builder @@ -124,78 +472,84 @@ fn sign_and_serialize<'p>( let mut digest_algs = vec![]; let mut certs = py_certs .iter() - .map(|p| p.raw.borrow_dependent()) + .map(|p| p.raw.borrow_dependent().clone()) .collect::>(); - for (cert, py_private_key, py_hash_alg, rsa_padding) in &py_signers { - let (authenticated_attrs, signature) = if options - .contains(types::PKCS7_NO_ATTRIBUTES.get(py)?)? - { - ( - None, - x509::sign::sign_data( - py, - py_private_key, - py_hash_alg, - rsa_padding, - &data_with_header, - )?, - ) - } else { - let mut authenticated_attrs = vec![ - Attribute { - type_id: PKCS7_CONTENT_TYPE_OID, - values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ - asn1::parse_single(&content_type_bytes).unwrap(), - ])), - }, - Attribute { - type_id: PKCS7_SIGNING_TIME_OID, - values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ - asn1::parse_single(&signing_time_bytes).unwrap(), - ])), - }, - ]; - - let digest = - asn1::write_single(&x509::ocsp::hash_data(py, py_hash_alg, &data_with_header)?)?; - // Gross hack: copy to PyBytes to extend the lifetime to 'p - let digest_bytes = pyo3::types::PyBytes::new(py, &digest); - authenticated_attrs.push(Attribute { - type_id: PKCS7_MESSAGE_DIGEST_OID, - values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ - asn1::parse_single(digest_bytes.as_bytes()).unwrap(), - ])), - }); - if !options.contains(types::PKCS7_NO_CAPABILITIES.get(py)?)? { + let ka_vec = cryptography_keepalive::KeepAlive::new(); + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + for (cert, py_private_key, py_hash_alg, rsa_padding) in py_signers.iter() { + let (authenticated_attrs, signature) = + if options.contains(&types::PKCS7_NO_ATTRIBUTES.get(py)?)? { + ( + None, + x509::sign::sign_data( + py, + py_private_key.clone(), + py_hash_alg.clone(), + rsa_padding.clone(), + &data_with_header, + )?, + ) + } else { + let mut authenticated_attrs = vec![ + Attribute { + type_id: PKCS7_CONTENT_TYPE_OID, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( + [asn1::parse_single(&content_type_bytes).unwrap()], + )), + }, + Attribute { + type_id: PKCS7_SIGNING_TIME_OID, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( + [asn1::parse_single(&signing_time_bytes).unwrap()], + )), + }, + ]; + + let digest = x509::ocsp::hash_data(py, py_hash_alg, &data_with_header)?; + let digest_wrapped = ka_vec.add(asn1::write_single(&digest.as_bytes())?); authenticated_attrs.push(Attribute { - type_id: PKCS7_SMIME_CAP_OID, + type_id: PKCS7_MESSAGE_DIGEST_OID, values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ - asn1::parse_single(&smime_cap_bytes).unwrap(), + asn1::parse_single(digest_wrapped).unwrap(), ])), }); - } - let signed_data = - asn1::write_single(&asn1::SetOfWriter::new(authenticated_attrs.as_slice()))?; - - ( - Some(common::Asn1ReadableOrWritable::new_write( - asn1::SetOfWriter::new(authenticated_attrs), - )), - x509::sign::sign_data(py, py_private_key, py_hash_alg, rsa_padding, &signed_data)?, - ) - }; - - let digest_alg = x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[py_hash_alg + 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( + [asn1::parse_single(&smime_cap_bytes).unwrap()], + )), + }); + } + + let signed_data = + asn1::write_single(&asn1::SetOfWriter::new(authenticated_attrs.as_slice()))?; + + ( + Some(common::Asn1ReadableOrWritable::new_write( + asn1::SetOfWriter::new(authenticated_attrs), + )), + x509::sign::sign_data( + py, + py_private_key.clone(), + py_hash_alg.clone(), + rsa_padding.clone(), + &signed_data, + )?, + ) + }; + + let digest_alg = x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[&*py_hash_alg .getattr(pyo3::intern!(py, "name"))? - .extract::<&str>()?] - .clone(); + .extract::()?] + .clone(); // Technically O(n^2), but no one will have that many signers. if !digest_algs.contains(&digest_alg) { digest_algs.push(digest_alg.clone()); } - certs.push(cert.raw.borrow_dependent()); + certs.push(cert.raw.borrow_dependent().clone()); signer_infos.push(pkcs7::SignerInfo { version: 1, @@ -205,13 +559,13 @@ fn sign_and_serialize<'p>( }, digest_algorithm: digest_alg, authenticated_attributes: authenticated_attrs, - digest_encryption_algorithm: x509::sign::compute_signature_algorithm( + digest_encryption_algorithm: compute_pkcs7_signature_algorithm( py, - py_private_key, - py_hash_alg, - rsa_padding, + py_private_key.clone(), + py_hash_alg.clone(), + rsa_padding.clone(), )?, - encrypted_digest: signature, + encrypted_digest: ka_bytes.add(signature), unauthenticated_attributes: None, }); } @@ -226,7 +580,9 @@ fn sign_and_serialize<'p>( let signed_data = pkcs7::SignedData { version: 1, - digest_algorithms: asn1::SetOfWriter::new(&digest_algs), + digest_algorithms: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( + &digest_algs, + )), content_info: pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: pkcs7::Content::Data(content.map(asn1::Explicit::new)), @@ -234,10 +590,14 @@ fn sign_and_serialize<'p>( certificates: if options.contains(types::PKCS7_NO_CERTS.get(py)?)? { None } else { - Some(asn1::SetOfWriter::new(&certs)) + Some(common::Asn1ReadableOrWritable::new_write( + asn1::SetOfWriter::new(&certs), + )) }, crls: None, - signer_infos: asn1::SetOfWriter::new(&signer_infos), + signer_infos: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( + &signer_infos, + )), }; let content_info = pkcs7::ContentInfo { @@ -246,13 +606,13 @@ fn sign_and_serialize<'p>( }; let ci_bytes = asn1::write_single(&content_info)?; - if encoding.is(types::ENCODING_SMIME.get(py)?) { + if encoding.is(&types::ENCODING_SMIME.get(py)?) { let mic_algs = digest_algs .iter() .map(|d| OIDS_TO_MIC_NAME[&d.oid()]) .collect::>() .join(","); - Ok(types::SMIME_ENCODE + Ok(types::SMIME_SIGNED_ENCODE .get(py)? .call1((&*data_without_header, &*ci_bytes, mic_algs, text_mode))? .extract()?) @@ -262,6 +622,26 @@ fn sign_and_serialize<'p>( } } +fn compute_pkcs7_signature_algorithm<'p>( + py: pyo3::Python<'p>, + private_key: pyo3::Bound<'p, pyo3::PyAny>, + hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, + rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, +) -> pyo3::PyResult> { + let key_type = x509::sign::identify_key_type(py, private_key.clone())?; + let has_pss_padding = rsa_padding.is_instance(&types::PSS.get(py)?)?; + // For RSA signatures (with no PSS padding), the OID is always the same no matter the + // digest algorithm. See RFC 3370 (section 3.2). + if key_type == x509::sign::KeyType::Rsa && !has_pss_padding { + Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Rsa(Some(())), + }) + } else { + x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding) + } +} + fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [u8]>) { let mut new_data_with_header = vec![]; let mut new_data_without_header = vec![]; @@ -300,7 +680,7 @@ fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [ fn load_pkcs7_certificates( py: pyo3::Python<'_>, pkcs7: Pkcs7, -) -> CryptographyResult<&pyo3::types::PyList> { +) -> CryptographyResult> { 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()); @@ -322,20 +702,20 @@ fn load_pkcs7_certificates( 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_der = pyo3::types::PyBytes::new(py, c.to_der()?.as_slice()).unbind(); let cert = load_der_x509_certificate(py, cert_der, None)?; - result.append(cert.into_py(py))?; + result.append(cert)?; } Ok(result) } } } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn load_pem_pkcs7_certificates<'p>( py: pyo3::Python<'p>, data: &[u8], -) -> CryptographyResult<&'p pyo3::types::PyList> { +) -> CryptographyResult> { cfg_if::cfg_if! { if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_pem(data).map_err(|_| { @@ -345,21 +725,23 @@ fn load_pem_pkcs7_certificates<'p>( })?; load_pkcs7_certificates(py, pkcs7_decoded) } else { - return Err(CryptographyError::from( + let _ = py; + let _ = data; + Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "PKCS#7 is not supported by this backend.", exceptions::Reasons::UNSUPPORTED_SERIALIZATION, )), - )); + )) } } } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] fn load_der_pkcs7_certificates<'p>( py: pyo3::Python<'p>, data: &[u8], -) -> CryptographyResult<&'p pyo3::types::PyList> { +) -> CryptographyResult> { cfg_if::cfg_if! { if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_der(data).map_err(|_| { @@ -369,25 +751,27 @@ fn load_der_pkcs7_certificates<'p>( })?; load_pkcs7_certificates(py, pkcs7_decoded) } else { - return Err(CryptographyError::from( + let _ = py; + let _ = data; + 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) +#[pyo3::pymodule] +#[pyo3(name = "pkcs7")] +pub(crate) mod pkcs7_mod { + #[pymodule_export] + use super::{ + decrypt_der, decrypt_pem, decrypt_smime, encrypt_and_serialize, + load_der_pkcs7_certificates, load_pem_pkcs7_certificates, serialize_certificates, + sign_and_serialize, + }; } #[cfg(test)] diff --git a/src/rust/src/test_support.rs b/src/rust/src/test_support.rs new file mode 100644 index 000000000000..8f4599723680 --- /dev/null +++ b/src/rust/src/test_support.rs @@ -0,0 +1,113 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +use crate::buf::CffiBuf; +use crate::error::CryptographyResult; +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +use crate::types; +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +use crate::x509::certificate::Certificate as PyCertificate; +use asn1::SimpleAsn1Readable; +use cryptography_x509::certificate::Certificate; +use cryptography_x509::common::Time; +use cryptography_x509::name::Name; +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +use pyo3::prelude::PyAnyMethods; + +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.test_support")] +struct TestCertificate { + #[pyo3(get)] + not_before_tag: u8, + #[pyo3(get)] + not_after_tag: u8, + #[pyo3(get)] + issuer_value_tags: Vec, + #[pyo3(get)] + subject_value_tags: Vec, +} + +fn parse_name_value_tags(rdns: &Name<'_>) -> Vec { + let mut tags = vec![]; + for rdn in rdns.unwrap_read().clone() { + let mut attributes = rdn.collect::>(); + assert_eq!(attributes.len(), 1); + + tags.push(attributes.pop().unwrap().value.tag().as_u8().unwrap()); + } + tags +} + +fn time_tag(t: &Time) -> u8 { + match t { + Time::UtcTime(_) => asn1::UtcTime::TAG.as_u8().unwrap(), + Time::GeneralizedTime(_) => asn1::GeneralizedTime::TAG.as_u8().unwrap(), + } +} + +#[pyo3::pyfunction] +fn test_parse_certificate(data: &[u8]) -> CryptographyResult { + let cert = asn1::parse_single::>(data)?; + + Ok(TestCertificate { + not_before_tag: time_tag(&cert.tbs_cert.validity.not_before), + not_after_tag: time_tag(&cert.tbs_cert.validity.not_after), + issuer_value_tags: parse_name_value_tags(&cert.tbs_cert.issuer), + subject_value_tags: parse_name_value_tags(&cert.tbs_cert.subject), + }) +} + +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[pyo3::pyfunction] +#[pyo3(signature = (encoding, sig, msg, certs, options))] +fn pkcs7_verify( + py: pyo3::Python<'_>, + encoding: pyo3::Bound<'_, pyo3::PyAny>, + sig: &[u8], + msg: Option>, + certs: Vec>, + options: pyo3::Bound<'_, pyo3::types::PyList>, +) -> CryptographyResult<()> { + let p7 = if encoding.is(&types::ENCODING_DER.get(py)?) { + openssl::pkcs7::Pkcs7::from_der(sig)? + } else if encoding.is(&types::ENCODING_PEM.get(py)?) { + openssl::pkcs7::Pkcs7::from_pem(sig)? + } else { + openssl::pkcs7::Pkcs7::from_smime(sig)?.0 + }; + + let mut flags = openssl::pkcs7::Pkcs7Flags::empty(); + if options.contains(types::PKCS7_TEXT.get(py)?)? { + flags |= openssl::pkcs7::Pkcs7Flags::TEXT; + } + + let store = { + let mut b = openssl::x509::store::X509StoreBuilder::new()?; + for cert in &certs { + let der = asn1::write_single(cert.get().raw.borrow_dependent())?; + b.add_cert(openssl::x509::X509::from_der(&der)?)?; + } + b.build() + }; + let certs = openssl::stack::Stack::new()?; + + p7.verify( + &certs, + &store, + msg.as_ref().map(|m| m.as_bytes()), + None, + flags, + )?; + + Ok(()) +} + +#[pyo3::pymodule] +pub(crate) mod test_support { + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + #[pymodule_export] + use super::pkcs7_verify; + #[pymodule_export] + use super::test_parse_certificate; +} diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs index 07cf417971b6..37ca3f424249 100644 --- a/src/rust/src/types.rs +++ b/src/rust/src/types.rs @@ -2,6 +2,8 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use pyo3::types::PyAnyMethods; + pub struct LazyPyImport { module: &'static str, names: &'static [&'static str], @@ -17,16 +19,16 @@ impl LazyPyImport { } } - 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 fn get<'p>(&'p self, py: pyo3::Python<'p>) -> pyo3::PyResult> { + let p = self.value.get_or_try_init(py, || { + let mut obj = py.import(self.module)?.into_any(); + for name in self.names { + obj = obj.getattr(*name)?; + } + Ok::<_, pyo3::PyErr>(obj.unbind()) + })?; + + Ok(p.clone_ref(py).into_bound(py)) } } @@ -43,6 +45,8 @@ pub static DEPRECATED_IN_41: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn41"]); pub static DEPRECATED_IN_42: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn42"]); +pub static DEPRECATED_IN_43: LazyPyImport = + LazyPyImport::new("cryptography.utils", &["DeprecatedIn43"]); pub static ENCODING: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", @@ -85,6 +89,10 @@ pub static PRIVATE_FORMAT_PKCS8: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat", "PKCS8"], ); +pub static PRIVATE_FORMAT_PKCS12: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization", + &["PrivateFormat", "PKCS12"], +); pub static PRIVATE_FORMAT_RAW: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat", "Raw"], @@ -145,6 +153,15 @@ pub static ENCRYPTION_BUILDER: LazyPyImport = LazyPyImport::new( &["_KeySerializationEncryption"], ); +pub static PBES_PBESV1SHA1AND3KEYTRIPLEDESCBC: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs12", + &["PBES", "PBESv1SHA1And3KeyTripleDESCBC"], +); +pub static PBES_PBESV2SHA256ANDAES256CBC: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs12", + &["PBES", "PBESv2SHA256AndAES256CBC"], +); + pub static SERIALIZE_SSH_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.ssh", &["_serialize_ssh_private_key"], @@ -246,11 +263,21 @@ 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 ADMISSION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Admission"]); +pub static NAMING_AUTHORITY: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["NamingAuthority"]); +pub static PROFESSION_INFO: LazyPyImport = + LazyPyImport::new("cryptography.x509", &["ProfessionInfo"]); +pub static ADMISSIONS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Admissions"]); 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 CRL_ENTRY_REASON_ENUM_TO_CODE: LazyPyImport = LazyPyImport::new( + "cryptography.x509.extensions", + &["_CRL_ENTRY_REASON_ENUM_TO_CODE"], +); pub static TLS_FEATURE_TYPE_TO_ENUM: LazyPyImport = LazyPyImport::new( "cryptography.x509.extensions", &["_TLS_FEATURE_TYPE_TO_ENUM"], @@ -293,6 +320,11 @@ pub static ASN1_TYPE_BMP_STRING: LazyPyImport = pub static ASN1_TYPE_UNIVERSAL_STRING: LazyPyImport = LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "UniversalString"]); +pub static PKCS7_OPTIONS: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["PKCS7Options"], +); + pub static PKCS7_BINARY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["PKCS7Options", "Binary"], @@ -318,21 +350,44 @@ pub static PKCS7_DETACHED_SIGNATURE: LazyPyImport = LazyPyImport::new( &["PKCS7Options", "DetachedSignature"], ); -pub static SMIME_ENCODE: LazyPyImport = LazyPyImport::new( +pub static SMIME_ENVELOPED_ENCODE: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["_smime_enveloped_encode"], +); + +pub static SMIME_ENVELOPED_DECODE: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["_smime_enveloped_decode"], +); + +pub static SMIME_REMOVE_TEXT_HEADERS: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs7", + &["_smime_remove_text_headers"], +); + +pub static SMIME_SIGNED_ENCODE: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", - &["_smime_encode"], + &["_smime_signed_encode"], +); + +pub static PKCS12KEYANDCERTIFICATES: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.serialization.pkcs12", + &["PKCS12KeyAndCertificates"], ); pub static HASHES_MODULE: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &[]); pub static HASH_ALGORITHM: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["HashAlgorithm"]); +#[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] 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 SHA256: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA256"]); pub static PREHASHED: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.utils", @@ -375,15 +430,6 @@ pub static CALCULATE_MAX_PSS_SALT_LENGTH: LazyPyImport = LazyPyImport::new( &["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"], @@ -441,8 +487,15 @@ pub static DSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( &["DSAPublicKey"], ); -pub static EXTRACT_BUFFER_LENGTH: LazyPyImport = - LazyPyImport::new("cryptography.utils", &["_extract_buffer_length"]); +pub static FFI_FROM_BUFFER: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.bindings._rust", + &["_openssl", "ffi", "from_buffer"], +); + +pub static FFI_CAST: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.bindings._rust", + &["_openssl", "ffi", "cast"], +); pub static BLOCK_CIPHER_ALGORITHM: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers", @@ -450,7 +503,7 @@ pub static BLOCK_CIPHER_ALGORITHM: LazyPyImport = LazyPyImport::new( ); pub static TRIPLE_DES: LazyPyImport = LazyPyImport::new( - "cryptography.hazmat.primitives.ciphers.algorithms", + "cryptography.hazmat.decrepit.ciphers.algorithms", &["TripleDES"], ); pub static AES: LazyPyImport = LazyPyImport::new( @@ -465,34 +518,80 @@ pub static AES256: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["AES256"], ); -pub static SM4: LazyPyImport = LazyPyImport::new( +pub static CHACHA20: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", - &["SM4"], + &["ChaCha20"], ); -pub static SEED: LazyPyImport = LazyPyImport::new( +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] +pub static SM4: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", - &["_SEEDInternal"], + &["SM4"], ); +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] +pub static SEED: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["SEED"]); +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] pub static CAMELLIA: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["Camellia"], ); +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] pub static BLOWFISH: LazyPyImport = LazyPyImport::new( - "cryptography.hazmat.primitives.ciphers.algorithms", - &["_BlowfishInternal"], + "cryptography.hazmat.decrepit.ciphers.algorithms", + &["Blowfish"], ); +#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] pub static CAST5: LazyPyImport = LazyPyImport::new( - "cryptography.hazmat.primitives.ciphers.algorithms", - &["_CAST5Internal"], + "cryptography.hazmat.decrepit.ciphers.algorithms", + &["CAST5"], ); #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] -pub static IDEA: LazyPyImport = LazyPyImport::new( - "cryptography.hazmat.primitives.ciphers.algorithms", - &["_IDEAInternal"], -); +pub static IDEA: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["IDEA"]); +pub static ARC4: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["ARC4"]); +pub static RC2: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["RC2"]); +pub static MODE_WITH_INITIALIZATION_VECTOR: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.modes", + &["ModeWithInitializationVector"], +); +pub static MODE_WITH_TWEAK: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.modes", + &["ModeWithTweak"], +); +pub static MODE_WITH_NONCE: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.modes", + &["ModeWithNonce"], +); +pub static MODE_WITH_AUTHENTICATION_TAG: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.ciphers.modes", + &["ModeWithAuthenticationTag"], +); pub static CBC: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CBC"]); +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +pub static CFB: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CFB"]); +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +pub static CFB8: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CFB8"]); +pub static OFB: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["OFB"]); +pub static ECB: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["ECB"]); +pub static CTR: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CTR"]); +pub static GCM: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["GCM"]); +pub static XTS: LazyPyImport = + LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["XTS"]); + +pub static LEGACY_PROVIDER_LOADED: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.bindings._rust", + &["openssl", "_legacy_provider_loaded"], +); #[cfg(test)] mod tests { diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs index 552f4eda7d81..adef55f6abf3 100644 --- a/src/rust/src/x509/certificate.rs +++ b/src/rust/src/x509/certificate.rs @@ -6,24 +6,26 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use cryptography_x509::certificate::Certificate as RawCertificate; +use cryptography_x509::common::Asn1Read; use cryptography_x509::common::{AlgorithmParameters, Asn1ReadableOrWritable}; use cryptography_x509::extensions::{ - AuthorityKeyIdentifier, BasicConstraints, DisplayText, DistributionPoint, - DistributionPointName, DuplicateExtensionsError, IssuerAlternativeName, KeyUsage, - MSCertificateTemplate, NameConstraints, PolicyConstraints, PolicyInformation, - PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions, - SequenceOfSubtrees, UserNotice, + Admission, Admissions, AuthorityKeyIdentifier, BasicConstraints, DisplayText, + DistributionPoint, DistributionPointName, DuplicateExtensionsError, ExtendedKeyUsage, + IssuerAlternativeName, KeyUsage, MSCertificateTemplate, NameConstraints, NamingAuthority, + PolicyConstraints, PolicyInformation, PolicyQualifierInfo, ProfessionInfo, Qualifier, + RawExtensions, SequenceOfAccessDescriptions, SequenceOfSubtrees, UserNotice, }; use cryptography_x509::extensions::{Extension, SubjectAlternativeName}; use cryptography_x509::{common, oid}; use cryptography_x509_verification::ops::CryptoOps; -use pyo3::{IntoPy, ToPyObject}; +use pyo3::types::{PyAnyMethods, PyListMethods}; 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::common::cstr_from_literal; use crate::x509::verify::PyCryptoOps; use crate::x509::{extensions, sct, sign}; use crate::{exceptions, types, x509}; @@ -37,13 +39,13 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Certificate { pub(crate) raw: OwnedCertificate, pub(crate) cached_extensions: pyo3::sync::GILOnceCell, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl Certificate { fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); @@ -57,7 +59,7 @@ impl Certificate { fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let subject = self.subject(py)?; - let subject_repr = subject.repr()?.extract::<&str>()?; + let subject_repr = subject.repr()?.extract::()?; Ok(format!("")) } @@ -65,30 +67,44 @@ impl Certificate { slf } - fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { + pub(crate) fn public_key<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { keys::load_der_public_key_bytes( py, self.raw.borrow_dependent().tbs_cert.spki.tlv().full_data(), ) } - fn fingerprint<'p>( + #[getter] + fn public_key_algorithm_oid<'p>( &self, py: pyo3::Python<'p>, - algorithm: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::PyAny> { - let serialized = asn1::write_single(&self.raw.borrow_dependent())?; + ) -> pyo3::PyResult> { + oid_to_py_oid( + py, + self.raw.borrow_dependent().tbs_cert.spki.algorithm.oid(), + ) + } + + pub(crate) fn fingerprint<'p>( + &self, + py: pyo3::Python<'p>, + algorithm: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { + 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)?) + h.finalize(py) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &'p pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { let result = asn1::write_single(self.raw.borrow_dependent())?; encode_der_data(py, "CERTIFICATE".to_string(), result, encoding) @@ -98,26 +114,29 @@ impl Certificate { fn serial_number<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { 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> { + fn version<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result, CryptographyError> { 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> { + fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { 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> { + fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(x509::parse_name(py, self.raw.borrow_dependent().subject()) .map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))?) } @@ -126,7 +145,7 @@ impl Certificate { fn tbs_certificate_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let result = asn1::write_single(&self.raw.borrow_dependent().tbs_cert)?; Ok(pyo3::types::PyBytes::new(py, &result)) } @@ -135,7 +154,7 @@ impl Certificate { fn tbs_precertificate_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let val = self.raw.borrow_dependent(); let mut tbs_precert = val.tbs_cert.clone(); // Remove the SCT list extension @@ -168,7 +187,7 @@ impl Certificate { let oid_obj = oid_to_py_oid(py, &oid)?; Err(exceptions::DuplicateExtension::new_err(( format!("Duplicate {} extension found", &oid), - oid_obj.into_py(py), + oid_obj.unbind(), )) .into()) } @@ -176,19 +195,18 @@ impl Certificate { } #[getter] - fn signature<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyBytes { + fn signature<'p>(&self, py: pyo3::Python<'p>) -> pyo3::Bound<'p, pyo3::types::PyBytes> { 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> { + fn not_valid_before<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { 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 message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to not_valid_before_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let dt = &self .raw .borrow_dependent() @@ -200,7 +218,10 @@ impl Certificate { } #[getter] - fn not_valid_before_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn not_valid_before_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let dt = &self .raw .borrow_dependent() @@ -212,14 +233,13 @@ impl Certificate { } #[getter] - fn not_valid_after<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn not_valid_after<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { 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 message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to not_valid_after_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let dt = &self .raw .borrow_dependent() @@ -231,7 +251,10 @@ impl Certificate { } #[getter] - fn not_valid_after_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn not_valid_after_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let dt = &self .raw .borrow_dependent() @@ -246,12 +269,15 @@ impl Certificate { fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { 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> { + fn signature_algorithm_oid<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { oid_to_py_oid(py, self.raw.borrow_dependent().signature_alg.oid()) } @@ -259,7 +285,7 @@ impl Certificate { fn signature_algorithm_parameters<'p>( &'p self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::PyAny> { + ) -> CryptographyResult> { sign::identify_signature_algorithm_parameters( py, &self.raw.borrow_dependent().signature_alg, @@ -318,7 +344,10 @@ impl Certificate { } } -fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, CryptographyError> { +fn cert_version( + py: pyo3::Python<'_>, + version: u8, +) -> Result, CryptographyError> { match version { 0 => Ok(types::CERTIFICATE_VERSION_V1.get(py)?), 2 => Ok(types::CERTIFICATE_VERSION_V3.get(py)?), @@ -331,11 +360,12 @@ fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, Crypt } } -#[pyo3::prelude::pyfunction] -fn load_pem_x509_certificate( +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] +pub(crate) fn load_pem_x509_certificate( py: pyo3::Python<'_>, data: &[u8], - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -348,13 +378,13 @@ fn load_pem_x509_certificate( )?; load_der_x509_certificate( py, - pyo3::types::PyBytes::new(py, parsed.contents()).into_py(py), + pyo3::types::PyBytes::new(py, parsed.contents()).unbind(), None, ) } -#[pyo3::prelude::pyfunction] -fn load_pem_x509_certificates( +#[pyo3::pyfunction] +pub(crate) fn load_pem_x509_certificates( py: pyo3::Python<'_>, data: &[u8], ) -> CryptographyResult> { @@ -364,7 +394,7 @@ fn load_pem_x509_certificates( .map(|p| { load_der_x509_certificate( py, - pyo3::types::PyBytes::new(py, p.contents()).into_py(py), + pyo3::types::PyBytes::new(py, p.contents()).unbind(), None, ) }) @@ -377,11 +407,12 @@ fn load_pem_x509_certificates( Ok(certs) } -#[pyo3::prelude::pyfunction] +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] pub(crate) fn load_der_x509_certificate( py: pyo3::Python<'_>, data: pyo3::Py, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -409,12 +440,8 @@ pub(crate) fn load_der_x509_certificate( fn warn_if_negative_serial(py: pyo3::Python<'_>, bytes: &'_ [u8]) -> pyo3::PyResult<()> { if bytes[0] & 0x80 != 0 { let warning_cls = types::DEPRECATED_IN_36.get(py)?; - pyo3::PyErr::warn( - py, - 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, - )?; + let message = cstr_from_literal!("Parsed a negative serial number, which is disallowed by RFC 5280. Loading this certificate will cause an exception in the next release of cryptography."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; } Ok(()) } @@ -432,86 +459,75 @@ fn warn_if_invalid_params( | AlgorithmParameters::DsaWithSha256(Some(..)) | AlgorithmParameters::DsaWithSha384(Some(..)) | AlgorithmParameters::DsaWithSha512(Some(..)) => { + // This can also be triggered by an Intel On Die certificate + // https://github.com/pyca/cryptography/issues/11723 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, - )?; + let message = cstr_from_literal!("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."); + pyo3::PyErr::warn(py, &warning_cls, message, 2)?; } _ => {} } Ok(()) } -fn parse_display_text( - py: pyo3::Python<'_>, +fn parse_display_text<'p>( + py: pyo3::Python<'p>, text: DisplayText<'_>, -) -> pyo3::PyResult { +) -> pyo3::PyResult> { match text { - DisplayText::IA5String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)), - DisplayText::Utf8String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)), + DisplayText::IA5String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).into_any()), + DisplayText::Utf8String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).into_any()), DisplayText::VisibleString(o) => { if asn1::VisibleString::new(o.as_str()).is_none() { let warning_cls = types::DEPRECATED_IN_41.get(py)?; - pyo3::PyErr::warn( - py, - 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, - )?; + let message = cstr_from_literal!("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."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; } - Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)) + Ok(pyo3::types::PyString::new(py, o.as_str()).into_any()) } DisplayText::BmpString(o) => { let py_bytes = pyo3::types::PyBytes::new(py, o.as_utf16_be_bytes()); // TODO: do the string conversion in rust perhaps - Ok(py_bytes - .call_method1( - pyo3::intern!(py, "decode"), - (pyo3::intern!(py, "utf_16_be"),), - )? - .to_object(py)) + Ok(py_bytes.call_method1( + pyo3::intern!(py, "decode"), + (pyo3::intern!(py, "utf_16_be"),), + )?) } } } -fn parse_user_notice( - py: pyo3::Python<'_>, - un: UserNotice<'_>, -) -> Result { +fn parse_user_notice<'p>( + py: pyo3::Python<'p>, + un: UserNotice<'_, Asn1Read>, +) -> CryptographyResult> { let et = match un.explicit_text { Some(data) => parse_display_text(py, data)?, - None => py.None(), + None => py.None().into_bound(py), }; let nr = match un.notice_ref { Some(data) => { let org = parse_display_text(py, data.organization)?; let numbers = pyo3::types::PyList::empty(py); - for num in data.notice_numbers.unwrap_read().clone() { - numbers.append(big_byte_slice_to_py_int(py, num.as_bytes())?.to_object(py))?; + for num in data.notice_numbers.clone() { + numbers.append(big_byte_slice_to_py_int(py, num.as_bytes())?)?; } - types::NOTICE_REFERENCE - .get(py)? - .call1((org, numbers))? - .to_object(py) + types::NOTICE_REFERENCE.get(py)?.call1((org, numbers))? } - None => py.None(), + None => py.None().into_bound(py), }; - Ok(types::USER_NOTICE.get(py)?.call1((nr, et))?.to_object(py)) + Ok(types::USER_NOTICE.get(py)?.call1((nr, et))?) } fn parse_policy_qualifiers<'a>( - py: pyo3::Python<'_>, - policy_qualifiers: &asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, -) -> Result { + py: pyo3::Python<'a>, + policy_qualifiers: &asn1::SequenceOf<'a, PolicyQualifierInfo<'a, Asn1Read>>, +) -> CryptographyResult> { let py_pq = pyo3::types::PyList::empty(py); for pqi in policy_qualifiers.clone() { let qualifier = match pqi.qualifier { Qualifier::CpsUri(data) => { if pqi.policy_qualifier_id == oid::CP_CPS_URI_OID { - pyo3::types::PyString::new(py, data.as_str()).to_object(py) + pyo3::types::PyString::new(py, data.as_str()).into_any() } else { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( @@ -533,95 +549,90 @@ fn parse_policy_qualifiers<'a>( }; py_pq.append(qualifier)?; } - Ok(py_pq.to_object(py)) + Ok(py_pq.into_any()) } -fn parse_cp( - py: pyo3::Python<'_>, +fn parse_cp<'p>( + py: pyo3::Python<'p>, ext: &Extension<'_>, -) -> Result { - let cp = ext.value::>>()?; +) -> CryptographyResult> { + 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); + let pi_oid = oid_to_py_oid(py, &policyinfo.policy_identifier)?; let py_pqis = match policyinfo.policy_qualifiers { - Some(policy_qualifiers) => { - parse_policy_qualifiers(py, policy_qualifiers.unwrap_read())? - } - None => py.None(), + Some(policy_qualifiers) => parse_policy_qualifiers(py, &policy_qualifiers)?, + None => py.None().into_bound(py), }; let pi = types::POLICY_INFORMATION .get(py)? - .call1((pi_oid, py_pqis))? - .to_object(py); + .call1((pi_oid, py_pqis))?; certificate_policies.append(pi)?; } - Ok(certificate_policies.to_object(py)) + Ok(certificate_policies.into_any()) } -fn parse_general_subtrees( - py: pyo3::Python<'_>, - subtrees: SequenceOfSubtrees<'_>, -) -> Result { +fn parse_general_subtrees<'p>( + py: pyo3::Python<'p>, + subtrees: SequenceOfSubtrees<'_, Asn1Read>, +) -> CryptographyResult> { let gns = pyo3::types::PyList::empty(py); - for gs in subtrees.unwrap_read().clone() { + for gs in subtrees { gns.append(x509::parse_general_name(py, gs.base)?)?; } - Ok(gns.to_object(py)) + Ok(gns.into_any()) } -pub(crate) fn parse_distribution_point_name( - py: pyo3::Python<'_>, - dp: DistributionPointName<'_>, -) -> Result<(pyo3::PyObject, pyo3::PyObject), CryptographyError> { +pub(crate) fn parse_distribution_point_name<'p>( + py: pyo3::Python<'p>, + dp: DistributionPointName<'p, Asn1Read>, +) -> CryptographyResult<(pyo3::Bound<'p, pyo3::PyAny>, pyo3::Bound<'p, pyo3::PyAny>)> { Ok(match dp { DistributionPointName::FullName(data) => ( - x509::parse_general_names(py, data.unwrap_read())?, - py.None(), + x509::parse_general_names(py, &data)?, + py.None().into_bound(py), ), DistributionPointName::NameRelativeToCRLIssuer(data) => { - (py.None(), x509::parse_rdn(py, data.unwrap_read())?) + (py.None().into_bound(py), x509::parse_rdn(py, &data)?) } }) } -fn parse_distribution_point( - py: pyo3::Python<'_>, - dp: DistributionPoint<'_>, -) -> Result { +fn parse_distribution_point<'p>( + py: pyo3::Python<'p>, + dp: DistributionPoint<'p, Asn1Read>, +) -> CryptographyResult> { let (full_name, relative_name) = match dp.distribution_point { Some(data) => parse_distribution_point_name(py, data)?, - None => (py.None(), py.None()), + None => (py.None().into_bound(py), py.None().into_bound(py)), }; - let reasons = - parse_distribution_point_reasons(py, dp.reasons.as_ref().map(|v| v.unwrap_read()))?; + let reasons = parse_distribution_point_reasons(py, dp.reasons.as_ref())?; let crl_issuer = match dp.crl_issuer { - Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, - None => py.None(), + Some(aci) => x509::parse_general_names(py, &aci)?, + None => py.None().into_bound(py), }; Ok(types::DISTRIBUTION_POINT .get(py)? - .call1((full_name, relative_name, reasons, crl_issuer))? - .to_object(py)) + .call1((full_name, relative_name, reasons, crl_issuer))?) } -pub(crate) fn parse_distribution_points( - py: pyo3::Python<'_>, +pub(crate) fn parse_distribution_points<'p>( + py: pyo3::Python<'p>, ext: &Extension<'_>, -) -> Result { - let dps = ext.value::>>()?; +) -> CryptographyResult> { + let dps = ext.value::>>()?; let py_dps = pyo3::types::PyList::empty(py); for dp in dps { let py_dp = parse_distribution_point(py, dp)?; py_dps.append(py_dp)?; } - Ok(py_dps.to_object(py)) + Ok(py_dps.into_any()) } -pub(crate) fn parse_distribution_point_reasons( - py: pyo3::Python<'_>, +pub(crate) fn parse_distribution_point_reasons<'p>( + py: pyo3::Python<'p>, reasons: Option<&asn1::BitString<'_>>, -) -> Result { +) -> CryptographyResult> { let reason_bit_mapping = types::REASON_BIT_MAPPING.get(py)?; Ok(match reasons { @@ -632,20 +643,20 @@ pub(crate) fn parse_distribution_point_reasons( vec.push(reason_bit_mapping.get_item(i)?); } } - pyo3::types::PyFrozenSet::new(py, &vec)?.to_object(py) + pyo3::types::PyFrozenSet::new(py, &vec)?.into_any() } - None => py.None(), + None => py.None().into_bound(py), }) } pub(crate) fn encode_distribution_point_reasons( py: pyo3::Python<'_>, - py_reasons: &pyo3::PyAny, + py_reasons: &pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { let reason_flag_mapping = types::CRL_REASON_FLAGS.get(py)?; let mut bits = vec![0, 0]; - for py_reason in py_reasons.iter()? { + for py_reason in py_reasons.try_iter()? { let bit = reason_flag_mapping .get_item(py_reason?)? .extract::()?; @@ -660,54 +671,149 @@ pub(crate) fn encode_distribution_point_reasons( pub(crate) fn parse_authority_key_identifier<'p>( py: pyo3::Python<'p>, - ext: &Extension<'_>, -) -> Result<&'p pyo3::PyAny, CryptographyError> { - let aki = ext.value::>()?; + ext: &Extension<'p>, +) -> Result, CryptographyError> { + let aki = ext.value::>()?; let serial = match aki.authority_cert_serial_number { - Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.to_object(py), + Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.unbind(), None => py.None(), }; let issuer = match aki.authority_cert_issuer { - Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, - None => py.None(), + Some(aci) => x509::parse_general_names(py, &aci)?, + None => py.None().into_bound(py), }; Ok(types::AUTHORITY_KEY_IDENTIFIER .get(py)? .call1((aki.key_identifier, issuer, serial))?) } -pub(crate) fn parse_access_descriptions( - py: pyo3::Python<'_>, +pub(crate) fn parse_access_descriptions<'p>( + py: pyo3::Python<'p>, ext: &Extension<'_>, -) -> Result { +) -> CryptographyResult> { let ads = pyo3::types::PyList::empty(py); - 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 parsed = ext.value::>()?; + for access in parsed { + let py_oid = oid_to_py_oid(py, &access.access_method)?; let gn = x509::parse_general_name(py, access.access_location)?; - let ad = types::ACCESS_DESCRIPTION - .get(py)? - .call1((py_oid, gn))? - .to_object(py); + let ad = types::ACCESS_DESCRIPTION.get(py)?.call1((py_oid, gn))?; ads.append(ad)?; } - Ok(ads.to_object(py)) + Ok(ads.into_any()) +} + +fn parse_naming_authority<'p>( + py: pyo3::Python<'p>, + authority: NamingAuthority<'_>, +) -> CryptographyResult> { + let py_id = match &authority.id { + Some(data) => oid_to_py_oid(py, data)?, + None => py.None().into_bound(py), + }; + let py_url = match authority.url { + Some(data) => pyo3::types::PyString::new(py, data.as_str()).into_any(), + None => py.None().into_bound(py), + }; + let py_text = match authority.text { + Some(data) => parse_display_text(py, data)?, + None => py.None().into_bound(py), + }; + + Ok(types::NAMING_AUTHORITY + .get(py)? + .call1((py_id, py_url, py_text))?) +} + +fn parse_profession_infos<'p, 'a>( + py: pyo3::Python<'p>, + profession_infos: &asn1::SequenceOf<'a, ProfessionInfo<'a, Asn1Read>>, +) -> CryptographyResult> { + let py_infos = pyo3::types::PyList::empty(py); + for info in profession_infos.clone() { + let py_naming_authority = match info.naming_authority { + Some(data) => parse_naming_authority(py, data)?, + None => py.None().into_bound(py), + }; + let py_profession_items = pyo3::types::PyList::empty(py); + for item in info.profession_items { + let py_item = parse_display_text(py, item)?; + py_profession_items.append(py_item)?; + } + let py_profession_oids = match info.profession_oids { + Some(oids) => { + let py_oids = pyo3::types::PyList::empty(py); + for oid in oids { + let py_oid = oid_to_py_oid(py, &oid)?; + py_oids.append(py_oid)?; + } + py_oids.into_any() + } + None => py.None().into_bound(py), + }; + let py_registration_number = match info.registration_number { + Some(data) => pyo3::types::PyString::new(py, data.as_str()).into_any(), + None => py.None().into_bound(py), + }; + let py_add_profession_info = match info.add_profession_info { + Some(data) => pyo3::types::PyBytes::new(py, data).into_any(), + None => py.None().into_bound(py), + }; + let py_info = types::PROFESSION_INFO.get(py)?.call1(( + py_naming_authority, + py_profession_items, + py_profession_oids, + py_registration_number, + py_add_profession_info, + ))?; + py_infos.append(py_info)?; + } + Ok(py_infos.into_any()) +} + +fn parse_admissions<'p, 'a>( + py: pyo3::Python<'p>, + admissions: &asn1::SequenceOf<'a, Admission<'a, Asn1Read>>, +) -> CryptographyResult> { + let py_admissions = pyo3::types::PyList::empty(py); + for admission in admissions.clone() { + let py_admission_authority = match admission.admission_authority { + Some(authority) => x509::parse_general_name(py, authority)?, + None => py.None().into_bound(py), + }; + let py_naming_authority = match admission.naming_authority { + Some(data) => parse_naming_authority(py, data)?, + None => py.None().into_bound(py), + }; + let py_infos = parse_profession_infos(py, &admission.profession_infos)?; + + let py_entry = types::ADMISSION.get(py)?.call1(( + py_admission_authority, + py_naming_authority, + py_infos, + ))?; + py_admissions.append(py_entry)?; + } + Ok(py_admissions.into_any()) } pub fn parse_cert_ext<'p>( py: pyo3::Python<'p>, - ext: &Extension<'_>, -) -> CryptographyResult> { + ext: &Extension<'p>, +) -> CryptographyResult>> { match ext.extn_id { oid::SUBJECT_ALTERNATIVE_NAME_OID => { - let gn_seq = ext.value::>()?; + let gn_seq = ext.value::>().map_err(|e| { + e.add_location(asn1::ParseLocation::Field("subject_alternative_name")) + })?; let sans = x509::parse_general_names(py, &gn_seq)?; Ok(Some( types::SUBJECT_ALTERNATIVE_NAME.get(py)?.call1((sans,))?, )) } oid::ISSUER_ALTERNATIVE_NAME_OID => { - let gn_seq = ext.value::>()?; + let gn_seq = ext.value::>().map_err(|e| { + e.add_location(asn1::ParseLocation::Field("issuer_alternative_name")) + })?; let ians = x509::parse_general_names(py, &gn_seq)?; Ok(Some( types::ISSUER_ALTERNATIVE_NAME.get(py)?.call1((ians,))?, @@ -718,7 +824,7 @@ pub fn parse_cert_ext<'p>( let features = pyo3::types::PyList::empty(py); for feature in ext.value::>()? { - let py_feature = tls_feature_type_to_enum.get_item(feature.to_object(py))?; + let py_feature = tls_feature_type_to_enum.get_item(feature)?; features.append(py_feature)?; } Ok(Some(types::TLS_FEATURE.get(py)?.call1((features,))?)) @@ -733,7 +839,7 @@ pub fn parse_cert_ext<'p>( } oid::EXTENDED_KEY_USAGE_OID => { let ekus = pyo3::types::PyList::empty(py); - for oid in ext.value::>()? { + for oid in ext.value::>()? { let oid_obj = oid_to_py_oid(py, &oid)?; ekus.append(oid_obj)?; } @@ -744,7 +850,7 @@ pub fn parse_cert_ext<'p>( Ok(Some(types::KEY_USAGE.get(py)?.call1(( kus.digital_signature(), - kus.content_comitment(), + kus.content_commitment(), kus.key_encipherment(), kus.data_encipherment(), kus.key_agreement(), @@ -804,14 +910,14 @@ pub fn parse_cert_ext<'p>( Ok(Some(types::FRESHEST_CRL.get(py)?.call1((dp,))?)) } oid::NAME_CONSTRAINTS_OID => { - let nc = ext.value::>()?; + let nc = ext.value::>()?; let permitted_subtrees = match nc.permitted_subtrees { Some(data) => parse_general_subtrees(py, data)?, - None => py.None(), + None => py.None().into_bound(py), }; let excluded_subtrees = match nc.excluded_subtrees { Some(data) => parse_general_subtrees(py, data)?, - None => py.None(), + None => py.None().into_bound(py), }; Ok(Some( types::NAME_CONSTRAINTS @@ -828,45 +934,62 @@ pub fn parse_cert_ext<'p>( ms_cert_tpl.minor_version, ))?)) } + oid::ADMISSIONS_OID => { + let admissions = ext.value::>()?; + let admission_authority = match admissions.admission_authority { + Some(authority) => x509::parse_general_name(py, authority)?, + None => py.None().into_bound(py), + }; + let py_admissions = parse_admissions(py, &admissions.contents_of_admissions)?; + Ok(Some( + types::ADMISSIONS + .get(py)? + .call1((admission_authority, py_admissions))?, + )) + } _ => Ok(None), } } pub(crate) fn time_from_py( py: pyo3::Python<'_>, - val: &pyo3::PyAny, + val: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { - let dt = x509::py_to_datetime(py, val)?; + let dt = x509::py_to_datetime(py, val.clone())?; time_from_datetime(dt) } pub(crate) fn time_from_datetime(dt: asn1::DateTime) -> CryptographyResult { if dt.year() >= 2050 { - Ok(common::Time::GeneralizedTime(asn1::GeneralizedTime::new( - dt, - )?)) + Ok(common::Time::GeneralizedTime( + asn1::X509GeneralizedTime::new(dt)?, + )) } else { Ok(common::Time::UtcTime(asn1::UtcTime::new(dt).unwrap())) } } -#[pyo3::prelude::pyfunction] -fn create_x509_certificate( +#[pyo3::pyfunction] +pub(crate) fn create_x509_certificate( py: pyo3::Python<'_>, - builder: &pyo3::PyAny, - private_key: &pyo3::PyAny, - hash_algorithm: &pyo3::PyAny, - rsa_padding: &pyo3::PyAny, + builder: &pyo3::Bound<'_, pyo3::PyAny>, + private_key: &pyo3::Bound<'_, pyo3::PyAny>, + hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { - let sigalg = - x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?; + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key.clone(), + hash_algorithm.clone(), + rsa_padding.clone(), + )?; 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, spki))? - .extract::<&[u8]>()?; + .extract::()?; let py_serial = builder .getattr(pyo3::intern!(py, "_serial_number"))? @@ -877,38 +1000,52 @@ fn create_x509_certificate( let py_not_before = builder.getattr(pyo3::intern!(py, "_not_valid_before"))?; let py_not_after = builder.getattr(pyo3::intern!(py, "_not_valid_after"))?; + let ka_vec = cryptography_keepalive::KeepAlive::new(); + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + + let serial_bytes = py_uint_to_big_endian_bytes(py, py_serial)?; + + let ka = cryptography_keepalive::KeepAlive::new(); + let tbs_cert = cryptography_x509::certificate::TbsCertificate { version: builder .getattr(pyo3::intern!(py, "_version"))? .getattr(pyo3::intern!(py, "value"))? .extract()?, - serial: asn1::BigInt::new(py_uint_to_big_endian_bytes(py, py_serial)?).unwrap(), + serial: asn1::BigInt::new(&serial_bytes).unwrap(), signature_alg: sigalg.clone(), - issuer: x509::common::encode_name(py, py_issuer_name)?, + issuer: x509::common::encode_name(py, &ka, &py_issuer_name)?, validity: cryptography_x509::certificate::Validity { - not_before: time_from_py(py, py_not_before)?, - not_after: time_from_py(py, py_not_after)?, + not_before: time_from_py(py, &py_not_before)?, + not_after: time_from_py(py, &py_not_after)?, }, - subject: x509::common::encode_name(py, py_subject_name)?, - spki: asn1::parse_single(spki_bytes)?, + subject: x509::common::encode_name(py, &ka, &py_subject_name)?, + spki: asn1::parse_single(&spki_bytes)?, issuer_unique_id: None, subject_unique_id: None, raw_extensions: x509::common::encode_extensions( py, - builder.getattr(pyo3::intern!(py, "_extensions"))?, + &ka_vec, + &ka_bytes, + &builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?, }; let tbs_bytes = asn1::write_single(&tbs_cert)?; - let signature = - x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?; + let signature = x509::sign::sign_data( + py, + private_key.clone(), + hash_algorithm.clone(), + rsa_padding.clone(), + &tbs_bytes, + )?; let data = asn1::write_single(&cryptography_x509::certificate::Certificate { tbs_cert, signature_alg: sigalg, - signature: asn1::BitString::new(signature, 0).unwrap(), + signature: asn1::BitString::new(&signature, 0).unwrap(), })?; - load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &data).into_py(py), None) + load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &data).unbind(), None) } pub(crate) fn set_bit(vals: &mut [u8], n: usize, set: bool) { @@ -918,14 +1055,3 @@ pub(crate) fn set_bit(vals: &mut [u8], n: usize, set: bool) { vals[idx] |= v; } } - -pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_function(pyo3::wrap_pyfunction!(load_der_x509_certificate, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_certificate, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_certificates, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(create_x509_certificate, module)?)?; - - module.add_class::()?; - - Ok(()) -} diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index a941f50b928c..3ebdd44003da 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -8,7 +8,7 @@ use cryptography_x509::extensions::{ }; use cryptography_x509::name::{GeneralName, Name, NameReadable, OtherName, UnvalidatedIA5String}; use pyo3::types::IntoPyDict; -use pyo3::{IntoPy, ToPyObject}; +use pyo3::types::{PyAnyMethods, PyListMethods}; use crate::asn1::{oid_to_py_oid, py_oid_to_oid}; use crate::error::{CryptographyError, CryptographyResult}; @@ -31,17 +31,18 @@ pub(crate) fn find_in_pem( } pub(crate) fn encode_name<'p>( - py: pyo3::Python<'p>, - py_name: &'p pyo3::PyAny, + py: pyo3::Python<'_>, + ka: &'p cryptography_keepalive::KeepAlive, + py_name: &pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult> { let mut rdns = vec![]; - for py_rdn in py_name.getattr(pyo3::intern!(py, "rdns"))?.iter()? { + for py_rdn in py_name.getattr(pyo3::intern!(py, "rdns"))?.try_iter()? { let py_rdn = py_rdn?; let mut attrs = vec![]; - for py_attr in py_rdn.iter()? { - attrs.push(encode_name_entry(py, py_attr?)?); + for py_attr in py_rdn.try_iter()? { + attrs.push(encode_name_entry(py, ka, &py_attr?)?); } rdns.push(asn1::SetOfWriter::new(attrs)); } @@ -51,97 +52,105 @@ pub(crate) fn encode_name<'p>( } pub(crate) fn encode_name_entry<'p>( - py: pyo3::Python<'p>, - py_name_entry: &'p pyo3::PyAny, + py: pyo3::Python<'_>, + ka: &'p cryptography_keepalive::KeepAlive, + py_name_entry: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { 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(types::ASN1_TYPE_BIT_STRING.get(py)?) { - let encoding = if attr_type.is(types::ASN1_TYPE_BMP_STRING.get(py)?) { - "utf_16_be" - } else if attr_type.is(types::ASN1_TYPE_UNIVERSAL_STRING.get(py)?) { - "utf_32_be" + let value: pyo3::pybacked::PyBackedBytes = + if !attr_type.is(&types::ASN1_TYPE_BIT_STRING.get(py)?) { + let encoding = if attr_type.is(&types::ASN1_TYPE_BMP_STRING.get(py)?) { + "utf_16_be" + } else if attr_type.is(&types::ASN1_TYPE_UNIVERSAL_STRING.get(py)?) { + "utf_32_be" + } else { + "utf8" + }; + py_name_entry + .getattr(pyo3::intern!(py, "value"))? + .call_method1(pyo3::intern!(py, "encode"), (encoding,))? + .extract()? } else { - "utf8" + py_name_entry + .getattr(pyo3::intern!(py, "value"))? + .extract()? }; - py_name_entry - .getattr(pyo3::intern!(py, "value"))? - .call_method1(pyo3::intern!(py, "encode"), (encoding,))? - .extract()? - } else { - py_name_entry - .getattr(pyo3::intern!(py, "value"))? - .extract()? - }; - let oid = py_oid_to_oid(py_name_entry.getattr(pyo3::intern!(py, "oid"))?)?; + let py_oid = py_name_entry.getattr(pyo3::intern!(py, "oid"))?; + let oid = py_oid_to_oid(py_oid)?; Ok(AttributeTypeValue { type_id: oid, - value: RawTlv::new(asn1::Tag::from_bytes(&[tag])?.0, value), + value: RawTlv::new(asn1::Tag::from_bytes(&[tag])?.0, ka.add(value)), }) } -#[pyo3::prelude::pyfunction] -fn encode_name_bytes<'p>( +#[pyo3::pyfunction] +pub(crate) fn encode_name_bytes<'p>( py: pyo3::Python<'p>, - py_name: &'p pyo3::PyAny, -) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let name = encode_name(py, py_name)?; + py_name: &pyo3::Bound<'p, pyo3::PyAny>, +) -> CryptographyResult> { + let ka = cryptography_keepalive::KeepAlive::new(); + let name = encode_name(py, &ka, py_name)?; let result = asn1::write_single(&name)?; Ok(pyo3::types::PyBytes::new(py, &result)) } pub(crate) fn encode_general_names<'a>( - py: pyo3::Python<'a>, - py_gns: &'a pyo3::PyAny, + py: pyo3::Python<'_>, + ka_bytes: &'a cryptography_keepalive::KeepAlive, + ka_str: &'a cryptography_keepalive::KeepAlive, + py_gns: &pyo3::Bound<'a, pyo3::PyAny>, ) -> Result>, CryptographyError> { let mut gns = vec![]; - for el in py_gns.iter()? { - let gn = encode_general_name(py, el?)?; + for el in py_gns.try_iter()? { + let gn = encode_general_name(py, ka_bytes, ka_str, &el?)?; gns.push(gn); } Ok(gns) } pub(crate) fn encode_general_name<'a>( - py: pyo3::Python<'a>, - gn: &'a pyo3::PyAny, + py: pyo3::Python<'_>, + ka_bytes: &'a cryptography_keepalive::KeepAlive, + ka_str: &'a cryptography_keepalive::KeepAlive, + gn: &pyo3::Bound<'a, pyo3::PyAny>, ) -> Result, CryptographyError> { - let gn_type = gn.get_type().as_ref(); + let gn_type = gn.get_type(); let gn_value = gn.getattr(pyo3::intern!(py, "value"))?; - if gn_type.is(types::DNS_NAME.get(py)?) { + if gn_type.is(&types::DNS_NAME.get(py)?) { Ok(GeneralName::DNSName(UnvalidatedIA5String( - gn_value.extract::<&str>()?, + ka_str.add(gn_value.extract()?), ))) - } else if gn_type.is(types::RFC822_NAME.get(py)?) { + } else if gn_type.is(&types::RFC822_NAME.get(py)?) { Ok(GeneralName::RFC822Name(UnvalidatedIA5String( - gn_value.extract::<&str>()?, + ka_str.add(gn_value.extract()?), ))) - } else if gn_type.is(types::DIRECTORY_NAME.get(py)?) { - let name = encode_name(py, gn_value)?; + } else if gn_type.is(&types::DIRECTORY_NAME.get(py)?) { + let name = encode_name(py, ka_bytes, &gn_value)?; Ok(GeneralName::DirectoryName(name)) - } else if gn_type.is(types::OTHER_NAME.get(py)?) { + } else if gn_type.is(&types::OTHER_NAME.get(py)?) { + let py_oid = gn.getattr(pyo3::intern!(py, "type_id"))?; 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| { + type_id: py_oid_to_oid(py_oid)?, + value: asn1::parse_single(ka_bytes.add(gn_value.extract()?)).map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( "OtherName value must be valid DER: {e:?}" )) })?, })) - } else if gn_type.is(types::UNIFORM_RESOURCE_IDENTIFIER.get(py)?) { + } else if gn_type.is(&types::UNIFORM_RESOURCE_IDENTIFIER.get(py)?) { Ok(GeneralName::UniformResourceIdentifier( - UnvalidatedIA5String(gn_value.extract::<&str>()?), + UnvalidatedIA5String(ka_str.add(gn_value.extract()?)), )) - } 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(types::REGISTERED_ID.get(py)?) { + } else if gn_type.is(&types::IP_ADDRESS.get(py)?) { + Ok(GeneralName::IPAddress(ka_bytes.add( + gn.call_method0(pyo3::intern!(py, "_packed"))?.extract()?, + ))) + } else if gn_type.is(&types::REGISTERED_ID.get(py)?) { let oid = py_oid_to_oid(gn_value)?; Ok(GeneralName::RegisteredID(oid)) } else { @@ -153,14 +162,17 @@ pub(crate) fn encode_general_name<'a>( pub(crate) fn encode_access_descriptions<'a>( py: pyo3::Python<'a>, - py_ads: &'a pyo3::PyAny, + py_ads: &pyo3::Bound<'a, pyo3::PyAny>, ) -> CryptographyResult> { let mut ads = vec![]; - for py_ad in py_ads.iter()? { + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); + for py_ad in py_ads.try_iter()? { let py_ad = py_ad?; - let access_method = py_oid_to_oid(py_ad.getattr(pyo3::intern!(py, "access_method"))?)?; - let access_location = - encode_general_name(py, py_ad.getattr(pyo3::intern!(py, "access_location"))?)?; + let py_oid = py_ad.getattr(pyo3::intern!(py, "access_method"))?; + let access_method = py_oid_to_oid(py_oid)?; + let py_access_location = py_ad.getattr(pyo3::intern!(py, "access_location"))?; + let access_location = encode_general_name(py, &ka_bytes, &ka_str, &py_access_location)?; ads.push(AccessDescription { access_method, access_location, @@ -172,7 +184,7 @@ pub(crate) fn encode_access_descriptions<'a>( pub(crate) fn parse_name<'p>( py: pyo3::Python<'p>, name: &NameReadable<'_>, -) -> Result<&'p pyo3::PyAny, CryptographyError> { +) -> Result, CryptographyError> { let py_rdns = pyo3::types::PyList::empty(py); for rdn in name.clone() { let py_rdn = parse_rdn(py, &rdn)?; @@ -181,25 +193,20 @@ pub(crate) fn parse_name<'p>( Ok(types::NAME.get(py)?.call1((py_rdns,))?) } -fn parse_name_attribute( - py: pyo3::Python<'_>, +fn parse_name_attribute<'p>( + py: pyo3::Python<'p>, attribute: AttributeTypeValue<'_>, -) -> Result { - let oid = oid_to_py_oid(py, &attribute.type_id)?.to_object(py); - let tag_val = attribute - .value - .tag() - .as_u8() - .ok_or_else(|| { - CryptographyError::from(pyo3::exceptions::PyValueError::new_err( - "Long-form tags are not supported in NameAttribute values", - )) - })? - .to_object(py); +) -> CryptographyResult> { + let oid = oid_to_py_oid(py, &attribute.type_id)?; + let tag_val = attribute.value.tag().as_u8().ok_or_else(|| { + CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + "Long-form tags are not supported in NameAttribute values", + )) + })?; 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()), + Some(3) => pyo3::types::PyBytes::new(py, attribute.value.data()).into_any(), // BMPString tag value Some(30) => { let py_bytes = pyo3::types::PyBytes::new(py, attribute.value.data()); @@ -213,20 +220,19 @@ fn parse_name_attribute( _ => { let parsed = std::str::from_utf8(attribute.value.data()) .map_err(|_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?; - pyo3::types::PyString::new(py, parsed) + pyo3::types::PyString::new(py, parsed).into_any() } }; - let kwargs = [("_validate", false)].into_py_dict(py); + let kwargs = [(pyo3::intern!(py, "_validate"), false)].into_py_dict(py)?; Ok(types::NAME_ATTRIBUTE .get(py)? - .call((oid, py_data, py_tag), Some(kwargs))? - .to_object(py)) + .call((oid, py_data, py_tag), Some(&kwargs))?) } pub(crate) fn parse_rdn<'a>( - py: pyo3::Python<'_>, + py: pyo3::Python<'a>, rdn: &asn1::SetOf<'a, AttributeTypeValue<'a>>, -) -> Result { +) -> CryptographyResult> { let py_attrs = pyo3::types::PyList::empty(py); for attribute in rdn.clone() { let na = parse_name_attribute(py, attribute)?; @@ -234,45 +240,37 @@ pub(crate) fn parse_rdn<'a>( } Ok(types::RELATIVE_DISTINGUISHED_NAME .get(py)? - .call1((py_attrs,))? - .to_object(py)) + .call1((py_attrs,))?) } -pub(crate) fn parse_general_name( - py: pyo3::Python<'_>, +pub(crate) fn parse_general_name<'p>( + py: pyo3::Python<'p>, gn: GeneralName<'_>, -) -> Result { +) -> CryptographyResult> { let py_gn = match gn { GeneralName::OtherName(data) => { - let oid = oid_to_py_oid(py, &data.type_id)?.to_object(py); + let oid = oid_to_py_oid(py, &data.type_id)?; types::OTHER_NAME .get(py)? .call1((oid, data.value.full_data()))? - .to_object(py) } GeneralName::RFC822Name(data) => types::RFC822_NAME .get(py)? - .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? - .to_object(py), + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))?, GeneralName::DNSName(data) => types::DNS_NAME .get(py)? - .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? - .to_object(py), + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))?, GeneralName::DirectoryName(data) => { let py_name = parse_name(py, data.unwrap_read())?; - types::DIRECTORY_NAME - .get(py)? - .call1((py_name,))? - .to_object(py) + types::DIRECTORY_NAME.get(py)?.call1((py_name,))? } GeneralName::UniformResourceIdentifier(data) => types::UNIFORM_RESOURCE_IDENTIFIER .get(py)? - .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? - .to_object(py), + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))?, GeneralName::IPAddress(data) => { if data.len() == 4 || data.len() == 16 { let addr = types::IPADDRESS_IPADDRESS.get(py)?.call1((data,))?; - types::IP_ADDRESS.get(py)?.call1((addr,))?.to_object(py) + types::IP_ADDRESS.get(py)?.call1((addr,))? } else { // if it's not an IPv4 or IPv6 we assume it's an IPNetwork and // verify length in this function. @@ -280,8 +278,8 @@ pub(crate) fn parse_general_name( } } GeneralName::RegisteredID(data) => { - let oid = oid_to_py_oid(py, &data)?.to_object(py); - types::REGISTERED_ID.get(py)?.call1((oid,))?.to_object(py) + let oid = oid_to_py_oid(py, &data)?; + types::REGISTERED_ID.get(py)?.call1((oid,))? } _ => { return Err(CryptographyError::from( @@ -295,21 +293,21 @@ pub(crate) fn parse_general_name( } pub(crate) fn parse_general_names<'a>( - py: pyo3::Python<'_>, + py: pyo3::Python<'a>, gn_seq: &asn1::SequenceOf<'a, GeneralName<'a>>, -) -> Result { +) -> CryptographyResult> { let gns = pyo3::types::PyList::empty(py); for gn in gn_seq.clone() { let py_gn = parse_general_name(py, gn)?; gns.append(py_gn)?; } - Ok(gns.to_object(py)) + Ok(gns.into_any()) } -fn create_ip_network( - py: pyo3::Python<'_>, +fn create_ip_network<'p>( + py: pyo3::Python<'p>, data: &[u8], -) -> Result { +) -> CryptographyResult> { let prefix = match data.len() { 8 => { let num = u32::from_be_bytes(data[4..].try_into().unwrap()); @@ -329,11 +327,11 @@ fn create_ip_network( let net = format!( "{}/{}", base.getattr(pyo3::intern!(py, "exploded"))? - .extract::<&str>()?, + .extract::()?, prefix? ); let addr = types::IPADDRESS_IPNETWORK.get(py)?.call1((net,))?; - Ok(types::IP_ADDRESS.get(py)?.call1((addr,))?.to_object(py)) + Ok(types::IP_ADDRESS.get(py)?.call1((addr,))?) } fn ipv4_netmask(num: u32) -> Result { @@ -356,11 +354,11 @@ fn ipv6_netmask(num: u128) -> Result { pub(crate) fn parse_and_cache_extensions< 'p, - F: Fn(&Extension<'_>) -> Result, CryptographyError>, + F: Fn(&Extension<'p>) -> Result>, CryptographyError>, >( py: pyo3::Python<'p>, cached_extensions: &pyo3::sync::GILOnceCell, - raw_extensions: &Option>, + raw_extensions: &Option>, parse_ext: F, ) -> pyo3::PyResult { cached_extensions @@ -371,7 +369,7 @@ pub(crate) fn parse_and_cache_extensions< 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), + oid_obj.unbind(), ))); } }; @@ -384,7 +382,7 @@ pub(crate) fn parse_and_cache_extensions< Some(e) => e, None => types::UNRECOGNIZED_EXTENSION .get(py)? - .call1((oid_obj, raw_ext.extn_value))?, + .call1((oid_obj.clone(), raw_ext.extn_value))?, }; let ext_obj = types::EXTENSION @@ -392,7 +390,7 @@ pub(crate) fn parse_and_cache_extensions< .call1((oid_obj, raw_ext.critical, extn_value))?; exts.append(ext_obj)?; } - Ok(types::EXTENSIONS.get(py)?.call1((exts,))?.to_object(py)) + Ok(types::EXTENSIONS.get(py)?.call1((exts,))?.unbind()) }) .map(|p| p.clone_ref(py)) } @@ -402,37 +400,36 @@ pub(crate) fn encode_extensions< F: Fn( pyo3::Python<'_>, &asn1::ObjectIdentifier, - &pyo3::PyAny, + &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult>>, >( py: pyo3::Python<'p>, - py_exts: &'p pyo3::PyAny, + ka_vec: &'p cryptography_keepalive::KeepAlive>, + ka_bytes: &'p cryptography_keepalive::KeepAlive, + py_exts: &pyo3::Bound<'p, pyo3::PyAny>, encode_ext: F, ) -> pyo3::PyResult>> { let mut exts = vec![]; - for py_ext in py_exts.iter()? { + for py_ext in py_exts.try_iter()? { let py_ext = py_ext?; - let oid = py_oid_to_oid(py_ext.getattr(pyo3::intern!(py, "oid"))?)?; + let py_oid = py_ext.getattr(pyo3::intern!(py, "oid"))?; + let oid = py_oid_to_oid(py_oid)?; let ext_val = py_ext.getattr(pyo3::intern!(py, "value"))?; - if ext_val.is_instance(types::UNRECOGNIZED_EXTENSION.get(py)?)? { + 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()?, - extn_value: ext_val - .getattr(pyo3::intern!(py, "value"))? - .extract::<&[u8]>()?, + extn_value: ka_bytes.add(ext_val.getattr(pyo3::intern!(py, "value"))?.extract()?), }); continue; } - match encode_ext(py, &oid, ext_val)? { + match encode_ext(py, &oid, &ext_val)? { Some(data) => { - // TODO: extra copy - let py_data = pyo3::types::PyBytes::new(py, &data); exts.push(Extension { extn_id: oid, critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, - extn_value: py_data.as_bytes(), + extn_value: ka_vec.add(data), }); } None => { @@ -450,14 +447,14 @@ pub(crate) fn encode_extensions< ))) } -#[pyo3::prelude::pyfunction] -fn encode_extension_value<'p>( +#[pyo3::pyfunction] +pub(crate) fn encode_extension_value<'p>( py: pyo3::Python<'p>, - py_ext: &'p pyo3::PyAny, -) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { + py_ext: pyo3::Bound<'p, pyo3::PyAny>, +) -> pyo3::PyResult> { let oid = py_oid_to_oid(py_ext.getattr(pyo3::intern!(py, "oid"))?)?; - if let Some(data) = x509::extensions::encode_extension(py, &oid, py_ext)? { + if let Some(data) = x509::extensions::encode_extension(py, &oid, &py_ext)? { // TODO: extra copy let py_data = pyo3::types::PyBytes::new(py, &data); return Ok(py_data); @@ -471,7 +468,7 @@ fn encode_extension_value<'p>( pub(crate) fn datetime_to_py<'p>( py: pyo3::Python<'p>, dt: &asn1::DateTime, -) -> pyo3::PyResult<&'p pyo3::PyAny> { +) -> pyo3::PyResult> { types::DATETIME_DATETIME.get(py)?.call1(( dt.year(), dt.month(), @@ -485,7 +482,7 @@ pub(crate) fn datetime_to_py<'p>( pub(crate) fn datetime_to_py_utc<'p>( py: pyo3::Python<'p>, dt: &asn1::DateTime, -) -> pyo3::PyResult<&'p pyo3::PyAny> { +) -> pyo3::PyResult> { let timezone = types::DATETIME_TIMEZONE_UTC.get(py)?; types::DATETIME_DATETIME.get(py)?.call1(( dt.year(), @@ -501,7 +498,7 @@ pub(crate) fn datetime_to_py_utc<'p>( pub(crate) fn py_to_datetime( py: pyo3::Python<'_>, - val: &pyo3::PyAny, + val: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { // We treat naive datetimes as UTC times, while aware datetimes get // normalized to UTC before conversion. @@ -534,9 +531,10 @@ pub(crate) fn datetime_now(py: pyo3::Python<'_>) -> pyo3::PyResult pyo3::PyResult<()> { - module.add_function(pyo3::wrap_pyfunction!(encode_extension_value, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(encode_name_bytes, module)?)?; - - Ok(()) +macro_rules! cstr_from_literal { + ($str:expr) => { + std::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap() + }; } + +pub(crate) use cstr_from_literal; diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs index f4d6feebc820..027c178efe42 100644 --- a/src/rust/src/x509/crl.rs +++ b/src/rust/src/x509/crl.rs @@ -6,28 +6,30 @@ use std::sync::Arc; use cryptography_x509::extensions::{Extension, IssuerAlternativeName}; use cryptography_x509::{ - common, + common::{self, Asn1Read}, crl::{ self, CertificateRevocationList as RawCertificateRevocationList, RevokedCertificate as RawRevokedCertificate, }, name, oid, }; -use pyo3::{IntoPy, ToPyObject}; +use pyo3::types::{PyAnyMethods, PyListMethods, PySliceMethods}; 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::common::cstr_from_literal; use crate::x509::{certificate, extensions, sign}; use crate::{exceptions, types, x509}; -#[pyo3::prelude::pyfunction] -fn load_der_x509_crl( +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] +pub(crate) fn load_der_x509_crl( py: pyo3::Python<'_>, data: pyo3::Py, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> Result { let _ = backend; @@ -52,11 +54,12 @@ fn load_der_x509_crl( }) } -#[pyo3::prelude::pyfunction] -fn load_pem_x509_crl( +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] +pub(crate) fn load_pem_x509_crl( py: pyo3::Python<'_>, data: &[u8], - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> Result { let _ = backend; @@ -67,7 +70,7 @@ fn load_pem_x509_crl( )?; load_der_x509_crl( py, - pyo3::types::PyBytes::new(py, block.contents()).into_py(py), + pyo3::types::PyBytes::new(py, block.contents()).unbind(), None, ) } @@ -80,8 +83,8 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] -struct CertificateRevocationList { +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] +pub(crate) struct CertificateRevocationList { owned: Arc, revoked_certs: pyo3::sync::GILOnceCell>, @@ -90,7 +93,7 @@ struct CertificateRevocationList { impl CertificateRevocationList { fn public_bytes_der(&self) -> CryptographyResult> { - Ok(asn1::write_single(&self.owned.borrow_dependent())?) + Ok(asn1::write_single(self.owned.borrow_dependent())?) } fn revoked_cert(&self, py: pyo3::Python<'_>, idx: usize) -> RevokedCertificate { @@ -110,7 +113,7 @@ impl CertificateRevocationList { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl CertificateRevocationList { fn __eq__(&self, other: pyo3::PyRef<'_, CertificateRevocationList>) -> bool { self.owned.borrow_dependent() == other.owned.borrow_dependent() @@ -120,9 +123,6 @@ impl CertificateRevocationList { 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| { @@ -138,11 +138,11 @@ impl CertificateRevocationList { } } - fn __getitem__( + fn __getitem__<'p>( &self, - py: pyo3::Python<'_>, - idx: &pyo3::PyAny, - ) -> pyo3::PyResult { + py: pyo3::Python<'p>, + idx: pyo3::Bound<'_, pyo3::PyAny>, + ) -> pyo3::PyResult> { self.revoked_certs.get_or_init(py, || { let mut revoked_certs = vec![]; let mut it = self.__iter__(); @@ -158,10 +158,10 @@ impl CertificateRevocationList { .indices(self.len().try_into().unwrap())?; let result = pyo3::types::PyList::empty(py); for i in (indices.start..indices.stop).step_by(indices.step.try_into().unwrap()) { - let revoked_cert = pyo3::PyCell::new(py, self.revoked_cert(py, i as usize))?; + let revoked_cert = pyo3::Bound::new(py, self.revoked_cert(py, i as usize))?; result.append(revoked_cert)?; } - Ok(result.to_object(py)) + Ok(result.into_any()) } else { let mut idx = idx.extract::()?; if idx < 0 { @@ -170,24 +170,27 @@ impl CertificateRevocationList { if idx >= (self.len() as isize) || idx < 0 { return Err(pyo3::exceptions::PyIndexError::new_err(())); } - Ok(pyo3::PyCell::new(py, self.revoked_cert(py, idx as usize))?.to_object(py)) + Ok(pyo3::Bound::new(py, self.revoked_cert(py, idx as usize))?.into_any()) } } fn fingerprint<'p>( &self, py: pyo3::Python<'p>, - algorithm: &pyo3::PyAny, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { + algorithm: pyo3::Bound<'_, pyo3::PyAny>, + ) -> pyo3::PyResult> { let data = self.public_bytes_der()?; - let mut h = Hash::new(py, algorithm, None)?; + 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> { + fn signature_algorithm_oid<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { oid_to_py_oid(py, self.owned.borrow_dependent().signature_algorithm.oid()) } @@ -195,7 +198,7 @@ impl CertificateRevocationList { fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { + ) -> pyo3::PyResult> { let oid = self.signature_algorithm_oid(py)?; match types::SIG_OIDS_TO_HASH.get(py)?.get_item(oid) { Ok(v) => Ok(v), @@ -210,7 +213,7 @@ impl CertificateRevocationList { fn signature_algorithm_parameters<'p>( &'p self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::PyAny> { + ) -> CryptographyResult> { sign::identify_signature_algorithm_parameters( py, &self.owned.borrow_dependent().signature_algorithm, @@ -226,7 +229,7 @@ impl CertificateRevocationList { fn tbs_certlist_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let b = asn1::write_single(&self.owned.borrow_dependent().tbs_cert_list)?; Ok(pyo3::types::PyBytes::new(py, &b)) } @@ -234,15 +237,15 @@ impl CertificateRevocationList { fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &'p pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - let result = asn1::write_single(&self.owned.borrow_dependent())?; + encoding: pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { + let result = asn1::write_single(self.owned.borrow_dependent())?; - encode_der_data(py, "X509 CRL".to_string(), result, encoding) + encode_der_data(py, "X509 CRL".to_string(), result, &encoding) } #[getter] - fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(x509::parse_name( py, self.owned @@ -254,37 +257,38 @@ impl CertificateRevocationList { } #[getter] - fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn next_update<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { 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, - )?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to next_update_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 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)), + None => Ok(py.None().into_bound(py)), } } #[getter] - fn next_update_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn next_update_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { 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)), + None => Ok(py.None().into_bound(py)), } } #[getter] - fn last_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn last_update<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { 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, - )?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to last_update_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; x509::datetime_to_py( py, self.owned @@ -296,7 +300,10 @@ impl CertificateRevocationList { } #[getter] - fn last_update_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn last_update_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { x509::datetime_to_py_utc( py, self.owned @@ -343,18 +350,15 @@ impl CertificateRevocationList { Ok(Some(certificate::parse_authority_key_identifier(py, ext)?)) } oid::ISSUING_DISTRIBUTION_POINT_OID => { - let idp = ext.value::>()?; + 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()), + None => (py.None().into_bound(py), py.None().into_bound(py)), }; let py_reasons = if let Some(reasons) = idp.only_some_reasons { - certificate::parse_distribution_point_reasons( - py, - Some(reasons.unwrap_read()), - )? + certificate::parse_distribution_point_reasons(py, Some(&reasons))? } else { - py.None() + py.None().into_bound(py) }; Ok(Some(types::ISSUING_DISTRIBUTION_POINT.get(py)?.call1(( full_name, @@ -378,7 +382,7 @@ impl CertificateRevocationList { fn get_revoked_certificate_by_serial_number( &self, py: pyo3::Python<'_>, - serial: &pyo3::types::PyLong, + serial: pyo3::Bound<'_, pyo3::types::PyInt>, ) -> pyo3::PyResult> { let serial_bytes = py_uint_to_big_endian_bytes(py, serial)?; let owned = OwnedRevokedCertificate::try_new(Arc::clone(&self.owned), |v| { @@ -407,7 +411,7 @@ impl CertificateRevocationList { fn is_signature_valid<'p>( slf: pyo3::PyRef<'_, Self>, py: pyo3::Python<'p>, - public_key: &'p pyo3::PyAny, + public_key: pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult { if slf.owned.borrow_dependent().tbs_cert_list.signature != slf.owned.borrow_dependent().signature_algorithm @@ -417,7 +421,7 @@ impl CertificateRevocationList { // Error on invalid public key -- below we treat any error as just // being an invalid signature. - sign::identify_public_key_type(py, public_key)?; + sign::identify_public_key_type(py, public_key.clone())?; Ok(sign::verify_signature_with_signature_algorithm( py, @@ -440,7 +444,7 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] +#[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] struct CRLIterator { contents: OwnedCRLIteratorData, } @@ -455,15 +459,22 @@ fn try_map_arc_data_mut_crl_iterator( ) -> Result, E>, ) -> Result { 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) })) + it.with_dependent_mut(|_, 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, unsafe { + std::mem::transmute::< + &mut Option>>, + &mut Option>>, + >(value) + }) + }) }) } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl CRLIterator { fn __len__(&self) -> usize { self.contents @@ -512,16 +523,19 @@ impl Clone for OwnedRevokedCertificate { } } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] -struct RevokedCertificate { +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] +pub(crate) struct RevokedCertificate { owned: OwnedRevokedCertificate, cached_extensions: pyo3::sync::GILOnceCell, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl RevokedCertificate { #[getter] - fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn serial_number<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { big_byte_slice_to_py_int( py, self.owned.borrow_dependent().user_certificate.as_bytes(), @@ -529,14 +543,13 @@ impl RevokedCertificate { } #[getter] - fn revocation_date<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn revocation_date<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { 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, - )?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to revocation_date_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; x509::datetime_to_py( py, self.owned.borrow_dependent().revocation_date.as_datetime(), @@ -544,7 +557,10 @@ impl RevokedCertificate { } #[getter] - fn revocation_date_utc<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn revocation_date_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { x509::datetime_to_py_utc( py, self.owned.borrow_dependent().revocation_date.as_datetime(), @@ -565,7 +581,7 @@ impl RevokedCertificate { pub(crate) fn parse_crl_reason_flags<'p>( py: pyo3::Python<'p>, reason: &crl::CRLReason, -) -> CryptographyResult<&'p pyo3::PyAny> { +) -> CryptographyResult> { let flag_name = match reason.value() { 0 => "unspecified", 1 => "key_compromise", @@ -590,8 +606,8 @@ pub(crate) fn parse_crl_reason_flags<'p>( pub fn parse_crl_entry_ext<'p>( py: pyo3::Python<'p>, - ext: &Extension<'_>, -) -> CryptographyResult> { + ext: &Extension<'p>, +) -> CryptographyResult>> { match ext.extn_id { oid::CRL_REASON_OID => { let flags = parse_crl_reason_flags(py, &ext.value::()?)?; @@ -611,20 +627,26 @@ pub fn parse_crl_entry_ext<'p>( } } -#[pyo3::prelude::pyfunction] -fn create_x509_crl( +#[pyo3::pyfunction] +pub(crate) fn create_x509_crl( py: pyo3::Python<'_>, - builder: &pyo3::PyAny, - private_key: &pyo3::PyAny, - hash_algorithm: &pyo3::PyAny, - rsa_padding: &pyo3::PyAny, + builder: &pyo3::Bound<'_, pyo3::PyAny>, + private_key: &pyo3::Bound<'_, pyo3::PyAny>, + hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { - let sigalg = - x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?; + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key.to_owned(), + hash_algorithm.to_owned(), + rsa_padding.to_owned(), + )?; let mut revoked_certs = vec![]; + let ka_vec = cryptography_keepalive::KeepAlive::new(); + let ka_bytes = cryptography_keepalive::KeepAlive::new(); for py_revoked_cert in builder .getattr(pyo3::intern!(py, "_revoked_certificates"))? - .iter()? + .try_iter()? { let py_revoked_cert = py_revoked_cert?; let serial_number = py_revoked_cert @@ -632,27 +654,31 @@ fn create_x509_crl( .extract()?; let py_revocation_date = py_revoked_cert.getattr(pyo3::intern!(py, "revocation_date_utc"))?; + let serial_bytes = ka_bytes.add(py_uint_to_big_endian_bytes(py, serial_number)?); revoked_certs.push(crl::RevokedCertificate { - user_certificate: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, serial_number)?) - .unwrap(), - revocation_date: x509::certificate::time_from_py(py, py_revocation_date)?, + user_certificate: asn1::BigUint::new(serial_bytes).unwrap(), + revocation_date: x509::certificate::time_from_py(py, &py_revocation_date)?, raw_crl_entry_extensions: x509::common::encode_extensions( py, - py_revoked_cert.getattr(pyo3::intern!(py, "extensions"))?, + &ka_vec, + &ka_bytes, + &py_revoked_cert.getattr(pyo3::intern!(py, "extensions"))?, extensions::encode_extension, )?, }); } + let ka = cryptography_keepalive::KeepAlive::new(); + let py_issuer_name = builder.getattr(pyo3::intern!(py, "_issuer_name"))?; let py_this_update = builder.getattr(pyo3::intern!(py, "_last_update"))?; let py_next_update = builder.getattr(pyo3::intern!(py, "_next_update"))?; let tbs_cert_list = crl::TBSCertList { version: Some(1), signature: sigalg.clone(), - issuer: x509::common::encode_name(py, py_issuer_name)?, - this_update: x509::certificate::time_from_py(py, py_this_update)?, - next_update: Some(x509::certificate::time_from_py(py, py_next_update)?), + issuer: x509::common::encode_name(py, &ka, &py_issuer_name)?, + this_update: x509::certificate::time_from_py(py, &py_this_update)?, + next_update: Some(x509::certificate::time_from_py(py, &py_next_update)?), revoked_certificates: if revoked_certs.is_empty() { None } else { @@ -662,29 +688,25 @@ fn create_x509_crl( }, raw_crl_extensions: x509::common::encode_extensions( py, - builder.getattr(pyo3::intern!(py, "_extensions"))?, + &ka_vec, + &ka_bytes, + &builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?, }; let tbs_bytes = asn1::write_single(&tbs_cert_list)?; - let signature = - x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?; + let signature = x509::sign::sign_data( + py, + private_key.clone(), + hash_algorithm.clone(), + rsa_padding.clone(), + &tbs_bytes, + )?; let data = asn1::write_single(&crl::CertificateRevocationList { tbs_cert_list, signature_algorithm: sigalg, - signature_value: asn1::BitString::new(signature, 0).unwrap(), + signature_value: asn1::BitString::new(&signature, 0).unwrap(), })?; - 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<()> { - module.add_function(pyo3::wrap_pyfunction!(load_der_x509_crl, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_crl, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(create_x509_crl, module)?)?; - - module.add_class::()?; - module.add_class::()?; - - Ok(()) + load_der_x509_crl(py, pyo3::types::PyBytes::new(py, &data).unbind(), None) } diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs index c49f6e04421a..ae669d941bf5 100644 --- a/src/rust/src/x509/csr.rs +++ b/src/rust/src/x509/csr.rs @@ -8,12 +8,12 @@ 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 pyo3::types::{PyAnyMethods, PyListMethods}; use crate::asn1::{encode_der_data, oid_to_py_oid, py_oid_to_oid}; use crate::backend::keys; use crate::error::{CryptographyError, CryptographyResult}; -use crate::x509::{certificate, sign}; +use crate::x509::{certificate, common::cstr_from_literal, sign}; use crate::{exceptions, types, x509}; self_cell::self_cell!( @@ -25,13 +25,13 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] -struct CertificateSigningRequest { +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] +pub(crate) struct CertificateSigningRequest { raw: OwnedCsr, cached_extensions: pyo3::sync::GILOnceCell, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl CertificateSigningRequest { fn __hash__(&self, py: pyo3::Python<'_>) -> u64 { let mut hasher = DefaultHasher::new(); @@ -47,7 +47,10 @@ impl CertificateSigningRequest { self.raw.borrow_owner().as_bytes(py) == other.raw.borrow_owner().as_bytes(py) } - fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { + fn public_key<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { keys::load_der_public_key_bytes( py, self.raw.borrow_dependent().csr_info.spki.tlv().full_data(), @@ -55,7 +58,18 @@ impl CertificateSigningRequest { } #[getter] - fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn public_key_algorithm_oid<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + oid_to_py_oid( + py, + self.raw.borrow_dependent().csr_info.spki.algorithm.oid(), + ) + } + + #[getter] + fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(x509::parse_name( py, self.raw.borrow_dependent().csr_info.subject.unwrap_read(), @@ -66,13 +80,13 @@ impl CertificateSigningRequest { fn tbs_certrequest_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { 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 { + fn signature<'p>(&self, py: pyo3::Python<'p>) -> pyo3::Bound<'p, pyo3::types::PyBytes> { pyo3::types::PyBytes::new(py, self.raw.borrow_dependent().signature.as_bytes()) } @@ -80,12 +94,15 @@ impl CertificateSigningRequest { fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { 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> { + fn signature_algorithm_oid<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { oid_to_py_oid(py, self.raw.borrow_dependent().signature_alg.oid()) } @@ -93,7 +110,7 @@ impl CertificateSigningRequest { fn signature_algorithm_parameters<'p>( &'p self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::PyAny> { + ) -> CryptographyResult> { sign::identify_signature_algorithm_parameters( py, &self.raw.borrow_dependent().signature_alg, @@ -103,8 +120,8 @@ impl CertificateSigningRequest { fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &'p pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { let result = asn1::write_single(self.raw.borrow_dependent())?; encode_der_data(py, "CERTIFICATE REQUEST".to_string(), result, encoding) @@ -113,13 +130,13 @@ impl CertificateSigningRequest { fn get_attribute_for_oid<'p>( &self, py: pyo3::Python<'p>, - oid: &pyo3::PyAny, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { + oid: pyo3::Bound<'p, pyo3::PyAny>, + ) -> pyo3::PyResult> { 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 warning_msg = cstr_from_literal!("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)?; + let rust_oid = py_oid_to_oid(oid.clone())?; for attribute in self .raw .borrow_dependent() @@ -140,7 +157,7 @@ impl CertificateSigningRequest { || val.tag() == asn1::PrintableString::TAG || val.tag() == asn1::IA5String::TAG { - return Ok(pyo3::types::PyBytes::new(py, val.data())); + return Ok(pyo3::types::PyBytes::new(py, val.data()).into_any()); } return Err(pyo3::exceptions::PyValueError::new_err(format!( "OID {} has a disallowed ASN.1 type: {:?}", @@ -151,12 +168,12 @@ impl CertificateSigningRequest { } Err(exceptions::AttributeNotFound::new_err(( format!("No {oid} attribute was found"), - oid.into_py(py), + oid.unbind(), ))) } #[getter] - fn attributes<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn attributes<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { let pyattrs = pyo3::types::PyList::empty(py); for attribute in self .raw @@ -211,7 +228,7 @@ impl CertificateSigningRequest { let public_key = slf.public_key(py)?; Ok(sign::verify_signature_with_signature_algorithm( py, - public_key.as_ref(py), + public_key, &slf.raw.borrow_dependent().signature_alg, slf.raw.borrow_dependent().signature.as_bytes(), &asn1::write_single(&slf.raw.borrow_dependent().csr_info)?, @@ -220,11 +237,12 @@ impl CertificateSigningRequest { } } -#[pyo3::prelude::pyfunction] -fn load_pem_x509_csr( +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] +pub(crate) fn load_pem_x509_csr( py: pyo3::Python<'_>, data: &[u8], - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -237,16 +255,17 @@ fn load_pem_x509_csr( )?; load_der_x509_csr( py, - pyo3::types::PyBytes::new(py, parsed.contents()).into_py(py), + pyo3::types::PyBytes::new(py, parsed.contents()).unbind(), None, ) } -#[pyo3::prelude::pyfunction] -fn load_der_x509_csr( +#[pyo3::pyfunction] +#[pyo3(signature = (data, backend=None))] +pub(crate) fn load_der_x509_csr( py: pyo3::Python<'_>, data: pyo3::Py, - backend: Option<&pyo3::PyAny>, + backend: Option>, ) -> CryptographyResult { let _ = backend; @@ -268,29 +287,38 @@ fn load_der_x509_csr( }) } -#[pyo3::prelude::pyfunction] -fn create_x509_csr( +#[pyo3::pyfunction] +pub(crate) fn create_x509_csr( py: pyo3::Python<'_>, - builder: &pyo3::PyAny, - private_key: &pyo3::PyAny, - hash_algorithm: &pyo3::PyAny, - rsa_padding: &pyo3::PyAny, + builder: &pyo3::Bound<'_, pyo3::PyAny>, + private_key: &pyo3::Bound<'_, pyo3::PyAny>, + hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, + rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { - let sigalg = - x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?; + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key.clone(), + hash_algorithm.clone(), + rsa_padding.clone(), + )?; 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, spki))? - .extract::<&[u8]>()?; + .extract::()?; + + let ka_vec = cryptography_keepalive::KeepAlive::new(); + let ka_bytes = cryptography_keepalive::KeepAlive::new(); let mut attrs = vec![]; let ext_bytes; if let Some(exts) = x509::common::encode_extensions( py, - builder.getattr(pyo3::intern!(py, "_extensions"))?, + &ka_vec, + &ka_bytes, + &builder.getattr(pyo3::intern!(py, "_extensions"))?, x509::extensions::encode_extension, )? { ext_bytes = asn1::write_single(&exts)?; @@ -302,13 +330,21 @@ fn create_x509_csr( }); } - for py_attr in builder.getattr(pyo3::intern!(py, "_attributes"))?.iter()? { - let (py_oid, value, tag): (&pyo3::PyAny, &[u8], Option) = py_attr?.extract()?; + let mut attr_values = vec![]; + for py_attr in builder + .getattr(pyo3::intern!(py, "_attributes"))? + .try_iter()? + { + let (py_oid, value, tag): ( + pyo3::Bound<'_, pyo3::PyAny>, + pyo3::pybacked::PyBackedBytes, + Option, + ) = py_attr?.extract()?; let oid = py_oid_to_oid(py_oid)?; let tag = if let Some(tag) = tag { asn1::Tag::from_bytes(&[tag])?.0 } else { - if std::str::from_utf8(value).is_err() { + if std::str::from_utf8(&value).is_err() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Attribute values must be valid utf-8.", @@ -318,40 +354,45 @@ fn create_x509_csr( asn1::Utf8String::TAG }; + attr_values.push((oid, tag, value)); + } + + for (oid, tag, value) in &attr_values { attrs.push(Attribute { - type_id: oid, + type_id: oid.clone(), values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ - common::RawTlv::new(tag, value), + common::RawTlv::new(*tag, value), ])), }); } let py_subject_name = builder.getattr(pyo3::intern!(py, "_subject_name"))?; + let ka = cryptography_keepalive::KeepAlive::new(); + let csr_info = CertificationRequestInfo { version: 0, - subject: x509::common::encode_name(py, py_subject_name)?, - spki: asn1::parse_single(spki_bytes)?, + subject: x509::common::encode_name(py, &ka, &py_subject_name)?, + spki: asn1::parse_single(&spki_bytes)?, attributes: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(attrs)), }; let tbs_bytes = asn1::write_single(&csr_info)?; - let signature = - x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?; + let signature = x509::sign::sign_data( + py, + private_key.clone(), + hash_algorithm.clone(), + rsa_padding.clone(), + &tbs_bytes, + )?; let data = asn1::write_single(&Csr { csr_info, signature_alg: sigalg, - signature: asn1::BitString::new(signature, 0).unwrap(), + signature: asn1::BitString::new(&signature, 0).unwrap(), })?; - 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<()> { - module.add_function(pyo3::wrap_pyfunction!(load_der_x509_csr, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_csr, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(create_x509_csr, module)?)?; - - module.add_class::()?; - - Ok(()) + load_der_x509_csr( + py, + pyo3::types::PyBytes::new(py, &data).clone().unbind(), + None, + ) } diff --git a/src/rust/src/x509/extensions.rs b/src/rust/src/x509/extensions.rs index 03fd1da9ff07..3b67dfa2ecd2 100644 --- a/src/rust/src/x509/extensions.rs +++ b/src/rust/src/x509/extensions.rs @@ -2,115 +2,123 @@ // 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 cryptography_x509::{common::Asn1Write, crl, extensions, oid}; use crate::asn1::{py_oid_to_oid, py_uint_to_big_endian_bytes}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, sct}; use crate::{types, x509}; +use pyo3::pybacked::PyBackedStr; +use pyo3::types::PyAnyMethods; fn encode_general_subtrees<'a>( - py: pyo3::Python<'a>, - subtrees: &'a pyo3::PyAny, -) -> Result>, CryptographyError> { + py: pyo3::Python<'_>, + ka_bytes: &'a cryptography_keepalive::KeepAlive, + ka_str: &'a cryptography_keepalive::KeepAlive, + subtrees: &pyo3::Bound<'a, pyo3::PyAny>, +) -> Result>, CryptographyError> { if subtrees.is_none() { Ok(None) } else { let mut subtree_seq = vec![]; - for name in subtrees.iter()? { - let gn = x509::common::encode_general_name(py, name?)?; + for name in subtrees.try_iter()? { + let gn = x509::common::encode_general_name(py, ka_bytes, ka_str, &name?)?; subtree_seq.push(extensions::GeneralSubtree { base: gn, minimum: 0, maximum: None, }); } - Ok(Some(common::Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(subtree_seq), - ))) + Ok(Some(asn1::SequenceOfWriter::new(subtree_seq))) } } pub(crate) fn encode_authority_key_identifier<'a>( py: pyo3::Python<'a>, - py_aki: &'a pyo3::PyAny, + py_aki: &pyo3::Bound<'a, pyo3::PyAny>, ) -> CryptographyResult> { - #[derive(pyo3::prelude::FromPyObject)] + #[derive(pyo3::FromPyObject)] struct PyAuthorityKeyIdentifier<'a> { - key_identifier: Option<&'a [u8]>, - authority_cert_issuer: Option<&'a pyo3::PyAny>, - authority_cert_serial_number: Option<&'a pyo3::types::PyLong>, + key_identifier: Option, + authority_cert_issuer: Option>, + authority_cert_serial_number: Option>, } let aki = py_aki.extract::>()?; + + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); let authority_cert_issuer = if let Some(authority_cert_issuer) = aki.authority_cert_issuer { - let gns = x509::common::encode_general_names(py, authority_cert_issuer)?; - Some(common::Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(gns), - )) + let gns = + x509::common::encode_general_names(py, &ka_bytes, &ka_str, &authority_cert_issuer)?; + Some(asn1::SequenceOfWriter::new(gns)) } else { None }; + let serial_bytes; let authority_cert_serial_number = if let Some(authority_cert_serial_number) = aki.authority_cert_serial_number { - let serial_bytes = py_uint_to_big_endian_bytes(py, authority_cert_serial_number)?; - Some(asn1::BigUint::new(serial_bytes).unwrap()) + serial_bytes = py_uint_to_big_endian_bytes(py, authority_cert_serial_number)?; + Some(asn1::BigUint::new(&serial_bytes).unwrap()) } else { None }; - Ok(asn1::write_single(&extensions::AuthorityKeyIdentifier { + Ok(asn1::write_single(&extensions::AuthorityKeyIdentifier::< + Asn1Write, + > { authority_cert_issuer, authority_cert_serial_number, - key_identifier: aki.key_identifier, + key_identifier: aki.key_identifier.as_deref(), })?) } pub(crate) fn encode_distribution_points<'p>( py: pyo3::Python<'p>, - py_dps: &'p pyo3::PyAny, + py_dps: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { - #[derive(pyo3::prelude::FromPyObject)] + #[derive(pyo3::FromPyObject)] struct PyDistributionPoint<'a> { - crl_issuer: Option<&'a pyo3::PyAny>, - full_name: Option<&'a pyo3::PyAny>, - relative_name: Option<&'a pyo3::PyAny>, - reasons: Option<&'a pyo3::PyAny>, + crl_issuer: Option>, + full_name: Option>, + relative_name: Option>, + reasons: Option>, } + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); let mut dps = vec![]; - for py_dp in py_dps.iter()? { + for py_dp in py_dps.try_iter()? { let py_dp = py_dp?.extract::>()?; let crl_issuer = if let Some(py_crl_issuer) = py_dp.crl_issuer { - let gns = x509::common::encode_general_names(py, py_crl_issuer)?; - Some(common::Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(gns), - )) + let gns = x509::common::encode_general_names(py, &ka_bytes, &ka_str, &py_crl_issuer)?; + Some(asn1::SequenceOfWriter::new(gns)) } else { None }; let distribution_point = if let Some(py_full_name) = py_dp.full_name { - let gns = x509::common::encode_general_names(py, py_full_name)?; + let gns = x509::common::encode_general_names(py, &ka_bytes, &ka_str, &py_full_name)?; Some(extensions::DistributionPointName::FullName( - common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), + asn1::SequenceOfWriter::new(gns), )) } else if let Some(py_relative_name) = py_dp.relative_name { let mut name_entries = vec![]; - for py_name_entry in py_relative_name.iter()? { - name_entries.push(x509::common::encode_name_entry(py, py_name_entry?)?); + for py_name_entry in py_relative_name.try_iter()? { + let ne = x509::common::encode_name_entry(py, &ka_bytes, &py_name_entry?)?; + name_entries.push(ne); } Some(extensions::DistributionPointName::NameRelativeToCRLIssuer( - common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), + asn1::SetOfWriter::new(name_entries), )) } else { None }; let reasons = if let Some(py_reasons) = py_dp.reasons { - let reasons = certificate::encode_distribution_point_reasons(py, py_reasons)?; - Some(common::Asn1ReadableOrWritable::new_write(reasons)) + let reasons = certificate::encode_distribution_point_reasons(py, &py_reasons)?; + Some(reasons) } else { None }; - dps.push(extensions::DistributionPoint { + dps.push(extensions::DistributionPoint:: { crl_issuer, distribution_point, reasons, @@ -119,8 +127,8 @@ pub(crate) fn encode_distribution_points<'p>( Ok(asn1::write_single(&asn1::SequenceOfWriter::new(dps))?) } -fn encode_basic_constraints(ext: &pyo3::PyAny) -> CryptographyResult> { - #[derive(pyo3::prelude::FromPyObject)] +fn encode_basic_constraints(ext: &pyo3::Bound<'_, pyo3::PyAny>) -> CryptographyResult> { + #[derive(pyo3::FromPyObject)] struct PyBasicConstraints { ca: bool, path_length: Option, @@ -133,57 +141,67 @@ fn encode_basic_constraints(ext: &pyo3::PyAny) -> CryptographyResult> { Ok(asn1::write_single(&bc)?) } -fn encode_key_usage(py: pyo3::Python<'_>, ext: &pyo3::PyAny) -> CryptographyResult> { +fn encode_key_usage( + py: pyo3::Python<'_>, + ext: &pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { let mut bs = [0, 0]; certificate::set_bit( &mut bs, 0, ext.getattr(pyo3::intern!(py, "digital_signature"))? - .is_true()?, + .is_truthy()?, ); certificate::set_bit( &mut bs, 1, ext.getattr(pyo3::intern!(py, "content_commitment"))? - .is_true()?, + .is_truthy()?, ); certificate::set_bit( &mut bs, 2, ext.getattr(pyo3::intern!(py, "key_encipherment"))? - .is_true()?, + .is_truthy()?, ); certificate::set_bit( &mut bs, 3, ext.getattr(pyo3::intern!(py, "data_encipherment"))? - .is_true()?, + .is_truthy()?, ); certificate::set_bit( &mut bs, 4, - ext.getattr(pyo3::intern!(py, "key_agreement"))?.is_true()?, + ext.getattr(pyo3::intern!(py, "key_agreement"))? + .is_truthy()?, ); certificate::set_bit( &mut bs, 5, - ext.getattr(pyo3::intern!(py, "key_cert_sign"))?.is_true()?, + ext.getattr(pyo3::intern!(py, "key_cert_sign"))? + .is_truthy()?, ); certificate::set_bit( &mut bs, 6, - ext.getattr(pyo3::intern!(py, "crl_sign"))?.is_true()?, + ext.getattr(pyo3::intern!(py, "crl_sign"))?.is_truthy()?, ); - if ext.getattr(pyo3::intern!(py, "key_agreement"))?.is_true()? { + if ext + .getattr(pyo3::intern!(py, "key_agreement"))? + .is_truthy()? + { certificate::set_bit( &mut bs, 7, - ext.getattr(pyo3::intern!(py, "encipher_only"))?.is_true()?, + ext.getattr(pyo3::intern!(py, "encipher_only"))? + .is_truthy()?, ); certificate::set_bit( &mut bs, 8, - ext.getattr(pyo3::intern!(py, "decipher_only"))?.is_true()?, + ext.getattr(pyo3::intern!(py, "decipher_only"))? + .is_truthy()?, ); } let (bits, unused_bits) = if bs[1] == 0 { @@ -201,19 +219,22 @@ fn encode_key_usage(py: pyo3::Python<'_>, ext: &pyo3::PyAny) -> CryptographyResu fn encode_certificate_policies( py: pyo3::Python<'_>, - ext: &pyo3::PyAny, + ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let mut policy_informations = vec![]; - for py_policy_info in ext.iter()? { + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); + for py_policy_info in ext.try_iter()? { let py_policy_info = py_policy_info?; let py_policy_qualifiers = py_policy_info.getattr(pyo3::intern!(py, "policy_qualifiers"))?; - let qualifiers = if py_policy_qualifiers.is_true()? { + let qualifiers = if py_policy_qualifiers.is_truthy()? { let mut qualifiers = vec![]; - for py_qualifier in py_policy_qualifiers.iter()? { + for py_qualifier in py_policy_qualifiers.try_iter()? { let py_qualifier = py_qualifier?; let qualifier = if py_qualifier.is_instance_of::() { - let cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { + let py_qualifier_str = ka_str.add(py_qualifier.extract::()?); + let cps_uri = match asn1::IA5String::new(py_qualifier_str) { Some(s) => s, None => { return Err(pyo3::exceptions::PyValueError::new_err( @@ -228,36 +249,37 @@ fn encode_certificate_policies( } } else { let py_notice = py_qualifier.getattr(pyo3::intern!(py, "notice_reference"))?; - let notice_ref = if py_notice.is_true()? { + let notice_ref = if py_notice.is_truthy()? { let mut notice_numbers = vec![]; for py_num in py_notice .getattr(pyo3::intern!(py, "notice_numbers"))? - .iter()? + .try_iter()? { - let bytes = py_uint_to_big_endian_bytes(ext.py(), py_num?.downcast()?)?; + let bytes = ka_bytes + .add(py_uint_to_big_endian_bytes(ext.py(), py_num?.extract()?)?); notice_numbers.push(asn1::BigUint::new(bytes).unwrap()); } - + let py_notice_str = ka_str.add( + py_notice + .getattr(pyo3::intern!(py, "organization"))? + .extract::()?, + ); Some(extensions::NoticeReference { organization: extensions::DisplayText::Utf8String( - asn1::Utf8String::new( - py_notice - .getattr(pyo3::intern!(py, "organization"))? - .extract()?, - ), - ), - notice_numbers: common::Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(notice_numbers), + asn1::Utf8String::new(py_notice_str), ), + notice_numbers: asn1::SequenceOfWriter::new(notice_numbers), }) } else { None }; let py_explicit_text = py_qualifier.getattr(pyo3::intern!(py, "explicit_text"))?; - let explicit_text = if py_explicit_text.is_true()? { + let explicit_text = if py_explicit_text.is_truthy()? { + let py_explicit_text_str = + ka_str.add(py_explicit_text.extract::()?); Some(extensions::DisplayText::Utf8String(asn1::Utf8String::new( - py_explicit_text.extract()?, + py_explicit_text_str, ))) } else { None @@ -273,14 +295,12 @@ fn encode_certificate_policies( }; qualifiers.push(qualifier); } - Some(common::Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(qualifiers), - )) + Some(asn1::SequenceOfWriter::new(qualifiers)) } else { None }; let py_policy_id = py_policy_info.getattr(pyo3::intern!(py, "policy_identifier"))?; - policy_informations.push(extensions::PolicyInformation { + policy_informations.push(extensions::PolicyInformation:: { policy_identifier: py_oid_to_oid(py_policy_id)?, policy_qualifiers: qualifiers, }); @@ -292,37 +312,47 @@ fn encode_certificate_policies( fn encode_issuing_distribution_point( py: pyo3::Python<'_>, - ext: &pyo3::PyAny, + ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); + let only_some_reasons = if ext .getattr(pyo3::intern!(py, "only_some_reasons"))? - .is_true()? + .is_truthy()? { let py_reasons = ext.getattr(pyo3::intern!(py, "only_some_reasons"))?; - let reasons = certificate::encode_distribution_point_reasons(ext.py(), py_reasons)?; - Some(common::Asn1ReadableOrWritable::new_write(reasons)) + let reasons = certificate::encode_distribution_point_reasons(ext.py(), &py_reasons)?; + Some(reasons) } else { None }; - let distribution_point = if ext.getattr(pyo3::intern!(py, "full_name"))?.is_true()? { + let distribution_point = if ext.getattr(pyo3::intern!(py, "full_name"))?.is_truthy()? { let py_full_name = ext.getattr(pyo3::intern!(py, "full_name"))?; - let gns = x509::common::encode_general_names(ext.py(), py_full_name)?; + let gns = x509::common::encode_general_names(ext.py(), &ka_bytes, &ka_str, &py_full_name)?; Some(extensions::DistributionPointName::FullName( - common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), + asn1::SequenceOfWriter::new(gns), )) - } else if ext.getattr(pyo3::intern!(py, "relative_name"))?.is_true()? { + } else if ext + .getattr(pyo3::intern!(py, "relative_name"))? + .is_truthy()? + { let mut name_entries = vec![]; - for py_name_entry in ext.getattr(pyo3::intern!(py, "relative_name"))?.iter()? { - name_entries.push(x509::common::encode_name_entry(ext.py(), py_name_entry?)?); + for py_name_entry in ext + .getattr(pyo3::intern!(py, "relative_name"))? + .try_iter()? + { + let name_entry = x509::common::encode_name_entry(ext.py(), &ka_bytes, &py_name_entry?)?; + name_entries.push(name_entry); } Some(extensions::DistributionPointName::NameRelativeToCRLIssuer( - common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), + asn1::SetOfWriter::new(name_entries), )) } else { None }; - let idp = crl::IssuingDistributionPoint { + let idp = crl::IssuingDistributionPoint:: { distribution_point, indirect_crl: ext.getattr(pyo3::intern!(py, "indirect_crl"))?.extract()?, only_contains_attribute_certs: ext @@ -339,49 +369,191 @@ fn encode_issuing_distribution_point( Ok(asn1::write_single(&idp)?) } -fn encode_oid_sequence(ext: &pyo3::PyAny) -> CryptographyResult> { +fn encode_oid_sequence(ext: &pyo3::Bound<'_, pyo3::PyAny>) -> CryptographyResult> { let mut oids = vec![]; - for el in ext.iter()? { + for el in ext.try_iter()? { let oid = py_oid_to_oid(el?)?; oids.push(oid); } Ok(asn1::write_single(&asn1::SequenceOfWriter::new(oids))?) } -fn encode_tls_features(py: pyo3::Python<'_>, ext: &pyo3::PyAny) -> CryptographyResult> { +fn encode_tls_features( + py: pyo3::Python<'_>, + ext: &pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { // Ideally we'd skip building up a vec and just write directly into the // writer. This isn't possible at the moment because the callback to write // an asn1::Sequence can't return an error, and we need to handle errors // from Python. let mut els = vec![]; - for el in ext.iter()? { + for el in ext.try_iter()? { els.push(el?.getattr(pyo3::intern!(py, "value"))?.extract::()?); } Ok(asn1::write_single(&asn1::SequenceOfWriter::new(els))?) } -fn encode_scts(ext: &pyo3::PyAny) -> CryptographyResult> { +fn encode_scts(ext: &pyo3::Bound<'_, pyo3::PyAny>) -> CryptographyResult> { let mut length = 0; - for sct in ext.iter()? { - let sct = sct?.downcast::>()?; - length += sct.borrow().sct_data.len() + 2; + for sct in ext.try_iter()? { + let sct = sct?.downcast::()?.clone(); + length += sct.get().sct_data.len() + 2; } let mut result = vec![]; result.extend_from_slice(&(length as u16).to_be_bytes()); - for sct in ext.iter()? { - let sct = sct?.downcast::>()?; - result.extend_from_slice(&(sct.borrow().sct_data.len() as u16).to_be_bytes()); - result.extend_from_slice(&sct.borrow().sct_data); + for sct in ext.try_iter()? { + let sct = sct?.downcast::()?.clone(); + result.extend_from_slice(&(sct.get().sct_data.len() as u16).to_be_bytes()); + result.extend_from_slice(&sct.get().sct_data); } Ok(asn1::write_single(&result.as_slice())?) } +fn encode_naming_authority<'a>( + py: pyo3::Python<'_>, + ka_str: &'a cryptography_keepalive::KeepAlive, + py_naming_authority: &pyo3::Bound<'a, pyo3::PyAny>, +) -> CryptographyResult> { + let py_oid = py_naming_authority.getattr(pyo3::intern!(py, "id"))?; + let id = if !py_oid.is_none() { + Some(py_oid_to_oid(py_oid)?) + } else { + None + }; + let py_url = py_naming_authority.getattr(pyo3::intern!(py, "url"))?; + let url = if !py_url.is_none() { + let py_url_str = ka_str.add(py_url.extract::()?); + match asn1::IA5String::new(py_url_str) { + Some(s) => Some(s), + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("url value must be a valid IA5String"), + )) + } + } + } else { + None + }; + let py_text = py_naming_authority.getattr(pyo3::intern!(py, "text"))?; + let text = if !py_text.is_none() { + let py_text_str = ka_str.add(py_text.extract::()?); + Some(extensions::DisplayText::Utf8String(asn1::Utf8String::new( + py_text_str, + ))) + } else { + None + }; + Ok(extensions::NamingAuthority { id, url, text }) +} + +fn encode_profession_info<'a>( + py: pyo3::Python<'a>, + ka_bytes: &'a cryptography_keepalive::KeepAlive, + ka_str: &'a cryptography_keepalive::KeepAlive, + py_info: &pyo3::Bound<'a, pyo3::PyAny>, +) -> CryptographyResult> { + let py_naming_authority = py_info.getattr(pyo3::intern!(py, "naming_authority"))?; + let naming_authority = if !py_naming_authority.is_none() { + Some(encode_naming_authority(py, ka_str, &py_naming_authority)?) + } else { + None + }; + let mut profession_items = vec![]; + let py_items = py_info.getattr(pyo3::intern!(py, "profession_items"))?; + for py_item in py_items.try_iter()? { + let py_item = py_item?; + let py_item_str = ka_str.add(py_item.extract::()?); + let item = extensions::DisplayText::Utf8String(asn1::Utf8String::new(py_item_str)); + profession_items.push(item); + } + let profession_items = asn1::SequenceOfWriter::new(profession_items); + let py_oids = py_info.getattr(pyo3::intern!(py, "profession_oids"))?; + let profession_oids = if !py_oids.is_none() { + let mut profession_oids = vec![]; + for py_oid in py_oids.try_iter()? { + let py_oid = py_oid?; + let oid = py_oid_to_oid(py_oid)?; + profession_oids.push(oid); + } + Some(asn1::SequenceOfWriter::new(profession_oids)) + } else { + None + }; + let py_registration_number = py_info.getattr(pyo3::intern!(py, "registration_number"))?; + let registration_number = if !py_registration_number.is_none() { + let py_registration_number_str = + ka_str.add(py_registration_number.extract::()?); + match asn1::PrintableString::new(py_registration_number_str) { + Some(s) => Some(s), + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "registration_number value must be a valid PrintableString", + ), + )) + } + } + } else { + None + }; + let py_add_profession_info = py_info.getattr(pyo3::intern!(py, "add_profession_info"))?; + let add_profession_info = if !py_add_profession_info.is_none() { + Some(ka_bytes.add(py_add_profession_info.extract::()?)) + } else { + None + }; + Ok(extensions::ProfessionInfo { + naming_authority, + profession_items, + profession_oids, + registration_number, + add_profession_info, + }) +} + +fn encode_admission<'a>( + py: pyo3::Python<'a>, + ka_bytes: &'a cryptography_keepalive::KeepAlive, + ka_str: &'a cryptography_keepalive::KeepAlive, + py_admission: &pyo3::Bound<'a, pyo3::PyAny>, +) -> CryptographyResult> { + let py_admission_authority = py_admission.getattr(pyo3::intern!(py, "admission_authority"))?; + let admission_authority = if !py_admission_authority.is_none() { + Some(x509::common::encode_general_name( + py, + ka_bytes, + ka_str, + &py_admission_authority, + )?) + } else { + None + }; + let py_naming_authority = py_admission.getattr(pyo3::intern!(py, "naming_authority"))?; + let naming_authority = if !py_naming_authority.is_none() { + Some(encode_naming_authority(py, ka_str, &py_naming_authority)?) + } else { + None + }; + + let py_profession_infos = py_admission.getattr(pyo3::intern!(py, "profession_infos"))?; + let mut profession_infos = vec![]; + for py_info in py_profession_infos.try_iter()? { + profession_infos.push(encode_profession_info(py, ka_bytes, ka_str, &py_info?)?); + } + let profession_infos = asn1::SequenceOfWriter::new(profession_infos); + Ok(extensions::Admission { + admission_authority, + naming_authority, + profession_infos, + }) +} + pub(crate) fn encode_extension( py: pyo3::Python<'_>, oid: &asn1::ObjectIdentifier, - ext: &pyo3::PyAny, + ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult>> { match oid { &oid::BASIC_CONSTRAINTS_OID => { @@ -391,8 +563,8 @@ pub(crate) fn encode_extension( &oid::SUBJECT_KEY_IDENTIFIER_OID => { let digest = ext .getattr(pyo3::intern!(py, "digest"))? - .extract::<&[u8]>()?; - Ok(Some(asn1::write_single(&digest)?)) + .extract::()?; + Ok(Some(asn1::write_single(&digest.as_ref())?)) } &oid::KEY_USAGE_OID => { let der = encode_key_usage(py, ext)?; @@ -422,25 +594,41 @@ pub(crate) fn encode_extension( Ok(Some(asn1::write_single(&pc)?)) } &oid::NAME_CONSTRAINTS_OID => { + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); + let permitted = ext.getattr(pyo3::intern!(py, "permitted_subtrees"))?; let excluded = ext.getattr(pyo3::intern!(py, "excluded_subtrees"))?; - let nc = extensions::NameConstraints { - permitted_subtrees: encode_general_subtrees(ext.py(), permitted)?, - excluded_subtrees: encode_general_subtrees(ext.py(), excluded)?, + let nc = extensions::NameConstraints:: { + permitted_subtrees: encode_general_subtrees( + ext.py(), + &ka_bytes, + &ka_str, + &permitted, + )?, + excluded_subtrees: encode_general_subtrees( + ext.py(), + &ka_bytes, + &ka_str, + &excluded, + )?, }; Ok(Some(asn1::write_single(&nc)?)) } &oid::INHIBIT_ANY_POLICY_OID => { let intval = ext .getattr(pyo3::intern!(py, "skip_certs"))? - .downcast::()?; + .downcast::()? + .clone(); let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; Ok(Some(asn1::write_single( - &asn1::BigUint::new(bytes).unwrap(), + &asn1::BigUint::new(&bytes).unwrap(), )?)) } &oid::ISSUER_ALTERNATIVE_NAME_OID | &oid::SUBJECT_ALTERNATIVE_NAME_OID => { - let gns = x509::common::encode_general_names(ext.py(), ext)?; + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); + let gns = x509::common::encode_general_names(ext.py(), &ka_bytes, &ka_str, ext)?; Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) } &oid::AUTHORITY_KEY_IDENTIFIER_OID => { @@ -470,20 +658,26 @@ pub(crate) fn encode_extension( Ok(Some(asn1::write_single(&asn1::Enumerated::new(value))?)) } &oid::CERTIFICATE_ISSUER_OID => { - let gns = x509::common::encode_general_names(ext.py(), ext)?; + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); + let gns = x509::common::encode_general_names(ext.py(), &ka_bytes, &ka_str, ext)?; Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) } &oid::INVALIDITY_DATE_OID => { - let dt = x509::py_to_datetime(py, ext.getattr(pyo3::intern!(py, "invalidity_date"))?)?; - Ok(Some(asn1::write_single(&asn1::GeneralizedTime::new(dt)?)?)) + let py_dt = ext.getattr(pyo3::intern!(py, "invalidity_date_utc"))?; + let dt = x509::py_to_datetime(py, py_dt)?; + Ok(Some(asn1::write_single(&asn1::X509GeneralizedTime::new( + dt, + )?)?)) } &oid::CRL_NUMBER_OID | &oid::DELTA_CRL_INDICATOR_OID => { let intval = ext .getattr(pyo3::intern!(py, "crl_number"))? - .downcast::()?; + .downcast::()? + .clone(); let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; Ok(Some(asn1::write_single( - &asn1::BigUint::new(bytes).unwrap(), + &asn1::BigUint::new(&bytes).unwrap(), )?)) } &oid::ISSUING_DISTRIBUTION_POINT_OID => { @@ -493,8 +687,8 @@ pub(crate) fn encode_extension( &oid::NONCE_OID => { let nonce = ext .getattr(pyo3::intern!(py, "nonce"))? - .extract::<&[u8]>()?; - Ok(Some(asn1::write_single(&nonce)?)) + .extract::()?; + Ok(Some(asn1::write_single(&nonce.as_ref())?)) } &oid::MS_CERTIFICATE_TEMPLATE => { let py_template_id = ext.getattr(pyo3::intern!(py, "template_id"))?; @@ -505,6 +699,34 @@ pub(crate) fn encode_extension( }; Ok(Some(asn1::write_single(&mstpl)?)) } + &oid::ADMISSIONS_OID => { + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + let ka_str = cryptography_keepalive::KeepAlive::new(); + let py_admission_authority = ext.getattr(pyo3::intern!(py, "authority"))?; + let admission_authority = if !py_admission_authority.is_none() { + Some(x509::common::encode_general_name( + py, + &ka_bytes, + &ka_str, + &py_admission_authority, + )?) + } else { + None + }; + let mut admissions = vec![]; + for py_admission in ext.try_iter()? { + let admission = encode_admission(py, &ka_bytes, &ka_str, &py_admission?)?; + admissions.push(admission); + } + + let contents_of_admissions = asn1::SequenceOfWriter::new(admissions); + + let admission = extensions::Admissions:: { + admission_authority, + contents_of_admissions, + }; + Ok(Some(asn1::write_single(&admission)?)) + } _ => Ok(None), } } diff --git a/src/rust/src/x509/ocsp.rs b/src/rust/src/x509/ocsp.rs index b86753110606..b632532f1573 100644 --- a/src/rust/src/x509/ocsp.rs +++ b/src/rust/src/x509/ocsp.rs @@ -7,10 +7,10 @@ use std::collections::HashMap; use cryptography_x509::common; use cryptography_x509::ocsp_req::CertID; use once_cell::sync::Lazy; +use pyo3::types::PyAnyMethods; 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< @@ -74,13 +74,15 @@ pub(crate) static HASH_NAME_TO_ALGORITHM_IDENTIFIERS: Lazy< pub(crate) fn certid_new<'p>( py: pyo3::Python<'p>, + ka: &'p cryptography_keepalive::KeepAlive, cert: &'p Certificate, issuer: &'p Certificate, - hash_algorithm: &'p pyo3::PyAny, + hash_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { 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( + let issuer_name_hash = + pyo3::pybacked::PyBackedBytes::from(hash_data(py, hash_algorithm, &issuer_der)?); + let issuer_key_hash = pyo3::pybacked::PyBackedBytes::from(hash_data( py, hash_algorithm, issuer @@ -90,15 +92,15 @@ pub(crate) fn certid_new<'p>( .spki .subject_public_key .as_bytes(), - )?; + )?); Ok(CertID { - hash_algorithm: x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[hash_algorithm + hash_algorithm: HASH_NAME_TO_ALGORITHM_IDENTIFIERS[&*hash_algorithm .getattr(pyo3::intern!(py, "name"))? - .extract::<&str>()?] - .clone(), - issuer_name_hash, - issuer_key_hash, + .extract::()?] + .clone(), + issuer_name_hash: ka.add(issuer_name_hash), + issuer_key_hash: ka.add(issuer_key_hash), serial_number: cert.raw.borrow_dependent().tbs_cert.serial, }) } @@ -108,13 +110,13 @@ pub(crate) fn certid_new_from_hash<'p>( issuer_name_hash: &'p [u8], issuer_key_hash: &'p [u8], serial_number: asn1::BigInt<'p>, - hash_algorithm: &'p pyo3::PyAny, + hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { + let hash_name = hash_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::()?; Ok(CertID { - hash_algorithm: x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[hash_algorithm - .getattr(pyo3::intern!(py, "name"))? - .extract::<&str>()?] - .clone(), + hash_algorithm: HASH_NAME_TO_ALGORITHM_IDENTIFIERS[&*hash_name].clone(), issuer_name_hash, issuer_key_hash, serial_number, @@ -123,10 +125,10 @@ pub(crate) fn certid_new_from_hash<'p>( pub(crate) fn hash_data<'p>( py: pyo3::Python<'p>, - py_hash_alg: &'p pyo3::PyAny, + py_hash_alg: &pyo3::Bound<'p, pyo3::PyAny>, data: &[u8], -) -> pyo3::PyResult<&'p [u8]> { +) -> pyo3::PyResult> { let mut h = Hash::new(py, py_hash_alg, None)?; h.update_bytes(data)?; - Ok(h.finalize(py)?.as_bytes()) + Ok(h.finalize(py)?) } diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs index baa2dd00dfb4..2b3ae3df3656 100644 --- a/src/rust/src/x509/ocsp_req.rs +++ b/src/rust/src/x509/ocsp_req.rs @@ -7,7 +7,7 @@ use cryptography_x509::{ ocsp_req::{self, OCSPRequest as RawOCSPRequest}, oid, }; -use pyo3::IntoPy; +use pyo3::types::{PyAnyMethods, PyListMethods}; use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes}; use crate::error::{CryptographyError, CryptographyResult}; @@ -22,8 +22,8 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyfunction] -fn load_der_ocsp_request( +#[pyo3::pyfunction] +pub(crate) fn load_der_ocsp_request( py: pyo3::Python<'_>, data: pyo3::Py, ) -> CryptographyResult { @@ -50,8 +50,8 @@ fn load_der_ocsp_request( }) } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] -struct OCSPRequest { +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] +pub(crate) struct OCSPRequest { raw: OwnedOCSPRequest, cached_extensions: pyo3::sync::GILOnceCell, @@ -71,7 +71,7 @@ impl OCSPRequest { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl OCSPRequest { #[getter] fn issuer_name_hash(&self) -> &[u8] { @@ -87,7 +87,7 @@ impl OCSPRequest { fn hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { let cert_id = self.cert_id(); match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&cert_id.hash_algorithm.params) { @@ -105,7 +105,7 @@ impl OCSPRequest { fn serial_number<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { let bytes = self.cert_id().serial_number.as_bytes(); Ok(big_byte_slice_to_py_int(py, bytes)?) } @@ -152,9 +152,9 @@ impl OCSPRequest { fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !encoding.is(types::ENCODING_DER.get(py)?) { + encoding: &pyo3::Bound<'p, pyo3::PyAny>, + ) -> CryptographyResult> { + if !encoding.is(&types::ENCODING_DER.get(py)?) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) @@ -165,43 +165,39 @@ impl OCSPRequest { } } -#[pyo3::prelude::pyfunction] -fn create_ocsp_request( +#[pyo3::pyfunction] +pub(crate) fn create_ocsp_request( py: pyo3::Python<'_>, - builder: &pyo3::PyAny, + builder: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let builder_request = builder.getattr(pyo3::intern!(py, "_request"))?; + let serial_number_bytes; + + let ka_vec = cryptography_keepalive::KeepAlive::new(); + let ka_bytes = cryptography_keepalive::KeepAlive::new(); // Declare outside the if-block so the lifetimes are right. - let (py_cert, py_issuer, py_hash): ( + let (py_cert, py_issuer, py_hash, issuer_name_hash, issuer_key_hash): ( pyo3::PyRef<'_, x509::certificate::Certificate>, pyo3::PyRef<'_, x509::certificate::Certificate>, - &pyo3::PyAny, + pyo3::Bound<'_, pyo3::PyAny>, + pyo3::pybacked::PyBackedBytes, + pyo3::pybacked::PyBackedBytes, ); let req_cert = if !builder_request.is_none() { - let tuple = builder_request.extract::<( - pyo3::PyRef<'_, x509::certificate::Certificate>, - pyo3::PyRef<'_, x509::certificate::Certificate>, - &pyo3::PyAny, - )>()?; - py_cert = tuple.0; - py_issuer = tuple.1; - py_hash = tuple.2; - ocsp::certid_new(py, &py_cert, &py_issuer, py_hash)? + (py_cert, py_issuer, py_hash) = builder_request.extract()?; + ocsp::certid_new(py, &ka_bytes, &py_cert, &py_issuer, &py_hash)? } else { - let (issuer_name_hash, issuer_key_hash, py_serial, py_hash): ( - &[u8], - &[u8], - &pyo3::types::PyLong, - &pyo3::PyAny, - ) = builder + let py_serial: pyo3::Bound<'_, pyo3::types::PyInt>; + (issuer_name_hash, issuer_key_hash, py_serial, py_hash) = builder .getattr(pyo3::intern!(py, "_request_hash"))? .extract()?; - let serial_number = asn1::BigInt::new(py_uint_to_big_endian_bytes(py, py_serial)?).unwrap(); + serial_number_bytes = py_uint_to_big_endian_bytes(py, py_serial)?; + let serial_number = asn1::BigInt::new(&serial_number_bytes).unwrap(); ocsp::certid_new_from_hash( py, - issuer_name_hash, - issuer_key_hash, + &issuer_name_hash, + &issuer_key_hash, serial_number, py_hash, )? @@ -209,7 +205,9 @@ fn create_ocsp_request( let extensions = x509::common::encode_extensions( py, - builder.getattr(pyo3::intern!(py, "_extensions"))?, + &ka_vec, + &ka_bytes, + &builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?; let reqs = [ocsp_req::Request { @@ -228,12 +226,5 @@ fn create_ocsp_request( optional_signature: None, }; let data = asn1::write_single(&ocsp_req)?; - load_der_ocsp_request(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) -} - -pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_function(pyo3::wrap_pyfunction!(load_der_ocsp_request, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(create_ocsp_request, module)?)?; - - Ok(()) + load_der_ocsp_request(py, pyo3::types::PyBytes::new(py, &data).unbind()) } diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs index e5f8b479576a..25b1dc20d6d0 100644 --- a/src/rust/src/x509/ocsp_resp.rs +++ b/src/rust/src/x509/ocsp_resp.rs @@ -10,17 +10,18 @@ use cryptography_x509::{ ocsp_resp::{self, OCSPResponse as RawOCSPResponse, SingleResponse as RawSingleResponse}, oid, }; -use pyo3::IntoPy; +use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid}; use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::common::cstr_from_literal; use crate::x509::{certificate, crl, extensions, ocsp, py_to_datetime, sct}; use crate::{exceptions, types, x509}; const BASIC_RESPONSE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 1); -#[pyo3::prelude::pyfunction] -fn load_der_ocsp_response( +#[pyo3::pyfunction] +pub(crate) fn load_der_ocsp_response( py: pyo3::Python<'_>, data: pyo3::Py, ) -> Result { @@ -46,7 +47,7 @@ fn load_der_ocsp_response( )) } }, - MALFORMED_REQUEST_RESPOSNE + MALFORMED_REQUEST_RESPONSE | INTERNAL_ERROR_RESPONSE | TRY_LATER_RESPONSE | SIG_REQUIRED_RESPONSE @@ -72,8 +73,8 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] -struct OCSPResponse { +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] +pub(crate) struct OCSPResponse { raw: Arc, cached_extensions: pyo3::sync::GILOnceCell, @@ -92,14 +93,14 @@ impl OCSPResponse { } const SUCCESSFUL_RESPONSE: u32 = 0; -const MALFORMED_REQUEST_RESPOSNE: u32 = 1; +const MALFORMED_REQUEST_RESPONSE: u32 = 1; const INTERNAL_ERROR_RESPONSE: u32 = 2; const TRY_LATER_RESPONSE: u32 = 3; // 4 is unused const SIG_REQUIRED_RESPONSE: u32 = 5; const UNAUTHORIZED_RESPONSE: u32 = 6; -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl OCSPResponse { #[getter] fn responses(&self) -> Result { @@ -124,11 +125,14 @@ impl OCSPResponse { } #[getter] - fn response_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn response_status<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let status = self.raw.borrow_dependent().response_status.value(); let attr = if status == SUCCESSFUL_RESPONSE { "SUCCESSFUL" - } else if status == MALFORMED_REQUEST_RESPOSNE { + } else if status == MALFORMED_REQUEST_RESPONSE { "MALFORMED_REQUEST" } else if status == INTERNAL_ERROR_RESPONSE { "INTERNAL_ERROR" @@ -144,35 +148,59 @@ impl OCSPResponse { } #[getter] - fn responder_name<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn responder_name<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { 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.unwrap_read())?) } - ocsp_resp::ResponderId::ByKey(_) => Ok(py.None().into_ref(py)), + ocsp_resp::ResponderId::ByKey(_) => Ok(py.None().into_bound(py)), } } #[getter] - fn responder_key_hash<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn responder_key_hash<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; match resp.tbs_response_data.responder_id { ocsp_resp::ResponderId::ByKey(key_hash) => { - Ok(pyo3::types::PyBytes::new(py, key_hash).as_ref()) + Ok(pyo3::types::PyBytes::new(py, key_hash).into_any()) } - ocsp_resp::ResponderId::ByName(_) => Ok(py.None().into_ref(py)), + ocsp_resp::ResponderId::ByName(_) => Ok(py.None().into_bound(py)), } } #[getter] - fn produced_at<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn produced_at<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let warning_cls = types::DEPRECATED_IN_43.get(py)?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to produced_at_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let resp = self.requires_successful_response()?; x509::datetime_to_py(py, resp.tbs_response_data.produced_at.as_datetime()) } #[getter] - fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn produced_at_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let resp = self.requires_successful_response()?; + x509::datetime_to_py_utc(py, resp.tbs_response_data.produced_at.as_datetime()) + } + + #[getter] + fn signature_algorithm_oid<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; oid_to_py_oid(py, resp.signature_algorithm.oid()) } @@ -181,28 +209,31 @@ impl OCSPResponse { fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { 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(_) => { - let exc_messsage = format!( + let exc_message = format!( "Signature algorithm OID: {} not recognized", self.requires_successful_response()? .signature_algorithm .oid() ); Err(CryptographyError::from( - exceptions::UnsupportedAlgorithm::new_err(exc_messsage), + exceptions::UnsupportedAlgorithm::new_err(exc_message), )) } } } #[getter] - fn signature<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { + fn signature<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; Ok(pyo3::types::PyBytes::new(py, resp.signature.as_bytes())) } @@ -211,14 +242,17 @@ impl OCSPResponse { fn tbs_response_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult> { let resp = self.requires_successful_response()?; let result = asn1::write_single(&resp.tbs_response_data)?; Ok(pyo3::types::PyBytes::new(py, &result)) } #[getter] - fn certificates<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, CryptographyError> { + fn certificates<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { let resp = self.requires_successful_response()?; let py_certs = pyo3::types::PyList::empty(py); let certs = match &resp.certs { @@ -241,7 +275,7 @@ impl OCSPResponse { .nth(i) .unwrap() }); - py_certs.append(pyo3::PyCell::new( + py_certs.append(pyo3::Bound::new( py, x509::certificate::Certificate { raw: raw_cert, @@ -253,7 +287,10 @@ impl OCSPResponse { } #[getter] - fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn serial_number<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_serial_number(&single_resp, py) @@ -277,47 +314,101 @@ impl OCSPResponse { fn hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_hash_algorithm(&single_resp, py) } #[getter] - fn certificate_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn certificate_status<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_certificate_status(&single_resp, py) } #[getter] - fn revocation_time<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn revocation_time<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let warning_cls = types::DEPRECATED_IN_43.get(py)?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to revocation_time_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_revocation_time(&single_resp, py) } #[getter] - fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn revocation_time_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_revocation_time_utc(&single_resp, py) + } + + #[getter] + fn revocation_reason<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_revocation_reason(&single_resp, py) } #[getter] - fn this_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn this_update<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let warning_cls = types::DEPRECATED_IN_43.get(py)?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to this_update_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_this_update(&single_resp, py) } #[getter] - fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn this_update_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_this_update_utc(&single_resp, py) + } + + #[getter] + fn next_update<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let warning_cls = types::DEPRECATED_IN_43.get(py)?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to next_update_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_next_update(&single_resp, py) } + #[getter] + fn next_update_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let resp = self.requires_successful_response()?; + let single_resp = single_response(resp)?; + singleresp_py_next_update_utc(&single_resp, py) + } + #[getter] fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { self.requires_successful_response()?; @@ -389,9 +480,9 @@ impl OCSPResponse { fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> CryptographyResult<&'p pyo3::types::PyBytes> { - if !encoding.is(types::ENCODING_DER.get(py)?) { + encoding: pyo3::Bound<'_, pyo3::PyAny>, + ) -> CryptographyResult> { + if !encoding.is(&types::ENCODING_DER.get(py)?) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) @@ -418,7 +509,11 @@ fn map_arc_data_ocsp_response( // 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) }) + f(inner_it.as_bytes(py), unsafe { + std::mem::transmute::<&ocsp_resp::OCSPResponse<'_>, &ocsp_resp::OCSPResponse<'_>>( + value, + ) + }) }) }) } @@ -430,11 +525,18 @@ fn try_map_arc_data_mut_ocsp_response_iterator( ) -> Result, E>, ) -> Result { 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) })) + it.with_dependent_mut(|_, 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, unsafe { + std::mem::transmute::< + &mut asn1::SequenceOf<'_, ocsp_resp::SingleResponse<'_>>, + &mut asn1::SequenceOf<'_, ocsp_resp::SingleResponse<'_>>, + >(value) + }) + }) }) } @@ -458,14 +560,14 @@ fn single_response<'a>( fn singleresp_py_serial_number<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, -) -> pyo3::PyResult<&'p pyo3::PyAny> { +) -> pyo3::PyResult> { big_byte_slice_to_py_int(py, resp.cert_id.serial_number.as_bytes()) } fn singleresp_py_certificate_status<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, -) -> pyo3::PyResult<&'p pyo3::PyAny> { +) -> pyo3::PyResult> { let attr = match resp.cert_status { ocsp_resp::CertStatus::Good(_) => pyo3::intern!(py, "GOOD"), ocsp_resp::CertStatus::Revoked(_) => pyo3::intern!(py, "REVOKED"), @@ -477,7 +579,7 @@ fn singleresp_py_certificate_status<'p>( fn singleresp_py_hash_algorithm<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, -) -> Result<&'p pyo3::PyAny, CryptographyError> { +) -> Result, CryptographyError> { match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&resp.cert_id.hash_algorithm.params) { Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( @@ -492,31 +594,48 @@ fn singleresp_py_hash_algorithm<'p>( fn singleresp_py_this_update<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, -) -> pyo3::PyResult<&'p pyo3::PyAny> { +) -> pyo3::PyResult> { x509::datetime_to_py(py, resp.this_update.as_datetime()) } +fn singleresp_py_this_update_utc<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult> { + x509::datetime_to_py_utc(py, resp.this_update.as_datetime()) +} + fn singleresp_py_next_update<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, -) -> pyo3::PyResult<&'p pyo3::PyAny> { +) -> pyo3::PyResult> { match &resp.next_update { Some(v) => x509::datetime_to_py(py, v.as_datetime()), - None => Ok(py.None().into_ref(py)), + None => Ok(py.None().into_bound(py)), + } +} + +fn singleresp_py_next_update_utc<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult> { + match &resp.next_update { + Some(v) => x509::datetime_to_py_utc(py, v.as_datetime()), + None => Ok(py.None().into_bound(py)), } } fn singleresp_py_revocation_reason<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, -) -> CryptographyResult<&'p pyo3::PyAny> { +) -> CryptographyResult> { match &resp.cert_status { ocsp_resp::CertStatus::Revoked(revoked_info) => match revoked_info.revocation_reason { - Some(ref v) => crl::parse_crl_reason_flags(py, v), - None => Ok(py.None().into_ref(py)), + Some(ref v) => Ok(crl::parse_crl_reason_flags(py, v)?), + None => Ok(py.None().into_bound(py)), }, ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { - Ok(py.None().into_ref(py)) + Ok(py.None().into_bound(py)) } } } @@ -524,24 +643,38 @@ fn singleresp_py_revocation_reason<'p>( fn singleresp_py_revocation_time<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, -) -> pyo3::PyResult<&'p pyo3::PyAny> { +) -> pyo3::PyResult> { match &resp.cert_status { ocsp_resp::CertStatus::Revoked(revoked_info) => { x509::datetime_to_py(py, revoked_info.revocation_time.as_datetime()) } ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { - Ok(py.None().into_ref(py)) + Ok(py.None().into_bound(py)) + } + } +} + +fn singleresp_py_revocation_time_utc<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult> { + match &resp.cert_status { + ocsp_resp::CertStatus::Revoked(revoked_info) => { + x509::datetime_to_py_utc(py, revoked_info.revocation_time.as_datetime()) + } + ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { + Ok(py.None().into_bound(py)) } } } -#[pyo3::prelude::pyfunction] -fn create_ocsp_response( +#[pyo3::pyfunction] +pub(crate) fn create_ocsp_response( py: pyo3::Python<'_>, - status: &pyo3::PyAny, - builder: &pyo3::PyAny, - private_key: &pyo3::PyAny, - hash_algorithm: &pyo3::PyAny, + status: &pyo3::Bound<'_, pyo3::PyAny>, + builder: &pyo3::Bound<'_, pyo3::PyAny>, + private_key: &pyo3::Bound<'_, pyo3::PyAny>, + hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let response_status = status .getattr(pyo3::intern!(py, "value"))? @@ -551,175 +684,179 @@ fn create_ocsp_response( let py_issuer: pyo3::PyRef<'_, x509::certificate::Certificate>; let borrowed_cert; let py_certs: Option>>; - let response_bytes = if response_status == SUCCESSFUL_RESPONSE { - let py_single_resp = builder.getattr(pyo3::intern!(py, "_response"))?; - py_cert = py_single_resp - .getattr(pyo3::intern!(py, "_cert"))? - .extract()?; - py_issuer = py_single_resp - .getattr(pyo3::intern!(py, "_issuer"))? - .extract()?; - let py_cert_hash_algorithm = py_single_resp.getattr(pyo3::intern!(py, "_algorithm"))?; - let (responder_cert, responder_encoding): ( - &pyo3::PyCell, - &pyo3::PyAny, - ) = builder - .getattr(pyo3::intern!(py, "_responder_id"))? - .extract()?; - - let py_cert_status = py_single_resp.getattr(pyo3::intern!(py, "_cert_status"))?; - let cert_status = if py_cert_status.is(types::OCSP_CERT_STATUS_GOOD.get(py)?) { - ocsp_resp::CertStatus::Good(()) - } 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 = 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)) - } else { - None - }; - // REVOKED - let py_revocation_time = - py_single_resp.getattr(pyo3::intern!(py, "_revocation_time"))?; - let revocation_time = - asn1::GeneralizedTime::new(py_to_datetime(py, py_revocation_time)?)?; - ocsp_resp::CertStatus::Revoked(ocsp_resp::RevokedInfo { - revocation_time, - revocation_reason, - }) + if response_status != SUCCESSFUL_RESPONSE { + let resp = ocsp_resp::OCSPResponse { + response_status: asn1::Enumerated::new(response_status), + response_bytes: None, }; - let next_update = if !py_single_resp - .getattr(pyo3::intern!(py, "_next_update"))? + let data = asn1::write_single(&resp)?; + return load_der_ocsp_response(py, pyo3::types::PyBytes::new(py, &data).unbind()); + } + + let py_single_resp = builder.getattr(pyo3::intern!(py, "_response"))?; + py_cert = py_single_resp + .getattr(pyo3::intern!(py, "_cert"))? + .extract()?; + py_issuer = py_single_resp + .getattr(pyo3::intern!(py, "_issuer"))? + .extract()?; + let py_cert_hash_algorithm = py_single_resp.getattr(pyo3::intern!(py, "_algorithm"))?; + let (responder_cert, responder_encoding): ( + pyo3::Bound<'_, x509::certificate::Certificate>, + pyo3::Bound<'_, pyo3::PyAny>, + ) = builder + .getattr(pyo3::intern!(py, "_responder_id"))? + .extract()?; + + let py_cert_status = py_single_resp.getattr(pyo3::intern!(py, "_cert_status"))?; + let cert_status = if py_cert_status.is(&types::OCSP_CERT_STATUS_GOOD.get(py)?) { + ocsp_resp::CertStatus::Good(()) + } 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 py_next_update = py_single_resp.getattr(pyo3::intern!(py, "_next_update"))?; - Some(asn1::GeneralizedTime::new(py_to_datetime( - py, - py_next_update, - )?)?) + 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)) } else { None }; - let py_this_update = py_single_resp.getattr(pyo3::intern!(py, "_this_update"))?; - let this_update = asn1::GeneralizedTime::new(py_to_datetime(py, py_this_update)?)?; - - let responses = vec![SingleResponse { - cert_id: ocsp::certid_new(py, &py_cert, &py_issuer, py_cert_hash_algorithm)?, - cert_status, - next_update, - this_update, - raw_single_extensions: None, - }]; - - borrowed_cert = responder_cert.borrow(); - let responder_id = if responder_encoding.is(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_dependent() - .tbs_cert - .spki - .subject_public_key - .as_bytes(), - )?) - } else { - ocsp_resp::ResponderId::ByName( - borrowed_cert - .raw - .borrow_dependent() - .tbs_cert - .subject - .clone(), - ) - }; - - let tbs_response_data = ocsp_resp::ResponseData { - version: 0, - produced_at: asn1::GeneralizedTime::new(x509::common::datetime_now(py)?)?, - responder_id, - responses: common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( - responses, - )), - raw_response_extensions: x509::common::encode_extensions( - py, - builder.getattr(pyo3::intern!(py, "_extensions"))?, - extensions::encode_extension, - )?, - }; - - let sigalg = x509::sign::compute_signature_algorithm( + // REVOKED + let py_revocation_time = py_single_resp.getattr(pyo3::intern!(py, "_revocation_time"))?; + let revocation_time = + asn1::X509GeneralizedTime::new(py_to_datetime(py, py_revocation_time)?)?; + ocsp_resp::CertStatus::Revoked(ocsp_resp::RevokedInfo { + revocation_time, + revocation_reason, + }) + }; + let next_update = if !py_single_resp + .getattr(pyo3::intern!(py, "_next_update"))? + .is_none() + { + let py_next_update = py_single_resp.getattr(pyo3::intern!(py, "_next_update"))?; + Some(asn1::X509GeneralizedTime::new(py_to_datetime( py, - private_key, - hash_algorithm, - py.None().into_ref(py), - )?; - let tbs_bytes = asn1::write_single(&tbs_response_data)?; - let signature = x509::sign::sign_data( + py_next_update, + )?)?) + } else { + None + }; + let py_this_update = py_single_resp.getattr(pyo3::intern!(py, "_this_update"))?; + let this_update = asn1::X509GeneralizedTime::new(py_to_datetime(py, py_this_update)?)?; + + let ka_vec = cryptography_keepalive::KeepAlive::new(); + let ka_bytes = cryptography_keepalive::KeepAlive::new(); + + let responses = vec![SingleResponse { + cert_id: ocsp::certid_new(py, &ka_bytes, &py_cert, &py_issuer, &py_cert_hash_algorithm)?, + cert_status, + next_update, + this_update, + raw_single_extensions: None, + }]; + + borrowed_cert = responder_cert.borrow(); + let by_key_hash; + let responder_id = if responder_encoding.is(&types::OCSP_RESPONDER_ENCODING_HASH.get(py)?) { + let sha1 = types::SHA1.get(py)?.call0()?; + by_key_hash = ocsp::hash_data( py, - private_key, - hash_algorithm, - py.None().into_ref(py), - &tbs_bytes, + &sha1, + borrowed_cert + .raw + .borrow_dependent() + .tbs_cert + .spki + .subject_public_key + .as_bytes(), )?; + ocsp_resp::ResponderId::ByKey(by_key_hash.as_bytes()) + } else { + ocsp_resp::ResponderId::ByName( + borrowed_cert + .raw + .borrow_dependent() + .tbs_cert + .subject + .clone(), + ) + }; - if !responder_cert - .call_method0(pyo3::intern!(py, "public_key"))? - .eq(private_key.call_method0(pyo3::intern!(py, "public_key"))?)? - { - return Err(CryptographyError::from( - pyo3::exceptions::PyValueError::new_err( - "Certificate public key and provided private key do not match", - ), - )); - } + let tbs_response_data = ocsp_resp::ResponseData { + version: 0, + produced_at: asn1::X509GeneralizedTime::new(x509::common::datetime_now(py)?)?, + responder_id, + responses: common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( + responses, + )), + raw_response_extensions: x509::common::encode_extensions( + py, + &ka_vec, + &ka_bytes, + &builder.getattr(pyo3::intern!(py, "_extensions"))?, + extensions::encode_extension, + )?, + }; - py_certs = builder.getattr(pyo3::intern!(py, "_certs"))?.extract()?; - let certs = py_certs.as_ref().map(|py_certs| { - common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( - py_certs - .iter() - .map(|c| c.raw.borrow_dependent().clone()) - .collect(), - )) - }); + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key.clone(), + hash_algorithm.clone(), + py.None().into_bound(py), + )?; + let tbs_bytes = asn1::write_single(&tbs_response_data)?; + let signature = x509::sign::sign_data( + py, + private_key.clone(), + hash_algorithm.clone(), + py.None().into_bound(py), + &tbs_bytes, + )?; + + if !responder_cert + .call_method0(pyo3::intern!(py, "public_key"))? + .eq(private_key.call_method0(pyo3::intern!(py, "public_key"))?)? + { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Certificate public key and provided private key do not match", + ), + )); + } - let basic_resp = ocsp_resp::BasicOCSPResponse { - tbs_response_data, - signature: asn1::BitString::new(signature, 0).unwrap(), - signature_algorithm: sigalg, - certs, - }; - Some(ocsp_resp::ResponseBytes { - response_type: (BASIC_RESPONSE_OID).clone(), - response: asn1::OctetStringEncoded::new(basic_resp), - }) - } else { - None + py_certs = builder.getattr(pyo3::intern!(py, "_certs"))?.extract()?; + let certs = py_certs.as_ref().map(|py_certs| { + common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( + py_certs + .iter() + .map(|c| c.raw.borrow_dependent().clone()) + .collect(), + )) + }); + + let basic_resp = ocsp_resp::BasicOCSPResponse { + tbs_response_data, + signature: asn1::BitString::new(&signature, 0).unwrap(), + signature_algorithm: sigalg, + certs, }; + let response_bytes = Some(ocsp_resp::ResponseBytes { + response_type: (BASIC_RESPONSE_OID).clone(), + response: asn1::OctetStringEncoded::new(basic_resp), + }); let resp = ocsp_resp::OCSPResponse { - response_status: asn1::Enumerated::new(response_status), + response_status: asn1::Enumerated::new(SUCCESSFUL_RESPONSE), response_bytes, }; let data = asn1::write_single(&resp)?; - load_der_ocsp_response(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) -} - -pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_function(pyo3::wrap_pyfunction!(load_der_ocsp_response, module)?)?; - module.add_function(pyo3::wrap_pyfunction!(create_ocsp_response, module)?)?; - - Ok(()) + load_der_ocsp_response(py, pyo3::types::PyBytes::new(py, &data).unbind()) } type RawOCSPResponseIterator<'a> = asn1::SequenceOf<'a, SingleResponse<'a>>; @@ -732,12 +869,12 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] +#[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPResponseIterator { contents: OwnedOCSPResponseIteratorData, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl OCSPResponseIterator { fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf @@ -764,8 +901,8 @@ self_cell::self_cell!( } ); -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] -struct OCSPSingleResponse { +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] +pub(crate) struct OCSPSingleResponse { raw: OwnedSingleResponse, } @@ -775,10 +912,13 @@ impl OCSPSingleResponse { } } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl OCSPSingleResponse { #[getter] - fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn serial_number<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { singleresp_py_serial_number(self.single_response(), py) } @@ -798,38 +938,89 @@ impl OCSPSingleResponse { fn hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, CryptographyError> { + ) -> Result, CryptographyError> { let single_resp = self.single_response(); singleresp_py_hash_algorithm(single_resp, py) } #[getter] - fn certificate_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn certificate_status<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { let single_resp = self.single_response(); singleresp_py_certificate_status(single_resp, py) } #[getter] - fn revocation_time<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn revocation_time<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let warning_cls = types::DEPRECATED_IN_43.get(py)?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to revocation_time_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let single_resp = self.single_response(); singleresp_py_revocation_time(single_resp, py) } #[getter] - fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + fn revocation_time_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let single_resp = self.single_response(); + singleresp_py_revocation_time_utc(single_resp, py) + } + + #[getter] + fn revocation_reason<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult> { let single_resp = self.single_response(); singleresp_py_revocation_reason(single_resp, py) } #[getter] - fn this_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn this_update<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let warning_cls = types::DEPRECATED_IN_43.get(py)?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to revocation_time_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let single_resp = self.single_response(); singleresp_py_this_update(single_resp, py) } #[getter] - fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn this_update_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let single_resp = self.single_response(); + singleresp_py_this_update_utc(single_resp, py) + } + + #[getter] + fn next_update<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let warning_cls = types::DEPRECATED_IN_43.get(py)?; + let message = cstr_from_literal!("Properties that return a naïve datetime object have been deprecated. Please switch to next_update_utc."); + pyo3::PyErr::warn(py, &warning_cls, message, 1)?; let single_resp = self.single_response(); singleresp_py_next_update(single_resp, py) } + + #[getter] + fn next_update_utc<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let single_resp = self.single_response(); + singleresp_py_next_update_utc(single_resp, py) + } } diff --git a/src/rust/src/x509/sct.rs b/src/rust/src/x509/sct.rs index b7cce3ff4036..65fd001d31d1 100644 --- a/src/rust/src/x509/sct.rs +++ b/src/rust/src/x509/sct.rs @@ -5,9 +5,9 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -use pyo3::ToPyObject; +use pyo3::types::{PyAnyMethods, PyDictMethods, PyListMethods}; -use crate::error::CryptographyError; +use crate::error::{CryptographyError, CryptographyResult}; use crate::types; struct TLSReader<'a> { @@ -127,7 +127,7 @@ impl TryFrom for SignatureAlgorithm { } } -#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Sct { log_id: [u8; 32], timestamp: u64, @@ -140,7 +140,7 @@ pub(crate) struct Sct { pub(crate) sct_data: Vec, } -#[pyo3::prelude::pymethods] +#[pyo3::pymethods] impl Sct { fn __eq__(&self, other: pyo3::PyRef<'_, Sct>) -> bool { self.sct_data == other.sct_data @@ -153,7 +153,7 @@ impl Sct { } #[getter] - fn version<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn version<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { types::CERTIFICATE_TRANSPARENCY_VERSION_V1.get(py) } @@ -163,12 +163,12 @@ impl Sct { } #[getter] - fn timestamp<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn timestamp<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { 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::>)?; + kwargs.set_item("tzinfo", None::>)?; types::DATETIME_DATETIME .get(py)? @@ -176,11 +176,11 @@ impl Sct { pyo3::intern!(py, "fromtimestamp"), (self.timestamp / 1000, utc), )? - .call_method("replace", (), Some(kwargs)) + .call_method("replace", (), Some(&kwargs)) } #[getter] - fn entry_type<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn entry_type<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { 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)?, @@ -191,14 +191,17 @@ impl Sct { fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { + ) -> pyo3::PyResult> { 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> { + fn signature_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { types::SIGNATURE_ALGORITHM .get(py)? .getattr(self.signature_algorithm.to_attr()) @@ -215,11 +218,11 @@ impl Sct { } } -pub(crate) fn parse_scts( - py: pyo3::Python<'_>, +pub(crate) fn parse_scts<'p>( + py: pyo3::Python<'p>, data: &[u8], entry_type: LogEntryType, -) -> Result { +) -> CryptographyResult> { let mut reader = TLSReader::new(data).read_length_prefixed()?; let py_scts = pyo3::types::PyList::empty(py); @@ -250,15 +253,9 @@ pub(crate) fn parse_scts( extension_bytes, sct_data: raw_sct_data, }; - py_scts.append(pyo3::PyCell::new(py, sct)?)?; + py_scts.append(pyo3::Bound::new(py, sct)?)?; } - Ok(py_scts.to_object(py)) -} - -pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_class::()?; - - Ok(()) + Ok(py_scts.into_any()) } #[cfg(test)] diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs index 4d9637d1f2de..d826dda8fbae 100644 --- a/src/rust/src/x509/sign.rs +++ b/src/rust/src/x509/sign.rs @@ -6,6 +6,8 @@ use std::collections::HashMap; use cryptography_x509::{common, oid}; use once_cell::sync::Lazy; +use pyo3::pybacked::PyBackedBytes; +use pyo3::types::PyAnyMethods; use crate::asn1::oid_to_py_oid; use crate::error::{CryptographyError, CryptographyResult}; @@ -48,16 +50,19 @@ enum HashType { Sha3_512, } -fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::PyResult { - if private_key.is_instance(types::RSA_PRIVATE_KEY.get(py)?)? { +pub(crate) fn identify_key_type( + py: pyo3::Python<'_>, + private_key: pyo3::Bound<'_, pyo3::PyAny>, +) -> pyo3::PyResult { + if private_key.is_instance(&types::RSA_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Rsa) - } else if private_key.is_instance(types::DSA_PRIVATE_KEY.get(py)?)? { + } else if private_key.is_instance(&types::DSA_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Dsa) - } else if private_key.is_instance(types::ELLIPTIC_CURVE_PRIVATE_KEY.get(py)?)? { + } else if private_key.is_instance(&types::ELLIPTIC_CURVE_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ec) - } else if private_key.is_instance(types::ED25519_PRIVATE_KEY.get(py)?)? { + } else if private_key.is_instance(&types::ED25519_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ed25519) - } else if private_key.is_instance(types::ED448_PRIVATE_KEY.get(py)?)? { + } else if private_key.is_instance(&types::ED448_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ed448) } else { Err(pyo3::exceptions::PyTypeError::new_err( @@ -68,21 +73,21 @@ fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::P fn identify_hash_type( py: pyo3::Python<'_>, - hash_algorithm: &pyo3::PyAny, + hash_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { if hash_algorithm.is_none() { return Ok(HashType::None); } - if !hash_algorithm.is_instance(types::HASH_ALGORITHM.get(py)?)? { + if !hash_algorithm.is_instance(&types::HASH_ALGORITHM.get(py)?)? { return Err(pyo3::exceptions::PyTypeError::new_err( "Algorithm must be a registered hash algorithm.", )); } - match hash_algorithm + match &*hash_algorithm .getattr(pyo3::intern!(py, "name"))? - .extract()? + .extract::()? { "sha224" => Ok(HashType::Sha224), "sha256" => Ok(HashType::Sha256), @@ -100,21 +105,21 @@ fn identify_hash_type( fn compute_pss_salt_length<'p>( py: pyo3::Python<'p>, - private_key: &'p pyo3::PyAny, - hash_algorithm: &'p pyo3::PyAny, - rsa_padding: &'p pyo3::PyAny, + private_key: pyo3::Bound<'p, pyo3::PyAny>, + hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, + rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, ) -> pyo3::PyResult { let py_saltlen = rsa_padding.getattr(pyo3::intern!(py, "_salt_length"))?; - if py_saltlen.is_instance(types::PADDING_MAX_LENGTH.get(py)?)? { + 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(types::PADDING_DIGEST_LENGTH.get(py)?)? { + } 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_of::() { + } else if py_saltlen.is_instance_of::() { py_saltlen.extract::() } else { Err(pyo3::exceptions::PyTypeError::new_err( @@ -125,22 +130,23 @@ fn compute_pss_salt_length<'p>( pub(crate) fn compute_signature_algorithm<'p>( py: pyo3::Python<'p>, - private_key: &'p pyo3::PyAny, - hash_algorithm: &'p pyo3::PyAny, - rsa_padding: &'p pyo3::PyAny, + private_key: pyo3::Bound<'p, pyo3::PyAny>, + hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, + rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, ) -> pyo3::PyResult> { - let key_type = identify_key_type(py, private_key)?; - let hash_type = identify_hash_type(py, hash_algorithm)?; + let key_type = identify_key_type(py, private_key.clone())?; + let hash_type = identify_hash_type(py, hash_algorithm.clone())?; // 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(types::PSS.get(py)?)? { + if 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(), params: hash_alg_params, }; - let salt_length = compute_pss_salt_length(py, private_key, hash_algorithm, rsa_padding)?; + let salt_length = + compute_pss_salt_length(py, private_key, hash_algorithm, rsa_padding.clone())?; let py_mgf_alg = rsa_padding .getattr(pyo3::intern!(py, "_mgf"))? .getattr(pyo3::intern!(py, "_algorithm"))?; @@ -157,7 +163,7 @@ pub(crate) fn compute_signature_algorithm<'p>( params: mgf_alg, }, salt_length, - _trailer_field: 1, + _trailer_field: None, }))); return Ok(common::AlgorithmIdentifier { @@ -276,12 +282,12 @@ pub(crate) fn compute_signature_algorithm<'p>( pub(crate) fn sign_data<'p>( py: pyo3::Python<'p>, - private_key: &'p pyo3::PyAny, - hash_algorithm: &'p pyo3::PyAny, - rsa_padding: &'p pyo3::PyAny, + private_key: pyo3::Bound<'p, pyo3::PyAny>, + hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, + rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, data: &[u8], -) -> pyo3::PyResult<&'p [u8]> { - let key_type = identify_key_type(py, private_key)?; +) -> pyo3::PyResult { + let key_type = identify_key_type(py, private_key.clone())?; let signature = match key_type { KeyType::Ed25519 | KeyType::Ed448 => { @@ -307,12 +313,12 @@ pub(crate) fn sign_data<'p>( pub(crate) fn verify_signature_with_signature_algorithm<'p>( py: pyo3::Python<'p>, - issuer_public_key: &'p pyo3::PyAny, + issuer_public_key: pyo3::Bound<'p, pyo3::PyAny>, signature_algorithm: &common::AlgorithmIdentifier<'_>, signature: &[u8], data: &[u8], ) -> CryptographyResult<()> { - let key_type = identify_public_key_type(py, issuer_public_key)?; + let key_type = identify_public_key_type(py, issuer_public_key.clone())?; let sig_key_type = identify_key_type_for_algorithm_params(&signature_algorithm.params)?; if key_type != sig_key_type { return Err(CryptographyError::from( @@ -351,17 +357,17 @@ pub(crate) fn verify_signature_with_signature_algorithm<'p>( pub(crate) fn identify_public_key_type( py: pyo3::Python<'_>, - public_key: &pyo3::PyAny, + public_key: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { - if public_key.is_instance(types::RSA_PUBLIC_KEY.get(py)?)? { + if public_key.is_instance(&types::RSA_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Rsa) - } else if public_key.is_instance(types::DSA_PUBLIC_KEY.get(py)?)? { + } else if public_key.is_instance(&types::DSA_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Dsa) - } else if public_key.is_instance(types::ELLIPTIC_CURVE_PUBLIC_KEY.get(py)?)? { + } else if public_key.is_instance(&types::ELLIPTIC_CURVE_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ec) - } else if public_key.is_instance(types::ED25519_PUBLIC_KEY.get(py)?)? { + } else if public_key.is_instance(&types::ED25519_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ed25519) - } else if public_key.is_instance(types::ED448_PUBLIC_KEY.get(py)?)? { + } else if public_key.is_instance(&types::ED448_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ed448) } else { Err(pyo3::exceptions::PyTypeError::new_err( @@ -424,7 +430,7 @@ fn identify_alg_params_for_hash_type( fn hash_oid_py_hash( py: pyo3::Python<'_>, oid: asn1::ObjectIdentifier, -) -> CryptographyResult<&pyo3::PyAny> { +) -> CryptographyResult> { match HASH_OIDS_TO_HASH.get(&oid) { Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( @@ -439,7 +445,7 @@ fn hash_oid_py_hash( pub(crate) fn identify_signature_hash_algorithm<'p>( py: pyo3::Python<'p>, signature_algorithm: &common::AlgorithmIdentifier<'_>, -) -> CryptographyResult<&'p pyo3::PyAny> { +) -> CryptographyResult> { let sig_oids_to_hash = types::SIG_OIDS_TO_HASH.get(py)?; match &signature_algorithm.params { common::AlgorithmParameters::RsaPss(opt_pss) => { @@ -467,7 +473,7 @@ pub(crate) fn identify_signature_hash_algorithm<'p>( pub(crate) fn identify_signature_algorithm_parameters<'p>( py: pyo3::Python<'p>, signature_algorithm: &common::AlgorithmIdentifier<'_>, -) -> CryptographyResult<&'p pyo3::PyAny> { +) -> CryptographyResult> { match &signature_algorithm.params { common::AlgorithmParameters::RsaPss(opt_pss) => { let pss = opt_pss.as_ref().ok_or_else(|| { @@ -511,7 +517,7 @@ pub(crate) fn identify_signature_algorithm_parameters<'p>( Ok(types::ECDSA.get(py)?.call1((signature_hash_algorithm,))?) } - _ => Ok(py.None().into_ref(py)), + _ => Ok(py.None().into_bound(py)), } } diff --git a/src/rust/src/x509/verify.rs b/src/rust/src/x509/verify.rs index 8cd9cfdf964b..39bfb7952a86 100644 --- a/src/rust/src/x509/verify.rs +++ b/src/rust/src/x509/verify.rs @@ -2,13 +2,16 @@ // 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::{ + certificate::Certificate, extensions::SubjectAlternativeName, oid::SUBJECT_ALTERNATIVE_NAME_OID, +}; use cryptography_x509_verification::{ ops::{CryptoOps, VerificationCertificate}, policy::{Policy, Subject}, trust_store::Store, types::{DNSName, IPAddress}, }; +use pyo3::types::{PyAnyMethods, PyListMethods}; use crate::backend::keys; use crate::error::{CryptographyError, CryptographyResult}; @@ -17,6 +20,8 @@ use crate::x509::certificate::Certificate as PyCertificate; use crate::x509::common::{datetime_now, datetime_to_py, py_to_datetime}; use crate::x509::sign; +use super::parse_general_names; + pub(crate) struct PyCryptoOps {} impl CryptoOps for PyCryptoOps { @@ -26,7 +31,7 @@ impl CryptoOps for PyCryptoOps { 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()) + Ok(keys::load_der_public_key_bytes(py, cert.tbs_cert.spki.tlv().full_data())?.unbind()) }) } @@ -34,13 +39,21 @@ impl CryptoOps for PyCryptoOps { pyo3::Python::with_gil(|py| -> CryptographyResult<()> { sign::verify_signature_with_signature_algorithm( py, - key.as_ref(py), + key.bind(py).clone(), &cert.signature_alg, cert.signature.as_bytes(), &asn1::write_single(&cert.tbs_cert)?, ) }) } + + fn clone_public_key(key: &Self::Key) -> Self::Key { + pyo3::Python::with_gil(|py| key.clone_ref(py)) + } + + fn clone_extra(extra: &Self::CertificateExtra) -> Self::CertificateExtra { + pyo3::Python::with_gil(|py| extra.clone_ref(py)) + } } pyo3::create_exception!( @@ -49,13 +62,37 @@ pyo3::create_exception!( pyo3::exceptions::PyException ); +macro_rules! policy_builder_set_once_check { + ($self: ident, $property: ident, $human_readable_name: literal) => { + if $self.$property.is_some() { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(concat!( + "The ", + $human_readable_name, + " may only be set once." + )), + )); + } + }; +} + #[pyo3::pyclass(frozen, module = "cryptography.x509.verification")] -struct PolicyBuilder { +pub(crate) struct PolicyBuilder { time: Option, store: Option>, max_chain_depth: Option, } +impl PolicyBuilder { + fn py_clone(&self, py: pyo3::Python<'_>) -> PolicyBuilder { + PolicyBuilder { + time: self.time.clone(), + store: self.store.as_ref().map(|s| s.clone_ref(py)), + max_chain_depth: self.max_chain_depth, + } + } +} + #[pyo3::pymethods] impl PolicyBuilder { #[new] @@ -70,32 +107,26 @@ impl PolicyBuilder { fn time( &self, py: pyo3::Python<'_>, - new_time: &pyo3::PyAny, + new_time: pyo3::Bound<'_, 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.", - ), - )); - } + policy_builder_set_once_check!(self, time, "validation time"); + 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, + ..self.py_clone(py) }) } - 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."), - )); - } + fn store( + &self, + py: pyo3::Python<'_>, + new_store: pyo3::Py, + ) -> CryptographyResult { + policy_builder_set_once_check!(self, store, "trust store"); + Ok(PolicyBuilder { - time: self.time.clone(), store: Some(new_store), - max_chain_depth: self.max_chain_depth, + ..self.py_clone(py) }) } @@ -104,20 +135,37 @@ impl PolicyBuilder { 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.", - ), - )); - } + policy_builder_set_once_check!(self, max_chain_depth, "maximum chain depth"); + 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), + ..self.py_clone(py) }) } + fn build_client_verifier(&self, py: pyo3::Python<'_>) -> 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 client verifier must have a trust store.", + ), + )); + } + }; + + let time = match self.time.as_ref() { + Some(t) => t.clone(), + None => datetime_now(py)?, + }; + + // TODO: Pass extension policies here once implemented in cryptography-x509-verification. + let policy = Policy::client(PyCryptoOps {}, time, self.max_chain_depth); + + Ok(PyClientVerifier { policy, store }) + } + fn build_server_verifier( &self, py: pyo3::Python<'_>, @@ -142,12 +190,14 @@ impl PolicyBuilder { let policy = OwnedPolicy::try_new(subject_owner, |subject_owner| { let subject = build_subject(py, subject_owner)?; - Ok::, pyo3::PyErr>(PyCryptoPolicy(Policy::new( + + // TODO: Pass extension policies here once implemented in cryptography-x509-verification. + Ok::, pyo3::PyErr>(Policy::server( PyCryptoOps {}, subject, time, self.max_chain_depth, - ))) + )) })?; Ok(PyServerVerifier { @@ -158,7 +208,7 @@ impl PolicyBuilder { } } -struct PyCryptoPolicy<'a>(Policy<'a, PyCryptoOps>); +type PyCryptoPolicy<'a> = Policy<'a, PyCryptoOps>; /// This enum exists solely to provide heterogeneously typed ownership for `OwnedPolicy`. enum SubjectOwner { @@ -180,12 +230,106 @@ self_cell::self_cell!( } ); +#[pyo3::pyclass( + frozen, + name = "VerifiedClient", + module = "cryptography.hazmat.bindings._rust.x509" +)] +pub(crate) struct PyVerifiedClient { + #[pyo3(get)] + subjects: Option>, + #[pyo3(get)] + chain: pyo3::Py, +} + +#[pyo3::pyclass( + frozen, + name = "ClientVerifier", + module = "cryptography.hazmat.bindings._rust.x509" +)] +pub(crate) struct PyClientVerifier { + policy: PyCryptoPolicy<'static>, + #[pyo3(get)] + store: pyo3::Py, +} + +impl PyClientVerifier { + fn as_policy(&self) -> &Policy<'_, PyCryptoOps> { + &self.policy + } +} + +#[pyo3::pymethods] +impl PyClientVerifier { + #[getter] + fn validation_time<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + datetime_to_py(py, &self.as_policy().validation_time) + } + + #[getter] + fn max_chain_depth(&self) -> u8 { + self.as_policy().max_chain_depth + } + + fn verify( + &self, + py: pyo3::Python<'_>, + leaf: pyo3::Py, + intermediates: Vec>, + ) -> CryptographyResult { + let policy = self.as_policy(); + let store = self.store.get(); + + let intermediates = intermediates + .iter() + .map(|i| VerificationCertificate::new(i.get().raw.borrow_dependent(), i.clone_ref(py))) + .collect::>(); + + let v = VerificationCertificate::new(leaf.get().raw.borrow_dependent(), leaf.clone_ref(py)); + + let chain = cryptography_x509_verification::verify( + &v, + &intermediates, + policy, + store.raw.borrow_dependent(), + ) + .or_else(|e| handle_validation_error(py, e))?; + + let py_chain = pyo3::types::PyList::empty(py); + for c in &chain { + py_chain.append(c.extra())?; + } + + // NOTE: These `unwrap()`s cannot fail, since the underlying policy + // enforces the presence of a SAN and the well-formedness of the + // extension set. + let leaf_san = &chain[0] + .certificate() + .extensions() + .ok() + .unwrap() + .get_extension(&SUBJECT_ALTERNATIVE_NAME_OID) + .unwrap(); + + let leaf_gns = leaf_san.value::>()?; + let py_gns = parse_general_names(py, &leaf_gns)?; + + Ok(PyVerifiedClient { + subjects: Some(py_gns.into()), + chain: py_chain.unbind(), + }) + } +} + #[pyo3::pyclass( frozen, name = "ServerVerifier", module = "cryptography.hazmat.bindings._rust.x509" )] -struct PyServerVerifier { +pub(crate) struct PyServerVerifier { #[pyo3(get, name = "subject")] py_subject: pyo3::Py, policy: OwnedPolicy, @@ -195,14 +339,17 @@ struct PyServerVerifier { impl PyServerVerifier { fn as_policy(&self) -> &Policy<'_, PyCryptoOps> { - &self.policy.borrow_dependent().0 + self.policy.borrow_dependent() } } #[pyo3::pymethods] impl PyServerVerifier { #[getter] - fn validation_time<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { + fn validation_time<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { datetime_to_py(py, &self.as_policy().validation_time) } @@ -216,25 +363,24 @@ impl PyServerVerifier { py: pyo3::Python<'p>, leaf: pyo3::Py, intermediates: Vec>, - ) -> CryptographyResult<&'p pyo3::types::PyList> { + ) -> CryptographyResult> { let policy = self.as_policy(); let store = self.store.get(); + let intermediates = intermediates + .iter() + .map(|i| VerificationCertificate::new(i.get().raw.borrow_dependent(), i.clone_ref(py))) + .collect::>(); + + let v = VerificationCertificate::new(leaf.get().raw.borrow_dependent(), leaf.clone_ref(py)); + 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), - ) - }), + &v, + &intermediates, policy, store.raw.borrow_dependent(), ) - .map_err(|e| VerificationError::new_err(format!("validation failed: {e:?}")))?; + .or_else(|e| handle_validation_error(py, e))?; let result = pyo3::types::PyList::empty(py); for c in chain { @@ -248,21 +394,22 @@ fn build_subject_owner( py: pyo3::Python<'_>, subject: &pyo3::Py, ) -> pyo3::PyResult { - let subject = subject.as_ref(py); + let subject = subject.bind(py); - if subject.is_instance(types::DNS_NAME.get(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)?)? { + // TODO: switch this to borrowing the string (using Bound::to_str) once our + // minimum Python version is 3.10 + .extract::()?; + Ok(SubjectOwner::DNSName(value)) + } 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())) + .downcast::()? + .clone(); + Ok(SubjectOwner::IPAddress(value.unbind())) } else { Err(pyo3::exceptions::PyTypeError::new_err( "unsupported subject type", @@ -290,6 +437,19 @@ fn build_subject<'a>( } } +fn handle_validation_error( + py: pyo3::Python<'_>, + e: cryptography_x509_verification::ValidationError<'_, PyCryptoOps>, +) -> CryptographyResult { + let mut msg = format!("validation failed: {e}"); + if let Some(cert) = e.certificate() { + let cert_repr = cert.extra().bind(py).repr()?; + msg = format!("{msg} (encountered processing {cert_repr})"); + } + + Err(CryptographyError::from(VerificationError::new_err(msg))) +} + type PyCryptoOpsStore<'a> = Store<'a, PyCryptoOps>; self_cell::self_cell!( @@ -306,7 +466,7 @@ self_cell::self_cell!( name = "Store", module = "cryptography.hazmat.bindings._rust.x509" )] -struct PyStore { +pub(crate) struct PyStore { raw: RawPyStore, } @@ -322,24 +482,9 @@ impl PyStore { 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), - ) + VerificationCertificate::new(t.get().raw.borrow_dependent(), 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_fernet.py b/tests/bench/test_fernet.py new file mode 100644 index 000000000000..c550aa78920c --- /dev/null +++ b/tests/bench/test_fernet.py @@ -0,0 +1,10 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography import fernet + + +def test_fernet_encrypt(benchmark): + f = fernet.Fernet(fernet.Fernet.generate_key()) + benchmark(f.encrypt, b"\x00" * 256) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index a289c5ba7415..901eec59776f 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -14,9 +14,6 @@ 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 -from cryptography.hazmat.primitives.ciphers.algorithms import AES -from cryptography.hazmat.primitives.ciphers.modes import CBC from ...doubles import ( DummyAsymmetricPadding, @@ -62,13 +59,13 @@ def test_openssl_version_text(self): # Verify the correspondence between these two. And do it in a way that # ensures coverage. if version.startswith("LibreSSL"): - assert backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - if backend._lib.CRYPTOGRAPHY_IS_LIBRESSL: + assert rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + if rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL: assert version.startswith("LibreSSL") if version.startswith("BoringSSL"): - assert backend._lib.CRYPTOGRAPHY_IS_BORINGSSL - if backend._lib.CRYPTOGRAPHY_IS_BORINGSSL: + assert rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + if rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert version.startswith("BoringSSL") def test_openssl_version_number(self): @@ -80,26 +77,6 @@ def test_supports_cipher(self): is False ) - def test_register_duplicate_cipher_adapter(self): - with pytest.raises(ValueError): - backend.register_cipher_adapter(AES, CBC, None) - - @pytest.mark.parametrize("mode", [DummyMode(), None]) - def test_nonexistent_cipher(self, mode, backend, monkeypatch): - # We can't use register_cipher_adapter because backend is a - # global singleton and we want to revert the change after the test - monkeypatch.setitem( - backend._cipher_registry, - (DummyCipherAlgorithm, type(mode)), - lambda backend, cipher, mode: backend._ffi.NULL, - ) - cipher = Cipher( - DummyCipherAlgorithm(), - mode, - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - cipher.encryptor() - def test_openssl_assert(self): backend.openssl_assert(True) with pytest.raises(InternalError): @@ -128,22 +105,8 @@ def test_evp_ciphers_registered(self): cipher = backend._lib.EVP_get_cipherbyname(b"aes-256-cbc") assert cipher != backend._ffi.NULL - def test_unknown_error_in_cipher_finalize(self): - cipher = Cipher(AES(b"\0" * 16), CBC(b"\0" * 16), backend=backend) - enc = cipher.encryptor() - enc.update(b"\0") - backend._lib.ERR_put_error(0, 0, 1, b"test_openssl.py", -1) - with pytest.raises(InternalError): - enc.finalize() - class TestOpenSSLRSA: - def test_generate_rsa_parameters_supported(self): - assert backend.generate_rsa_parameters_supported(1, 1024) is False - assert backend.generate_rsa_parameters_supported(4, 1024) is False - assert backend.generate_rsa_parameters_supported(3, 1024) is True - assert backend.generate_rsa_parameters_supported(3, 511) is False - def test_rsa_padding_unsupported_pss_mgf1_hash(self): assert ( backend.rsa_padding_supported( @@ -238,15 +201,6 @@ def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_2048): class TestOpenSSLSerializationWithOpenSSL: - def test_unsupported_evp_pkey_type(self): - key = backend._lib.EVP_PKEY_new() - key = backend._ffi.gc(key, backend._lib.EVP_PKEY_free) - with raises_unsupported_algorithm(None): - rust_openssl.keys.private_key_from_ptr( - int(backend._ffi.cast("uintptr_t", key)), - unsafe_skip_rsa_key_validation=False, - ) - def test_very_long_pem_serialization_password(self): password = b"x" * 1025 diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py deleted file mode 100644 index 371a7c990188..000000000000 --- a/tests/hazmat/backends/test_openssl_memleak.py +++ /dev/null @@ -1,391 +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 json -import os -import platform -import subprocess -import sys -import textwrap - -import pytest - -from cryptography.hazmat.bindings.openssl.binding import Binding - -MEMORY_LEAK_SCRIPT = """ -import sys - - -def main(argv): - import gc - import json - - import cffi - - from cryptography.hazmat.bindings._rust import _openssl - - heap = {} - start_heap = {} - start_heap_realloc_delta = [0] # 1-item list so callbacks can mutate it - - BACKTRACE_ENABLED = False - if BACKTRACE_ENABLED: - backtrace_ffi = cffi.FFI() - backtrace_ffi.cdef(''' - int backtrace(void **, int); - char **backtrace_symbols(void *const *, int); - ''') - backtrace_lib = backtrace_ffi.dlopen(None) - - def backtrace(): - buf = backtrace_ffi.new("void*[]", 24) - length = backtrace_lib.backtrace(buf, len(buf)) - return (buf, length) - - def symbolize_backtrace(trace): - (buf, length) = trace - symbols = backtrace_lib.backtrace_symbols(buf, length) - stack = [ - backtrace_ffi.string(symbols[i]).decode() - for i in range(length) - ] - _openssl.lib.Cryptography_free_wrapper( - symbols, backtrace_ffi.NULL, 0 - ) - return stack - else: - def backtrace(): - return None - - def symbolize_backtrace(trace): - return None - - @_openssl.ffi.callback("void *(size_t, const char *, int)") - def malloc(size, path, line): - ptr = _openssl.lib.Cryptography_malloc_wrapper(size, path, line) - heap[ptr] = (size, path, line, backtrace()) - return ptr - - @_openssl.ffi.callback("void *(void *, size_t, const char *, int)") - def realloc(ptr, size, path, line): - if ptr != _openssl.ffi.NULL: - del heap[ptr] - new_ptr = _openssl.lib.Cryptography_realloc_wrapper( - ptr, size, path, line - ) - heap[new_ptr] = (size, path, line, backtrace()) - - # It is possible that something during the test will cause a - # realloc of memory allocated during the startup phase. (This - # was observed in conda-forge Windows builds of this package with - # provider operation_bits pointers in crypto/provider_core.c.) If - # we don't pay attention to that, the realloc'ed pointer will show - # up as a leak; but we also don't want to allow this kind of realloc - # to consume large amounts of additional memory. So we track the - # realloc and the change in memory consumption. - startup_info = start_heap.pop(ptr, None) - if startup_info is not None: - start_heap[new_ptr] = heap[new_ptr] - start_heap_realloc_delta[0] += size - startup_info[0] - - return new_ptr - - @_openssl.ffi.callback("void(void *, const char *, int)") - def free(ptr, path, line): - if ptr != _openssl.ffi.NULL: - del heap[ptr] - _openssl.lib.Cryptography_free_wrapper(ptr, path, line) - - result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( - malloc, realloc, free - ) - assert result == 1 - - # Trigger a bunch of initialization stuff. - import hashlib - from cryptography.hazmat.backends.openssl.backend import backend - - hashlib.sha256() - - start_heap.update(heap) - - try: - func(*argv[1:]) - finally: - gc.collect() - gc.collect() - gc.collect() - - if _openssl.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - _openssl.lib.OSSL_PROVIDER_unload(backend._binding._legacy_provider) - _openssl.lib.OSSL_PROVIDER_unload(backend._binding._default_provider) - - _openssl.lib.OPENSSL_cleanup() - - # Swap back to the original functions so that if OpenSSL tries to free - # something from its atexit handle it won't be going through a Python - # function, which will be deallocated when this function returns - result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( - _openssl.ffi.addressof( - _openssl.lib, "Cryptography_malloc_wrapper" - ), - _openssl.ffi.addressof( - _openssl.lib, "Cryptography_realloc_wrapper" - ), - _openssl.ffi.addressof(_openssl.lib, "Cryptography_free_wrapper"), - ) - assert result == 1 - - remaining = set(heap) - set(start_heap) - - # The constant here is the number of additional bytes of memory - # consumption that are allowed in reallocs of start_heap memory. - if remaining or start_heap_realloc_delta[0] > 3072: - info = dict( - (int(_openssl.ffi.cast("size_t", ptr)), { - "size": heap[ptr][0], - "path": _openssl.ffi.string(heap[ptr][1]).decode(), - "line": heap[ptr][2], - "backtrace": symbolize_backtrace(heap[ptr][3]), - }) - for ptr in remaining - ) - info["start_heap_realloc_delta"] = start_heap_realloc_delta[0] - sys.stdout.write(json.dumps(info)) - sys.stdout.flush() - sys.exit(255) - -main(sys.argv) -""" - - -def assert_no_memory_leaks(s, argv=[]): - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join(sys.path) - - # When using pytest-cov it attempts to instrument subprocesses. This - # causes the memleak tests to raise exceptions. - # we don't need coverage so we remove the env vars. - env.pop("COV_CORE_CONFIG", None) - env.pop("COV_CORE_DATAFILE", None) - env.pop("COV_CORE_SOURCE", None) - - argv = [sys.executable, "-c", 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( - argv, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - assert proc.stdout is not None - assert proc.stderr is not None - try: - proc.wait() - if proc.returncode == 255: - # 255 means there was a leak, load the info about what mallocs - # weren't freed. - out = json.loads(proc.stdout.read().decode()) - raise AssertionError(out) - elif proc.returncode != 0: - # Any exception type will do to be honest - raise ValueError(proc.stdout.read(), proc.stderr.read()) - finally: - proc.stdout.close() - proc.stderr.close() - - -def skip_if_memtesting_not_supported(): - return pytest.mark.skipif( - not Binding().lib.Cryptography_HAS_MEM_FUNCTIONS - or platform.python_implementation() == "PyPy", - reason="Requires OpenSSL memory functions (>=1.1.0) and not PyPy", - ) - - -@pytest.mark.skip_fips(reason="FIPS self-test sets allow_customize = 0") -@skip_if_memtesting_not_supported() -class TestAssertNoMemoryLeaks: - def test_no_leak_no_malloc(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - pass - """ - ) - ) - - def test_no_leak_free(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography.hazmat.bindings.openssl.binding import Binding - b = Binding() - name = b.lib.X509_NAME_new() - b.lib.X509_NAME_free(name) - """ - ) - ) - - def test_no_leak_gc(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography.hazmat.bindings.openssl.binding import Binding - b = Binding() - name = b.lib.X509_NAME_new() - b.ffi.gc(name, b.lib.X509_NAME_free) - """ - ) - ) - - def test_leak(self): - with pytest.raises(AssertionError): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography.hazmat.bindings.openssl.binding import ( - Binding - ) - b = Binding() - b.lib.X509_NAME_new() - """ - ) - ) - - def test_errors(self): - with pytest.raises(ValueError, match="ZeroDivisionError"): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - raise ZeroDivisionError - """ - ) - ) - - -@pytest.mark.skip_fips(reason="FIPS self-test sets allow_customize = 0") -@skip_if_memtesting_not_supported() -class TestOpenSSLMemoryLeaks: - def test_ec_private_numbers_private_key(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives.asymmetric import ec - - ec.EllipticCurvePrivateNumbers( - private_value=int( - '280814107134858470598753916394807521398239633534281633982576099083' - '35787109896602102090002196616273211495718603965098' - ), - public_numbers=ec.EllipticCurvePublicNumbers( - curve=ec.SECP384R1(), - x=int( - '10036914308591746758780165503819213553101287571902957054148542' - '504671046744460374996612408381962208627004841444205030' - ), - y=int( - '17337335659928075994560513699823544906448896792102247714689323' - '575406618073069185107088229463828921069465902299522926' - ) - ) - ).private_key(backend) - """ - ) - ) - - def test_ec_derive_private_key(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives.asymmetric import ec - ec.derive_private_key(1, ec.SECP256R1(), backend) - """ - ) - ) - - def test_x25519_pubkey_from_private_key(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography.hazmat.primitives.asymmetric import x25519 - private_key = x25519.X25519PrivateKey.generate() - private_key.public_key() - """ - ) - ) - - @pytest.mark.parametrize( - "path", - ["pkcs12/cert-aes256cbc-no-key.p12", "pkcs12/cert-key-aes256cbc.p12"], - ) - def test_load_pkcs12_key_and_certificates(self, path): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(path): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives.serialization import pkcs12 - import cryptography_vectors - - with cryptography_vectors.open_vector_file(path, "rb") as f: - pkcs12.load_key_and_certificates( - f.read(), b"cryptography", backend - ) - """ - ), - [path], - ) - - def test_write_pkcs12_key_and_certificates(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - import os - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import serialization - from cryptography.hazmat.primitives.serialization import pkcs12 - import cryptography_vectors - - path = os.path.join('x509', 'custom', 'ca', 'ca.pem') - with cryptography_vectors.open_vector_file(path, "rb") as f: - cert = x509.load_pem_x509_certificate( - f.read(), backend - ) - path2 = os.path.join('x509', 'custom', 'dsa_selfsigned_ca.pem') - with cryptography_vectors.open_vector_file(path2, "rb") as f: - cert2 = x509.load_pem_x509_certificate( - f.read(), backend - ) - path3 = os.path.join('x509', 'letsencryptx3.pem') - with cryptography_vectors.open_vector_file(path3, "rb") as f: - cert3 = x509.load_pem_x509_certificate( - f.read(), backend - ) - key_path = os.path.join("x509", "custom", "ca", "ca_key.pem") - with cryptography_vectors.open_vector_file(key_path, "rb") as f: - key = serialization.load_pem_private_key( - f.read(), None, backend - ) - encryption = serialization.NoEncryption() - pkcs12.serialize_key_and_certificates( - b"name", key, cert, [cert2, cert3], encryption) - """ - ) - ) diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index 64c3cfdec05c..db6410d5d1e5 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -8,7 +8,6 @@ from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl.binding import ( Binding, - _legacy_provider_error, _openssl_assert, _verify_package_version, ) @@ -25,7 +24,7 @@ def test_ssl_ctx_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() # SSL_OP_ALL is 0 on BoringSSL - if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b.lib.SSL_OP_ALL > 0 ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL @@ -40,7 +39,7 @@ def test_ssl_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() # SSL_OP_ALL is 0 on BoringSSL - if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b.lib.SSL_OP_ALL > 0 ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL @@ -56,7 +55,7 @@ def test_ssl_options(self): def test_conditional_removal(self): b = Binding() - if not b.lib.CRYPTOGRAPHY_IS_LIBRESSL: + if not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL: assert b.lib.TLS_ST_OK else: with pytest.raises(AttributeError): @@ -77,19 +76,13 @@ def test_openssl_assert_error_on_stack(self): error = exc_info.value.err_code[0] assert error.lib == b.lib.ERR_LIB_EVP assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH - if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b"data not multiple of block length" in error.reason_text def test_version_mismatch(self): with pytest.raises(ImportError): _verify_package_version("nottherightversion") - def test_legacy_provider_error(self): - with pytest.raises(RuntimeError): - _legacy_provider_error(False) - - _legacy_provider_error(True) - def test_rust_internal_error(self): with pytest.raises(InternalError) as exc_info: rust_openssl.raise_openssl_error() @@ -110,5 +103,5 @@ def test_rust_internal_error(self): error = exc_info.value.err_code[0] assert error.lib == b.lib.ERR_LIB_EVP assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH - if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b"data not multiple of block length" in error.reason_text diff --git a/tests/hazmat/primitives/decrepit/__init__.py b/tests/hazmat/primitives/decrepit/__init__.py new file mode 100644 index 000000000000..b509336233c2 --- /dev/null +++ b/tests/hazmat/primitives/decrepit/__init__.py @@ -0,0 +1,3 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/decrepit/test_3des.py similarity index 96% rename from tests/hazmat/primitives/test_3des.py rename to tests/hazmat/primitives/decrepit/test_3des.py index 007ecfe21271..2b7a10470c0f 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/decrepit/test_3des.py @@ -6,16 +6,16 @@ Test using the NIST Test Vectors """ - import binascii import os import pytest -from cryptography.hazmat.primitives.ciphers import algorithms, modes +from cryptography.hazmat.decrepit.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import modes -from ...utils import load_nist_vectors -from .utils import generate_encrypt_test +from ....utils import load_nist_vectors +from ..utils import generate_encrypt_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/decrepit/test_algorithms.py b/tests/hazmat/primitives/decrepit/test_algorithms.py new file mode 100644 index 000000000000..0dbdac7c5da8 --- /dev/null +++ b/tests/hazmat/primitives/decrepit/test_algorithms.py @@ -0,0 +1,405 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + + +import binascii +import os + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + ARC4, + CAST5, + IDEA, + SEED, + Blowfish, + TripleDES, +) +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes + +from ....utils import load_nist_vectors, raises_unsupported_algorithm +from ..utils import generate_encrypt_test + + +class TestARC4: + @pytest.mark.parametrize( + ("key", "keysize"), + [ + (b"0" * 10, 40), + (b"0" * 14, 56), + (b"0" * 16, 64), + (b"0" * 20, 80), + (b"0" * 32, 128), + (b"0" * 48, 192), + (b"0" * 64, 256), + ], + ) + def test_key_size(self, key, keysize): + cipher = ARC4(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + ARC4(binascii.unhexlify(b"0" * 34)) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + ARC4("0" * 10) # type: ignore[arg-type] + + +def test_invalid_mode_algorithm(): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ciphers.Cipher( + ARC4(b"\x00" * 16), + modes.GCM(b"\x00" * 12), + ) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ciphers.Cipher( + ARC4(b"\x00" * 16), + modes.CBC(b"\x00" * 12), + ) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ciphers.Cipher( + ARC4(b"\x00" * 16), + modes.CTR(b"\x00" * 12), + ) + + +class TestTripleDES: + @pytest.mark.parametrize("key", [b"0" * 16, b"0" * 32, b"0" * 48]) + def test_key_size(self, key): + cipher = TripleDES(binascii.unhexlify(key)) + assert cipher.key_size == 192 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + TripleDES(binascii.unhexlify(b"0" * 12)) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + TripleDES("0" * 16) # type: ignore[arg-type] + + +class TestBlowfish: + @pytest.mark.parametrize( + ("key", "keysize"), + [(b"0" * (keysize // 4), keysize) for keysize in range(32, 449, 8)], + ) + def test_key_size(self, key, keysize): + cipher = Blowfish(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + Blowfish(binascii.unhexlify(b"0" * 6)) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + Blowfish("0" * 8) # type: ignore[arg-type] + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + Blowfish(b"\x00" * 56), modes.ECB() + ), + skip_message="Does not support Blowfish ECB", +) +class TestBlowfishModeECB: + test_ecb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-ecb.txt"], + lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + Blowfish(b"\x00" * 56), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support Blowfish CBC", +) +class TestBlowfishModeCBC: + test_cbc = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-cbc.txt"], + lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + Blowfish(b"\x00" * 56), modes.OFB(b"\x00" * 8) + ), + skip_message="Does not support Blowfish OFB", +) +class TestBlowfishModeOFB: + test_ofb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-ofb.txt"], + lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + Blowfish(b"\x00" * 56), modes.CFB(b"\x00" * 8) + ), + skip_message="Does not support Blowfish CFB", +) +class TestBlowfishModeCFB: + test_cfb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-cfb.txt"], + lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) + + +class TestCAST5: + @pytest.mark.parametrize( + ("key", "keysize"), + [(b"0" * (keysize // 4), keysize) for keysize in range(40, 129, 8)], + ) + def test_key_size(self, key, keysize): + cipher = CAST5(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + CAST5(binascii.unhexlify(b"0" * 34)) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + CAST5("0" * 10) # type: ignore[arg-type] + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + CAST5(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support CAST5 ECB", +) +class TestCAST5ModeECB: + test_ecb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-ecb.txt"], + lambda key, **kwargs: CAST5(binascii.unhexlify(key)), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + CAST5(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support CAST5 CBC", +) +class TestCAST5ModeCBC: + test_cbc = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-cbc.txt"], + lambda key, **kwargs: CAST5(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + CAST5(b"\x00" * 16), modes.OFB(b"\x00" * 8) + ), + skip_message="Does not support CAST5 OFB", +) +class TestCAST5ModeOFB: + test_ofb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-ofb.txt"], + lambda key, **kwargs: CAST5(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + CAST5(b"\x00" * 16), modes.CFB(b"\x00" * 8) + ), + skip_message="Does not support CAST5 CFB", +) +class TestCAST5ModeCFB: + test_cfb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-cfb.txt"], + lambda key, **kwargs: CAST5(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) + + +class TestIDEA: + def test_key_size(self): + cipher = IDEA(b"\x00" * 16) + assert cipher.key_size == 128 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + IDEA(b"\x00" * 17) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + IDEA("0" * 16) # type: ignore[arg-type] + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + IDEA(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support IDEA ECB", +) +class TestIDEAModeECB: + test_ecb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-ecb.txt"], + lambda key, **kwargs: IDEA(binascii.unhexlify(key)), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + IDEA(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support IDEA CBC", +) +class TestIDEAModeCBC: + test_cbc = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-cbc.txt"], + lambda key, **kwargs: IDEA(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + IDEA(b"\x00" * 16), modes.OFB(b"\x00" * 8) + ), + skip_message="Does not support IDEA OFB", +) +class TestIDEAModeOFB: + test_ofb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-ofb.txt"], + lambda key, **kwargs: IDEA(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + IDEA(b"\x00" * 16), modes.CFB(b"\x00" * 8) + ), + skip_message="Does not support IDEA CFB", +) +class TestIDEAModeCFB: + test_cfb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-cfb.txt"], + lambda key, **kwargs: IDEA(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) + + +class TestSEED: + def test_key_size(self): + cipher = SEED(b"\x00" * 16) + assert cipher.key_size == 128 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + SEED(b"\x00" * 17) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + SEED("0" * 16) # type: ignore[arg-type] + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + SEED(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support SEED ECB", +) +class TestSEEDModeECB: + test_ecb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["rfc-4269.txt"], + lambda key, **kwargs: SEED(binascii.unhexlify(key)), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + SEED(b"\x00" * 16), modes.CBC(b"\x00" * 16) + ), + skip_message="Does not support SEED CBC", +) +class TestSEEDModeCBC: + test_cbc = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["rfc-4196.txt"], + lambda key, **kwargs: SEED(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + SEED(b"\x00" * 16), modes.OFB(b"\x00" * 16) + ), + skip_message="Does not support SEED OFB", +) +class TestSEEDModeOFB: + test_ofb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["seed-ofb.txt"], + lambda key, **kwargs: SEED(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + SEED(b"\x00" * 16), modes.CFB(b"\x00" * 16) + ), + skip_message="Does not support SEED CFB", +) +class TestSEEDModeCFB: + test_cfb = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["seed-cfb.txt"], + lambda key, **kwargs: SEED(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/decrepit/test_arc4.py similarity index 85% rename from tests/hazmat/primitives/test_arc4.py rename to tests/hazmat/primitives/decrepit/test_arc4.py index b589518adfec..116f4b15ccff 100644 --- a/tests/hazmat/primitives/test_arc4.py +++ b/tests/hazmat/primitives/decrepit/test_arc4.py @@ -8,10 +8,10 @@ import pytest -from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.decrepit.ciphers import algorithms -from ...utils import load_nist_vectors -from .utils import generate_stream_encryption_test +from ....utils import load_nist_vectors +from ..utils import generate_stream_encryption_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/decrepit/test_rc2.py b/tests/hazmat/primitives/decrepit/test_rc2.py new file mode 100644 index 000000000000..dd2ce5d4b4b8 --- /dev/null +++ b/tests/hazmat/primitives/decrepit/test_rc2.py @@ -0,0 +1,36 @@ +# 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. + +""" +Test using the NIST Test Vectors +""" + +import binascii +import os + +import pytest + +from cryptography.hazmat.decrepit.ciphers.algorithms import RC2 +from cryptography.hazmat.primitives.ciphers import modes + +from ....utils import load_nist_vectors +from ..utils import generate_encrypt_test + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + RC2(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support RC2 CBC", +) +class TestRC2ModeCBC: + test_kat = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "RC2"), + [ + "rc2-cbc.txt", + ], + lambda key, **kwargs: RC2(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py index a4624cefc555..b94ee52ad2d7 100644 --- a/tests/hazmat/primitives/test_aead.py +++ b/tests/hazmat/primitives/test_aead.py @@ -37,8 +37,10 @@ def _aead_supported(cls): return False -def large_mmap(): - return mmap.mmap(-1, 2**32, prot=mmap.PROT_READ) +def large_mmap(length: int = 2**32): + # Silencing mypy prot argument warning on Windows, even though this + # function is only used in non-Windows-based tests. + return mmap.mmap(-1, length, prot=mmap.PROT_READ) # type: ignore[call-arg,attr-defined,unused-ignore] @pytest.mark.skipif( @@ -56,7 +58,8 @@ def test_chacha20poly1305_unsupported_on_older_openssl(backend): ) class TestChaCha20Poly1305: @pytest.mark.skipif( - sys.platform not in {"linux", "darwin"}, reason="mmap required" + sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, + reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = ChaCha20Poly1305.generate_key() @@ -197,7 +200,8 @@ def test_buffer_protocol(self, backend): ) class TestAESCCM: @pytest.mark.skipif( - sys.platform not in {"linux", "darwin"}, reason="mmap required" + sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, + reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESCCM.generate_key(128) @@ -296,6 +300,9 @@ def test_nonce_too_long(self, backend): with pytest.raises(ValueError): aesccm.encrypt(nonce, pt, None) + with pytest.raises(ValueError): + aesccm.decrypt(nonce, pt, None) + @pytest.mark.parametrize( ("nonce", "data", "associated_data"), [ @@ -356,6 +363,16 @@ def test_buffer_protocol(self, backend): computed_pt2 = aesccm2.decrypt(bytearray(nonce), ct2, ad) assert computed_pt2 == pt + def test_max_data_length(self): + plaintext = b"A" * 65535 + aad = b"authenticated but unencrypted data" + aesccm = AESCCM(AESCCM.generate_key(128)) + nonce = os.urandom(13) + + ciphertext = aesccm.encrypt(nonce, plaintext, aad) + decrypted_data = aesccm.decrypt(nonce, ciphertext, aad) + assert decrypted_data == plaintext + def _load_gcm_vectors(): vectors = _load_all_params( @@ -375,7 +392,8 @@ def _load_gcm_vectors(): class TestAESGCM: @pytest.mark.skipif( - sys.platform not in {"linux", "darwin"}, reason="mmap required" + sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, + reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESGCM.generate_key(128) @@ -451,6 +469,8 @@ def test_invalid_nonce_length(self, length, backend): aesgcm = AESGCM(key) with pytest.raises(ValueError): aesgcm.encrypt(b"\x00" * length, b"hi", None) + with pytest.raises(ValueError): + aesgcm.decrypt(b"\x00" * length, b"hi", None) def test_bad_key(self, backend): with pytest.raises(TypeError): @@ -520,7 +540,8 @@ def test_aesocb3_unsupported_on_older_openssl(backend): ) class TestAESOCB3: @pytest.mark.skipif( - sys.platform not in {"linux", "darwin"}, reason="mmap required" + sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, + reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESOCB3.generate_key(128) @@ -695,7 +716,8 @@ def test_buffer_protocol(self, backend): ) class TestAESSIV: @pytest.mark.skipif( - sys.platform not in {"linux", "darwin"}, reason="mmap required" + sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, + reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESSIV.generate_key(256) @@ -839,7 +861,8 @@ def test_buffer_protocol(self, backend): ) class TestAESGCMSIV: @pytest.mark.skipif( - sys.platform not in {"linux", "darwin"}, reason="mmap required" + sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, + reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESGCMSIV.generate_key(256) diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 1f3dfd0014b4..64ec26687952 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -8,10 +8,12 @@ import pytest +from cryptography.exceptions import AlreadyFinalized, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives.ciphers import algorithms, base, modes from ...doubles import DummyMode -from ...utils import load_nist_vectors +from ...utils import load_nist_vectors, raises_unsupported_algorithm from .utils import _load_all_params, generate_encrypt_test @@ -61,7 +63,7 @@ def test_xts_too_short(self, backend): enc.update(b"0" * 15) @pytest.mark.supported( - only_if=lambda backend: (not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL), + only_if=lambda backend: not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL, skip_message="duplicate key encryption error added in OpenSSL 1.1.1d", ) def test_xts_no_duplicate_keys_encryption(self, backend): @@ -304,3 +306,61 @@ def test_alternate_aes_classes(mode, alg_cls, backend): dec = cipher.decryptor() pt = dec.update(ct) + dec.finalize() assert pt == data + + +def test_reset_nonce(backend): + data = b"helloworld" * 10 + nonce = b"\x00" * 16 + nonce_alt = b"\xee" * 16 + cipher = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.CTR(nonce), + ) + cipher_alt = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.CTR(nonce_alt), + ) + enc = cipher.encryptor() + ct1 = enc.update(data) + assert len(ct1) == len(data) + for _ in range(2): + enc.reset_nonce(nonce) + assert enc.update(data) == ct1 + # Reset the nonce to a different value + # and check it matches with a different context + enc_alt = cipher_alt.encryptor() + ct2 = enc_alt.update(data) + enc.reset_nonce(nonce_alt) + assert enc.update(data) == ct2 + enc_alt.finalize() + enc.finalize() + with pytest.raises(AlreadyFinalized): + enc.reset_nonce(nonce) + dec = cipher.decryptor() + assert dec.update(ct1) == data + for _ in range(2): + dec.reset_nonce(nonce) + assert dec.update(ct1) == data + # Reset the nonce to a different value + # and check it matches with a different context + dec_alt = cipher_alt.decryptor() + dec.reset_nonce(nonce_alt) + assert dec.update(ct2) == dec_alt.update(ct2) + dec_alt.finalize() + dec.finalize() + with pytest.raises(AlreadyFinalized): + dec.reset_nonce(nonce) + + +def test_reset_nonce_invalid_mode(backend): + iv = b"\x00" * 16 + c = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.CBC(iv), + ) + enc = c.encryptor() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + enc.reset_nonce(iv) + dec = c.decryptor() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + dec.reset_nonce(iv) diff --git a/tests/hazmat/primitives/test_aes_gcm.py b/tests/hazmat/primitives/test_aes_gcm.py index d82e37470cae..30cf9ca07b36 100644 --- a/tests/hazmat/primitives/test_aes_gcm.py +++ b/tests/hazmat/primitives/test_aes_gcm.py @@ -8,20 +8,14 @@ import pytest +from cryptography.exceptions import _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from ...utils import load_nist_vectors +from ...utils import load_nist_vectors, raises_unsupported_algorithm 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) @@ -80,7 +74,9 @@ def test_gcm_ciphertext_limit(self, backend): backend=backend, ) encryptor = cipher.encryptor() - _advance(encryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16) + rust_openssl.ciphers._advance( + encryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16 + ) encryptor.update(b"0" * 16) with pytest.raises(ValueError): encryptor.update(b"0") @@ -88,7 +84,9 @@ def test_gcm_ciphertext_limit(self, backend): encryptor.update_into(b"0", bytearray(1)) decryptor = cipher.decryptor() - _advance(decryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16) + rust_openssl.ciphers._advance( + decryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16 + ) decryptor.update(b"0" * 16) with pytest.raises(ValueError): decryptor.update(b"0") @@ -102,45 +100,21 @@ def test_gcm_aad_limit(self, backend): backend=backend, ) encryptor = cipher.encryptor() - _advance_aad(encryptor, modes.GCM._MAX_AAD_BYTES - 16) + rust_openssl.ciphers._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) + rust_openssl.ciphers._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), - modes.GCM(b"\x01" * 16), - backend=backend, - ).encryptor() - encryptor.update(b"0" * 8) - assert encryptor._bytes_processed == 8 # type: ignore[attr-defined] - encryptor.update(b"0" * 7) - assert encryptor._bytes_processed == 15 # type: ignore[attr-defined] - encryptor.update(b"0" * 18) - assert encryptor._bytes_processed == 33 # type: ignore[attr-defined] - - def test_gcm_aad_increments(self, backend): - encryptor = base.Cipher( - algorithms.AES(b"\x00" * 16), - modes.GCM(b"\x01" * 16), - backend=backend, - ).encryptor() - encryptor.authenticate_additional_data(b"0" * 8) - assert ( - encryptor._aad_bytes_processed == 8 # type: ignore[attr-defined] - ) - encryptor.authenticate_additional_data(b"0" * 18) - assert ( - encryptor._aad_bytes_processed == 26 # type: ignore[attr-defined] - ) - def test_gcm_tag_decrypt_none(self, backend): key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") @@ -257,3 +231,16 @@ def test_alternate_aes_classes(self, alg, backend): dec = cipher.decryptor() pt = dec.update(ct) + dec.finalize_with_tag(enc.tag) assert pt == data + + def test_reset_nonce_invalid_mode(self, backend): + nonce = b"\x00" * 12 + c = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(nonce), + ) + enc = c.encryptor() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + enc.reset_nonce(nonce) + dec = c.decryptor() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + dec.reset_nonce(nonce) diff --git a/tests/hazmat/primitives/test_argon2.py b/tests/hazmat/primitives/test_argon2.py new file mode 100644 index 000000000000..7ea79d8b9359 --- /dev/null +++ b/tests/hazmat/primitives/test_argon2.py @@ -0,0 +1,160 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + + +import binascii +import os + +import pytest + +from cryptography.exceptions import AlreadyFinalized, InvalidKey +from cryptography.hazmat.primitives.kdf.argon2 import Argon2id +from tests.utils import ( + load_nist_vectors, + load_vectors_from_file, + raises_unsupported_algorithm, +) + +vectors = load_vectors_from_file( + os.path.join("KDF", "argon2id.txt"), load_nist_vectors +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.argon2_supported(), + skip_message="Supports argon2 so can't test unsupported path", +) +def test_unsupported_backend(backend): + with raises_unsupported_algorithm(None): + Argon2id( + salt=b"salt" * 2, length=32, iterations=1, lanes=1, memory_cost=32 + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.argon2_supported(), + skip_message="Argon2id not supported by this version of OpenSSL", +) +class TestArgon2id: + @pytest.mark.parametrize("params", vectors) + def test_derive(self, params, backend): + salt = binascii.unhexlify(params["salt"]) + ad = binascii.unhexlify(params["ad"]) if "ad" in params else None + secret = ( + binascii.unhexlify(params["secret"]) + if "secret" in params + else None + ) + length = int(params["length"]) + iterations = int(params["iter"]) + lanes = int(params["lanes"]) + memory_cost = int(params["memcost"]) + password = binascii.unhexlify(params["pass"]) + derived_key = params["output"].lower() + + argon2id = Argon2id( + salt=salt, + length=length, + iterations=iterations, + lanes=lanes, + memory_cost=memory_cost, + ad=ad, + secret=secret, + ) + assert binascii.hexlify(argon2id.derive(password)) == derived_key + + def test_invalid_types(self, backend): + with pytest.raises(TypeError): + Argon2id( + salt="notbytes", # type: ignore[arg-type] + length=32, + iterations=1, + lanes=1, + memory_cost=32, + ad=None, + secret=None, + ) + + with pytest.raises(TypeError): + Argon2id( + salt=b"b" * 8, + length=32, + iterations=1, + lanes=1, + memory_cost=32, + ad="string", # type: ignore[arg-type] + secret=None, + ) + + with pytest.raises(TypeError): + Argon2id( + salt=b"b" * 8, + length=32, + iterations=1, + lanes=1, + memory_cost=32, + ad=None, + secret="string", # type: ignore[arg-type] + ) + + @pytest.mark.parametrize( + "params", + [ + (b"b" * 7, 3, 1, 1, 32), # salt < 8 + (b"b" * 8, 3, 1, 1, 32), # length < 4 + (b"b" * 8, 32, 0, 1, 32), # iterations < 1 + (b"b" * 8, 32, 1, 0, 32), # lanes < 1 + (b"b" * 8, 32, 1, 1, 7), # memory_cost < 8 * lanes + (b"b" * 8, 32, 1, 32, 200), # memory_cost < 8 * lanes + ], + ) + def test_invalid_values(self, params, backend): + (salt, length, iterations, lanes, memory_cost) = params + with pytest.raises(ValueError): + Argon2id( + salt=salt, + length=length, + iterations=iterations, + lanes=lanes, + memory_cost=memory_cost, + ) + + def test_already_finalized(self, backend): + argon2id = Argon2id( + salt=b"salt" * 2, length=32, iterations=1, lanes=1, memory_cost=32 + ) + argon2id.derive(b"password") + with pytest.raises(AlreadyFinalized): + argon2id.derive(b"password") + + def test_already_finalized_verify(self, backend): + argon2id = Argon2id( + salt=b"salt" * 2, length=32, iterations=1, lanes=1, memory_cost=32 + ) + digest = argon2id.derive(b"password") + with pytest.raises(AlreadyFinalized): + argon2id.verify(b"password", digest) + + @pytest.mark.parametrize("digest", [b"invalidkey", b"0" * 32]) + def test_invalid_verify(self, digest, backend): + argon2id = Argon2id( + salt=b"salt" * 2, length=32, iterations=1, lanes=1, memory_cost=32 + ) + with pytest.raises(InvalidKey): + argon2id.verify(b"password", digest) + + def test_verify(self, backend): + argon2id = Argon2id( + salt=b"salt" * 2, + length=32, + iterations=1, + lanes=1, + memory_cost=32, + ad=None, + secret=None, + ) + digest = argon2id.derive(b"password") + Argon2id( + salt=b"salt" * 2, length=32, iterations=1, lanes=1, memory_cost=32 + ).verify(b"password", digest) diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py deleted file mode 100644 index b8f34dfcef58..000000000000 --- a/tests/hazmat/primitives/test_blowfish.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - - -import binascii -import os - -import pytest - -from cryptography.hazmat.primitives.ciphers import algorithms, modes - -from ...utils import load_nist_vectors -from .utils import generate_encrypt_test - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._BlowfishInternal(b"\x00" * 56), modes.ECB() - ), - skip_message="Does not support Blowfish ECB", -) -class TestBlowfishModeECB: - test_ecb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "Blowfish"), - ["bf-ecb.txt"], - lambda key, **kwargs: algorithms._BlowfishInternal( - binascii.unhexlify(key) - ), - lambda **kwargs: modes.ECB(), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._BlowfishInternal(b"\x00" * 56), modes.CBC(b"\x00" * 8) - ), - skip_message="Does not support Blowfish CBC", -) -class TestBlowfishModeCBC: - test_cbc = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "Blowfish"), - ["bf-cbc.txt"], - lambda key, **kwargs: algorithms._BlowfishInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._BlowfishInternal(b"\x00" * 56), modes.OFB(b"\x00" * 8) - ), - skip_message="Does not support Blowfish OFB", -) -class TestBlowfishModeOFB: - test_ofb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "Blowfish"), - ["bf-ofb.txt"], - lambda key, **kwargs: algorithms._BlowfishInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._BlowfishInternal(b"\x00" * 56), modes.CFB(b"\x00" * 8) - ), - skip_message="Does not support Blowfish CFB", -) -class TestBlowfishModeCFB: - test_cfb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "Blowfish"), - ["bf-cfb.txt"], - lambda key, **kwargs: algorithms._BlowfishInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), - ) diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py deleted file mode 100644 index 327a463b60e5..000000000000 --- a/tests/hazmat/primitives/test_cast5.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - - -import binascii -import os - -import pytest - -from cryptography.hazmat.primitives.ciphers import algorithms, modes - -from ...utils import load_nist_vectors -from .utils import generate_encrypt_test - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._CAST5Internal(b"\x00" * 16), modes.ECB() - ), - skip_message="Does not support CAST5 ECB", -) -class TestCAST5ModeECB: - test_ecb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "CAST5"), - ["cast5-ecb.txt"], - lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify(key) - ), - lambda **kwargs: modes.ECB(), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._CAST5Internal(b"\x00" * 16), modes.CBC(b"\x00" * 8) - ), - skip_message="Does not support CAST5 CBC", -) -class TestCAST5ModeCBC: - test_cbc = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "CAST5"), - ["cast5-cbc.txt"], - lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._CAST5Internal(b"\x00" * 16), modes.OFB(b"\x00" * 8) - ), - skip_message="Does not support CAST5 OFB", -) -class TestCAST5ModeOFB: - test_ofb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "CAST5"), - ["cast5-ofb.txt"], - lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._CAST5Internal(b"\x00" * 16), modes.CFB(b"\x00" * 8) - ), - skip_message="Does not support CAST5 CFB", -) -class TestCAST5ModeCFB: - test_cfb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "CAST5"), - ["cast5-cfb.txt"], - lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), - ) diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py index 7c52ad598d3c..3ade8b9e2eb1 100644 --- a/tests/hazmat/primitives/test_chacha20.py +++ b/tests/hazmat/primitives/test_chacha20.py @@ -9,6 +9,7 @@ import pytest +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives.ciphers import Cipher, algorithms from ...utils import load_nist_vectors @@ -90,3 +91,51 @@ def test_partial_blocks(self, backend): ct_partial_3 = enc_partial.update(pt[len_partial * 2 :]) assert ct_full == ct_partial_1 + ct_partial_2 + ct_partial_3 + + def test_reset_nonce(self, backend): + data = b"helloworld" * 10 + key = b"\x00" * 32 + nonce = b"\x00" * 16 + nonce_alt = b"\xee" * 16 + cipher = Cipher(algorithms.ChaCha20(key, nonce), None) + cipher_alt = Cipher(algorithms.ChaCha20(key, nonce_alt), None) + enc = cipher.encryptor() + ct1 = enc.update(data) + assert len(ct1) == len(data) + for _ in range(2): + enc.reset_nonce(nonce) + assert enc.update(data) == ct1 + # Reset the nonce to a different value + # and check it matches with a different context + enc_alt = cipher_alt.encryptor() + ct2 = enc_alt.update(data) + enc.reset_nonce(nonce_alt) + assert enc.update(data) == ct2 + enc_alt.finalize() + enc.finalize() + with pytest.raises(AlreadyFinalized): + enc.reset_nonce(nonce) + dec = cipher.decryptor() + assert dec.update(ct1) == data + for _ in range(2): + dec.reset_nonce(nonce) + assert dec.update(ct1) == data + # Reset the nonce to a different value + # and check it matches with a different context + dec_alt = cipher_alt.decryptor() + dec.reset_nonce(nonce_alt) + assert dec.update(ct2) == dec_alt.update(ct2) + dec_alt.finalize() + dec.finalize() + with pytest.raises(AlreadyFinalized): + dec.reset_nonce(nonce) + + def test_nonce_reset_invalid_length(self, backend): + key = b"\x00" * 32 + nonce = b"\x00" * 16 + cipher = Cipher(algorithms.ChaCha20(key, nonce), None) + enc = cipher.encryptor() + with pytest.raises(ValueError): + enc.reset_nonce(nonce[:-1]) + with pytest.raises(ValueError): + enc.reset_nonce(nonce + b"\x00") diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index 1659fa2cd605..20dcb54d1b1d 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -4,31 +4,49 @@ import binascii -import mmap import os import sys import pytest -from cryptography.exceptions import AlreadyFinalized, _Reasons +from cryptography import utils +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import modes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, - ARC4, Camellia, - TripleDES, - _BlowfishInternal, - _CAST5Internal, - _IDEAInternal, - _SEEDInternal, ) -from ...utils import ( - load_nist_vectors, - load_vectors_from_file, - raises_unsupported_algorithm, -) +from ...utils import load_nist_vectors, load_vectors_from_file +from .test_aead import large_mmap + + +def test_deprecated_ciphers_import_with_warning(): + with pytest.warns(utils.CryptographyDeprecationWarning): + from cryptography.hazmat.primitives.ciphers.algorithms import ( + Blowfish, # noqa: F401 + ) + with pytest.warns(utils.CryptographyDeprecationWarning): + from cryptography.hazmat.primitives.ciphers.algorithms import ( + CAST5, # noqa: F401 + ) + with pytest.warns(utils.CryptographyDeprecationWarning): + from cryptography.hazmat.primitives.ciphers.algorithms import ( + IDEA, # noqa: F401 + ) + with pytest.warns(utils.CryptographyDeprecationWarning): + from cryptography.hazmat.primitives.ciphers.algorithms import ( + SEED, # noqa: F401 + ) + with pytest.warns(utils.CryptographyDeprecationWarning): + from cryptography.hazmat.primitives.ciphers.algorithms import ( + ARC4, # noqa: F401 + ) + with pytest.warns(utils.CryptographyDeprecationWarning): + from cryptography.hazmat.primitives.ciphers.algorithms import ( + TripleDES, # noqa: F401 + ) class TestAES: @@ -95,131 +113,6 @@ def test_invalid_key_type(self): Camellia("0" * 32) # type: ignore[arg-type] -class TestTripleDES: - @pytest.mark.parametrize("key", [b"0" * 16, b"0" * 32, b"0" * 48]) - def test_key_size(self, key): - cipher = TripleDES(binascii.unhexlify(key)) - assert cipher.key_size == 192 - - def test_invalid_key_size(self): - with pytest.raises(ValueError): - TripleDES(binascii.unhexlify(b"0" * 12)) - - def test_invalid_key_type(self): - with pytest.raises(TypeError, match="key must be bytes"): - TripleDES("0" * 16) # type: ignore[arg-type] - - -class TestBlowfish: - @pytest.mark.parametrize( - ("key", "keysize"), - [(b"0" * (keysize // 4), keysize) for keysize in range(32, 449, 8)], - ) - def test_key_size(self, key, keysize): - cipher = _BlowfishInternal(binascii.unhexlify(key)) - assert cipher.key_size == keysize - - def test_invalid_key_size(self): - with pytest.raises(ValueError): - _BlowfishInternal(binascii.unhexlify(b"0" * 6)) - - def test_invalid_key_type(self): - with pytest.raises(TypeError, match="key must be bytes"): - _BlowfishInternal("0" * 8) # type: ignore[arg-type] - - -class TestCAST5: - @pytest.mark.parametrize( - ("key", "keysize"), - [(b"0" * (keysize // 4), keysize) for keysize in range(40, 129, 8)], - ) - def test_key_size(self, key, keysize): - cipher = _CAST5Internal(binascii.unhexlify(key)) - assert cipher.key_size == keysize - - def test_invalid_key_size(self): - with pytest.raises(ValueError): - _CAST5Internal(binascii.unhexlify(b"0" * 34)) - - def test_invalid_key_type(self): - with pytest.raises(TypeError, match="key must be bytes"): - _CAST5Internal("0" * 10) # type: ignore[arg-type] - - -class TestARC4: - @pytest.mark.parametrize( - ("key", "keysize"), - [ - (b"0" * 10, 40), - (b"0" * 14, 56), - (b"0" * 16, 64), - (b"0" * 20, 80), - (b"0" * 32, 128), - (b"0" * 48, 192), - (b"0" * 64, 256), - ], - ) - def test_key_size(self, key, keysize): - cipher = ARC4(binascii.unhexlify(key)) - assert cipher.key_size == keysize - - def test_invalid_key_size(self): - with pytest.raises(ValueError): - ARC4(binascii.unhexlify(b"0" * 34)) - - def test_invalid_key_type(self): - with pytest.raises(TypeError, match="key must be bytes"): - ARC4("0" * 10) # type: ignore[arg-type] - - -class TestIDEA: - def test_key_size(self): - cipher = _IDEAInternal(b"\x00" * 16) - assert cipher.key_size == 128 - - def test_invalid_key_size(self): - with pytest.raises(ValueError): - _IDEAInternal(b"\x00" * 17) - - def test_invalid_key_type(self): - with pytest.raises(TypeError, match="key must be bytes"): - _IDEAInternal("0" * 16) # type: ignore[arg-type] - - -class TestSEED: - def test_key_size(self): - cipher = _SEEDInternal(b"\x00" * 16) - assert cipher.key_size == 128 - - def test_invalid_key_size(self): - with pytest.raises(ValueError): - _SEEDInternal(b"\x00" * 17) - - def test_invalid_key_type(self): - with pytest.raises(TypeError, match="key must be bytes"): - _SEEDInternal("0" * 16) # type: ignore[arg-type] - - -def test_invalid_mode_algorithm(): - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - ciphers.Cipher( - ARC4(b"\x00" * 16), - modes.GCM(b"\x00" * 12), - ) - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - ciphers.Cipher( - ARC4(b"\x00" * 16), - modes.CBC(b"\x00" * 12), - ) - - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): - ciphers.Cipher( - ARC4(b"\x00" * 16), - modes.CTR(b"\x00" * 12), - ) - - @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( AES(b"\x00" * 16), modes.ECB() @@ -362,7 +255,7 @@ def test_update_into_buffer_too_small_gcm(self, backend): sys.platform not in {"linux", "darwin"}, reason="mmap required" ) def test_update_auto_chunking(): - large_data = mmap.mmap(-1, 2**29 + 2**20, prot=mmap.PROT_READ) + large_data = large_mmap(length=2**29 + 2**20) key = b"\x00" * 16 c = ciphers.Cipher(AES(key), modes.ECB()) diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py index 18ba898e7a85..5e81563a6b14 100644 --- a/tests/hazmat/primitives/test_cmac.py +++ b/tests/hazmat/primitives/test_cmac.py @@ -12,10 +12,9 @@ InvalidSignature, _Reasons, ) +from cryptography.hazmat.decrepit.ciphers.algorithms import ARC4, TripleDES from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, - ARC4, - TripleDES, ) from cryptography.hazmat.primitives.cmac import CMAC diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index 9caded2cc2ac..c1f847a212a1 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -11,6 +11,7 @@ import pytest +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh @@ -164,10 +165,7 @@ def test_large_key_generate_dh(self, backend): ) def test_dh_parameters_allows_rfc3526_groups(self, backend, vector): p = int.from_bytes(binascii.unhexlify(vector["p"]), "big") - if ( - backend._fips_enabled - and p.bit_length() < backend._fips_dh_min_modulus - ): + if backend._fips_enabled and p < backend._fips_dh_min_modulus: pytest.skip("modulus too small for FIPS mode") params = dh.DHParameterNumbers(p, int(vector["g"])) @@ -382,7 +380,7 @@ def test_bad_exchange(self, backend, vector): @pytest.mark.skip_fips(reason="key_size too small for FIPS") @pytest.mark.supported( only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER + not rust_openssl.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER ), skip_message="256-bit DH keys are not supported in OpenSSL 3.0.0+", ) @@ -443,6 +441,16 @@ def test_dh_vectors_with_q(self, backend, vector): assert int.from_bytes(symkey1, "big") == int(vector["z"], 16) assert int.from_bytes(symkey2, "big") == int(vector["z"], 16) + def test_exchange_old_key(self, backend): + k = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhpub_cryptography_old.pem"), + lambda f: serialization.load_pem_public_key(f.read()), + mode="rb", + ) + assert isinstance(k, dh.DHPublicKey) + # Ensure this doesn't raise. + k.parameters().generate_private_key().exchange(k) + def test_public_key_equality(self, backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "DH", "dhpub.pem"), diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index c3990cd5af44..fa75b8d9a000 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -12,6 +12,7 @@ from cryptography import utils from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric.utils import ( @@ -46,9 +47,8 @@ def _skip_if_dsa_not_supported( ) -> None: if not backend.dsa_hash_supported(algorithm): pytest.skip( - "{} does not support the provided args. p: {}, hash: {}".format( - backend, p.bit_length(), algorithm.name - ) + f"{backend} does not support the provided args. " + f"p: {p.bit_length()}, hash: {algorithm.name}" ) @@ -522,6 +522,14 @@ def test_sign(self, backend): public_key = private_key.public_key() public_key.verify(signature, message, algorithm) + def test_sign_verify_buffer(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = bytearray(b"one little message") + algorithm = hashes.SHA1() + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(bytearray(signature), message, algorithm) + def test_prehashed_sign(self, backend): private_key = DSA_KEY_1024.private_key(backend) message = b"one little message" @@ -543,6 +551,30 @@ def test_prehashed_digest_mismatch(self, backend): with pytest.raises(ValueError): private_key.sign(digest, prehashed_alg) + @pytest.mark.supported( + only_if=lambda _: ( + rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_OPENSSL_309_OR_GREATER + ), + skip_message="Requires OpenSSL 3.0.9+, LibreSSL, or BoringSSL", + ) + def test_nilpotent(self): + try: + key = load_vectors_from_file( + os.path.join("asymmetric", "DSA", "custom", "nilpotent.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), password=None + ), + ) + except ValueError: + # LibreSSL simply rejects this key on load. + return + assert isinstance(key, dsa.DSAPrivateKey) + + with pytest.raises(ValueError): + key.sign(b"anything", hashes.SHA256()) + class TestDSANumbers: def test_dsa_parameter_numbers(self): diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index d794d429524e..2a30c6661f55 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -13,8 +13,13 @@ import pytest from cryptography import exceptions, utils, x509 +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.ec import ( + EllipticCurvePrivateKey, + EllipticCurvePublicKey, +) from cryptography.hazmat.primitives.asymmetric.utils import ( Prehashed, encode_dss_signature, @@ -26,6 +31,7 @@ load_fips_ecdsa_signing_vectors, load_kasvs_ecdh_vectors, load_nist_vectors, + load_rfc6979_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) @@ -46,9 +52,8 @@ def _skip_ecdsa_vector(backend, curve: ec.EllipticCurve, hash_type): ec.ECDSA(hash_type()), curve ): pytest.skip( - "ECDSA not supported with this hash {} and curve {}.".format( - hash_type().name, curve.name - ) + f"ECDSA not supported with this hash {hash_type().name} and " + f"curve {curve.name}." ) @@ -133,12 +138,22 @@ def test_derive_point_at_infinity(backend): # BoringSSL rejects infinity points before it ever gets to us, so it # uses a more generic error message. match = ( - "infinity" if not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL else "Invalid" + "infinity" if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL else "Invalid" ) with pytest.raises(ValueError, match=match): ec.derive_private_key(q, ec.SECP256R1()) +def test_derive_point_invalid_key(backend): + curve = ec.SECP256R1() + _skip_curve_unsupported(backend, curve) + with pytest.raises(ValueError): + ec.derive_private_key( + 0xE2563328DFABF68188606B91324281C1D58A4456431B09D510B35FECC9F307CA1822846FA2671371A9A81BAC0E35749D, + curve, + ) + + def test_ec_numbers(): numbers = ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) @@ -423,7 +438,7 @@ def test_load_invalid_ec_key_from_pem(self, backend): # uses a more generic error message. match = ( r"infinity|invalid form" - if not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL else None ) with pytest.raises(ValueError, match=match): @@ -507,31 +522,122 @@ def test_signature_failures(self, backend, subtests): signature, vector["message"], ec.ECDSA(hash_type()) ) + def test_unsupported_deterministic_nonce(self, backend): + if backend.ecdsa_deterministic_supported(): + pytest.skip( + f"ECDSA deterministic signing is supported by this" + f" backend {backend}" + ) + with pytest.raises(exceptions.UnsupportedAlgorithm): + ec.ECDSA(hashes.SHA256(), deterministic_signing=True) + + def test_deterministic_nonce(self, backend, subtests): + if not backend.ecdsa_deterministic_supported(): + pytest.skip( + f"ECDSA deterministic signing is not supported by this" + f" backend {backend}" + ) + + supported_hash_algorithms = { + "SHA1": hashes.SHA1(), + "SHA224": hashes.SHA224(), + "SHA256": hashes.SHA256(), + "SHA384": hashes.SHA384(), + "SHA512": hashes.SHA512(), + } + curves = { + "B-163": ec.SECT163R2(), + "B-233": ec.SECT233R1(), + "B-283": ec.SECT283R1(), + "B-409": ec.SECT409R1(), + "B-571": ec.SECT571R1(), + "K-163": ec.SECT163K1(), + "K-233": ec.SECT233K1(), + "K-283": ec.SECT283K1(), + "K-409": ec.SECT409K1(), + "K-571": ec.SECT571K1(), + "P-192": ec.SECP192R1(), + "P-224": ec.SECP224R1(), + "P-256": ec.SECP256R1(), + "P-384": ec.SECP384R1(), + "P-521": ec.SECP521R1(), + } + vectors = load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "RFC6979", "evppkey_ecdsa_rfc6979.txt" + ), + load_rfc6979_vectors, + ) + + for vector in vectors: + with subtests.test(): + input = bytes(vector["input"], "utf-8") + output = bytes.fromhex(vector["output"]) + key = bytes("\n".join(vector["key"]), "utf-8") + curve = curves[vector["key_name"].split("_")[0]] + _skip_curve_unsupported(backend, curve) + + if "digest_sign" in vector: + algorithm = vector["digest_sign"] + hash_algorithm = supported_hash_algorithms[algorithm] + algorithm = ec.ECDSA( + hash_algorithm, + deterministic_signing=vector["deterministic_nonce"], + ) + private_key = serialization.load_pem_private_key( + key, password=None + ) + assert isinstance(private_key, EllipticCurvePrivateKey) + signature = private_key.sign(input, algorithm) + assert signature == output + else: + assert "digest_verify" in vector + algorithm = vector["digest_verify"] + assert algorithm in supported_hash_algorithms + hash_algorithm = supported_hash_algorithms[algorithm] + algorithm = ec.ECDSA(hash_algorithm) + public_key = serialization.load_pem_public_key(key) + assert isinstance(public_key, EllipticCurvePublicKey) + if vector["verify_error"]: + with pytest.raises(exceptions.InvalidSignature): + public_key.verify(output, input, algorithm) + else: + public_key.verify(output, input, algorithm) + def test_sign(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" - algorithm = ec.ECDSA(hashes.SHA1()) + algorithm = ec.ECDSA(hashes.SHA256()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(message, algorithm) 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.SHA256()) + 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" - h = hashes.Hash(hashes.SHA1(), backend) + h = hashes.Hash(hashes.SHA256(), backend) h.update(message) data = h.finalize() - algorithm = ec.ECDSA(Prehashed(hashes.SHA1())) + algorithm = ec.ECDSA(Prehashed(hashes.SHA256())) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(data, algorithm) public_key = private_key.public_key() - public_key.verify(signature, message, ec.ECDSA(hashes.SHA1())) + public_key.verify(signature, message, ec.ECDSA(hashes.SHA256())) def test_sign_prehashed_digest_mismatch(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" - h = hashes.Hash(hashes.SHA1(), backend) + h = hashes.Hash(hashes.SHA224(), backend) h.update(message) data = h.finalize() algorithm = ec.ECDSA(Prehashed(hashes.SHA256())) @@ -542,7 +648,7 @@ def test_sign_prehashed_digest_mismatch(self, backend): def test_verify(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" - algorithm = ec.ECDSA(hashes.SHA1()) + algorithm = ec.ECDSA(hashes.SHA256()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(message, algorithm) public_key = private_key.public_key() @@ -551,20 +657,22 @@ def test_verify(self, backend): def test_verify_prehashed(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" - algorithm = ec.ECDSA(hashes.SHA1()) + algorithm = ec.ECDSA(hashes.SHA256()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(message, algorithm) - h = hashes.Hash(hashes.SHA1(), backend) + h = hashes.Hash(hashes.SHA256(), backend) h.update(message) data = h.finalize() public_key = private_key.public_key() - public_key.verify(signature, data, ec.ECDSA(Prehashed(hashes.SHA1()))) + public_key.verify( + signature, data, ec.ECDSA(Prehashed(hashes.SHA256())) + ) def test_verify_prehashed_digest_mismatch(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" private_key = ec.generate_private_key(ec.SECP256R1(), backend) - h = hashes.Hash(hashes.SHA1(), backend) + h = hashes.Hash(hashes.SHA224(), backend) h.update(message) data = h.finalize() public_key = private_key.public_key() @@ -675,6 +783,24 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.supported( + only_if=lambda backend: backend._fips_enabled, + skip_message="Requires FIPS", + ) + def test_traditional_serialization_fips(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode(), + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + assert isinstance(key, ec.EllipticCurvePrivateKey) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.BestAvailableEncryption(b"password"), + ) + @pytest.mark.parametrize( ("encoding", "fmt"), [ diff --git a/tests/hazmat/primitives/test_ed25519.py b/tests/hazmat/primitives/test_ed25519.py index 8e6b33b1fd62..26f7d0c71b07 100644 --- a/tests/hazmat/primitives/test_ed25519.py +++ b/tests/hazmat/primitives/test_ed25519.py @@ -117,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 diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py index d363f38dfd96..6c7bdedea39d 100644 --- a/tests/hazmat/primitives/test_ed448.py +++ b/tests/hazmat/primitives/test_ed448.py @@ -86,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 diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index 1d096772aed0..092ba9af41d4 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -19,7 +19,7 @@ class TestHashContext: def test_hash_reject_unicode(self, backend): m = hashes.Hash(hashes.SHA1(), backend=backend) with pytest.raises(TypeError): - m.update("\u00FC") # type: ignore[arg-type] + m.update("\u00fc") # type: ignore[arg-type] def test_hash_algorithm_instance(self, backend): with pytest.raises(TypeError): diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 04c3e8588f01..52d3e8ee9b07 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -33,7 +33,7 @@ class TestHMAC: def test_hmac_reject_unicode(self, backend): h = hmac.HMAC(b"mykey", hashes.SHA1(), backend=backend) with pytest.raises(TypeError): - h.update("\u00FC") # type: ignore[arg-type] + h.update("\u00fc") # type: ignore[arg-type] def test_hmac_algorithm_instance(self, backend): with pytest.raises(TypeError): diff --git a/tests/hazmat/primitives/test_idea.py b/tests/hazmat/primitives/test_idea.py deleted file mode 100644 index 6631a93f91cc..000000000000 --- a/tests/hazmat/primitives/test_idea.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - - -import binascii -import os - -import pytest - -from cryptography.hazmat.primitives.ciphers import algorithms, modes - -from ...utils import load_nist_vectors -from .utils import generate_encrypt_test - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._IDEAInternal(b"\x00" * 16), modes.ECB() - ), - skip_message="Does not support IDEA ECB", -) -class TestIDEAModeECB: - test_ecb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "IDEA"), - ["idea-ecb.txt"], - lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify(key) - ), - lambda **kwargs: modes.ECB(), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._IDEAInternal(b"\x00" * 16), modes.CBC(b"\x00" * 8) - ), - skip_message="Does not support IDEA CBC", -) -class TestIDEAModeCBC: - test_cbc = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "IDEA"), - ["idea-cbc.txt"], - lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._IDEAInternal(b"\x00" * 16), modes.OFB(b"\x00" * 8) - ), - skip_message="Does not support IDEA OFB", -) -class TestIDEAModeOFB: - test_ofb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "IDEA"), - ["idea-ofb.txt"], - lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._IDEAInternal(b"\x00" * 16), modes.CFB(b"\x00" * 8) - ), - skip_message="Does not support IDEA CFB", -) -class TestIDEAModeCFB: - test_cfb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "IDEA"), - ["idea-cfb.txt"], - lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), - ) diff --git a/tests/hazmat/primitives/test_kbkdf.py b/tests/hazmat/primitives/test_kbkdf.py index 4329e3df60cd..e812b464ce93 100644 --- a/tests/hazmat/primitives/test_kbkdf.py +++ b/tests/hazmat/primitives/test_kbkdf.py @@ -159,6 +159,21 @@ def test_r_type(self, backend): backend=backend, ) + def test_zero_llen(self, backend): + with pytest.raises(ValueError): + KBKDFHMAC( + hashes.SHA256(), + Mode.CounterMode, + 32, + 4, + 0, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + def test_l_type(self, backend): with pytest.raises(TypeError): KBKDFHMAC( @@ -615,6 +630,21 @@ def test_r_type(self, backend): backend=backend, ) + def test_zero_llen(self, backend): + with pytest.raises(ValueError): + KBKDFCMAC( + algorithms.AES, + Mode.CounterMode, + 32, + 4, + 0, + CounterLocation.BeforeFixed, + b"label", + b"context", + None, + backend=backend, + ) + def test_l_type(self, backend): with pytest.raises(TypeError): KBKDFCMAC( @@ -871,7 +901,7 @@ def test_unsupported_algorithm(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): KBKDFCMAC( - algorithms.ARC4, + algorithms.ChaCha20, Mode.CounterMode, 32, 4, diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py index 1a9a01f6cf15..df1ee4ec1131 100644 --- a/tests/hazmat/primitives/test_padding.py +++ b/tests/hazmat/primitives/test_padding.py @@ -47,9 +47,9 @@ def __str__(self): str(mybytes()) padder = padding.PKCS7(128).padder() - padder.update(mybytes(b"abc")) + data = padder.update(mybytes(b"abc")) + padder.finalize() unpadder = padding.PKCS7(128).unpadder() - unpadder.update(mybytes(padder.finalize())) + unpadder.update(mybytes(data)) assert unpadder.finalize() == b"abc" @pytest.mark.parametrize( @@ -62,7 +62,7 @@ def __str__(self): b"111111111111111122222222222222\x02\x02", ), (128, b"1" * 16, b"1" * 16 + b"\x10" * 16), - (128, b"1" * 17, b"1" * 17 + b"\x0F" * 15), + (128, b"1" * 17, b"1" * 17 + b"\x0f" * 15), ], ) def test_pad(self, size, unpadded, padded): @@ -80,6 +80,8 @@ def test_pad(self, size, unpadded, padded): b"111111111111111122222222222222", b"111111111111111122222222222222\x02\x02", ), + (128, b"1" * 16, b"1" * 16 + b"\x10" * 16), + (128, b"1" * 17, b"1" * 17 + b"\x0f" * 15), ], ) def test_unpad(self, size, unpadded, padded): @@ -185,7 +187,7 @@ def __str__(self): b"111111111111111122222222222222\x00\x02", ), (128, b"1" * 16, b"1" * 16 + b"\x00" * 15 + b"\x10"), - (128, b"1" * 17, b"1" * 17 + b"\x00" * 14 + b"\x0F"), + (128, b"1" * 17, b"1" * 17 + b"\x00" * 14 + b"\x0f"), ], ) def test_pad(self, size, unpadded, padded): diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index cd9c279ac4b0..71b16b538229 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -9,8 +9,7 @@ import pytest from cryptography import x509 -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends.openssl.backend import _RC2 +from cryptography.hazmat.decrepit.ciphers.algorithms import RC2 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dsa, @@ -19,6 +18,7 @@ ed25519, rsa, ) +from cryptography.hazmat.primitives.ciphers.modes import CBC from cryptography.hazmat.primitives.serialization import ( Encoding, PublicFormat, @@ -81,13 +81,15 @@ def test_load_pkcs12_ec_keys(self, filename, password, backend): ], ) @pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported(_RC2(), None), + only_if=lambda backend: backend.cipher_supported( + RC2(b"0" * 16), CBC(b"0" * 8) + ), skip_message="Does not support RC2", ) def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): self._test_load_pkcs12_ec_keys(filename, password, backend) - def test_load_pkcs12_cert_only(self, backend): + def test_load_key_and_cert_cert_only(self, backend): cert, _ = _load_ca(backend) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", "cert-aes256cbc-no-key.p12"), @@ -100,7 +102,7 @@ def test_load_pkcs12_cert_only(self, backend): assert parsed_key is None assert parsed_more_certs == [cert] - def test_load_pkcs12_key_only(self, backend): + def test_load_key_and_certificates_key_only(self, backend): _, key = _load_ca(backend) assert isinstance(key, ec.EllipticCurvePrivateKey) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( @@ -115,6 +117,19 @@ def test_load_pkcs12_key_only(self, backend): assert parsed_cert is None assert parsed_more_certs == [] + def test_load_pkcs12_key_only(self, backend): + _, key = _load_ca(backend) + assert isinstance(key, ec.EllipticCurvePrivateKey) + p12 = load_vectors_from_file( + os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"), + lambda data: load_pkcs12(data.read(), b"cryptography", backend), + mode="rb", + ) + assert isinstance(p12.key, ec.EllipticCurvePrivateKey) + assert p12.key.private_numbers() == key.private_numbers() + assert p12.cert is None + assert p12.additional_certs == [] + def test_non_bytes(self, backend): with pytest.raises(TypeError): load_key_and_certificates( @@ -397,7 +412,47 @@ def test_generate_cas_friendly_names(self, backend): p12_cert = load_pkcs12(p12, None, backend) cas = p12_cert.additional_certs + assert cas[0].certificate == cert2 + assert cas[0].friendly_name == b"cert2" + assert cas[1].certificate == cert3 + assert cas[1].friendly_name is None + + @pytest.mark.parametrize( + ("encryption_algorithm", "password"), + [ + (serialization.BestAvailableEncryption(b"password"), b"password"), + ( + serialization.PrivateFormat.PKCS12.encryption_builder().build( + b"not a password" + ), + b"not a password", + ), + (serialization.NoEncryption(), None), + ], + ) + def test_generate_cas_friendly_names_no_key( + self, backend, encryption_algorithm, password + ): + cert2 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + p12 = serialize_key_and_certificates( + None, + None, + None, + [ + PKCS12Certificate(cert2, b"cert2"), + PKCS12Certificate(cert3, None), + ], + encryption_algorithm, + ) + + p12_cert = load_pkcs12(p12, password, backend) + cas = p12_cert.additional_certs + assert cas[0].certificate == cert2 assert cas[0].friendly_name == b"cert2" + assert cas[1].certificate == cert3 assert cas[1].friendly_name is None def test_generate_wrong_types(self, backend): @@ -414,7 +469,7 @@ def test_generate_wrong_types(self, backend): ) with pytest.raises(TypeError) as exc: serialize_key_and_certificates(b"name", key, key, None, encryption) - assert str(exc.value) == "cert must be a certificate or None" + assert "object cannot be converted to 'Certificate'" in str(exc.value) with pytest.raises(TypeError) as exc: serialize_key_and_certificates(b"name", key, cert, None, key) @@ -428,7 +483,9 @@ def test_generate_wrong_types(self, backend): with pytest.raises(TypeError) as exc: serialize_key_and_certificates(None, key, cert, [key], encryption) - assert str(exc.value) == "all values in cas must be certificates" + assert "failed to extract enum CertificateOrPKCS12Certificate" in str( + exc.value + ) def test_generate_no_cert(self, backend): _, key = _load_ca(backend) @@ -486,6 +543,27 @@ def test_generate_cert_only(self, encryption_algorithm, password, backend): assert parsed_key is None assert parsed_more_certs == [cert] + def test_generate_cert_only_none_cas(self, backend): + # Same as test_generate_cert_only, but passing None instead of an + # empty list for cas. + cert, _ = _load_ca(backend) + p12 = serialize_key_and_certificates( + None, None, cert, None, serialization.NoEncryption() + ) + parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( + p12, None + ) + assert parsed_cert is None + assert parsed_key is None + assert parsed_more_certs == [cert] + + def test_invalid_utf8_friendly_name(self, backend): + cert, _ = _load_ca(backend) + with pytest.raises(ValueError): + serialize_key_and_certificates( + b"\xc9", None, cert, None, serialization.NoEncryption() + ) + def test_must_supply_something(self): with pytest.raises(ValueError) as exc: serialize_key_and_certificates( @@ -553,17 +631,6 @@ def test_key_serialization_encryption( iters, iter_der, ): - if ( - enc_alg is PBES.PBESv2SHA256AndAES256CBC - ) and not backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - pytest.skip("PBESv2 is not supported on OpenSSL < 3.0") - - if ( - mac_alg is not None - and not backend._lib.Cryptography_HAS_PKCS12_SET_MAC - ): - pytest.skip("PKCS12_set_mac is not supported (boring)") - builder = serialization.PrivateFormat.PKCS12.encryption_builder() if enc_alg is not None: builder = builder.key_cert_algorithm(enc_alg) @@ -610,52 +677,43 @@ def test_key_serialization_encryption( ) assert parsed_more_certs == [cacert] - @pytest.mark.supported( - only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER - ), - skip_message="Requires OpenSSL < 3.0.0 (or Libre/Boring)", - ) - @pytest.mark.parametrize( - ("algorithm"), - [ + def test_set_mac_key_certificate_mismatch(self, backend): + cacert, _ = _load_ca(backend) + key = ec.generate_private_key(ec.SECP256R1()) + encryption = ( serialization.PrivateFormat.PKCS12.encryption_builder() - .key_cert_algorithm(PBES.PBESv2SHA256AndAES256CBC) - .build(b"password"), - ], - ) - def test_key_serialization_encryption_unsupported( - self, algorithm, backend - ): - cacert, cakey = _load_ca(backend) - with pytest.raises(UnsupportedAlgorithm): + .hmac_hash(hashes.SHA256()) + .build(b"password") + ) + + with pytest.raises(ValueError): serialize_key_and_certificates( - b"name", cakey, cacert, [], algorithm + b"name", key, cacert, [], encryption ) - @pytest.mark.supported( - only_if=lambda backend: ( - not backend._lib.Cryptography_HAS_PKCS12_SET_MAC - ), - skip_message="Requires OpenSSL without PKCS12_set_mac (boring only)", - ) @pytest.mark.parametrize( - "algorithm", + "encryption_algorithm", [ - serialization.PrivateFormat.PKCS12.encryption_builder() - .key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) - .hmac_hash(hashes.SHA256()) - .build(b"password"), + serialization.NoEncryption(), + serialization.BestAvailableEncryption(b"password"), ], ) - def test_key_serialization_encryption_set_mac_unsupported( - self, algorithm, backend - ): - cacert, cakey = _load_ca(backend) - with pytest.raises(UnsupportedAlgorithm): - serialize_key_and_certificates( - b"name", cakey, cacert, [], algorithm - ) + def test_generate_localkeyid(self, backend, encryption_algorithm): + cert, key = _load_ca(backend) + + p12 = serialize_key_and_certificates( + None, key, cert, None, encryption_algorithm + ) + # Dirty, but does the trick. Should be there: + # * 2x if unencrypted (once for the key and once for the cert) + # * 1x if encrypted (the cert one is encrypted, but the key one is + # plaintext) + count = ( + 2 + if isinstance(encryption_algorithm, serialization.NoEncryption) + else 1 + ) + assert p12.count(cert.fingerprint(hashes.SHA1())) == count @pytest.mark.skip_fips( @@ -746,7 +804,7 @@ def test_certificate_equality(self, backend): assert c2a != c2b assert c2a != c3a - assert c2n != "test" + assert c2n != "test" # type: ignore[comparison-overlap] def test_certificate_hash(self, backend): cert2 = _load_cert( @@ -919,19 +977,15 @@ def test_key_and_certificates_repr(self, backend): cert2 = _load_cert( backend, os.path.join("x509", "cryptography.io.pem") ) - assert ( - repr( - PKCS12KeyAndCertificates( - key, - PKCS12Certificate(cert, None), - [PKCS12Certificate(cert2, b"name2")], - ) - ) - == ", additional_certs=[])>".format( + assert repr( + PKCS12KeyAndCertificates( key, - cert, - cert2, + PKCS12Certificate(cert, None), + [PKCS12Certificate(cert2, b"name2")], ) + ) == ( + f", " + f"additional_certs=[" + f"])>" ) diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 03b04cd389e5..64f14b9dc8a0 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -6,17 +6,28 @@ import email.parser import os import typing +from email.message import EmailMessage import pytest -from cryptography import x509 +from cryptography import exceptions, x509 from cryptography.exceptions import _Reasons +from cryptography.hazmat.bindings._rust import test_support from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa from cryptography.hazmat.primitives.serialization import pkcs7 +from tests.x509.test_x509 import _generate_ca_and_leaf +from ...hazmat.primitives.fixtures_rsa import ( + RSA_KEY_2048_ALT, +) +from ...hazmat.primitives.test_rsa import rsa_key_2048 from ...utils import load_vectors_from_file, raises_unsupported_algorithm +# Make ruff happy since we're importing fixtures that pytest patches in as +# func args +__all__ = ["rsa_key_2048"] + @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported(), @@ -90,68 +101,12 @@ def test_load_pkcs7_unsupported_type(self, backend): ) def test_load_pkcs7_empty_certificates(self): - der = b"\x30\x0B\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02" + 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 -# verification for the signing tests. -def _pkcs7_verify(encoding, sig, msg, certs, options, backend): - sig_bio = backend._bytes_to_bio(sig) - if encoding is serialization.Encoding.DER: - p7 = backend._lib.d2i_PKCS7_bio(sig_bio.bio, backend._ffi.NULL) - elif encoding is serialization.Encoding.PEM: - p7 = backend._lib.PEM_read_bio_PKCS7( - sig_bio.bio, - backend._ffi.NULL, - backend._ffi.NULL, - backend._ffi.NULL, - ) - else: - p7 = backend._lib.SMIME_read_PKCS7(sig_bio.bio, backend._ffi.NULL) - backend.openssl_assert(p7 != backend._ffi.NULL) - p7 = backend._ffi.gc(p7, backend._lib.PKCS7_free) - flags = 0 - for option in options: - if option is pkcs7.PKCS7Options.Text: - flags |= backend._lib.PKCS7_TEXT - store = backend._lib.X509_STORE_new() - backend.openssl_assert(store != backend._ffi.NULL) - store = backend._ffi.gc(store, backend._lib.X509_STORE_free) - # This list is to keep the x509 values alive until end of function - ossl_certs = [] - for cert in certs: - ossl_cert = backend._cert2ossl(cert) - ossl_certs.append(ossl_cert) - res = backend._lib.X509_STORE_add_cert(store, ossl_cert) - backend.openssl_assert(res == 1) - if msg is None: - res = backend._lib.PKCS7_verify( - p7, - backend._ffi.NULL, - store, - backend._ffi.NULL, - backend._ffi.NULL, - flags, - ) - else: - msg_bio = backend._bytes_to_bio(msg) - # libressl 3.7.0 has a bug when NULL is passed as an `out_bio`. Work - # around it for now. - out_bio = backend._create_mem_bio_gc() - res = backend._lib.PKCS7_verify( - p7, backend._ffi.NULL, store, msg_bio.bio, out_bio, flags - ) - backend.openssl_assert(res == 1) - # OpenSSL 3.0 leaves a random bio error on the stack: - # https://github.com/openssl/openssl/issues/16681 - if backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - backend._consume_errors() - - def _load_cert_key(): key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "ca_key.pem"), @@ -172,7 +127,7 @@ def _load_cert_key(): only_if=lambda backend: backend.pkcs7_supported(), skip_message="Requires OpenSSL with PKCS7 support", ) -class TestPKCS7Builder: +class TestPKCS7SignatureBuilder: def test_invalid_data(self, backend): builder = pkcs7.PKCS7SignatureBuilder() with pytest.raises(TypeError): @@ -315,23 +270,25 @@ def test_smime_sign_detached(self, backend): # Parse the message to get the signed data, which is the # first payload in the message message = email.parser.BytesParser().parsebytes(sig) - signed_data = message.get_payload()[0].get_payload().encode() - _pkcs7_verify( + payload = message.get_payload() + assert isinstance(payload, list) + assert isinstance(payload[0], email.message.Message) + signed_data = payload[0].get_payload() + assert isinstance(signed_data, str) + test_support.pkcs7_verify( serialization.Encoding.SMIME, sig, - signed_data, + signed_data.encode(), [cert], options, - backend, ) assert data not in sig_binary - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, data, [cert], options, - backend, ) def test_sign_byteslike(self, backend): @@ -346,13 +303,12 @@ def test_sign_byteslike(self, backend): sig = builder.sign(serialization.Encoding.SMIME, options) assert bytes(data) in sig - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.SMIME, sig, data, [cert], options, - backend, ) data = bytearray(b"") @@ -363,13 +319,12 @@ def test_sign_byteslike(self, backend): ) sig = builder.sign(serialization.Encoding.SMIME, options) - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.SMIME, sig, data, [cert], options, - backend, ) def test_sign_pem(self, backend): @@ -383,13 +338,12 @@ def test_sign_pem(self, backend): ) sig = builder.sign(serialization.Encoding.PEM, options) - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.PEM, sig, None, [cert], options, - backend, ) @pytest.mark.parametrize( @@ -413,8 +367,8 @@ def test_sign_alternate_digests_der( options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert expected_value in sig - _pkcs7_verify( - serialization.Encoding.DER, sig, None, [cert], options, backend + test_support.pkcs7_verify( + serialization.Encoding.DER, sig, None, [cert], options ) @pytest.mark.parametrize( @@ -455,13 +409,12 @@ def test_sign_attached(self, backend): # When not passing detached signature the signed data is embedded into # the PKCS7 structure itself assert data in sig_binary - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, - backend, ) def test_sign_binary(self, backend): @@ -481,22 +434,20 @@ def test_sign_binary(self, backend): # so data should not be present in sig_no_binary, but should be present # in sig_binary assert data not in sig_no_binary - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig_no_binary, None, [cert], options, - backend, ) assert data in sig_binary - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, - backend, ) def test_sign_smime_canonicalization(self, backend): @@ -514,13 +465,12 @@ def test_sign_smime_canonicalization(self, backend): # so data should not be present in the sig assert data not in sig_binary assert b"hello\r\nworld" in sig_binary - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, - backend, ) def test_sign_text(self, backend): @@ -545,16 +495,61 @@ def test_sign_text(self, backend): # Parse the message to get the signed data, which is the # first payload in the message message = email.parser.BytesParser().parsebytes(sig_pem) - signed_data = message.get_payload()[0].as_bytes( + payload = message.get_payload() + assert isinstance(payload, list) + assert isinstance(payload[0], email.message.Message) + signed_data = payload[0].as_bytes( policy=message.policy.clone(linesep="\r\n") ) - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.SMIME, sig_pem, signed_data, [cert], options, - backend, + ) + + def test_smime_capabilities(self, backend): + data = b"hello world" + cert, key = _load_cert_key() + builder = ( + pkcs7.PKCS7SignatureBuilder() + .set_data(data) + .add_signer(cert, key, hashes.SHA256()) + ) + + sig_binary = builder.sign(serialization.Encoding.DER, []) + + # 1.2.840.113549.1.9.15 (SMIMECapabilities) as an ASN.1 DER encoded OID + assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" in sig_binary + + # 2.16.840.1.101.3.4.1.42 (aes256-CBC-PAD) as an ASN.1 DER encoded OID + aes256_cbc_pad_oid = b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x2a" + # 2.16.840.1.101.3.4.1.22 (aes192-CBC-PAD) as an ASN.1 DER encoded OID + aes192_cbc_pad_oid = b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x16" + # 2.16.840.1.101.3.4.1.2 (aes128-CBC-PAD) as an ASN.1 DER encoded OID + aes128_cbc_pad_oid = b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x02" + + # Each algorithm in SMIMECapabilities should be inside its own + # SEQUENCE. + # This is encoded as SEQUENCE_IDENTIFIER + LENGTH + ALGORITHM_OID. + # This tests that each algorithm is indeed encoded inside its own + # sequence. See RFC 2633, Appendix A for more details. + sequence_identifier = b"\x30" + for oid in [ + aes256_cbc_pad_oid, + aes192_cbc_pad_oid, + aes128_cbc_pad_oid, + ]: + len_oid = len(oid).to_bytes(length=1, byteorder="big") + assert sequence_identifier + len_oid + oid in sig_binary + + test_support.pkcs7_verify( + serialization.Encoding.DER, + sig_binary, + None, + [cert], + [], ) def test_sign_no_capabilities(self, backend): @@ -577,13 +572,12 @@ def test_sign_no_capabilities(self, backend): assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" not in sig_binary # 1.2.840.113549.1.9.5 signingTime as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x05" in sig_binary - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, - backend, ) def test_sign_no_attributes(self, backend): @@ -604,13 +598,12 @@ def test_sign_no_attributes(self, backend): assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" not in sig_binary # 1.2.840.113549.1.9.5 signingTime as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x05" not in sig_binary - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, - backend, ) def test_sign_no_certs(self, backend): @@ -677,17 +670,22 @@ def test_rsa_pkcs_padding_options(self, pad, backend): sig.count(b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x08") == 1 ) else: - # This should be a pkcs1 sha512 signature + # This should be a pkcs1 RSA signature, which uses the + # `rsaEncryption` OID (1.2.840.113549.1.1.1) no matter which + # digest algorithm is used. + # See RFC 3370 section 3.2 for more details. + # This OID appears twice, once in the certificate itself and + # another in the SignerInfo data structure in the + # `digest_encryption_algorithm` field. assert ( - sig.count(b"\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x0D") == 1 + sig.count(b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01") == 2 ) - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig, None, [rsa_cert], options, - backend, ) def test_not_rsa_key_with_padding(self, backend): @@ -749,13 +747,12 @@ def test_multiple_signers(self, backend): sig = builder.sign(serialization.Encoding.DER, options) # There should be three SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 3 - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig, None, [cert, rsa_cert], options, - backend, ) def test_multiple_signers_different_hash_algs(self, backend): @@ -787,13 +784,12 @@ def test_multiple_signers_different_hash_algs(self, backend): # There should be two SHA384 and two SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x02") == 2 assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 2 - _pkcs7_verify( + test_support.pkcs7_verify( serialization.Encoding.DER, sig, None, [cert, rsa_cert], options, - backend, ) def test_add_additional_cert_not_a_cert(self, backend): @@ -848,6 +844,519 @@ def test_add_multiple_additional_certs(self, backend): ) +def _load_rsa_cert_key(): + 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", + ) + 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", + ) + return cert, key + + +@pytest.mark.supported( + only_if=lambda backend: backend.pkcs7_supported() + and backend.rsa_encryption_supported(padding.PKCS1v15()), + skip_message="Requires OpenSSL with PKCS7 support and PKCS1 v1.5 padding " + "support", +) +class TestPKCS7EnvelopeBuilder: + def test_invalid_data(self, backend): + builder = pkcs7.PKCS7EnvelopeBuilder() + with pytest.raises(TypeError): + builder.set_data("not bytes") # type: ignore[arg-type] + + def test_set_data_twice(self, backend): + builder = pkcs7.PKCS7EnvelopeBuilder().set_data(b"test") + with pytest.raises(ValueError): + builder.set_data(b"test") + + def test_encrypt_no_recipient(self, backend): + builder = pkcs7.PKCS7EnvelopeBuilder().set_data(b"test") + with pytest.raises(ValueError): + builder.encrypt(serialization.Encoding.SMIME, []) + + def test_encrypt_no_data(self, backend): + cert, _ = _load_rsa_cert_key() + builder = pkcs7.PKCS7EnvelopeBuilder().add_recipient(cert) + with pytest.raises(ValueError): + builder.encrypt(serialization.Encoding.SMIME, []) + + def test_unsupported_encryption(self, backend): + cert_non_rsa, _ = _load_cert_key() + with pytest.raises(TypeError): + pkcs7.PKCS7EnvelopeBuilder().add_recipient(cert_non_rsa) + + def test_not_a_cert(self, backend): + with pytest.raises(TypeError): + pkcs7.PKCS7EnvelopeBuilder().add_recipient( + b"notacert", # type: ignore[arg-type] + ) + + def test_encrypt_invalid_options(self, backend): + cert, _ = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder().set_data(b"test").add_recipient(cert) + ) + with pytest.raises(ValueError): + builder.encrypt( + serialization.Encoding.SMIME, + [b"invalid"], # type: ignore[list-item] + ) + + def test_encrypt_invalid_encoding(self, backend): + cert, _ = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder().set_data(b"test").add_recipient(cert) + ) + with pytest.raises(ValueError): + builder.encrypt(serialization.Encoding.Raw, []) + + @pytest.mark.parametrize( + "invalid_options", + [ + [pkcs7.PKCS7Options.NoAttributes], + [pkcs7.PKCS7Options.NoCapabilities], + [pkcs7.PKCS7Options.NoCerts], + [pkcs7.PKCS7Options.DetachedSignature], + [pkcs7.PKCS7Options.Binary, pkcs7.PKCS7Options.Text], + ], + ) + def test_encrypt_invalid_encryption_options( + self, backend, invalid_options + ): + cert, _ = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder().set_data(b"test").add_recipient(cert) + ) + with pytest.raises(ValueError): + builder.encrypt(serialization.Encoding.DER, invalid_options) + + @pytest.mark.parametrize( + "options", + [ + [pkcs7.PKCS7Options.Text], + [pkcs7.PKCS7Options.Binary], + ], + ) + def test_smime_encrypt_smime_encoding(self, backend, options): + data = b"hello world\n" + cert, private_key = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) + ) + enveloped = builder.encrypt(serialization.Encoding.SMIME, options) + assert b"MIME-Version: 1.0\n" in enveloped + assert b"Content-Transfer-Encoding: base64\n" in enveloped + message = email.parser.BytesParser().parsebytes(enveloped) + assert message.get_content_disposition() == "attachment" + assert message.get_filename() == "smime.p7m" + assert message.get_content_type() == "application/pkcs7-mime" + assert message.get_param("smime-type") == "enveloped-data" + assert message.get_param("name") == "smime.p7m" + + payload = message.get_payload(decode=True) + assert isinstance(payload, bytes) + + # We want to know if we've serialized something that has the parameters + # we expect, so we match on specific byte strings of OIDs & DER values. + # OID 2.16.840.1.101.3.4.1.2 (aes128-CBC) + assert b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x02" in payload + # OID 1.2.840.113549.1.1.1 (rsaEncryption (PKCS #1)) + assert b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01" in payload + # cryptography CA (the recipient's Common Name) + assert ( + b"\x0c\x0f\x63\x72\x79\x70\x74\x6f\x67\x72\x61\x70\x68\x79" + b"\x20\x43\x41" + ) in payload + + decrypted_bytes = pkcs7.pkcs7_decrypt_smime( + enveloped, + cert, + private_key, + [o for o in options if o != pkcs7.PKCS7Options.Binary], + ) + + # New lines are canonicalized to '\r\n' when not using Binary + expected_data = ( + data + if pkcs7.PKCS7Options.Binary in options + else data.replace(b"\n", b"\r\n") + ) + assert decrypted_bytes == expected_data + + @pytest.mark.parametrize( + "options", + [ + [pkcs7.PKCS7Options.Text], + [pkcs7.PKCS7Options.Binary], + ], + ) + def test_smime_encrypt_der_encoding(self, backend, options): + data = b"hello world\n" + cert, private_key = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) + ) + enveloped = builder.encrypt(serialization.Encoding.DER, options) + + # We want to know if we've serialized something that has the parameters + # we expect, so we match on specific byte strings of OIDs & DER values. + # OID 2.16.840.1.101.3.4.1.2 (aes128-CBC) + assert b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x02" in enveloped + # OID 1.2.840.113549.1.1.1 (rsaEncryption (PKCS #1)) + assert b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01" in enveloped + # cryptography CA (the recipient's Common Name) + assert ( + b"\x0c\x0f\x63\x72\x79\x70\x74\x6f\x67\x72\x61\x70\x68\x79" + b"\x20\x43\x41" + ) in enveloped + + decrypted_bytes = pkcs7.pkcs7_decrypt_der( + enveloped, + cert, + private_key, + [o for o in options if o != pkcs7.PKCS7Options.Binary], + ) + # New lines are canonicalized to '\r\n' when not using Binary + expected_data = ( + data + if pkcs7.PKCS7Options.Binary in options + else data.replace(b"\n", b"\r\n") + ) + assert decrypted_bytes == expected_data + + @pytest.mark.parametrize( + "options", + [ + [pkcs7.PKCS7Options.Text], + [pkcs7.PKCS7Options.Binary], + ], + ) + def test_smime_encrypt_pem_encoding(self, backend, options): + data = b"hello world\n" + cert, private_key = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) + ) + enveloped = builder.encrypt(serialization.Encoding.PEM, options) + decrypted_bytes = pkcs7.pkcs7_decrypt_pem( + enveloped, + cert, + private_key, + [o for o in options if o != pkcs7.PKCS7Options.Binary], + ) + + # New lines are canonicalized to '\r\n' when not using Binary + expected_data = ( + data + if pkcs7.PKCS7Options.Binary in options + else data.replace(b"\n", b"\r\n") + ) + assert decrypted_bytes == expected_data + + def test_smime_encrypt_multiple_recipients(self, backend): + data = b"hello world\n" + cert, _ = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(cert) + .add_recipient(cert) + ) + enveloped = builder.encrypt(serialization.Encoding.DER, []) + # cryptography CA (the recipient's Common Name) + common_name_bytes = ( + b"\x0c\x0f\x63\x72\x79\x70\x74\x6f\x67\x72\x61" + b"\x70\x68\x79\x20\x43\x41" + ) + assert enveloped.count(common_name_bytes) == 2 + + +@pytest.mark.supported( + only_if=lambda backend: backend.pkcs7_supported() + and backend.rsa_encryption_supported(padding.PKCS1v15()), + skip_message="Requires OpenSSL with PKCS7 support and PKCS1 v1.5 padding " + "support", +) +class TestPKCS7Decrypt: + @pytest.fixture(name="data") + def fixture_data(self, backend) -> bytes: + return b"Hello world!\n" + + @pytest.fixture(name="certificate") + def fixture_certificate(self, backend) -> x509.Certificate: + certificate, _ = _load_rsa_cert_key() + return certificate + + @pytest.fixture(name="private_key") + def fixture_private_key(self, backend) -> rsa.RSAPrivateKey: + _, private_key = _load_rsa_cert_key() + return private_key + + def test_unsupported_certificate_encryption(self, backend, private_key): + cert_non_rsa, _ = _load_cert_key() + with pytest.raises(TypeError): + pkcs7.pkcs7_decrypt_der(b"", cert_non_rsa, private_key, []) + + def test_not_a_cert(self, backend, private_key): + with pytest.raises(TypeError): + pkcs7.pkcs7_decrypt_der(b"", b"wrong_type", private_key, []) # type: ignore[arg-type] + + def test_not_a_pkey(self, backend, certificate): + with pytest.raises(TypeError): + pkcs7.pkcs7_decrypt_der(b"", certificate, b"wrong_type", []) # type: ignore[arg-type] + + @pytest.mark.parametrize( + "invalid_options", + [ + [b"invalid"], + [pkcs7.PKCS7Options.NoAttributes], + [pkcs7.PKCS7Options.Binary], + ], + ) + def test_pkcs7_decrypt_invalid_options( + self, backend, invalid_options, data, certificate, private_key + ): + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_der( + data, certificate, private_key, invalid_options + ) + + @pytest.mark.parametrize("options", [[], [pkcs7.PKCS7Options.Text]]) + def test_pkcs7_decrypt_der( + self, backend, data, certificate, private_key, options + ): + # Encryption + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(certificate) + ) + enveloped = builder.encrypt(serialization.Encoding.DER, options) + + # Test decryption: new lines are canonicalized to '\r\n' when + # encryption has no Binary option + decrypted = pkcs7.pkcs7_decrypt_der( + enveloped, certificate, private_key, options + ) + assert decrypted == data.replace(b"\n", b"\r\n") + + @pytest.mark.parametrize( + "header", + [ + "content-type: text/plain", + "CONTENT-TYPE: text/plain", + "MIME-Version: 1.0\r\nContent-Type: text/plain; charset='UTF-8'" + "\r\nContent-Transfer-Encoding: 7bit\r\nFrom: sender@example.com" + "\r\nTo: recipient@example.com\r\nSubject: Test Email", + ], + ) + def test_pkcs7_decrypt_der_text_handmade_header( + self, backend, certificate, private_key, header + ): + # Encryption of data with a custom header + base_data = "Hello world!\r\n" + data = f"{header}\r\n\r\n{base_data}".encode() + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(certificate) + ) + enveloped = builder.encrypt( + serialization.Encoding.DER, [pkcs7.PKCS7Options.Binary] + ) + + # Test decryption with text option + decrypted = pkcs7.pkcs7_decrypt_der( + enveloped, certificate, private_key, [pkcs7.PKCS7Options.Text] + ) + assert decrypted == base_data.encode() + + @pytest.mark.parametrize("options", [[], [pkcs7.PKCS7Options.Text]]) + def test_pkcs7_decrypt_pem( + self, backend, data, certificate, private_key, options + ): + # Encryption + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(certificate) + ) + enveloped = builder.encrypt(serialization.Encoding.PEM, options) + + # Test decryption: new lines are canonicalized to '\r\n' when + # encryption has no Binary option + decrypted = pkcs7.pkcs7_decrypt_pem( + enveloped, certificate, private_key, options + ) + assert decrypted == data.replace(b"\n", b"\r\n") + + def test_pkcs7_decrypt_pem_with_wrong_tag( + self, backend, data, certificate, private_key + ): + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_pem( + certificate.public_bytes(serialization.Encoding.PEM), + certificate, + private_key, + [], + ) + + @pytest.mark.parametrize("options", [[], [pkcs7.PKCS7Options.Text]]) + def test_pkcs7_decrypt_smime( + self, backend, data, certificate, private_key, options + ): + # Encryption + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(certificate) + ) + enveloped = builder.encrypt(serialization.Encoding.SMIME, options) + + # Test decryption + decrypted = pkcs7.pkcs7_decrypt_smime( + enveloped, certificate, private_key, options + ) + assert decrypted == data.replace(b"\n", b"\r\n") + + def test_pkcs7_decrypt_no_encrypted_content( + self, backend, data, certificate, private_key + ): + enveloped = load_vectors_from_file( + os.path.join("pkcs7", "enveloped-no-content.der"), + loader=lambda pemfile: pemfile.read(), + mode="rb", + ) + + # Test decryption with text option + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_der(enveloped, certificate, private_key, []) + + def test_pkcs7_decrypt_text_no_header( + self, backend, data, certificate, private_key + ): + # Encryption of data without a header (no "Text" option) + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(certificate) + ) + enveloped = builder.encrypt(serialization.Encoding.DER, []) + + # Test decryption with text option + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_der( + enveloped, certificate, private_key, [pkcs7.PKCS7Options.Text] + ) + + def test_pkcs7_decrypt_text_html_content_type( + self, backend, certificate, private_key + ): + # Encryption of data with a text/html content type header + data = b"Content-Type: text/html\r\n\r\nHello world!
" + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(certificate) + ) + enveloped = builder.encrypt( + serialization.Encoding.DER, [pkcs7.PKCS7Options.Binary] + ) + + # Test decryption with text option + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_der( + enveloped, certificate, private_key, [pkcs7.PKCS7Options.Text] + ) + + def test_smime_decrypt_no_recipient_match( + self, backend, data, certificate, rsa_key_2048: rsa.RSAPrivateKey + ): + # Encrypt some data with one RSA chain + builder = ( + pkcs7.PKCS7EnvelopeBuilder() + .set_data(data) + .add_recipient(certificate) + ) + enveloped = builder.encrypt(serialization.Encoding.DER, []) + + # Prepare another RSA chain + another_private_key = RSA_KEY_2048_ALT.private_key( + unsafe_skip_rsa_key_validation=True + ) + _, another_cert = _generate_ca_and_leaf( + rsa_key_2048, another_private_key + ) + + # Test decryption with another RSA chain + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_der( + enveloped, another_cert, another_private_key, [] + ) + + def test_smime_decrypt_unsupported_key_encryption_algorithm( + self, backend, data, certificate, private_key + ): + enveloped = load_vectors_from_file( + os.path.join("pkcs7", "enveloped-rsa-oaep.pem"), + loader=lambda pemfile: pemfile.read(), + mode="rb", + ) + + with pytest.raises(exceptions.UnsupportedAlgorithm): + pkcs7.pkcs7_decrypt_pem(enveloped, certificate, private_key, []) + + def test_smime_decrypt_unsupported_content_encryption_algorithm( + self, backend, data, certificate, private_key + ): + enveloped = load_vectors_from_file( + os.path.join("pkcs7", "enveloped-aes-256-cbc.pem"), + loader=lambda pemfile: pemfile.read(), + mode="rb", + ) + + with pytest.raises(exceptions.UnsupportedAlgorithm): + pkcs7.pkcs7_decrypt_pem(enveloped, certificate, private_key, []) + + def test_smime_decrypt_not_enveloped( + self, backend, data, certificate, private_key + ): + # Create a signed email + cert, key = _load_cert_key() + options = [pkcs7.PKCS7Options.DetachedSignature] + builder = ( + pkcs7.PKCS7SignatureBuilder() + .set_data(data) + .add_signer(cert, key, hashes.SHA256()) + ) + signed = builder.sign(serialization.Encoding.DER, options) + + # Test decryption failure with signed email + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_der(signed, certificate, private_key, []) + + def test_smime_decrypt_smime_not_encrypted( + self, backend, certificate, private_key + ): + # Create a plain email + email_message = EmailMessage() + email_message.set_content("Hello world!") + + # Test decryption failure with plain email + with pytest.raises(ValueError): + pkcs7.pkcs7_decrypt_smime( + email_message.as_bytes(), certificate, private_key, [] + ) + + @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported(), skip_message="Requires OpenSSL with PKCS7 support", @@ -935,3 +1444,26 @@ def test_pkcs7_functions_unsupported(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): pkcs7.load_pem_pkcs7_certificates(b"nonsense") + + +@pytest.mark.supported( + only_if=lambda backend: backend.pkcs7_supported() + and not backend.rsa_encryption_supported(padding.PKCS1v15()), + skip_message="Requires OpenSSL with no PKCS1 v1.5 padding support", +) +class TestPKCS7EnvelopeBuilderUnsupported: + def test_envelope_builder_unsupported(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + pkcs7.PKCS7EnvelopeBuilder() + + +@pytest.mark.supported( + only_if=lambda backend: backend.pkcs7_supported() + and not backend.rsa_encryption_supported(padding.PKCS1v15()), + skip_message="Requires OpenSSL with no PKCS1 v1.5 padding support", +) +class TestPKCS7DecryptUnsupported: + def test_pkcs7_decrypt_unsupported(self, backend): + cert, key = _load_rsa_cert_key() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + pkcs7.pkcs7_decrypt_der(b"", cert, key, []) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 10a84cb08665..92cf9da1ba92 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -15,6 +15,7 @@ UnsupportedAlgorithm, _Reasons, ) +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.hazmat.primitives.asymmetric import utils as asym_utils @@ -72,7 +73,7 @@ def rsa_key_2048() -> rsa.RSAPrivateKey: class DummyMGF(padding.MGF): _salt_length = 0 - _algorithm = hashes.SHA1() + _algorithm = hashes.SHA256() def _check_fips_key_length(backend, private_key): @@ -251,14 +252,7 @@ def test_load_pss_vect_example_keys(self, pkcs1_example): assert public_num.e == public_num2.e @pytest.mark.supported( - only_if=lambda backend: ( - 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 - ) - ), + only_if=lambda backend: not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL, skip_message="Does not support RSA PSS loading", ) @pytest.mark.parametrize( @@ -309,14 +303,7 @@ def test_load_pss_pub_keys_strips_constraints(self, backend): ) @pytest.mark.supported( - only_if=lambda backend: ( - 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 - ) - ), + only_if=lambda backend: rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL, skip_message="Test requires a backend without RSA-PSS key support", ) def test_load_pss_unsupported(self, backend): @@ -602,7 +589,7 @@ def test_pss_digest_length(self, rsa_key_2048, backend): backend.hash_supported(hashes.SHA512()) and backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ) @@ -617,7 +604,7 @@ def test_pss_minimum_key_size_for_digest(self, backend): private_key.sign( b"no failure", padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA512(), @@ -626,7 +613,7 @@ def test_pss_minimum_key_size_for_digest(self, backend): @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), @@ -645,7 +632,7 @@ def test_pss_signing_digest_too_large_for_key_size( private_key.sign( b"msg", padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA512(), @@ -654,7 +641,7 @@ def test_pss_signing_digest_too_large_for_key_size( @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), @@ -668,7 +655,7 @@ def test_pss_signing_salt_length_too_long( private_key.sign( b"failure coming", padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), salt_length=1000000 + mgf=padding.MGF1(hashes.SHA256()), salt_length=1000000 ), hashes.SHA256(), ) @@ -678,7 +665,7 @@ def test_unsupported_padding( ): private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.sign(b"msg", DummyAsymmetricPadding(), hashes.SHA1()) + private_key.sign(b"msg", DummyAsymmetricPadding(), hashes.SHA256()) def test_padding_incorrect_type( self, rsa_key_2048: rsa.RSAPrivateKey, backend @@ -693,7 +680,7 @@ def test_padding_incorrect_type( @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) @@ -708,7 +695,7 @@ def test_unsupported_pss_mgf( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH, ), - hashes.SHA1(), + hashes.SHA256(), ) @pytest.mark.supported( @@ -763,9 +750,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) @@ -774,7 +767,7 @@ def test_sign(self, rsa_key_2048: rsa.RSAPrivateKey, backend): @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) @@ -784,7 +777,7 @@ def test_prehashed_sign(self, rsa_key_2048: rsa.RSAPrivateKey, backend): h = hashes.Hash(hashes.SHA256(), backend) h.update(message) digest = h.finalize() - pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) signature = private_key.sign(digest, pss, prehashed_alg) public_key = private_key.public_key() @@ -824,12 +817,12 @@ def test_prehashed_digest_length( ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) - def test_unsupported_hash(self, rsa_key_512: rsa.RSAPrivateKey, backend): - private_key = rsa_key_512 + def test_unsupported_hash(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 message = b"one little message" pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): @@ -852,20 +845,20 @@ def test_unsupported_hash_pss_mgf1(self, rsa_key_2048: rsa.RSAPrivateKey): @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) def test_prehashed_digest_mismatch( - self, rsa_key_512: rsa.RSAPrivateKey, backend + self, rsa_key_2048: rsa.RSAPrivateKey, backend ): - private_key = rsa_key_512 + private_key = rsa_key_2048 message = b"one little message" h = hashes.Hash(hashes.SHA512(), backend) h.update(message) digest = h.finalize() - pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) - prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) with pytest.raises(ValueError): private_key.sign(digest, pss, prehashed_alg) @@ -1101,18 +1094,12 @@ def test_pss_verify_auto_salt_length( @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) - @pytest.mark.supported( - only_if=lambda backend: backend.signature_hash_supported( - hashes.SHA1() - ), - skip_message="Does not support SHA1 signature.", - ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_wrong_data(self, backend): public_key = rsa.RSAPublicNumbers( @@ -1133,27 +1120,21 @@ def test_invalid_pss_signature_wrong_data(self, backend): signature, b"incorrect data", padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), + mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), - hashes.SHA1(), + hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) - @pytest.mark.supported( - only_if=lambda backend: backend.signature_hash_supported( - hashes.SHA1() - ), - skip_message="Does not support SHA1 signature.", - ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_wrong_key(self, backend): signature = binascii.unhexlify( @@ -1176,27 +1157,21 @@ def test_invalid_pss_signature_wrong_key(self, backend): signature, b"sign me", padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), + mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), - hashes.SHA1(), + hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) - @pytest.mark.supported( - only_if=lambda backend: backend.signature_hash_supported( - hashes.SHA1() - ), - skip_message="Does not support SHA1 signature.", - ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): # 2048 bit PSS signature @@ -1219,25 +1194,19 @@ def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): signature, b"sign me", padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), + mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), - hashes.SHA1(), + hashes.SHA256(), ) - @pytest.mark.supported( - only_if=lambda backend: backend.signature_hash_supported( - hashes.SHA1() - ), - skip_message="Does not support SHA1 signature.", - ) def test_invalid_pss_signature_recover( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() pss_padding = padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), + mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) signature = private_key.sign(b"sign me", pss_padding, hashes.SHA256()) @@ -1279,7 +1248,7 @@ def test_padding_incorrect_type( @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) @@ -1301,7 +1270,7 @@ def test_unsupported_pss_mgf( @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA512()), salt_length=padding.PSS.MAX_LENGTH, ) ), @@ -1326,7 +1295,7 @@ def test_pss_verify_digest_too_large_for_key_size( signature, b"msg doesn't matter", padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), + mgf=padding.MGF1(algorithm=hashes.SHA512()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA512(), @@ -1335,18 +1304,12 @@ def test_pss_verify_digest_too_large_for_key_size( @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) - @pytest.mark.supported( - only_if=lambda backend: backend.signature_hash_supported( - hashes.SHA1() - ), - skip_message="Does not support SHA1 signature.", - ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pss_verify_salt_length_too_long(self, backend): signature = binascii.unhexlify( @@ -1368,16 +1331,22 @@ def test_pss_verify_salt_length_too_long(self, backend): b"sign me", padding.PSS( mgf=padding.MGF1( - algorithm=hashes.SHA1(), + algorithm=hashes.SHA256(), ), salt_length=1000000, ), - hashes.SHA1(), + hashes.SHA256(), ) - 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) @@ -1663,16 +1632,16 @@ def test_calculate_max_pss_salt_length(self): def test_invalid_salt_length_not_integer(self): with pytest.raises(TypeError): padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), + mgf=padding.MGF1(hashes.SHA256()), salt_length=b"not_a_length", # type:ignore[arg-type] ) def test_invalid_salt_length_negative_integer(self): with pytest.raises(ValueError): - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=-1) + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=-1) def test_valid_pss_parameters(self): - algorithm = hashes.SHA1() + algorithm = hashes.SHA256() salt_length = algorithm.digest_size mgf = padding.MGF1(algorithm) pss = padding.PSS(mgf=mgf, salt_length=salt_length) @@ -1680,14 +1649,14 @@ def test_valid_pss_parameters(self): assert pss._salt_length == salt_length def test_valid_pss_parameters_maximum(self): - algorithm = hashes.SHA1() + algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) pss = padding.PSS(mgf=mgf, salt_length=padding.PSS.MAX_LENGTH) assert pss._mgf == mgf assert pss._salt_length == padding.PSS.MAX_LENGTH def test_mgf_property(self): - algorithm = hashes.SHA1() + algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) pss = padding.PSS(mgf=mgf, salt_length=padding.PSS.MAX_LENGTH) assert pss.mgf == mgf @@ -1700,14 +1669,14 @@ def test_invalid_hash_algorithm(self): padding.MGF1(b"not_a_hash") # type:ignore[arg-type] def test_valid_mgf1_parameters(self): - algorithm = hashes.SHA1() + algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) assert mgf._algorithm == algorithm class TestOAEP: def test_invalid_algorithm(self): - mgf = padding.MGF1(hashes.SHA1()) + mgf = padding.MGF1(hashes.SHA256()) with pytest.raises(TypeError): padding.OAEP( mgf=mgf, @@ -1716,14 +1685,14 @@ def test_invalid_algorithm(self): ) def test_algorithm_property(self): - algorithm = hashes.SHA1() + algorithm = hashes.SHA256() 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() + algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) oaep = padding.OAEP(mgf=mgf, algorithm=algorithm, label=None) assert oaep.mgf == mgf @@ -1888,8 +1857,8 @@ def test_decrypt_oaep_sha2_vectors(self, backend, subtests): @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ) ), @@ -1906,8 +1875,8 @@ def test_invalid_oaep_decryption( ciphertext = private_key.public_key().encrypt( b"secure data", padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), ) @@ -1920,8 +1889,8 @@ def test_invalid_oaep_decryption( private_key_alt.decrypt( ciphertext, padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), ) @@ -1996,7 +1965,7 @@ def test_unsupported_oaep_mgf( b"0" * 256, padding.OAEP( mgf=DummyMGF(), - algorithm=hashes.SHA1(), + algorithm=hashes.SHA256(), label=None, ), ) @@ -2006,8 +1975,8 @@ class TestRSAEncryption: @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ) ), @@ -2030,8 +1999,8 @@ class TestRSAEncryption: ), [ padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ) ], @@ -2168,6 +2137,8 @@ def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): skip_message="Requires FIPS", ) def test_rsa_fips_small_key(self, rsa_key_512: rsa.RSAPrivateKey, backend): + # Ideally this would use a larger disallowed key like RSA-1024, but + # RHEL-8 thinks that RSA-1024 is allowed by FIPS. with pytest.raises(ValueError): rsa_key_512.sign(b"somedata", padding.PKCS1v15(), hashes.SHA512()) @@ -2196,7 +2167,7 @@ def test_unsupported_oaep_mgf( b"ciphertext", padding.OAEP( mgf=DummyMGF(), - algorithm=hashes.SHA1(), + algorithm=hashes.SHA256(), label=None, ), ) @@ -2427,6 +2398,8 @@ def test_recover_prime_factors(self, subtests): def test_invalid_recover_prime_factors(self): with pytest.raises(ValueError): rsa.rsa_recover_prime_factors(34, 3, 7) + with pytest.raises(ValueError): + rsa.rsa_recover_prime_factors(629, 17, 20) class TestRSAPrivateKeySerialization: @@ -2463,6 +2436,21 @@ def test_private_bytes_encrypted_pem( priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.supported( + only_if=lambda backend: backend._fips_enabled, + skip_message="Requires FIPS", + ) + def test_traditional_serialization_fips( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + key = rsa_key_2048 + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.BestAvailableEncryption(b"password"), + ) + @pytest.mark.parametrize( ("encoding", "fmt"), [ diff --git a/tests/hazmat/primitives/test_seed.py b/tests/hazmat/primitives/test_seed.py deleted file mode 100644 index f36ce1e4ecea..000000000000 --- a/tests/hazmat/primitives/test_seed.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - - -import binascii -import os - -import pytest - -from cryptography.hazmat.primitives.ciphers import algorithms, modes - -from ...utils import load_nist_vectors -from .utils import generate_encrypt_test - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._SEEDInternal(b"\x00" * 16), modes.ECB() - ), - skip_message="Does not support SEED ECB", -) -class TestSEEDModeECB: - test_ecb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "SEED"), - ["rfc-4269.txt"], - lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify(key) - ), - lambda **kwargs: modes.ECB(), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._SEEDInternal(b"\x00" * 16), modes.CBC(b"\x00" * 16) - ), - skip_message="Does not support SEED CBC", -) -class TestSEEDModeCBC: - test_cbc = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "SEED"), - ["rfc-4196.txt"], - lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._SEEDInternal(b"\x00" * 16), modes.OFB(b"\x00" * 16) - ), - skip_message="Does not support SEED OFB", -) -class TestSEEDModeOFB: - test_ofb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "SEED"), - ["seed-ofb.txt"], - lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), - ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms._SEEDInternal(b"\x00" * 16), modes.CFB(b"\x00" * 16) - ), - skip_message="Does not support SEED CFB", -) -class TestSEEDModeCFB: - test_cfb = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "SEED"), - ["seed-cfb.txt"], - lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify(key) - ), - lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), - ) diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 58693a4912d2..32e0ded0ead5 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 ): @@ -592,34 +608,61 @@ def test_rsa_traditional_encrypted_values(self, backend): numbers = pkey.private_numbers() assert numbers.p == int( - "fb7d316fc51531b36d93adaefaf52db6ad5beb793d37c4cf9dfc1ddd17cfbafb", + "f8337fbcd4b54e14d4226889725d9dc713e40c87e62ce1886a517c729b3d133d" + "c519bfb026081788509d2b503bc0966bdc67c45771e41f9844cee1be968b3263" + "735d6c47d981dacfde1fe2110c4acbfe656599890b8f131c20d246891959f45d" + "06d4fadf205f94f9ea050c661efdc760d7471a1963bf16333837ef6dc4f8dbaf", 16, ) assert numbers.q == int( - "df98264e646de9a0fbeab094e31caad5bc7adceaaae3c800ca0275dd4bb307f5", + "bf8c2ad54acf67f8b687849f91ece4761901e8abc8b0bc8604f55e64ad413a62" + "02dbb28eac0463f87811c1ca826b0eeafb53d115b50de5a775f74c5e9cf8161b" + "fc030f5e402664388ea1ef7d0ade85559e4e68cef519cb4f582ec41f994249d8" + "b860a7433f0612322827a87b3cc0d785075811b76bccbc90ff153a11592fa307", 16, ) assert numbers.d == int( - "db4848c36f478dd5d38f35ae519643b6b810d404bcb76c00e44015e56ca1cab0" - "7bb7ae91f6b4b43fcfc82a47d7ed55b8c575152116994c2ce5325ec24313b911", + "09a768d21f58866d690aeb78f0d92732aa03fa843f960b0799dfc31e7d73f1e6" + "503953c582becd4de92d293b3a86a42b2837531fdfc54db75e0d30701801a85c" + "120e997bce2b19290234710e2fd4cbe750d3fdaab65893c539057a21b8a2201b" + "4e418b6dff47423905a8e0b17fdd14bd3b0834ccb0a7c203d8e62e6ab4c6552d" + "9b777847c874e743ac15942a21816bb177919215ee235064fb0a7b3baaafac14" + "92e29b2fc80dc16b633525d83eed73fa47a55a9894148a50358eb94c62b19e84" + "f3d7daf866cd6a606920d54ba41d7aa648e777d5269fe00b12a8cf5ccf823f62" + "c1e8dc442ec3a7e3356913f444919baa4a5c7299345817543b4add5f9c1a477f", 16, ) assert numbers.dmp1 == int( - "ce997f967192c2bcc3853186f1559fd355c190c58ddc15cbf5de9b6df954c727", + "e0cdcc51dd1b0648c9470d0608e710040359179c73778d2300a123a5ae43a84c" + "d75c1609d6b8978fe8ec2211febcd5c186151a79d57738c2b2f7eaf1b3eb09cd" + "97ed3328f4b1afdd7ca3c61f88d1aa6895b06b5afc742f6bd7b27d1eaa2e96ad" + "3785ea5ff4337e7cc9609f3553b6aa42655a4a225afcf57f98d8d8ecc46e5e93", 16, ) assert numbers.dmq1 == int( - "b018a57ab20ffaa3862435445d863369b852cf70a67c55058213e3fe10e3848d", + "904aeda559429e870c315025c88e9497a644fada154795ecbb657f6305e4c22f" + "3d09f51b66d7b3db63cfb49571e3660c7ba16b3b17f5cd0f765d0189b0636e7c" + "4c3e9de0192112944c560e8bba996005dc4822c9ec772ee1a9832938c881d811" + "4aeb7c74bad03efacba6fc5341b3df6695deb111e44209b68c819809a38eb017", 16, ) assert numbers.iqmp == int( - "6a8d830616924f5cf2d1bc1973f97fde6b63e052222ac7be06aa2532d10bac76", + "378a3ae1978c381dce3b486b038601cf06dfa77687fdcd2d56732380bff4f32e" + "ec20027034bcd53be80162e4054ab7fefdbc3e5fe923aa8130d2c9ab01d6a70f" + "da3615f066886ea610e06c29cf5c2e0649a40ca936f290b779cd9e2bc3b87095" + "26667f75a1016e268ae3b9501ae4696ec8c1af09dc567804151fdeb1486ee512", 16, ) assert numbers.public_numbers.e == 65537 assert numbers.public_numbers.n == int( - "dba786074f2f0350ce1d99f5aed5b520cfe0deb5429ec8f2a88563763f566e77" - "9814b7c310e5326edae31198eed439b845dd2db99eaa60f5c16a43f4be6bcf37", + "b9b651fefc4dd4c9b1c0312ee69f0803990d5a539785dd14f1f6880d9198ee1f" + "71b3babb1ebe977786b30bea170f24b7a0e7b116f2c6908cf374923984924187" + "86de9d4e0f5f3e56d7be9eb971d3f8a4f812057cf9f9053b829d1c54d1a340fe" + "5c90a6e228a5871da900770141b4c6e6f298409718cb16467a4f5ff63882b204" + "255028f49745dedc7ca4b5cba6d78acf32b650f06bf81862eda0856a14e8767e" + "d4086342284a6f9752e96435f7119a05cc3220a954774a931dbebe1f1ab0df9d" + "aeaedb132741c3b5c48e1a1426ccd954fb9b5140c14daec9a79be9c7c8e50610" + "dfb489c7539999cfc14ac75765bab4ae8a8df5d96c3de34c12435b1a02cf6ec9", 16, ) diff --git a/tests/hazmat/primitives/test_ssh.py b/tests/hazmat/primitives/test_ssh.py index d3372566e93f..82f398305e21 100644 --- a/tests/hazmat/primitives/test_ssh.py +++ b/tests/hazmat/primitives/test_ssh.py @@ -55,6 +55,10 @@ class TestOpenSSHSerialization: ("ecdsa-nopsw.key.pub", "ecdsa-nopsw.key-cert.pub"), ("ed25519-psw.key.pub", None), ("ed25519-nopsw.key.pub", "ed25519-nopsw.key-cert.pub"), + ("sk-ecdsa-psw.key.pub", None), + ("sk-ecdsa-nopsw.key.pub", None), + ("sk-ed25519-psw.key.pub", None), + ("sk-ed25519-nopsw.key.pub", None), ], ) def test_load_ssh_public_key(self, key_file, cert_file, backend): @@ -80,10 +84,14 @@ def test_load_ssh_public_key(self, key_file, cert_file, backend): ) else: public_key = load_ssh_public_key(pub_data, backend) - assert ( - public_key.public_bytes(Encoding.OpenSSH, PublicFormat.OpenSSH) - == nocomment_data - ) + if not key_file.startswith("sk-"): + # SK keys do not round-trip + assert ( + public_key.public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH + ) + == nocomment_data + ) self.run_partial_pubkey(pub_data, backend) @@ -390,7 +398,7 @@ def make_file( b"\x04" * 65, ), priv_type=None, - priv_fields=(b"nistp256", b"\x04" * 65, b"\x7F" * 32), + priv_fields=(b"nistp256", b"\x04" * 65, b"\x7f" * 32), comment=b"comment", checkval1=b"1234", checkval2=b"1234", @@ -1800,3 +1808,20 @@ def test_sign_and_byte_compare_ed25519(self, monkeypatch, backend): b"t8yRa8IRbxvOyA9TZYDGG1dRE3DiR0fuudU20v6vqfTd1gx0S5QyEdECXLl9ZI3" b"AwZgc=" ) + + +class TestSSHSK: + @staticmethod + def ssh_str(application): + data = ( + len(application).to_bytes(length=4, byteorder="big") + + application.encode() + ) + return memoryview(data) + + def test_load_application(self): + ssh.load_application(self.ssh_str("ssh:test")) + + def test_load_application_valueerror(self): + with pytest.raises(ValueError): + ssh.load_application(self.ssh_str("hss:test")) diff --git a/tests/hazmat/primitives/twofactor/test_hotp.py b/tests/hazmat/primitives/twofactor/test_hotp.py index 31e01a495256..acc6ba0dfd24 100644 --- a/tests/hazmat/primitives/twofactor/test_hotp.py +++ b/tests/hazmat/primitives/twofactor/test_hotp.py @@ -107,3 +107,13 @@ def test_buffer_protocol(self, backend): key = bytearray(b"a long key with lots of entropy goes here") hotp = HOTP(key, 6, SHA1(), backend) assert hotp.generate(10) == b"559978" + + def test_invalid_counter(self, backend): + key = os.urandom(16) + hotp = HOTP(key, 6, SHA1(), backend) + + with pytest.raises(TypeError): + hotp.generate(2.5) # type: ignore[arg-type] + + with pytest.raises(ValueError): + hotp.generate(2**64) diff --git a/tests/hazmat/primitives/twofactor/test_totp.py b/tests/hazmat/primitives/twofactor/test_totp.py index f68a8339c443..00c7a7a2d1e0 100644 --- a/tests/hazmat/primitives/twofactor/test_totp.py +++ b/tests/hazmat/primitives/twofactor/test_totp.py @@ -142,3 +142,10 @@ def test_buffer_protocol(self, backend): totp = TOTP(key, 8, hashes.SHA512(), 30, backend) time = 60 assert totp.generate(time) == b"53049576" + + def test_invalid_time(self, backend): + key = b"12345678901234567890" + totp = TOTP(key, 8, hashes.SHA1(), 30, backend) + + with pytest.raises(TypeError): + totp.generate("test") # type: ignore[arg-type] diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index b15955fd25fb..16dc612e528e 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -16,6 +16,9 @@ InvalidTag, NotYetFinalized, ) +from cryptography.hazmat.decrepit.ciphers import ( + algorithms as decrepit_algorithms, +) from cryptography.hazmat.primitives import hashes, hmac, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import ( @@ -430,15 +433,15 @@ def _kbkdf_cmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params): "cmac_aes128": algorithms.AES, "cmac_aes192": algorithms.AES, "cmac_aes256": algorithms.AES, - "cmac_tdes2": algorithms.TripleDES, - "cmac_tdes3": algorithms.TripleDES, + "cmac_tdes2": decrepit_algorithms.TripleDES, + "cmac_tdes3": decrepit_algorithms.TripleDES, } algorithm = supported_cipher_algorithms.get(prf) assert algorithm is not None # TripleDES is disallowed in FIPS mode. - if backend._fips_enabled and algorithm is algorithms.TripleDES: + if backend._fips_enabled and algorithm is decrepit_algorithms.TripleDES: pytest.skip("TripleDES is not supported in FIPS mode.") ctrkdf = KBKDFCMAC( @@ -519,13 +522,42 @@ def rsa_verification_test(backend, params, hash_alg, pad_factory): public_key.verify(signature, msg, pad, hash_alg) +def _rsa_recover_euler_private_exponent(e: int, p: int, q: int) -> int: + """ + Compute the RSA private_exponent (d) given the public exponent (e) + and the RSA primes p and q, following the usage of the original + RSA paper. + + As in the original RSA paper, this uses the Euler totient function + instead of the Carmichael totient function, and thus may generate a + larger value of the private exponent than necessary. + + See cryptography.hazmat.primitives.asymmetric.rsa_recover_private_exponent + for the public-facing version of this function, which uses the + preferred Carmichael totient function. + """ + phi_n = (p - 1) * (q - 1) + return rsa._modinv(e, phi_n) + + def _check_rsa_private_numbers(skey): assert skey pkey = skey.public_numbers assert pkey assert pkey.e assert pkey.n - assert skey.d + + # Historically there have been two ways to calculate valid values of the + # private_exponent (d) given the public exponent (e): + # - using the Carmichael totient function (gives smaller and more + # computationally-efficient values, and is required by some standards) + # - using the Euler totient function (matching the original RSA paper) + # Allow for either here. + assert skey.d in ( + rsa.rsa_recover_private_exponent(pkey.e, skey.p, skey.q), + _rsa_recover_euler_private_exponent(pkey.e, skey.p, skey.q), + ) + assert skey.p * skey.q == pkey.n assert skey.dmp1 == rsa.rsa_crt_dmp1(skey.d, skey.p) assert skey.dmq1 == rsa.rsa_crt_dmq1(skey.d, skey.q) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index ef4ef70e25b0..9e8b71f35ded 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -138,7 +138,7 @@ def test_ttl_required_in_decrypt_at_time(self, backend): current_time=int(time.time()), ) - @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) + @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xff\x00\x80"]) def test_roundtrips(self, message, backend): f = Fernet(Fernet.generate_key(), backend=backend) assert f.decrypt(f.encrypt(message)) == message @@ -277,3 +277,34 @@ def test_rotate_decrypt_no_shared_keys(self, backend): with pytest.raises(InvalidToken): mf2.rotate(mf1.encrypt(b"abc")) + + def test_extract_timestamp_first_fernet_valid_token(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + mf1 = MultiFernet([f1]) + current_time = 1526138327 + token = mf1.encrypt_at_time(b"encrypt me", current_time) + assert mf1.extract_timestamp(token) == current_time + + def test_extract_timestamp_second_fernet_valid_token(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) + mf1 = MultiFernet([f1, f2]) + current_time = 1526138327 + token = f2.encrypt_at_time(b"encrypt me", current_time) + assert mf1.extract_timestamp(token) == current_time + + def test_extract_timestamp_invalid_token(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + mf1 = MultiFernet([f1]) + with pytest.raises(InvalidToken): + mf1.extract_timestamp(b"nonsensetoken") + with pytest.raises(InvalidToken): + mf1.extract_timestamp(b"\x80abc") + with pytest.raises(InvalidToken): + mf1.extract_timestamp(b"\x00") + with pytest.raises(InvalidToken): + mf1.extract_timestamp("nonsensetoken") + with pytest.raises(InvalidToken): + mf1.extract_timestamp("abc") + with pytest.raises(InvalidToken): + mf1.extract_timestamp("") diff --git a/tests/test_utils.py b/tests/test_utils.py index 9f6e271500cc..5e5f506f82b1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -39,6 +39,13 @@ ) +def test_int_to_bytes_rejects_zero_length(): + with pytest.raises(ValueError): + cryptography.utils.int_to_bytes(123, 0) + with pytest.raises(ValueError): + cryptography.utils.int_to_bytes(0, 0) + + def test_check_backend_support_skip(): supported = pretend.stub( kwargs={"only_if": lambda backend: False, "skip_message": "Nope"} @@ -2721,7 +2728,7 @@ def test_load_fips_ecdsa_key_pair_vectors(): { "curve": "sect233k1", "d": int( - "1da7422b50e3ff051f2aaaed10acea6cbf6110c517da2f4e" "aca8b5b87", + "1da7422b50e3ff051f2aaaed10acea6cbf6110c517da2f4eaca8b5b87", 16, ), "x": int( @@ -2738,7 +2745,7 @@ def test_load_fips_ecdsa_key_pair_vectors(): { "curve": "sect233k1", "d": int( - "530951158f7b1586978c196603c12d25607d2cb0557efadb" "23cd0ce8", + "530951158f7b1586978c196603c12d25607d2cb0557efadb23cd0ce8", 16, ), "x": int( @@ -3776,7 +3783,7 @@ def test_load_kasvs_ecdh_vectors(): ), }, "Z": int( - "b1259ceedfb663d9515089cf727e7024fb3d86cbcec611b4" "ba0b4ab6", + "b1259ceedfb663d9515089cf727e7024fb3d86cbcec611b4ba0b4ab6", 16, ), "curve": "secp224r1", @@ -4015,7 +4022,7 @@ def test_load_kasvs_ecdh_kdf_vectors(): 16, ), "Z": int( - "43f23b2c760d686fc99cc008b63aea92f866e224265af60d" "2d8ae540", + "43f23b2c760d686fc99cc008b63aea92f866e224265af60d2d8ae540", 16, ), "DKM": int("ad65fa2d12541c3a21f3cd223efb", 16), diff --git a/tests/utils.py b/tests/utils.py index 595e8dc04e1c..b9734a6dc5ac 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -620,7 +620,7 @@ def load_kasvs_ecdh_vectors(vector_data): if len(parm) == 2: names = parm[1].strip().split() for n in names: - tags.append("[%s]" % n) + tags.append(f"[{n}]") break # Sets Metadata @@ -701,6 +701,58 @@ def load_kasvs_ecdh_vectors(vector_data): return vectors +def load_rfc6979_vectors(vector_data): + """ + Loads data out of the ECDSA and DSA RFC6979 vector files. + """ + vectors = [] + keys: typing.Dict[str, typing.List[str]] = dict() + reading_key = False + current_key_name = None + + data: typing.Dict[str, object] = dict() + for line in vector_data: + line = line.strip() + + if reading_key and current_key_name: + keys[current_key_name].append(line) + if line.startswith("-----END"): + reading_key = False + current_key_name = None + + if line.startswith("PrivateKey=") or line.startswith("PublicKey="): + reading_key = True + current_key_name = line.split("=")[1].strip() + keys[current_key_name] = [] + elif line.startswith("DigestSign = "): + data["digest_sign"] = line.split("=")[1].strip() + data["deterministic_nonce"] = False + elif line.startswith("DigestVerify = "): + data["digest_verify"] = line.split("=")[1].strip() + data["verify_error"] = False + elif line.startswith("Key = "): + key_name = line.split("=")[1].strip() + assert key_name in keys + data["key"] = keys[key_name] + data["key_name"] = key_name + elif line.startswith("NonceType = "): + nonce_type = line.split("=")[1].strip() + data["deterministic_nonce"] = nonce_type == "deterministic" + elif line.startswith("Input = "): + data["input"] = line.split("=")[1].strip(' "') + elif line.startswith("Output = "): + data["output"] = line.split("=")[1].strip() + elif line.startswith("Result = "): + data["verify_error"] = line.split("=")[1].strip() == "VERIFY_ERROR" + + elif not line: + if data: + vectors.append(data) + data = {} + + return vectors + + def load_x963_vectors(vector_data): """ Loads data out of the X9.63 vector data diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index c85eb6e7a669..d3b26a2ab3ba 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -113,9 +113,8 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): digest, hashes.SHA1 ): pytest.skip( - "Invalid params for FIPS. key: {} bits, digest: {}".format( - key.key_size, digest.name - ) + f"Invalid params for FIPS. key: {key.key_size} bits, " + f"digest: {digest.name}" ) sig = key.sign( diff --git a/tests/x509/test_name.py b/tests/x509/test_name.py index 4c9ccc3b791c..a1ceffce6556 100644 --- a/tests/x509/test_name.py +++ b/tests/x509/test_name.py @@ -159,6 +159,7 @@ def test_valid(self, subtests): "2.5.4.10=abc", Name([NameAttribute(NameOID.ORGANIZATION_NAME, "abc")]), ), + ("", Name([])), ]: with subtests.test(): result = Name.from_rfc4514_string(value) diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py index 335694c7f9a9..d7723b288cf5 100644 --- a/tests/x509/test_ocsp.py +++ b/tests/x509/test_ocsp.py @@ -6,10 +6,11 @@ import base64 import datetime import os +from typing import Optional import pytest -from cryptography import x509 +from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa @@ -68,6 +69,35 @@ def _generate_root(private_key=None, algorithm=hashes.SHA256()): return cert, private_key +def _check_ocsp_response_times( + ocsp_resp: ocsp.OCSPResponse, + this_update: datetime.datetime, + next_update: Optional[datetime.datetime], + revocation_time: Optional[datetime.datetime], +) -> None: + with pytest.warns(utils.DeprecatedIn43): + assert ocsp_resp.this_update == this_update + assert ocsp_resp.this_update_utc == this_update.replace( + tzinfo=datetime.timezone.utc + ) + + with pytest.warns(utils.DeprecatedIn43): + assert ocsp_resp.next_update == next_update + assert ocsp_resp.next_update_utc == ( + next_update.replace(tzinfo=datetime.timezone.utc) + if next_update is not None + else None + ) + + with pytest.warns(utils.DeprecatedIn43): + assert ocsp_resp.revocation_time == revocation_time + assert ocsp_resp.revocation_time_utc == ( + revocation_time.replace(tzinfo=datetime.timezone.utc) + if revocation_time is not None + else None + ) + + class TestOCSPRequest: def test_bad_request(self): with pytest.raises(ValueError): @@ -78,11 +108,12 @@ def test_load_request(self): os.path.join("x509", "ocsp", "req-sha1.der"), ocsp.load_der_ocsp_request, ) + assert isinstance(req, ocsp.OCSPRequest) assert req.issuer_name_hash == ( - b"8\xcaF\x8c\x07D\x8d\xf4\x81\x96" b"\xc7mmLpQ\x9e`\xa7\xbd" + b"8\xcaF\x8c\x07D\x8d\xf4\x81\x96\xc7mmLpQ\x9e`\xa7\xbd" ) assert req.issuer_key_hash == ( - b"yu\xbb\x84:\xcb,\xdez\t\xbe1" b"\x1bC\xbc\x1c*MSX" + b"yu\xbb\x84:\xcb,\xdez\t\xbe1\x1bC\xbc\x1c*MSX" ) assert isinstance(req.hash_algorithm, hashes.SHA1) assert req.serial_number == int( @@ -633,16 +664,26 @@ def test_sign_good_cert(self): resp = builder.sign(private_key, hashes.SHA256()) assert resp.responder_name == root_cert.subject assert resp.responder_key_hash is None - assert (current_time - resp.produced_at).total_seconds() < 10 + with pytest.warns(utils.DeprecatedIn43): + assert (current_time - resp.produced_at).total_seconds() < 10 + assert ( + current_time.replace(tzinfo=datetime.timezone.utc) + - resp.produced_at_utc + ).total_seconds() < 10 assert ( resp.signature_algorithm_oid == x509.SignatureAlgorithmOID.ECDSA_WITH_SHA256 ) assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD - assert resp.revocation_time is None assert resp.revocation_reason is None - assert resp.this_update == this_update - assert resp.next_update == next_update + + _check_ocsp_response_times( + resp, + this_update=this_update, + next_update=next_update, + revocation_time=None, + ) + private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) @@ -673,10 +714,13 @@ def test_sign_revoked_cert(self): ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED - assert resp.revocation_time == revoked_date assert resp.revocation_reason is None - assert resp.this_update == this_update - assert resp.next_update == next_update + _check_ocsp_response_times( + resp, + this_update=this_update, + next_update=next_update, + revocation_time=revoked_date, + ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) @@ -706,8 +750,12 @@ def test_sign_unknown_cert(self): ) resp = builder.sign(private_key, hashes.SHA384()) assert resp.certificate_status == ocsp.OCSPCertStatus.UNKNOWN - assert resp.this_update == this_update - assert resp.next_update == next_update + _check_ocsp_response_times( + resp, + this_update=this_update, + next_update=next_update, + revocation_time=None, + ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA384()) ) @@ -765,10 +813,13 @@ def test_sign_revoked_no_next_update(self): ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED - assert resp.revocation_time == revoked_date assert resp.revocation_reason is None - assert resp.this_update == this_update - assert resp.next_update is None + _check_ocsp_response_times( + resp, + this_update=this_update, + next_update=None, + revocation_time=revoked_date, + ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) @@ -799,10 +850,13 @@ def test_sign_revoked_with_reason(self): ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED - assert resp.revocation_time == revoked_date assert resp.revocation_reason is x509.ReasonFlags.key_compromise - assert resp.this_update == this_update - assert resp.next_update == next_update + _check_ocsp_response_times( + resp, + this_update=this_update, + next_update=next_update, + revocation_time=revoked_date, + ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) @@ -1120,6 +1174,7 @@ def test_load_response(self): os.path.join("x509", "letsencryptx3.pem"), x509.load_pem_x509_certificate, ) + assert isinstance(resp, ocsp.OCSPResponse) assert resp.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL assert ( resp.signature_algorithm_oid @@ -1152,12 +1207,19 @@ def test_load_response(self): assert resp.certificates == [] assert resp.responder_key_hash is None assert resp.responder_name == issuer.subject - assert resp.produced_at == datetime.datetime(2018, 8, 30, 11, 15) + with pytest.warns(utils.DeprecatedIn43): + assert resp.produced_at == datetime.datetime(2018, 8, 30, 11, 15) + assert resp.produced_at_utc == datetime.datetime( + 2018, 8, 30, 11, 15, tzinfo=datetime.timezone.utc + ) assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD - assert resp.revocation_time is None assert resp.revocation_reason is None - assert resp.this_update == datetime.datetime(2018, 8, 30, 11, 0) - assert resp.next_update == datetime.datetime(2018, 9, 6, 11, 0) + _check_ocsp_response_times( + resp, + this_update=datetime.datetime(2018, 8, 30, 11, 0), + next_update=datetime.datetime(2018, 9, 6, 11, 0), + revocation_time=None, + ) assert resp.issuer_key_hash == ( b"\xa8Jjc\x04}\xdd\xba\xe6\xd19\xb7\xa6Ee\xef\xf3\xa8\xec\xa1" ) @@ -1177,6 +1239,7 @@ def test_load_multi_valued_response(self): with pytest.raises(ValueError): resp.serial_number + assert isinstance(next(resp.responses), ocsp.OCSPSingleResponse) assert len(list(resp.responses)) == 20 def test_multi_valued_responses(self): @@ -1212,9 +1275,20 @@ def test_multi_valued_responses(self): ) assert elem.certificate_status == ocsp.OCSPCertStatus.GOOD - - assert elem.this_update == datetime.datetime(2020, 2, 22, 0, 0) - assert elem.next_update == datetime.datetime(2020, 2, 29, 1, 0) + with pytest.warns(utils.DeprecatedIn43): + assert elem.this_update == datetime.datetime( + 2020, 2, 22, 0, 0 + ) + assert elem.this_update_utc == datetime.datetime( + 2020, 2, 22, 0, 0, tzinfo=datetime.timezone.utc + ) + with pytest.warns(utils.DeprecatedIn43): + assert elem.next_update == datetime.datetime( + 2020, 2, 29, 1, 0 + ) + assert elem.next_update_utc == datetime.datetime( + 2020, 2, 29, 1, 0, tzinfo=datetime.timezone.utc + ) elif req_revoked.serial_number == serial: assert elem.certificate_status == ocsp.OCSPCertStatus.REVOKED @@ -1222,8 +1296,12 @@ def test_multi_valued_responses(self): elem.revocation_reason == x509.ReasonFlags.cessation_of_operation ) - assert elem.revocation_time == datetime.datetime( - 2018, 5, 30, 14, 1, 39 + with pytest.warns(utils.DeprecatedIn43): + assert elem.revocation_time == datetime.datetime( + 2018, 5, 30, 14, 1, 39 + ) + assert elem.revocation_time_utc == datetime.datetime( + 2018, 5, 30, 14, 1, 39, tzinfo=datetime.timezone.utc ) def test_load_unauthorized(self): @@ -1246,18 +1324,26 @@ def test_load_unauthorized(self): resp.responder_key_hash with pytest.raises(ValueError): resp.responder_name - with pytest.raises(ValueError): + with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.produced_at with pytest.raises(ValueError): - resp.certificate_status + resp.produced_at_utc with pytest.raises(ValueError): + resp.certificate_status + with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.revocation_time with pytest.raises(ValueError): - resp.revocation_reason + resp.revocation_time_utc with pytest.raises(ValueError): + resp.revocation_reason + with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.this_update with pytest.raises(ValueError): + resp.this_update_utc + with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.next_update + with pytest.raises(ValueError): + resp.next_update_utc with pytest.raises(ValueError): resp.issuer_key_hash with pytest.raises(ValueError): @@ -1275,8 +1361,12 @@ def test_load_revoked(self): ocsp.load_der_ocsp_response, ) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED - assert resp.revocation_time == datetime.datetime( - 2016, 9, 2, 21, 28, 48 + with pytest.warns(utils.DeprecatedIn43): + assert resp.revocation_time == datetime.datetime( + 2016, 9, 2, 21, 28, 48 + ) + assert resp.revocation_time_utc == datetime.datetime( + 2016, 9, 2, 21, 28, 48, tzinfo=datetime.timezone.utc ) assert resp.revocation_reason is None @@ -1331,7 +1421,9 @@ def test_load_revoked_no_next_update(self): ocsp.load_der_ocsp_response, ) assert resp.serial_number == 16160 - assert resp.next_update is None + with pytest.warns(utils.DeprecatedIn43): + assert resp.next_update is None + assert resp.next_update_utc is None def test_response_extensions(self): resp = _load_data( @@ -1496,10 +1588,13 @@ def test_sign_ed25519(self, backend): ) resp = builder.sign(private_key, None) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED - assert resp.revocation_time == revoked_date assert resp.revocation_reason is x509.ReasonFlags.key_compromise - assert resp.this_update == this_update - assert resp.next_update == next_update + _check_ocsp_response_times( + resp, + this_update=this_update, + next_update=next_update, + revocation_time=revoked_date, + ) assert resp.signature_hash_algorithm is None assert ( resp.signature_algorithm_oid == x509.SignatureAlgorithmOID.ED25519 @@ -1539,10 +1634,13 @@ def test_sign_ed448(self, backend): ) resp = builder.sign(private_key, None) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED - assert resp.revocation_time == revoked_date assert resp.revocation_reason is x509.ReasonFlags.key_compromise - assert resp.this_update == this_update - assert resp.next_update == next_update + _check_ocsp_response_times( + resp, + this_update=this_update, + next_update=next_update, + revocation_time=revoked_date, + ) assert resp.signature_hash_algorithm is None assert resp.signature_algorithm_oid == x509.SignatureAlgorithmOID.ED448 private_key.public_key().verify( diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 1a6fc7b437cc..39f4997ad61c 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -14,7 +14,7 @@ from cryptography import utils, x509 from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm -from cryptography.hazmat.bindings._rust import asn1 +from cryptography.hazmat.bindings._rust import test_support from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dh, @@ -31,12 +31,14 @@ from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, ) +from cryptography.x509.extensions import ExtendedKeyUsage from cryptography.x509.name import _ASN1Type from cryptography.x509.oid import ( AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, + PublicKeyAlgorithmOID, SignatureAlgorithmOID, SubjectInformationAccessOID, ) @@ -792,6 +794,42 @@ def test_get_revoked_certificate_doesnt_reorder( assert crl[2].serial_number == 3 +class TestRSAECertificate: + def test_load_cert_pub_key(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "ca", "rsae_ca.pem"), + x509.load_pem_x509_certificate, + ) + assert isinstance(cert, x509.Certificate) + expected_pub_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", + ).public_key() + assert isinstance(expected_pub_key, rsa.RSAPublicKey) + pub_key = cert.public_key() + assert isinstance(pub_key, rsa.RSAPublicKey) + assert ( + cert.public_key_algorithm_oid + == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 + ) + assert pub_key == expected_pub_key + pss = cert.signature_algorithm_parameters + assert isinstance(pss, padding.PSS) + assert isinstance(pss._mgf, padding.MGF1) + assert isinstance(pss._mgf._algorithm, hashes.SHA256) + assert pss._salt_length == 0x14 + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + pub_key.verify( + cert.signature, + cert.tbs_certificate_bytes, + pss, + cert.signature_hash_algorithm, + ) + + class TestRSAPSSCertificate: def test_load_cert_pub_key(self, backend): cert = _load_cert( @@ -806,6 +844,9 @@ def test_load_cert_pub_key(self, backend): assert isinstance(expected_pub_key, rsa.RSAPublicKey) pub_key = cert.public_key() assert isinstance(pub_key, rsa.RSAPublicKey) + assert ( + cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.RSASSA_PSS + ) assert pub_key == expected_pub_key pss = cert.signature_algorithm_parameters assert isinstance(pss, padding.PSS) @@ -898,6 +939,11 @@ def test_load_pem_cert(self, backend): assert isinstance( cert.signature_algorithm_parameters, padding.PKCS1v15 ) + assert isinstance(cert.public_key(), rsa.RSAPublicKey) + assert ( + cert.public_key_algorithm_oid + == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 + ) def test_check_pkcs1_signature_algorithm_parameters(self, backend): cert = _load_cert( @@ -995,6 +1041,11 @@ def test_alternate_rsa_with_sha1_oid(self, backend): cert.signature_algorithm_oid == SignatureAlgorithmOID._RSA_WITH_SHA1 ) + assert isinstance(cert.public_key(), rsa.RSAPublicKey) + assert ( + cert.public_key_algorithm_oid + == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 + ) def test_load_bmpstring_explicittext(self, backend): cert = _load_cert( @@ -1810,6 +1861,138 @@ def test_verify_directly_issued_by_unsupported_key_type(self, backend): with pytest.raises(TypeError): cert.verify_directly_issued_by(leaf) + def test_admissions_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", + "custom", + "admissions_extension_optional_data_not_provided.pem", + ), + x509.load_pem_x509_certificate, + ) + ext = cert.extensions.get_extension_for_class(x509.Admissions) + assert ext.value == x509.Admissions( + authority=x509.DirectoryName( + value=x509.Name( + [ + x509.NameAttribute( + oid=x509.NameOID.COUNTRY_NAME, value="DE" + ), + x509.NameAttribute( + oid=x509.NameOID.ORGANIZATION_NAME, + value="Elektronisches Gesundheitsberuferegister", + ), + ] + ) + ), + admissions=[ + x509.Admission( + admission_authority=x509.RegisteredID( + value=x509.NameOID.ORGANIZATION_NAME + ), + naming_authority=x509.NamingAuthority( + id=x509.ObjectIdentifier("1.2.276.0.76.4.223"), + url="", + text="Betriebsstätte GKV-Spitzenverband", + ), + profession_infos=[ + x509.ProfessionInfo( + naming_authority=x509.NamingAuthority( + id=x509.ObjectIdentifier("1.2.276.0.76.4.225"), + url="https://example.com", + text=( + "Betriebsstätte Deutscher " + "Apothekerverband" + ), + ), + profession_items=["Ã\x84rztin/Arzt", ""], + profession_oids=[ + x509.ObjectIdentifier("1.2.276.0.76.4.30"), + x509.ObjectIdentifier("1.2.276.0.76.4.31"), + ], + registration_number="9-999/99999999", + add_profession_info=( + b'\x16"additional profession info example' + ), + ) + ], + ), + x509.Admission( + admission_authority=x509.OtherName( + type_id=x509.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + naming_authority=None, + profession_infos=[ + x509.ProfessionInfo( + naming_authority=x509.NamingAuthority( + id=x509.ObjectIdentifier("1.2.276.0.76.4.227"), + url=None, + text=( + "Betriebsstätte der Deutsche Krankenhaus " + "TrustCenter und Informationsverarbeitung " + "GmbH" + ), + ), + profession_items=["Krankenhaus"], + profession_oids=[ + x509.ObjectIdentifier("1.2.276.0.76.4.53"), + x509.ObjectIdentifier("1.2.276.0.76.4.246"), + ], + registration_number="9.9.9-99999999", + add_profession_info=None, + ), + x509.ProfessionInfo( + naming_authority=None, + profession_items=[ + "Krankenhaus", + "Betriebsstätte Geburtshilfe", + ], + profession_oids=[ + x509.ObjectIdentifier("1.2.276.0.76.4.53") + ], + registration_number="", + add_profession_info=None, + ), + ], + ), + x509.Admission( + admission_authority=None, + naming_authority=None, + profession_infos=[ + x509.ProfessionInfo( + naming_authority=None, + profession_items=[], + profession_oids=None, + registration_number=None, + add_profession_info=None, + ) + ], + ), + x509.Admission( + admission_authority=None, + naming_authority=x509.NamingAuthority(None, None, None), + profession_infos=[], + ), + x509.Admission( + admission_authority=None, + naming_authority=None, + profession_infos=[], + ), + ], + ) + + cert = _load_cert( + os.path.join( + "x509", + "custom", + "admissions_extension_authority_not_provided.pem", + ), + x509.load_pem_x509_certificate, + ) + ext = cert.extensions.get_extension_for_class(x509.Admissions) + assert ext.value == x509.Admissions(authority=None, admissions=[]) + class TestRSACertificateRequest: @pytest.mark.parametrize( @@ -1834,6 +2017,10 @@ def test_load_rsa_certificate_request(self, path, loader_func, backend): ) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) + assert ( + request.public_key_algorithm_oid + == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 + ) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ @@ -2250,6 +2437,12 @@ def test_build_cert( cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert ( + cert.public_key_algorithm_oid + == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 + ) assert cert.signature_algorithm_oid == hashalg_oid assert type(cert.signature_hash_algorithm) is hashalg _check_cert_times( @@ -2369,7 +2562,7 @@ def test_build_cert_printable_string_country_name( cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) - parsed = asn1.test_parse_certificate( + parsed = test_support.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) @@ -2555,7 +2748,7 @@ def test_extreme_times( not_valid_before=not_valid_before, not_valid_after=not_valid_after, ) - parsed = asn1.test_parse_certificate( + parsed = test_support.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) # UTC TIME @@ -3028,7 +3221,7 @@ def test_earliest_time(self, rsa_key_2048: rsa.RSAPrivateKey, backend): ) cert = cert_builder.sign(private_key, hashes.SHA256(), backend) _check_cert_times(cert, not_valid_before=time, not_valid_after=time) - parsed = asn1.test_parse_certificate( + parsed = test_support.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) # UTC TIME @@ -3308,6 +3501,9 @@ def test_build_cert_with_dsa_private_key( assert cert.version is x509.Version.v3 assert cert.signature_algorithm_oid == hashalg_oid + public_key = cert.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.DSA _check_cert_times( cert, not_valid_before=not_valid_before, @@ -3380,6 +3576,12 @@ def test_build_cert_with_ec_private_key( cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 + public_key = cert.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + assert ( + cert.public_key_algorithm_oid + == PublicKeyAlgorithmOID.EC_PUBLIC_KEY + ) assert cert.signature_algorithm_oid == hashalg_oid assert type(cert.signature_hash_algorithm) is hashalg _check_cert_times( @@ -3480,6 +3682,7 @@ def test_build_cert_with_ed25519(self, backend): assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 assert cert.signature_hash_algorithm is None assert isinstance(cert.public_key(), ed25519.Ed25519PublicKey) + assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED25519 assert cert.version is x509.Version.v3 _check_cert_times( cert, @@ -3542,6 +3745,7 @@ def test_build_cert_with_public_ed25519_rsa_sig( ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), ed25519.Ed25519PublicKey) + assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED25519 @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), @@ -3583,6 +3787,7 @@ def test_build_cert_with_ed448(self, backend): assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED448 assert cert.signature_hash_algorithm is None assert isinstance(cert.public_key(), ed448.Ed448PublicKey) + assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED448 assert cert.version is x509.Version.v3 _check_cert_times( cert, @@ -3645,6 +3850,7 @@ def test_build_cert_with_public_ed448_rsa_sig( ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), ed448.Ed448PublicKey) + assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED448 @pytest.mark.supported( only_if=lambda backend: ( @@ -3653,10 +3859,18 @@ def test_build_cert_with_public_ed448_rsa_sig( skip_message="Requires OpenSSL with x25519 & x448 support", ) @pytest.mark.parametrize( - ("priv_key_cls", "pub_key_cls"), + ("priv_key_cls", "pub_key_cls", "pub_key_oid"), [ - (x25519.X25519PrivateKey, x25519.X25519PublicKey), - (x448.X448PrivateKey, x448.X448PublicKey), + ( + x25519.X25519PrivateKey, + x25519.X25519PublicKey, + PublicKeyAlgorithmOID.X25519, + ), + ( + x448.X448PrivateKey, + x448.X448PublicKey, + PublicKeyAlgorithmOID.X448, + ), ], ) def test_build_cert_with_public_x25519_x448_rsa_sig( @@ -3664,6 +3878,7 @@ def test_build_cert_with_public_x25519_x448_rsa_sig( rsa_key_2048: rsa.RSAPrivateKey, priv_key_cls, pub_key_cls, + pub_key_oid, backend, ): issuer_private_key = rsa_key_2048 @@ -3699,6 +3914,7 @@ def test_build_cert_with_public_x25519_x448_rsa_sig( ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), pub_key_cls) + assert cert.public_key_algorithm_oid == pub_key_oid def test_build_cert_with_rsa_key_too_small( self, rsa_key_512: rsa.RSAPrivateKey, backend @@ -5650,6 +5866,15 @@ def test_bad_time_in_validity(self, backend): x509.load_pem_x509_certificate, ) + def test_invalid_empty_eku(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "empty-eku.pem"), + x509.load_pem_x509_certificate, + ) + + with pytest.raises(ValueError, match="InvalidSize"): + cert.extensions.get_extension_for_class(ExtendedKeyUsage) + class TestNameAttribute: EXPECTED_TYPES: typing.ClassVar[ @@ -5726,13 +5951,20 @@ def test_init_none_value(self): None, # type:ignore[arg-type] ) - def test_init_bad_country_code_value(self): + def test_init_bad_length(self): with pytest.raises(ValueError): x509.NameAttribute(NameOID.COUNTRY_NAME, "United States") # unicode string of length 2, but > 2 bytes with pytest.raises(ValueError): - x509.NameAttribute(NameOID.COUNTRY_NAME, "\U0001F37A\U0001F37A") + x509.NameAttribute(NameOID.COUNTRY_NAME, "\U0001f37a\U0001f37a") + + with pytest.raises(ValueError): + x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, "Too Long") + with pytest.raises(ValueError): + x509.NameAttribute(NameOID.COMMON_NAME, "Too Long" * 10) + with pytest.raises(ValueError): + x509.NameAttribute(NameOID.COMMON_NAME, "") def test_invalid_type(self): with pytest.raises(TypeError): @@ -5802,6 +6034,9 @@ def test_distinguished_name_custom_attrs(self): {NameOID.COMMON_NAME: "CommonName", NameOID.EMAIL_ADDRESS: "E"} ) == ("CommonName=Santa Claus,E=santa@north.pole") + def test_empty_name(self): + assert x509.Name([]).rfc4514_string() == "" + def test_empty_value(self): na = x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "") assert na.rfc4514_string() == r"ST=" @@ -5953,10 +6188,11 @@ def test_valid(self): x509.ObjectIdentifier("1.39.999") x509.ObjectIdentifier("2.5.29.3") x509.ObjectIdentifier("2.999.37.5.22.8") + x509.ObjectIdentifier(f"2.25.{2**128 - 1}") def test_oid_arc_too_large(self): with pytest.raises(ValueError): - x509.ObjectIdentifier(f"2.25.{2**128 - 1}") + x509.ObjectIdentifier(f"2.25.{2**128}") class TestName: @@ -6169,6 +6405,7 @@ def test_load_pem_cert(self, backend): # self-signed, so this will work public_key = cert.public_key() assert isinstance(public_key, ed25519.Ed25519PublicKey) + assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED25519 public_key.verify(cert.signature, cert.tbs_certificate_bytes) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 9579446940964433301 @@ -6215,6 +6452,7 @@ def test_load_pem_cert(self, backend): # self-signed, so this will work public_key = cert.public_key() assert isinstance(public_key, ed448.Ed448PublicKey) + assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED448 public_key.verify(cert.signature, cert.tbs_certificate_bytes) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 448 @@ -6587,6 +6825,14 @@ def test_no_attributes(self, backend): ) assert len(request.attributes) == 0 + def test_zero_element_attribute(self): + request = _load_cert( + os.path.join("x509", "requests", "zero-element-attribute.pem"), + x509.load_pem_x509_csr, + ) + with pytest.raises(ValueError, match="Only single-valued"): + request.attributes + def test_load_pem_x509_certificates(): with pytest.raises(ValueError): diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py index fc3e3e06f00e..f1a32b83c09a 100644 --- a/tests/x509/test_x509_ext.py +++ b/tests/x509/test_x509_ext.py @@ -444,6 +444,26 @@ def test_public_bytes(self): ext = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) assert ext.public_bytes() == b"\x18\x0f20150101010100Z" + def test_timezone_aware_api(self): + naive_date = datetime.datetime(2015, 1, 1, 1, 1) + ext_naive = x509.InvalidityDate(invalidity_date=naive_date) + assert ext_naive.invalidity_date_utc == datetime.datetime( + 2015, 1, 1, 1, 1, tzinfo=datetime.timezone.utc + ) + + tz_aware_date = datetime.datetime( + 2015, + 1, + 1, + 1, + 1, + tzinfo=datetime.timezone(datetime.timedelta(hours=-8)), + ) + ext_aware = x509.InvalidityDate(invalidity_date=tz_aware_date) + assert ext_aware.invalidity_date_utc == datetime.datetime( + 2015, 1, 1, 9, 1, tzinfo=datetime.timezone.utc + ) + class TestNoticeReference: def test_notice_numbers_not_all_int(self): @@ -2304,6 +2324,14 @@ def test_uri(self, backend): x509.UniformResourceIdentifier("http://path.to.root/root.crt"), ] + def test_malformed(self): + cert = _load_cert( + os.path.join("x509", "custom", "malformed-ian.pem"), + x509.load_pem_x509_certificate, + ) + with pytest.raises(ValueError, match="issuer_alternative_name"): + cert.extensions + class TestCRLNumber: def test_eq(self): @@ -2500,7 +2528,7 @@ def test_uri(self, backend): assert ext is not None uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) assert uri == [ - "gopher://xn--80ato2c.cryptography:70/path?q=s#hel" "lo", + "gopher://xn--80ato2c.cryptography:70/path?q=s#hello", "http://someregulardomain.com", ] @@ -2689,6 +2717,14 @@ def test_certbuilder(self, rsa_key_2048: rsa.RSAPrivateKey, backend): ] assert result == sans + def test_malformed(self): + cert = _load_cert( + os.path.join("x509", "custom", "malformed-san.pem"), + x509.load_pem_x509_certificate, + ) + with pytest.raises(ValueError, match="subject_alternative_name"): + cert.extensions + class TestExtendedKeyUsageExtension: def test_eku(self, backend): @@ -5360,7 +5396,6 @@ def test_vectors(self, filename, expected, backend): (TypeError, False, False, "notabool", False, None, None, None), (TypeError, False, False, False, "notabool", None, None, None), (ValueError, True, True, False, False, None, None, None), - (ValueError, False, False, True, True, None, None, None), (ValueError, False, False, False, False, None, None, None), ], ) @@ -5994,11 +6029,11 @@ def test_simple(self, backend): == x509.certificate_transparency.SignatureAlgorithm.ECDSA ) assert sct.signature == ( - b"\x30\x45\x02\x21\x00\xB8\x03\xAD\x34\xF6\xFC\x0F\x2C\xFF\x84\xA0" - b"\x86\xE5\xD7\xCF\x5A\xF0\x0A\x07\x62\x6A\x7F\xB3\xA6\x44\x64\xF1" - b"\x95\xA4\x48\x45\x11\x02\x20\x2F\x61\x8D\x53\x1B\x6F\x4A\xB8\x0A" - b"\x67\xB2\x07\xE1\x8F\x6D\xAD\xD1\x04\x4A\x5E\xB3\x89\xEF\x7C\x60" - b"\xC2\x68\x53\xF9\x3D\x1F\x6D" + b"\x30\x45\x02\x21\x00\xb8\x03\xad\x34\xf6\xfc\x0f\x2c\xff\x84\xa0" + b"\x86\xe5\xd7\xcf\x5a\xf0\x0a\x07\x62\x6a\x7f\xb3\xa6\x44\x64\xf1" + b"\x95\xa4\x48\x45\x11\x02\x20\x2f\x61\x8d\x53\x1b\x6f\x4a\xb8\x0a" + b"\x67\xb2\x07\xe1\x8f\x6d\xad\xd1\x04\x4a\x5e\xb3\x89\xef\x7c\x60" + b"\xc2\x68\x53\xf9\x3d\x1f\x6d" ) assert sct.extension_bytes == b"" @@ -6296,6 +6331,947 @@ def test_public_bytes(self): ) +class TestNamingAuthority: + def test_invalid_init(self): + with pytest.raises(TypeError): + x509.NamingAuthority( + 42, # type:ignore[arg-type] + None, + None, + ) + with pytest.raises(TypeError): + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), + 42, # type:ignore[arg-type] + None, + ) + with pytest.raises(TypeError): + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), + "https://example.com", + 42, # type:ignore[arg-type] + ) + + def test_eq(self): + authority1 = x509.NamingAuthority(None, None, None) + authority2 = x509.NamingAuthority(None, None, None) + assert authority1 == authority2 + + authority1 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ) + authority2 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ) + assert authority1 == authority2 + + def test_ne(self): + authority1 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ) + authority2 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), None, None + ) + authority3 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", None + ) + authority4 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), None, "spam" + ) + authority5 = x509.NamingAuthority(None, "https://example.com", "spam") + authority6 = x509.NamingAuthority(None, None, "spam") + authority7 = x509.NamingAuthority(None, "https://example.com", None) + authority8 = x509.NamingAuthority(None, None, None) + assert authority1 != authority2 + assert authority1 != authority3 + assert authority1 != authority4 + assert authority1 != authority5 + assert authority1 != authority6 + assert authority1 != authority7 + assert authority1 != authority8 + assert authority1 != object() + + def test_repr(self): + authority = x509.NamingAuthority(None, None, None) + assert repr(authority) == ( + "" + ) + + authority = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ) + assert repr(authority) == ( + ", " + "url=https://example.com, text=spam)>" + ) + + def test_hash(self): + authority1 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ) + authority2 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ) + authority3 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), None, None + ) + authority4 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", None + ) + authority5 = x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), None, "spam" + ) + authority6 = x509.NamingAuthority(None, "https://example.com", "spam") + authority7 = x509.NamingAuthority(None, None, "spam") + authority8 = x509.NamingAuthority(None, "https://example.com", None) + authority9 = x509.NamingAuthority(None, None, None) + + assert hash(authority1) == hash(authority2) + assert hash(authority1) != hash(authority3) + assert hash(authority1) != hash(authority4) + assert hash(authority1) != hash(authority5) + assert hash(authority1) != hash(authority6) + assert hash(authority1) != hash(authority7) + assert hash(authority1) != hash(authority8) + assert hash(authority1) != hash(authority9) + + +class TestProfessionInfo: + def test_invalid_init(self): + with pytest.raises(TypeError): + x509.ProfessionInfo( + None, + None, # type:ignore[arg-type] + None, + None, + None, + ) + with pytest.raises(TypeError): + x509.ProfessionInfo( + "spam", # type:ignore[arg-type] + [], + [], + None, + None, + ) + with pytest.raises(TypeError): + x509.ProfessionInfo( + None, + [42], # type:ignore[list-item] + [], + None, + None, + ) + with pytest.raises(TypeError): + x509.ProfessionInfo( + None, + [], + "spam", # type:ignore[arg-type] + None, + None, + ) + with pytest.raises(TypeError): + x509.ProfessionInfo( + None, + [], + [], + 42, # type:ignore[arg-type] + None, + ) + with pytest.raises(TypeError): + x509.ProfessionInfo( + None, + [], + [], + None, + 42, # type:ignore[arg-type] + ) + + def test_eq(self): + info1 = x509.ProfessionInfo(None, [], [], None, None) + info2 = x509.ProfessionInfo(None, [], [], None, None) + assert info1 == info2 + + info1 = x509.ProfessionInfo(None, [], None, None, None) + info2 = x509.ProfessionInfo(None, [], None, None, None) + assert info1 == info2 + + info1 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + info2 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + assert info1 == info2 + + def test_ne(self): + info1 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + info2 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + None, + ) + info3 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + None, + None, + ) + info4 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [], + None, + None, + ) + info5 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [], + [], + None, + None, + ) + info6 = x509.ProfessionInfo(None, ["spam"], [], None, None) + info7 = x509.ProfessionInfo( + None, [], [x509.ObjectIdentifier("1.2.3")], None, None + ) + info8 = x509.ProfessionInfo(None, [], [], "spam", None) + info9 = x509.ProfessionInfo(None, [], [], None, b"\x01\x02\x03") + info10 = x509.ProfessionInfo(None, [], [], None, None) + info11 = x509.ProfessionInfo(None, [], None, None, None) + + assert info1 != info2 + assert info1 != info2 + assert info1 != info3 + assert info1 != info4 + assert info1 != info5 + assert info1 != info6 + assert info1 != info7 + assert info1 != info8 + assert info1 != info9 + assert info1 != info10 + assert info1 != info11 + assert info1 != object() + + def test_repr(self): + info = x509.ProfessionInfo(None, [], [], None, None) + assert repr(info) == ( + "" + ) + + info = x509.ProfessionInfo(None, [], None, None, None) + assert repr(info) == ( + "" + ) + + info = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + assert repr(info) == ( + ", " + "url=https://example.com, text=spam)>, " + "profession_items=['spam'], " + "profession_oids=" + "[], " + "registration_number=eggs, " + "add_profession_info=b'\\x01\\x02\\x03')>" + ) + + def test_hash(self): + info1 = x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + info2 = x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + info3 = x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + ["spam"], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + info4 = x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), + [], + [x509.ObjectIdentifier("1.2.3.4")], + "eggs", + b"\x01\x02\x03", + ) + info5 = x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), + [], + [], + "eggs", + b"\x01\x02\x03", + ) + info6 = x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), + [], + [], + None, + b"\x01\x02\x03", + ) + info7 = x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), [], [], None, None + ) + info8 = x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), [], None, None, None + ) + info9 = x509.ProfessionInfo(None, [], None, None, None) + + assert hash(info1) == hash(info2) + assert hash(info1) != hash(info3) + assert hash(info1) != hash(info4) + assert hash(info1) != hash(info5) + assert hash(info1) != hash(info6) + assert hash(info1) != hash(info7) + assert hash(info1) != hash(info8) + assert hash(info1) != hash(info9) + + +class TestAdmission: + def test_invalid_init(self): + with pytest.raises(TypeError): + x509.Admission( + 42, # type:ignore[arg-type] + None, + [], + ) + with pytest.raises(TypeError): + x509.Admission( + None, + 42, # type:ignore[arg-type] + [], + ) + with pytest.raises(TypeError): + x509.Admission( + None, + None, + 42, # type:ignore[arg-type] + ) + with pytest.raises(TypeError): + x509.Admission( + None, + None, + [42], # type:ignore[list-item] + ) + + def test_eq(self): + admission1 = x509.Admission(None, None, []) + admission2 = x509.Admission(None, None, []) + assert admission1 == admission2 + + admission1 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission2 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + assert admission1 == admission2 + + def test_ne(self): + admission1 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission2 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [], + ) + admission3 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + None, + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission4 = x509.Admission( + None, + None, + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission5 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + None, + [], + ) + admission6 = x509.Admission( + None, + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [], + ) + admission7 = x509.Admission(None, None, []) + + assert admission1 != admission2 + assert admission1 != admission3 + assert admission1 != admission4 + assert admission1 != admission5 + assert admission1 != admission6 + assert admission1 != admission7 + assert admission1 != object() + + def test_repr(self): + admission = x509.Admission(None, None, []) + assert repr(admission) == ( + "" + ) + + admission = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + assert repr(admission) == ( + ", " + "value=b'\\x04\\x04\\x13\\x02DE')>, " + "naming_authority=, " + "url=https://example.com, text=spam)>, " + "profession_infos=[, " + "url=https://example.org, text=eggs)>, " + "profession_items=['bacon'], " + "profession_oids=[], " + "registration_number=sausage, " + "add_profession_info=b'\\x01\\x02\\x03')>])>" + ) + + def test_hash(self): + admission1 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission2 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission3 = x509.Admission( + x509.UniformResourceIdentifier(value="https://www.example.de"), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission4 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority(None, None, None), + [ + x509.ProfessionInfo( + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3.4"), + "https://example.org", + "eggs", + ), + ["bacon"], + [x509.ObjectIdentifier("1.2.3.4.5")], + "sausage", + b"\x01\x02\x03", + ) + ], + ) + admission5 = x509.Admission( + x509.OtherName( + type_id=x509.oid.NameOID.COUNTRY_NAME, + value=b"\x04\x04\x13\x02DE", + ), + x509.NamingAuthority( + x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam" + ), + [], + ) + admission6 = x509.Admission(None, None, []) + + assert hash(admission1) == hash(admission2) + assert hash(admission1) != hash(admission3) + assert hash(admission1) != hash(admission4) + assert hash(admission1) != hash(admission5) + assert hash(admission1) != hash(admission6) + + +class TestAdmissions: + def test_invalid_init(self): + with pytest.raises(TypeError): + x509.Admissions( + 42, # type:ignore[arg-type] + [], + ) + with pytest.raises(TypeError): + x509.Admissions( + None, + 42, # type:ignore[arg-type] + ) + with pytest.raises(TypeError): + x509.Admissions( + None, + [42], # type:ignore[list-item] + ) + with pytest.raises(TypeError): + x509.Admissions( + None, + [None], # type:ignore[list-item] + ) + + def test_eq(self): + admissions1 = x509.Admissions(None, []) + admissions2 = x509.Admissions(None, []) + assert admissions1 == admissions2 + + admissions1 = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), + [x509.Admission(None, None, [])], + ) + admissions2 = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), + [x509.Admission(None, None, [])], + ) + assert admissions1 == admissions2 + + def test_ne(self): + admissions1 = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), + [x509.Admission(None, None, [])], + ) + admissions2 = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), [] + ) + admissions3 = x509.Admissions( + None, + [x509.Admission(None, None, [])], + ) + admissions4 = x509.Admissions(None, []) + + assert admissions1 != admissions2 + assert admissions1 != admissions3 + assert admissions1 != admissions4 + assert admissions1 != object() + + def test_repr(self): + admissions = x509.Admissions(None, []) + assert repr(admissions) == ( + "" + ) + + admissions = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), + [x509.Admission(None, None, [])], + ) + assert repr(admissions) == ( + ", " + "admissions=[])>" + ) + + def test_hash(self): + admissions1 = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), + [x509.Admission(None, None, [])], + ) + admissions2 = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), + [x509.Admission(None, None, [])], + ) + admissions3 = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.de"), [] + ) + admissions4 = x509.Admissions( + None, + [x509.Admission(None, None, [])], + ) + admissions5 = x509.Admissions(None, []) + assert hash(admissions1) == hash(admissions2) + assert hash(admissions1) != hash(admissions3) + assert hash(admissions1) != hash(admissions4) + assert hash(admissions1) != hash(admissions5) + + def test_public_bytes(self): + ext = x509.Admissions(None, []) + assert ext.public_bytes() == b"0\x020\x00" + + ext = x509.Admissions( + x509.UniformResourceIdentifier(value="https://www.example.com/"), + [], + ) + assert ( + ext.public_bytes() == b"0\x1c\x86\x18https://www.example.com/0\x00" + ) + + # test for encoding none values + ext = x509.Admissions( + None, + [ + x509.Admission( + None, + x509.NamingAuthority(None, None, None), + [x509.ProfessionInfo(None, [], [], None, None)], + ), + x509.Admission( + None, + None, + [ + x509.ProfessionInfo( + x509.NamingAuthority(None, None, None), + [], + [], + None, + None, + ) + ], + ), + ], + ) + assert ext.public_bytes() == ( + b"0\x1e0\x1c0\x0c\xa1\x020\x000\x060\x040\x000\x000\x0c0\n0\x08\xa0\x020\x000\x000\x00" + ) + + # example values taken from https://gemspec.gematik.de/downloads/gemSpec/gemSpec_OID/gemSpec_OID_V3.17.0.pdf + ext = x509.Admissions( + authority=x509.DirectoryName( + value=x509.Name( + [ + x509.NameAttribute( + x509.oid.NameOID.COUNTRY_NAME, "DE" + ), + x509.NameAttribute( + x509.NameOID.ORGANIZATIONAL_UNIT_NAME, + "Elektronisches Gesundheitsberuferegister", + ), + ] + ) + ), + admissions=[ + x509.Admission( + admission_authority=x509.DNSName("gematik.de"), + naming_authority=x509.NamingAuthority( + x509.ObjectIdentifier("1.2.276.0.76.3.1.91"), + "https://gematik.de/", + ( + "Gesellschaft für Telematikanwendungen " + "der Gesundheitskarte mbH" + ), + ), + profession_infos=[ + x509.ProfessionInfo( + naming_authority=x509.NamingAuthority( + x509.ObjectIdentifier("1.2.276.0.76.3.1.1"), + "https://www.kbv.de/", + "KBV Kassenärztliche Bundesvereinigung", + ), + registration_number="123456789", + profession_items=[ + "Ärztin/Arzt", + ( + "Orthopädieschuhmacher/-in " + "und Orthopädietechniker/-in" + ), + ], + profession_oids=[ + x509.ObjectIdentifier("1.2.276.0.76.4.30"), + x509.ObjectIdentifier("1.2.276.0.76.4.305"), + ], + # DER-encoded: + # `OtherName( + # type_id=ObjectIdentifier('1.2.276.0.76.4.60'), + # value=b'\x0c\x1dProbe-Client Broker-Betreiber' + # )` + add_profession_info=( + b"\xa0*\x06\x07*\x82\x14\x00L\x04<\xa0\x1f" + b"\x0c\x1dProbe-Client Broker-Betreiber" + ), + ) + ], + ), + ], + ) + assert ext.public_bytes() == ( + b"0\x82\x01\xa6\xa4B0@1\x0b0\t\x06\x03U\x04\x06\x13\x02DE110/\x06" + b"\x03U\x04\x0b\x0c(Elektronisches Gesundheitsberuferegister0\x82" + b"\x01^0\x82\x01Z\xa0\x0c\x82\ngematik.de\xa1b0`\x06\x08*\x82\x14" + b"\x00L\x03\x01[\x16\x13https://gematik.de/\x0c?Gesellschaft f\xc3" + b"\xbcr Telematikanwendungen der Gesundheitskarte mbH0\x81\xe50" + b"\x81\xe2\xa0I0G\x06\x08*\x82\x14\x00L\x03\x01\x01\x16\x13https://www." + b"kbv.de/\x0c&KBV Kassen\xc3\xa4rztliche Bundesvereinigung0G\x0c" + b"\x0c\xc3\x84rztin/Arzt\x0c7Orthop\xc3\xa4dieschuhmacher/-in und " + b"Orthop\xc3\xa4dietechniker/-in0\x13\x06\x07*\x82\x14\x00L\x04\x1e" + b"\x06\x08*\x82\x14\x00L\x04\x821\x13\t123456789\x04,\xa0*\x06" + b"\x07*\x82\x14\x00L\x04<\xa0\x1f\x0c\x1dProbe-Client Broker-" + b"Betreiber" + ) + + # test for non-ascii url value in naming authority + ext = x509.Admissions( + None, + [ + x509.Admission( + None, + x509.NamingAuthority(None, "😄", None), + [], + ), + ], + ) + with pytest.raises(ValueError): + ext.public_bytes() + + # test for non-ascii registration number value in profession info + ext = x509.Admissions( + None, + [ + x509.Admission( + None, + None, + [x509.ProfessionInfo(None, [], [], "\x00", None)], + ), + ], + ) + with pytest.raises(ValueError): + ext.public_bytes() + + # test that none passed for `profession_oids` is encoded as none + ext = x509.Admissions( + None, + [ + x509.Admission( + None, + None, + [x509.ProfessionInfo(None, [], None, None, None)], + ), + ], + ) + assert ext.public_bytes() == b"0\n0\x080\x060\x040\x020\x00" + + def test_all_extension_oid_members_have_names_defined(): for oid in dir(ExtensionOID): if oid.startswith("__"): diff --git a/tests/x509/verification/test_limbo.py b/tests/x509/verification/test_limbo.py index 194b64f1f0bd..d0402c4ce30a 100644 --- a/tests/x509/verification/test_limbo.py +++ b/tests/x509/verification/test_limbo.py @@ -12,7 +12,9 @@ from cryptography import x509 from cryptography.x509 import load_pem_x509_certificate from cryptography.x509.verification import ( + ClientVerifier, PolicyBuilder, + ServerVerifier, Store, VerificationError, ) @@ -27,7 +29,10 @@ # 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 + # Most CABF validators do not enforce the CABF key requirements on + # subscriber keys (i.e., in the leaf certificate). + "pedantic-webpki-subscriber-key", + # 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 @@ -62,6 +67,15 @@ # 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 allow root CAs where the AKI contains fields other than keyIdentifier, + # which is technically forbidden under CABF. No other implementations + # enforce this requirement. + "webpki::aki::root-with-aki-authoritycertissuer", + "webpki::aki::root-with-aki-authoritycertserialnumber", + "webpki::aki::root-with-aki-all-fields", + # We allow RSA keys that aren't divisible by 8, which is technically + # forbidden under CABF. No other implementation checks this either. + "webpki::forbidden-rsa-not-divisable-by-8-in-root", # 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. @@ -72,27 +86,26 @@ def _get_limbo_peer(expected_peer): kind = expected_peer["kind"] - assert kind in ("DNS", "IP") + assert kind in ("DNS", "IP", "RFC822") value = expected_peer["value"] if kind == "DNS": return x509.DNSName(value) - else: + elif kind == "IP": return x509.IPAddress(ipaddress.ip_address(value)) + else: + return x509.RFC822Name(value) def _limbo_testcase(id_, testcase): if id_ in LIMBO_SKIP_TESTCASES: - return + pytest.skip(f"explicitly skipped testcase: {id_}") features = testcase["features"] - if LIMBO_UNSUPPORTED_FEATURES.intersection(features): - return - assert testcase["validation_kind"] == "SERVER" + unsupported = LIMBO_UNSUPPORTED_FEATURES.intersection(features) + if unsupported: + pytest.skip(f"explicitly skipped features: {unsupported}") + 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()) @@ -105,7 +118,6 @@ def _limbo_testcase(id_, testcase): 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) @@ -121,12 +133,39 @@ def _limbo_testcase(id_, testcase): if max_chain_depth is not None: builder = builder.max_chain_depth(max_chain_depth) - verifier = builder.build_server_verifier(peer_name) + verifier: ServerVerifier | ClientVerifier + if testcase["validation_kind"] == "SERVER": + assert testcase["extended_key_usage"] == [] or testcase[ + "extended_key_usage" + ] == ["serverAuth"] + peer_name = _get_limbo_peer(testcase["expected_peer_name"]) + # Some tests exercise invalid leaf SANs, which get caught before + # validation even begins. + try: + verifier = builder.build_server_verifier(peer_name) + except ValueError: + assert not should_pass + return + else: + assert testcase["extended_key_usage"] == ["clientAuth"] + verifier = builder.build_client_verifier() if should_pass: - built_chain = verifier.verify( - peer_certificate, untrusted_intermediates - ) + if isinstance(verifier, ServerVerifier): + built_chain = verifier.verify( + peer_certificate, untrusted_intermediates + ) + else: + verified_client = verifier.verify( + peer_certificate, untrusted_intermediates + ) + + expected_subjects = [ + _get_limbo_peer(p) for p in testcase["expected_peer_names"] + ] + assert expected_subjects == verified_client.subjects + + built_chain = verified_client.chain # Assert that the verifier returns chains in [EE, ..., TA] order. assert built_chain[0] == peer_certificate diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py index 8c2be7054227..879f41c3eb77 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -11,7 +11,11 @@ from cryptography import x509 from cryptography.x509.general_name import DNSName, IPAddress -from cryptography.x509.verification import PolicyBuilder, Store +from cryptography.x509.verification import ( + PolicyBuilder, + Store, + VerificationError, +) from tests.x509.test_x509 import _load_cert @@ -105,6 +109,68 @@ def test_store_rejects_non_certificates(self): Store(["not a cert"]) # type: ignore[list-item] +class TestClientVerifier: + def test_build_client_verifier_missing_store(self): + with pytest.raises( + ValueError, match="A client verifier must have a trust store" + ): + PolicyBuilder().build_client_verifier() + + def test_verify(self): + # 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]) + + validation_time = datetime.datetime.fromisoformat( + "2018-11-16T00:00:00+00:00" + ) + builder = PolicyBuilder().store(store) + builder = builder.time(validation_time).max_chain_depth(16) + verifier = builder.build_client_verifier() + + assert verifier.validation_time == validation_time.replace(tzinfo=None) + assert verifier.max_chain_depth == 16 + assert verifier.store is store + + verified_client = verifier.verify(leaf, []) + assert verified_client.chain == [leaf] + + assert verified_client.subjects is not None + assert x509.DNSName("www.cryptography.io") in verified_client.subjects + assert x509.DNSName("cryptography.io") in verified_client.subjects + assert len(verified_client.subjects) == 2 + + def test_verify_fails_renders_oid(self): + leaf = _load_cert( + os.path.join("x509", "custom", "ekucrit-testuser-cert.pem"), + x509.load_pem_x509_certificate, + ) + + store = Store([leaf]) + + validation_time = datetime.datetime.fromisoformat( + "2024-06-26T00:00:00+00:00" + ) + + builder = PolicyBuilder().store(store) + builder = builder.time(validation_time) + verifier = builder.build_client_verifier() + + pattern = ( + r"invalid extension: 2\.5\.29\.37: " + r"Certificate extension has incorrect criticality" + ) + with pytest.raises( + VerificationError, + match=pattern, + ): + verifier.verify(leaf, []) + + class TestServerVerifier: @pytest.mark.parametrize( ("validation_time", "valid"), @@ -138,3 +204,21 @@ def test_verify_tz_aware(self, validation_time, valid): match="cert is not valid at validation time", ): verifier.verify(leaf, []) + + def test_error_message(self): + # 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) + verifier = builder.build_server_verifier(DNSName("cryptography.io")) + + with pytest.raises( + x509.verification.VerificationError, + match=r"", + ): + verifier.verify(leaf, []) diff --git a/vectors/README.rst b/vectors/README.rst new file mode 100644 index 000000000000..e4e9191d4ec4 --- /dev/null +++ b/vectors/README.rst @@ -0,0 +1,5 @@ +pyca/cryptography vectors +========================= + +This package contains test vectors which are used in ``pyca/cryptography``'s +tests. diff --git a/vectors/cryptography_vectors/KDF/argon2id.txt b/vectors/cryptography_vectors/KDF/argon2id.txt new file mode 100644 index 000000000000..035e2a53ceb0 --- /dev/null +++ b/vectors/cryptography_vectors/KDF/argon2id.txt @@ -0,0 +1,62 @@ +# Test vectors from RFC 9106, +# https://github.com/openssl/openssl/blob/01f4b44e075a796d62d3b007a80c5c04d0e77bfb/test/recipes/30-test_evp_data/evpkdf_argon2.txt +# and the argon2 CLI tool. Adapted for the pyca/cryptography NIST loaders + +COUNT = 0 +length = 32 +lanes = 4 +iter = 3 +memcost = 32 +secret = 0303030303030303 +pass = 0101010101010101010101010101010101010101010101010101010101010101 +salt = 02020202020202020202020202020202 +ad = 040404040404040404040404 +output = 0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659 + +COUNT = 1 +length = 32 +lanes = 4 +iter = 3 +memcost = 32 +pass = +salt = 02020202020202020202020202020202 +output = 0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a + +COUNT = 2 +length = 32 +lanes = 4 +iter = 3 +memcost = 32 +pass = 0101010101010101010101010101010101010101010101010101010101010101 +salt = 02020202020202020202020202020202 +output = 03aab965c12001c9d7d0d2de33192c0494b684bb148196d73c1df1acaf6d0c2e + +# echo -n "password" | argon2 pycasalt -id -t 1 -k 131072 -p 2 -l 64 +COUNT = 3 +length = 64 +lanes = 2 +iter = 1 +memcost = 131072 +salt = 7079636173616c74 +pass = 70617373776f7264 +output = e9e42714a15947f6ce1fdabbb667dfc9fd1af7c473f021cc3402506bfa7750533f33aa44e3aebcf336680f4a2bdc371758574ad48470f05a9ee2ffd70c150b4c + +# echo -n "password" | argon2 pycasalt -id -t 4 -k 50 -p 4 -l 8 +COUNT = 4 +length = 8 +lanes = 4 +iter = 4 +memcost = 50 +salt = 7079636173616c74 +pass = 70617373776f7264 +output = e469b777841e543f + +# echo -n "password" | argon2 pycasalt -id -t 1 -k 8 -p 1 -l 4 +COUNT = 5 +length = 4 +lanes = 1 +iter = 1 +memcost = 8 +salt = 7079636173616c74 +pass = 70617373776f7264 +output = 009c7809 \ No newline at end of file diff --git a/vectors/cryptography_vectors/__about__.py b/vectors/cryptography_vectors/__about__.py index 6040ee84583e..44a2c76a2caf 100644 --- a/vectors/cryptography_vectors/__about__.py +++ b/vectors/cryptography_vectors/__about__.py @@ -6,4 +6,4 @@ "__version__", ] -__version__ = "42.0.0" +__version__ = "44.0.1" diff --git a/vectors/cryptography_vectors/asymmetric/DER_Serialization/testrsa.der b/vectors/cryptography_vectors/asymmetric/DER_Serialization/testrsa.der index 79cc1cec0735..4902784ce13d 100644 Binary files a/vectors/cryptography_vectors/asymmetric/DER_Serialization/testrsa.der and b/vectors/cryptography_vectors/asymmetric/DER_Serialization/testrsa.der differ diff --git a/vectors/cryptography_vectors/asymmetric/DH/dhpub_cryptography_old.pem b/vectors/cryptography_vectors/asymmetric/DH/dhpub_cryptography_old.pem new file mode 100644 index 000000000000..22f9caaa13e0 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/DH/dhpub_cryptography_old.pem @@ -0,0 +1,15 @@ +-----BEGIN PUBLIC KEY----- +MIICJTCCARcGCSqGSIb3DQEDATCCAQgCggEBAP//////////yQ/aoiFowjTExmKL +gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt +bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR +7ORbPcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkH +cJaWbWcMNU5KvJgE8XRsCMoYIXwykF5GLjbOO+OedywYDoY DmyeDouwHoo+1xV3w +b0xSyd4ry/aVWBcYOZVJfOqVauUV0iYYmPoFEBVyjlqKrKpo//////////8CAQID +ggEGAAKCAQEAoely6vSHw+/Q3zGYLaJj7eeQkfd25K8SvtC+FMY9D7jwS4g71pyr +U3FJ98Fi45Wdksh+d4u7U089trF5Xbgui29bZ0HcQZtfHEEz0Mh69tkipCm2/QIj +6eDlo6sPk9hhhvgg4MMGiWKhCtHrub3x1FHdmf7KjOhrGeb5apiudo7blGFzGhZ3 +NFnbff+ArVNd+rdVmSoZn0aMhXRConlDu/44IYe5/24VLl7G+BzZlIZO4P2M83fd +mBOvR13cmYssQjEFTbaZVQvQHa3t0+aywfdCgsXGmTTK6QDCBP8D+vf1bmhEswzs +oYn1GLtJ3VyYyMBPDBomd2ctchZgTzsX1w== +-----END PUBLIC KEY----- + diff --git a/vectors/cryptography_vectors/asymmetric/DSA/custom/nilpotent.pem b/vectors/cryptography_vectors/asymmetric/DSA/custom/nilpotent.pem new file mode 100644 index 000000000000..6588c20173cc --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/DSA/custom/nilpotent.pem @@ -0,0 +1,5 @@ +-----BEGIN DSA PRIVATE KEY----- +MGECAQACFQHH+MnFXh4NNlZiV/zUVb5a5ib3kwIVAOP8ZOKvDwabKzEr/moq3y1z +E3vJAhUAl/2Ylx9fWbzHdh1URsc/c6IM/TECAQECFCsjU4AZRcuks45g1NMOUeCB +Epvg +-----END DSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/ECDSA/RFC6979/evppkey_ecdsa_rfc6979.txt b/vectors/cryptography_vectors/asymmetric/ECDSA/RFC6979/evppkey_ecdsa_rfc6979.txt new file mode 100644 index 000000000000..3bc27a603c29 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/ECDSA/RFC6979/evppkey_ecdsa_rfc6979.txt @@ -0,0 +1,2807 @@ +# +# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +# Tests start with one of these keywords +# Cipher Decrypt Derive Digest Encoding KDF MAC PBE +# PrivPubKeyPair Sign Verify VerifyRecover +# and continue until a blank line. Lines starting with a pound sign are ignored. + + +Title = RFC 6979 P-192 deterministic ECDSA tests + +PrivateKey=P-192_PRIV +-----BEGIN PRIVATE KEY----- +MDkCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQEEHzAdAgEBBBhvqwNJNOTA/Jrmf1tWWanX0f79GH7g +n9Q= +-----END PRIVATE KEY----- + +PublicKey=P-192_PUB +-----BEGIN PUBLIC KEY----- +MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAErCx39Sn5Fon+oOpe/sfyENjuoLngR+1WO8cj5XZw +vUiH68cyxSMGPQp8lXvJfBxD +-----END PUBLIC KEY----- + +PrivPubKeyPair=P-192_PRIV:P-192_PUB + +DigestSign = SHA1 +Key = P-192_PRIV +NonceType = deterministic +Input = "sample" +Output = 303502190098C6BD12B23EAF5E2A2045132086BE3EB8EBD62ABF6698FF021857A22B07DEA9530F8DE9471B1DC6624472E8E2844BC25B64 + +DigestVerify = SHA1 +Key = P-192_PUB +Input = "sample" +Output = 303502190098C6BD12B23EAF5E2A2045132086BE3EB8EBD62ABF6698FF021857A22B07DEA9530F8DE9471B1DC6624472E8E2844BC25B64 + +DigestVerify = SHA1 +Key = P-192_PUB +Input = "sample" +Output = 303502190098C6BD12B23EAF5E2A2045132086BE3EB8EBD62ABF6698FF021857A22B07DEA9530F8DE9471B1DC6624472E8E2844BC25B65 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-192_PRIV +NonceType = deterministic +Input = "sample" +Output = 3036021900A1F00DAD97AEEC91C95585F36200C65F3C01812AA60378F5021900E07EC1304C7C6C9DEBBE980B9692668F81D4DE7922A0F97A + +DigestVerify = SHA224 +Key = P-192_PUB +Input = "sample" +Output = 3036021900A1F00DAD97AEEC91C95585F36200C65F3C01812AA60378F5021900E07EC1304C7C6C9DEBBE980B9692668F81D4DE7922A0F97A + +DigestVerify = SHA224 +Key = P-192_PUB +Input = "sample" +Output = 3036021900A1F00DAD97AEEC91C95585F36200C65F3C01812AA60378F5021900E07EC1304C7C6C9DEBBE980B9692668F81D4DE7922A0F97B +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-192_PRIV +NonceType = deterministic +Input = "sample" +Output = 303502184B0B8CE98A92866A2820E20AA6B75B56382E0F9BFD5ECB55021900CCDB006926EA9565CBADC840829D8C384E06DE1F1E381B85 + +DigestVerify = SHA256 +Key = P-192_PUB +Input = "sample" +Output = 303502184B0B8CE98A92866A2820E20AA6B75B56382E0F9BFD5ECB55021900CCDB006926EA9565CBADC840829D8C384E06DE1F1E381B85 + +DigestVerify = SHA256 +Key = P-192_PUB +Input = "sample" +Output = 303502184B0B8CE98A92866A2820E20AA6B75B56382E0F9BFD5ECB55021900CCDB006926EA9565CBADC840829D8C384E06DE1F1E381B84 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-192_PRIV +NonceType = deterministic +Input = "sample" +Output = 3036021900DA63BF0B9ABCF948FBB1E9167F136145F7A20426DCC287D5021900C3AA2C960972BD7A2003A57E1C4C77F0578F8AE95E31EC5E + +DigestVerify = SHA384 +Key = P-192_PUB +Input = "sample" +Output = 3036021900DA63BF0B9ABCF948FBB1E9167F136145F7A20426DCC287D5021900C3AA2C960972BD7A2003A57E1C4C77F0578F8AE95E31EC5E + +DigestVerify = SHA384 +Key = P-192_PUB +Input = "sample" +Output = 3036021900DA63BF0B9ABCF948FBB1E9167F136145F7A20426DCC287D5021900C3AA2C960972BD7A2003A57E1C4C77F0578F8AE95E31EC5F +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-192_PRIV +NonceType = deterministic +Input = "sample" +Output = 303402184D60C5AB1996BD848343B31C00850205E2EA6922DAC2E4B802183F6E837448F027A1BF4B34E796E32A811CBB4050908D8F67 + +DigestVerify = SHA512 +Key = P-192_PUB +Input = "sample" +Output = 303402184D60C5AB1996BD848343B31C00850205E2EA6922DAC2E4B802183F6E837448F027A1BF4B34E796E32A811CBB4050908D8F67 + +DigestVerify = SHA512 +Key = P-192_PUB +Input = "sample" +Output = 303402184D60C5AB1996BD848343B31C00850205E2EA6922DAC2E4B802183F6E837448F027A1BF4B34E796E32A811CBB4050908D8F66 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = P-192_PRIV +NonceType = deterministic +Input = "test" +Output = 303502180F2141A0EBBC44D2E1AF90A50EBCFCE5E197B3B7D4DE036D021900EB18BC9E1F3D7387500CB99CF5F7C157070A8961E38700B7 + +DigestVerify = SHA1 +Key = P-192_PUB +Input = "test" +Output = 303502180F2141A0EBBC44D2E1AF90A50EBCFCE5E197B3B7D4DE036D021900EB18BC9E1F3D7387500CB99CF5F7C157070A8961E38700B7 + +DigestVerify = SHA1 +Key = P-192_PUB +Input = "test" +Output = 303502180F2141A0EBBC44D2E1AF90A50EBCFCE5E197B3B7D4DE036D021900EB18BC9E1F3D7387500CB99CF5F7C157070A8961E38700B6 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-192_PRIV +NonceType = deterministic +Input = "test" +Output = 303502186945A1C1D1B2206B8145548F633BB61CEF04891BAF26ED34021900B7FB7FDFC339C0B9BD61A9F5A8EAF9BE58FC5CBA2CB15293 + +DigestVerify = SHA224 +Key = P-192_PUB +Input = "test" +Output = 303502186945A1C1D1B2206B8145548F633BB61CEF04891BAF26ED34021900B7FB7FDFC339C0B9BD61A9F5A8EAF9BE58FC5CBA2CB15293 + +DigestVerify = SHA224 +Key = P-192_PUB +Input = "test" +Output = 303502186945A1C1D1B2206B8145548F633BB61CEF04891BAF26ED34021900B7FB7FDFC339C0B9BD61A9F5A8EAF9BE58FC5CBA2CB15292 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-192_PRIV +NonceType = deterministic +Input = "test" +Output = 303402183A718BD8B4926C3B52EE6BBE67EF79B18CB6EB62B1AD97AE02185662E6848A4A19B1F1AE2F72ACD4B8BBE50F1EAC65D9124F + +DigestVerify = SHA256 +Key = P-192_PUB +Input = "test" +Output = 303402183A718BD8B4926C3B52EE6BBE67EF79B18CB6EB62B1AD97AE02185662E6848A4A19B1F1AE2F72ACD4B8BBE50F1EAC65D9124F + +DigestVerify = SHA256 +Key = P-192_PUB +Input = "test" +Output = 303402183A718BD8B4926C3B52EE6BBE67EF79B18CB6EB62B1AD97AE02185662E6848A4A19B1F1AE2F72ACD4B8BBE50F1EAC65D9124E +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-192_PRIV +NonceType = deterministic +Input = "test" +Output = 3035021900B234B60B4DB75A733E19280A7A6034BD6B1EE88AF533236702187994090B2D59BB782BE57E74A44C9A1C700413F8ABEFE77A + +DigestVerify = SHA384 +Key = P-192_PUB +Input = "test" +Output = 3035021900B234B60B4DB75A733E19280A7A6034BD6B1EE88AF533236702187994090B2D59BB782BE57E74A44C9A1C700413F8ABEFE77A + +DigestVerify = SHA384 +Key = P-192_PUB +Input = "test" +Output = 3035021900B234B60B4DB75A733E19280A7A6034BD6B1EE88AF533236702187994090B2D59BB782BE57E74A44C9A1C700413F8ABEFE77B +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-192_PRIV +NonceType = deterministic +Input = "test" +Output = 3035021900FE4F4AE86A58B6507946715934FE2D8FF9D95B6B098FE739021874CF5605C98FBA0E1EF34D4B5A1577A7DCF59457CAE52290 + +DigestVerify = SHA512 +Key = P-192_PUB +Input = "test" +Output = 3035021900FE4F4AE86A58B6507946715934FE2D8FF9D95B6B098FE739021874CF5605C98FBA0E1EF34D4B5A1577A7DCF59457CAE52290 + +DigestVerify = SHA512 +Key = P-192_PUB +Input = "test" +Output = 3035021900FE4F4AE86A58B6507946715934FE2D8FF9D95B6B098FE739021874CF5605C98FBA0E1EF34D4B5A1577A7DCF59457CAE52291 +Result = VERIFY_ERROR + +Title = RFC 6979 P-224 deterministic ECDSA tests + +PrivateKey=P-224_PRIV +-----BEGIN PRIVATE KEY----- +MDoCAQAwEAYHKoZIzj0CAQYFK4EEACEEIzAhAgEBBBzyICZuEQW/4wg+A+x6OmVGUfReNxZ+iGAL +8lfB +-----END PRIVATE KEY----- + +PublicKey=P-224_PUB +-----BEGIN PUBLIC KEY----- +ME4wEAYHKoZIzj0CAQYFK4EEACEDOgAEAM8I2lrXGeQnB/pDEpLeoRJE1k/FFhDZSxMNbO6rbz3r +5FXj2/hUFvcDDL2U808tbyMsafPBOFo= +-----END PUBLIC KEY----- + +PrivPubKeyPair=P-224_PRIV:P-224_PUB + +DigestSign = SHA1 +Key = P-224_PRIV +NonceType = deterministic +Input = "sample" +Output = 303C021C22226F9D40A96E19C4A301CE5B74B115303C0F3A4FD30FC257FB57AC021C66D1CDD83E3AF75605DD6E2FEFF196D30AA7ED7A2EDF7AF475403D69 + +DigestVerify = SHA1 +Key = P-224_PUB +Input = "sample" +Output = 303C021C22226F9D40A96E19C4A301CE5B74B115303C0F3A4FD30FC257FB57AC021C66D1CDD83E3AF75605DD6E2FEFF196D30AA7ED7A2EDF7AF475403D69 + +DigestVerify = SHA1 +Key = P-224_PUB +Input = "sample" +Output = 303C021C22226F9D40A96E19C4A301CE5B74B115303C0F3A4FD30FC257FB57AC021C66D1CDD83E3AF75605DD6E2FEFF196D30AA7ED7A2EDF7AF475403D68 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-224_PRIV +NonceType = deterministic +Input = "sample" +Output = 303D021C1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E021D00A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC + +DigestVerify = SHA224 +Key = P-224_PUB +Input = "sample" +Output = 303D021C1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E021D00A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC + +DigestVerify = SHA224 +Key = P-224_PUB +Input = "sample" +Output = 303D021C1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E021D00A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBD +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-224_PRIV +NonceType = deterministic +Input = "sample" +Output = 303D021C61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA021D00BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101 + +DigestVerify = SHA256 +Key = P-224_PUB +Input = "sample" +Output = 303D021C61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA021D00BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101 + +DigestVerify = SHA256 +Key = P-224_PUB +Input = "sample" +Output = 303D021C61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA021D00BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10100 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-224_PRIV +NonceType = deterministic +Input = "sample" +Output = 303D021C0B115E5E36F0F9EC81F1325A5952878D745E19D7BB3EABFABA77E953021D00830F34CCDFE826CCFDC81EB4129772E20E122348A2BBD889A1B1AF1D + +DigestVerify = SHA384 +Key = P-224_PUB +Input = "sample" +Output = 303D021C0B115E5E36F0F9EC81F1325A5952878D745E19D7BB3EABFABA77E953021D00830F34CCDFE826CCFDC81EB4129772E20E122348A2BBD889A1B1AF1D + +DigestVerify = SHA384 +Key = P-224_PUB +Input = "sample" +Output = 303D021C0B115E5E36F0F9EC81F1325A5952878D745E19D7BB3EABFABA77E953021D00830F34CCDFE826CCFDC81EB4129772E20E122348A2BBD889A1B1AF1C +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-224_PRIV +NonceType = deterministic +Input = "sample" +Output = 303D021C074BD1D979D5F32BF958DDC61E4FB4872ADCAFEB2256497CDAC30397021D00A4CECA196C3D5A1FF31027B33185DC8EE43F288B21AB342E5D8EB084 + +DigestVerify = SHA512 +Key = P-224_PUB +Input = "sample" +Output = 303D021C074BD1D979D5F32BF958DDC61E4FB4872ADCAFEB2256497CDAC30397021D00A4CECA196C3D5A1FF31027B33185DC8EE43F288B21AB342E5D8EB084 + +DigestVerify = SHA512 +Key = P-224_PUB +Input = "sample" +Output = 303D021C074BD1D979D5F32BF958DDC61E4FB4872ADCAFEB2256497CDAC30397021D00A4CECA196C3D5A1FF31027B33185DC8EE43F288B21AB342E5D8EB085 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = P-224_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D00DEAA646EC2AF2EA8AD53ED66B2E2DDAA49A12EFD8356561451F3E21C021D0095987796F6CF2062AB8135271DE56AE55366C045F6D9593F53787BD2 + +DigestVerify = SHA1 +Key = P-224_PUB +Input = "test" +Output = 303E021D00DEAA646EC2AF2EA8AD53ED66B2E2DDAA49A12EFD8356561451F3E21C021D0095987796F6CF2062AB8135271DE56AE55366C045F6D9593F53787BD2 + +DigestVerify = SHA1 +Key = P-224_PUB +Input = "test" +Output = 303E021D00DEAA646EC2AF2EA8AD53ED66B2E2DDAA49A12EFD8356561451F3E21C021D0095987796F6CF2062AB8135271DE56AE55366C045F6D9593F53787BD3 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-224_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D00C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019021D00902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4 + +DigestVerify = SHA224 +Key = P-224_PUB +Input = "test" +Output = 303E021D00C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019021D00902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4 + +DigestVerify = SHA224 +Key = P-224_PUB +Input = "test" +Output = 303E021D00C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019021D00902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F5 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-224_PRIV +NonceType = deterministic +Input = "test" +Output = 303D021D00AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6021C178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD + +DigestVerify = SHA256 +Key = P-224_PUB +Input = "test" +Output = 303D021D00AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6021C178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD + +DigestVerify = SHA256 +Key = P-224_PUB +Input = "test" +Output = 303D021D00AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6021C178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFC +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-224_PRIV +NonceType = deterministic +Input = "test" +Output = 303C021C389B92682E399B26518A95506B52C03BC9379A9DADF3391A21FB0EA4021C414A718ED3249FF6DBC5B50C27F71F01F070944DA22AB1F78F559AAB + +DigestVerify = SHA384 +Key = P-224_PUB +Input = "test" +Output = 303C021C389B92682E399B26518A95506B52C03BC9379A9DADF3391A21FB0EA4021C414A718ED3249FF6DBC5B50C27F71F01F070944DA22AB1F78F559AAB + +DigestVerify = SHA384 +Key = P-224_PUB +Input = "test" +Output = 303C021C389B92682E399B26518A95506B52C03BC9379A9DADF3391A21FB0EA4021C414A718ED3249FF6DBC5B50C27F71F01F070944DA22AB1F78F559AAA +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-224_PRIV +NonceType = deterministic +Input = "test" +Output = 303C021C049F050477C5ADD858CAC56208394B5A55BAEBBE887FDF765047C17C021C077EB13E7005929CEFA3CD0403C7CDCC077ADF4E44F3C41B2F60ECFF + +DigestVerify = SHA512 +Key = P-224_PUB +Input = "test" +Output = 303C021C049F050477C5ADD858CAC56208394B5A55BAEBBE887FDF765047C17C021C077EB13E7005929CEFA3CD0403C7CDCC077ADF4E44F3C41B2F60ECFF + +DigestVerify = SHA512 +Key = P-224_PUB +Input = "test" +Output = 303C021C049F050477C5ADD858CAC56208394B5A55BAEBBE887FDF765047C17C021C077EB13E7005929CEFA3CD0403C7CDCC077ADF4E44F3C41B2F60ECFE +Result = VERIFY_ERROR + +Title = RFC 6979 P-256 deterministic ECDSA tests + +PrivateKey=P-256_PRIV +-----BEGIN PRIVATE KEY----- +MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDJr6nYRbp1FmtcIVdnsdaTTlDD2zbo +mxJ7imIrEg9nIQ== +-----END PRIVATE KEY----- + +PublicKey=P-256_PUB +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYP7UuiVanTHJYet0xjVtaMBJuJI7Yfps5mliLmDy +n7Z5A/4QCLi8maQa6elWKLxk8vGyDC1+n1F3o8KU1EYimQ== +-----END PUBLIC KEY----- + +PrivPubKeyPair=P-256_PRIV:P-256_PUB + +DigestSign = SHA1 +Key = P-256_PRIV +NonceType = deterministic +Input = "sample" +Output = 3044022061340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D3202206D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB + +DigestVerify = SHA1 +Key = P-256_PUB +Input = "sample" +Output = 3044022061340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D3202206D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB + +DigestVerify = SHA1 +Key = P-256_PUB +Input = "sample" +Output = 3044022061340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D3202206D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EA +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-256_PRIV +NonceType = deterministic +Input = "sample" +Output = 3045022053B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F022100B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C + +DigestVerify = SHA224 +Key = P-256_PUB +Input = "sample" +Output = 3045022053B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F022100B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C + +DigestVerify = SHA224 +Key = P-256_PUB +Input = "sample" +Output = 3045022053B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F022100B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74D +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-256_PRIV +NonceType = deterministic +Input = "sample" +Output = 3046022100EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716022100F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8 + +DigestVerify = SHA256 +Key = P-256_PUB +Input = "sample" +Output = 3046022100EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716022100F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8 + +DigestVerify = SHA256 +Key = P-256_PUB +Input = "sample" +Output = 3046022100EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716022100F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA9 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-256_PRIV +NonceType = deterministic +Input = "sample" +Output = 304402200EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF771902204861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954 + +DigestVerify = SHA384 +Key = P-256_PUB +Input = "sample" +Output = 304402200EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF771902204861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954 + +DigestVerify = SHA384 +Key = P-256_PUB +Input = "sample" +Output = 304402200EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF771902204861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940955 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-256_PRIV +NonceType = deterministic +Input = "sample" +Output = 30450221008496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F0002202362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE + +DigestVerify = SHA512 +Key = P-256_PUB +Input = "sample" +Output = 30450221008496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F0002202362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE + +DigestVerify = SHA512 +Key = P-256_PUB +Input = "sample" +Output = 30450221008496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F0002202362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FF +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = P-256_PRIV +NonceType = deterministic +Input = "test" +Output = 304402200CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89022001B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B1 + +DigestVerify = SHA1 +Key = P-256_PUB +Input = "test" +Output = 304402200CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89022001B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B1 + +DigestVerify = SHA1 +Key = P-256_PUB +Input = "test" +Output = 304402200CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89022001B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B0 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-256_PRIV +NonceType = deterministic +Input = "test" +Output = 3046022100C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692022100C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2D + +DigestVerify = SHA224 +Key = P-256_PUB +Input = "test" +Output = 3046022100C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692022100C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2D + +DigestVerify = SHA224 +Key = P-256_PUB +Input = "test" +Output = 3046022100C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692022100C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2C +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-256_PRIV +NonceType = deterministic +Input = "test" +Output = 3045022100F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D383670220019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083 + +DigestVerify = SHA256 +Key = P-256_PUB +Input = "test" +Output = 3045022100F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D383670220019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083 + +DigestVerify = SHA256 +Key = P-256_PUB +Input = "test" +Output = 3045022100F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D383670220019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0082 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-256_PRIV +NonceType = deterministic +Input = "test" +Output = 304602210083910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB60221008DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2C + +DigestVerify = SHA384 +Key = P-256_PUB +Input = "test" +Output = 304602210083910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB60221008DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2C + +DigestVerify = SHA384 +Key = P-256_PUB +Input = "test" +Output = 304602210083910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB60221008DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2D +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-256_PRIV +NonceType = deterministic +Input = "test" +Output = 30440220461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04022039AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E55 + +DigestVerify = SHA512 +Key = P-256_PUB +Input = "test" +Output = 30440220461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04022039AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E55 + +DigestVerify = SHA512 +Key = P-256_PUB +Input = "test" +Output = 30440220461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04022039AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E54 +Result = VERIFY_ERROR + +Title = RFC 6979 P-384 deterministic ECDSA tests + +PrivateKey=P-384_PRIV +-----BEGIN PRIVATE KEY----- +ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDBrnT2tLhuMHAWxmHW2ZZ9N4jw7Znvyl7qa +pHdAeHE32JbVck5McKgl+HLJ6mDS7fU= +-----END PRIVATE KEY----- + +PublicKey=P-384_PUB +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7DpOQVtOGaRWhhgCn0J/pdqai8SukuAuBqrlKGswDGTe ++PDqkFWGYGSiVFFUgLwTgBXZty19VyROqO+awMYhiWcIpZNn+d+59UyoSz8cnbEoiyMcOuDU/nNE +/SUzJkcg +-----END PUBLIC KEY----- + +PrivPubKeyPair=P-384_PRIV:P-384_PUB + +DigestSign = SHA1 +Key = P-384_PRIV +NonceType = deterministic +Input = "sample" +Output = 3066023100EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF72014540C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2023100A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A443 + +DigestVerify = SHA1 +Key = P-384_PUB +Input = "sample" +Output = 3066023100EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF72014540C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2023100A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A443 + +DigestVerify = SHA1 +Key = P-384_PUB +Input = "sample" +Output = 3066023100EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF72014540C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2023100A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A442 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-384_PRIV +NonceType = deterministic +Input = "sample" +Output = 3065023042356E76B55A6D9B4631C865445DBE54E056D3B3431766D0509244793C3F9366450F76EE3DE43F5A125333A6BE0601220231009DA0C81787064021E78DF658F2FBB0B042BF304665DB721F077A4298B095E4834C082C03D83028EFBF93A3C23940CA8D + +DigestVerify = SHA224 +Key = P-384_PUB +Input = "sample" +Output = 3065023042356E76B55A6D9B4631C865445DBE54E056D3B3431766D0509244793C3F9366450F76EE3DE43F5A125333A6BE0601220231009DA0C81787064021E78DF658F2FBB0B042BF304665DB721F077A4298B095E4834C082C03D83028EFBF93A3C23940CA8D + +DigestVerify = SHA224 +Key = P-384_PUB +Input = "sample" +Output = 3065023042356E76B55A6D9B4631C865445DBE54E056D3B3431766D0509244793C3F9366450F76EE3DE43F5A125333A6BE0601220231009DA0C81787064021E78DF658F2FBB0B042BF304665DB721F077A4298B095E4834C082C03D83028EFBF93A3C23940CA8C +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-384_PRIV +NonceType = deterministic +Input = "sample" +Output = 3065023021B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD023100F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0 + +DigestVerify = SHA256 +Key = P-384_PUB +Input = "sample" +Output = 3065023021B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD023100F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0 + +DigestVerify = SHA256 +Key = P-384_PUB +Input = "sample" +Output = 3065023021B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD023100F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB1 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-384_PRIV +NonceType = deterministic +Input = "sample" +Output = 306602310094EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE4602310099EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8 + +DigestVerify = SHA384 +Key = P-384_PUB +Input = "sample" +Output = 306602310094EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE4602310099EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8 + +DigestVerify = SHA384 +Key = P-384_PUB +Input = "sample" +Output = 306602310094EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE4602310099EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC9 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-384_PRIV +NonceType = deterministic +Input = "sample" +Output = 3065023100ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047C0046861DA4A799CFE30F35CC900056D7C99CD78824337090230512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA9135329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD5 + +DigestVerify = SHA512 +Key = P-384_PUB +Input = "sample" +Output = 3065023100ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047C0046861DA4A799CFE30F35CC900056D7C99CD78824337090230512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA9135329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD5 + +DigestVerify = SHA512 +Key = P-384_PUB +Input = "sample" +Output = 3065023100ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047C0046861DA4A799CFE30F35CC900056D7C99CD78824337090230512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA9135329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD4 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = P-384_PRIV +NonceType = deterministic +Input = "test" +Output = 306502304BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7023100D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8C4B7186201A2991695BA1C84541327E966FA7B50F7382282 + +DigestVerify = SHA1 +Key = P-384_PUB +Input = "test" +Output = 306502304BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7023100D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8C4B7186201A2991695BA1C84541327E966FA7B50F7382282 + +DigestVerify = SHA1 +Key = P-384_PUB +Input = "test" +Output = 306502304BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7023100D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8C4B7186201A2991695BA1C84541327E966FA7B50F7382283 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-384_PRIV +NonceType = deterministic +Input = "test" +Output = 3065023100E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE888FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72023007041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B66 + +DigestVerify = SHA224 +Key = P-384_PUB +Input = "test" +Output = 3065023100E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE888FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72023007041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B66 + +DigestVerify = SHA224 +Key = P-384_PUB +Input = "test" +Output = 3065023100E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE888FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72023007041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B67 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-384_PRIV +NonceType = deterministic +Input = "test" +Output = 306402306D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B02302D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265 + +DigestVerify = SHA256 +Key = P-384_PUB +Input = "test" +Output = 306402306D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B02302D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265 + +DigestVerify = SHA256 +Key = P-384_PUB +Input = "test" +Output = 306402306D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B02302D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787264 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-384_PRIV +NonceType = deterministic +Input = "test" +Output = 30660231008203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB023100DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5 + +DigestVerify = SHA384 +Key = P-384_PUB +Input = "test" +Output = 30660231008203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB023100DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5 + +DigestVerify = SHA384 +Key = P-384_PUB +Input = "test" +Output = 30660231008203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB023100DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A4 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-384_PRIV +NonceType = deterministic +Input = "test" +Output = 3066023100A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277023100976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFAF576F6B2E8B224634A2092CD3792E0159AD9CEE37659C736 + +DigestVerify = SHA512 +Key = P-384_PUB +Input = "test" +Output = 3066023100A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277023100976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFAF576F6B2E8B224634A2092CD3792E0159AD9CEE37659C736 + +DigestVerify = SHA512 +Key = P-384_PUB +Input = "test" +Output = 3066023100A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277023100976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFAF576F6B2E8B224634A2092CD3792E0159AD9CEE37659C737 +Result = VERIFY_ERROR + +Title = RFC 6979 P-521 deterministic ECDSA tests + +PrivateKey=P-521_PRIV +-----BEGIN PRIVATE KEY----- +MF8CAQAwEAYHKoZIzj0CAQYFK4EEACMESDBGAgEBBEH60G2qYro7JdL7QBM9p1cgXeZ/W7ABj+6M +huG2jH51yqiW6zLx9HxwhVg2ptFvzBRm9tj77GfbiewMCLDplrg1OA== +-----END PRIVATE KEY----- + +PublicKey=P-521_PUB +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBiUVQ0HhZMuAOqiO2lPIT+MMSH4bcl6BOWnFn205b +zTcRI9RuRdtrXVNwp/IPtjMVXTj/oW0r12HcrEdLmi9QI6QASTEByWLNTS/d94IoXmRYQTnC+RtH ++H/4I1TWYw90aiig2yV0G1s0qCgAiyKswj+ST6r71NM/gepmlW3+qiv9/PU= +-----END PUBLIC KEY----- + +PrivPubKeyPair=P-521_PRIV:P-521_PUB + +DigestSign = SHA1 +Key = P-521_PRIV +NonceType = deterministic +Input = "sample" +Output = 3081870241343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910FE092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D024200E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D16 + +DigestVerify = SHA1 +Key = P-521_PUB +Input = "sample" +Output = 3081870241343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910FE092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D024200E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D16 + +DigestVerify = SHA1 +Key = P-521_PUB +Input = "sample" +Output = 3081870241343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910FE092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D024200E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D17 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-521_PRIV +NonceType = deterministic +Input = "sample" +Output = 308187024201776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E024150CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41F + +DigestVerify = SHA224 +Key = P-521_PUB +Input = "sample" +Output = 308187024201776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E024150CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41F + +DigestVerify = SHA224 +Key = P-521_PUB +Input = "sample" +Output = 308187024201776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E024150CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41E +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-521_PRIV +NonceType = deterministic +Input = "sample" +Output = 308187024201511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A702414A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC + +DigestVerify = SHA256 +Key = P-521_PUB +Input = "sample" +Output = 308187024201511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A702414A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC + +DigestVerify = SHA256 +Key = P-521_PUB +Input = "sample" +Output = 308187024201511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A702414A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFD +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-521_PRIV +NonceType = deterministic +Input = "sample" +Output = 308188024201EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451024201F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5FDE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D61 + +DigestVerify = SHA384 +Key = P-521_PUB +Input = "sample" +Output = 308188024201EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451024201F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5FDE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D61 + +DigestVerify = SHA384 +Key = P-521_PUB +Input = "sample" +Output = 308188024201EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451024201F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5FDE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D60 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-521_PRIV +NonceType = deterministic +Input = "sample" +Output = 308187024200C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA0241617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A + +DigestVerify = SHA512 +Key = P-521_PUB +Input = "sample" +Output = 308187024200C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA0241617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A + +DigestVerify = SHA512 +Key = P-521_PUB +Input = "sample" +Output = 308187024200C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA0241617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67B +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = P-521_PRIV +NonceType = deterministic +Input = "test" +Output = 3081880242013BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B0693F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367024201E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FF + +DigestVerify = SHA1 +Key = P-521_PUB +Input = "test" +Output = 3081880242013BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B0693F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367024201E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FF + +DigestVerify = SHA1 +Key = P-521_PUB +Input = "test" +Output = 3081880242013BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B0693F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367024201E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FE +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = P-521_PRIV +NonceType = deterministic +Input = "test" +Output = 308188024201C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086BFE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB02420177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5BEC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A4 + +DigestVerify = SHA224 +Key = P-521_PUB +Input = "test" +Output = 308188024201C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086BFE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB02420177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5BEC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A4 + +DigestVerify = SHA224 +Key = P-521_PUB +Input = "test" +Output = 308188024201C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086BFE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB02420177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5BEC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A5 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = P-521_PRIV +NonceType = deterministic +Input = "test" +Output = 30818702410E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8024200CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86 + +DigestVerify = SHA256 +Key = P-521_PUB +Input = "test" +Output = 30818702410E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8024200CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86 + +DigestVerify = SHA256 +Key = P-521_PUB +Input = "test" +Output = 30818702410E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8024200CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E87 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = P-521_PRIV +NonceType = deterministic +Input = "test" +Output = 3081880242014BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C89DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C02420133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B979 + +DigestVerify = SHA384 +Key = P-521_PUB +Input = "test" +Output = 3081880242014BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C89DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C02420133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B979 + +DigestVerify = SHA384 +Key = P-521_PUB +Input = "test" +Output = 3081880242014BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C89DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C02420133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B978 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = P-521_PRIV +NonceType = deterministic +Input = "test" +Output = 3081880242013E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D024201FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3 + +DigestVerify = SHA512 +Key = P-521_PUB +Input = "test" +Output = 3081880242013E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D024201FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3 + +DigestVerify = SHA512 +Key = P-521_PUB +Input = "test" +Output = 3081880242013E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D024201FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE2 +Result = VERIFY_ERROR + +Title = RFC 6979 K-163 deterministic ECDSA tests + +PrivateKey=K-163_PRIV +-----BEGIN PRIVATE KEY----- +MDICAQAwEAYHKoZIzj0CAQYFK4EEAAEEGzAZAgEBBBSaTWeSKVp/cw/D8rScvA9i6GInLw== +-----END PRIVATE KEY----- + +PublicKey=K-163_PUB +-----BEGIN PUBLIC KEY----- +MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEB5ruCQ2wXsJS1ctEUvNWvhmKT/lvB4LiljTdyaMe9AOG +6Ja6oYtTr6Wj +-----END PUBLIC KEY----- + +PrivPubKeyPair=K-163_PRIV:K-163_PUB + +DigestSign = SHA1 +Key = K-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E0215030C45B80BA0E1406C4EFBBB7000D6DE4FA465D5050215038D87DF89493522FC4CD7DE1553BD9DBBA2123011 + +DigestVerify = SHA1 +Key = K-163_PUB +Input = "sample" +Output = 302E0215030C45B80BA0E1406C4EFBBB7000D6DE4FA465D5050215038D87DF89493522FC4CD7DE1553BD9DBBA2123011 + +DigestVerify = SHA1 +Key = K-163_PUB +Input = "sample" +Output = 302E0215030C45B80BA0E1406C4EFBBB7000D6DE4FA465D5050215038D87DF89493522FC4CD7DE1553BD9DBBA2123010 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302D0215038A2749F7EA13BD5DA0C76C842F512D5A65FFAF32021464F841F70112B793FD773F5606BFA5AC2A04C1E8 + +DigestVerify = SHA224 +Key = K-163_PUB +Input = "sample" +Output = 302D0215038A2749F7EA13BD5DA0C76C842F512D5A65FFAF32021464F841F70112B793FD773F5606BFA5AC2A04C1E8 + +DigestVerify = SHA224 +Key = K-163_PUB +Input = "sample" +Output = 302D0215038A2749F7EA13BD5DA0C76C842F512D5A65FFAF32021464F841F70112B793FD773F5606BFA5AC2A04C1E9 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E02150113A63990598A3828C407C0F4D2438D990DF99A7F021501313A2E03F5412DDB296A22E2C455335545672D9F + +DigestVerify = SHA256 +Key = K-163_PUB +Input = "sample" +Output = 302E02150113A63990598A3828C407C0F4D2438D990DF99A7F021501313A2E03F5412DDB296A22E2C455335545672D9F + +DigestVerify = SHA256 +Key = K-163_PUB +Input = "sample" +Output = 302E02150113A63990598A3828C407C0F4D2438D990DF99A7F021501313A2E03F5412DDB296A22E2C455335545672D9E +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E0215034D4DE955871BB84FEA4E7D068BA5E9A11BD8B6C4021502BAAF4D4FD57F175C405A2F39F9755D9045C820BD + +DigestVerify = SHA384 +Key = K-163_PUB +Input = "sample" +Output = 302E0215034D4DE955871BB84FEA4E7D068BA5E9A11BD8B6C4021502BAAF4D4FD57F175C405A2F39F9755D9045C820BD + +DigestVerify = SHA384 +Key = K-163_PUB +Input = "sample" +Output = 302E0215034D4DE955871BB84FEA4E7D068BA5E9A11BD8B6C4021502BAAF4D4FD57F175C405A2F39F9755D9045C820BC +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E0215038E487F218D696A7323B891F0CCF055D895B77ADC021500972D7721093F9B3835A5EB7F0442FA8DCAA873C4 + +DigestVerify = SHA512 +Key = K-163_PUB +Input = "sample" +Output = 302E0215038E487F218D696A7323B891F0CCF055D895B77ADC021500972D7721093F9B3835A5EB7F0442FA8DCAA873C4 + +DigestVerify = SHA512 +Key = K-163_PUB +Input = "sample" +Output = 302E0215038E487F218D696A7323B891F0CCF055D895B77ADC021500972D7721093F9B3835A5EB7F0442FA8DCAA873C5 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = K-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E021501375BEF93F21582F601497036A7DC8014A99C2B7902150254B7F1472FFFEE9002D081BB8CE819CCE6E687F9 + +DigestVerify = SHA1 +Key = K-163_PUB +Input = "test" +Output = 302E021501375BEF93F21582F601497036A7DC8014A99C2B7902150254B7F1472FFFEE9002D081BB8CE819CCE6E687F9 + +DigestVerify = SHA1 +Key = K-163_PUB +Input = "test" +Output = 302E021501375BEF93F21582F601497036A7DC8014A99C2B7902150254B7F1472FFFEE9002D081BB8CE819CCE6E687F8 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302D02150110F17EF209957214E35E8C2E83CBE73B3BFDEE2C021457D5022392D359851B95DEC2444012502A5349CB + +DigestVerify = SHA224 +Key = K-163_PUB +Input = "test" +Output = 302D02150110F17EF209957214E35E8C2E83CBE73B3BFDEE2C021457D5022392D359851B95DEC2444012502A5349CB + +DigestVerify = SHA224 +Key = K-163_PUB +Input = "test" +Output = 302D02150110F17EF209957214E35E8C2E83CBE73B3BFDEE2C021457D5022392D359851B95DEC2444012502A5349CA +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302C0214354D5CD24F9C41F85D02E856FA2B0001C83AF53E021420B200677731CD4FE48612A92F72A19853A82B65 + +DigestVerify = SHA256 +Key = K-163_PUB +Input = "test" +Output = 302C0214354D5CD24F9C41F85D02E856FA2B0001C83AF53E021420B200677731CD4FE48612A92F72A19853A82B65 + +DigestVerify = SHA256 +Key = K-163_PUB +Input = "test" +Output = 302C0214354D5CD24F9C41F85D02E856FA2B0001C83AF53E021420B200677731CD4FE48612A92F72A19853A82B64 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E0215011B6A84206515495AD8DBB2E5785D6D018D75817E021501A7D4C1E17D4030A5D748ADEA785C77A54581F6D0 + +DigestVerify = SHA384 +Key = K-163_PUB +Input = "test" +Output = 302E0215011B6A84206515495AD8DBB2E5785D6D018D75817E021501A7D4C1E17D4030A5D748ADEA785C77A54581F6D0 + +DigestVerify = SHA384 +Key = K-163_PUB +Input = "test" +Output = 302E0215011B6A84206515495AD8DBB2E5785D6D018D75817E021501A7D4C1E17D4030A5D748ADEA785C77A54581F6D1 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E02150148934745B351F6367FF5BB56B1848A2F508902A90215036214B19444FAB504DBA61D4D6FF2D2F9640F4837 + +DigestVerify = SHA512 +Key = K-163_PUB +Input = "test" +Output = 302E02150148934745B351F6367FF5BB56B1848A2F508902A90215036214B19444FAB504DBA61D4D6FF2D2F9640F4837 + +DigestVerify = SHA512 +Key = K-163_PUB +Input = "test" +Output = 302E02150148934745B351F6367FF5BB56B1848A2F508902A90215036214B19444FAB504DBA61D4D6FF2D2F9640F4836 +Result = VERIFY_ERROR + +Title = RFC 6979 K-233 deterministic ECDSA tests + +PrivateKey=K-233_PRIV +-----BEGIN PRIVATE KEY----- +MDsCAQAwEAYHKoZIzj0CAQYFK4EEABoEJDAiAgEBBB0QOyFCvcKjw7VQgNCd8YCPeTNtojmfXKcX +HRvpsA== +-----END PRIVATE KEY----- + +PublicKey=K-233_PUB +-----BEGIN PUBLIC KEY----- +MFIwEAYHKoZIzj0CAQYFK4EEABoDPgAEAGgohvNsaEc8GiIXIMKxK5vhNFi6kH4cRzZZV3nyAbIG +ObQb4JJwkJmbeBejs5KNIFA6OVRgROwToQMJ +-----END PUBLIC KEY----- + +PrivPubKeyPair=K-233_PRIV:K-233_PUB + +DigestSign = SHA1 +Key = K-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303E021D5474541C988A9A1F73899F55EF28963DFFBBF0C2B1A1EE787C6A76C6A4021D46301F9EC6624257BFC70D72186F17898EDBD0A3522560A88DD1B7D45A + +DigestVerify = SHA1 +Key = K-233_PUB +Input = "sample" +Output = 303E021D5474541C988A9A1F73899F55EF28963DFFBBF0C2B1A1EE787C6A76C6A4021D46301F9EC6624257BFC70D72186F17898EDBD0A3522560A88DD1B7D45A + +DigestVerify = SHA1 +Key = K-233_PUB +Input = "sample" +Output = 303E021D5474541C988A9A1F73899F55EF28963DFFBBF0C2B1A1EE787C6A76C6A4021D46301F9EC6624257BFC70D72186F17898EDBD0A3522560A88DD1B7D45B +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303E021D667F2FCE3E1C497EBD8E4B7C6372A8234003FE4ED6D4515814E7E11430021D6A1C41340DAA730320DB9475F10E29A127D7AE3432F155E1F7954E1B57 + +DigestVerify = SHA224 +Key = K-233_PUB +Input = "sample" +Output = 303E021D667F2FCE3E1C497EBD8E4B7C6372A8234003FE4ED6D4515814E7E11430021D6A1C41340DAA730320DB9475F10E29A127D7AE3432F155E1F7954E1B57 + +DigestVerify = SHA224 +Key = K-233_PUB +Input = "sample" +Output = 303E021D667F2FCE3E1C497EBD8E4B7C6372A8234003FE4ED6D4515814E7E11430021D6A1C41340DAA730320DB9475F10E29A127D7AE3432F155E1F7954E1B56 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303E021D38AD9C1D2CB29906E7D63C24601AC55736B438FB14F4093D6C32F63A10021D647AAD2599C21B6EE89BE7FF957D98F684B7921DE1FD3CC82C079624F4 + +DigestVerify = SHA256 +Key = K-233_PUB +Input = "sample" +Output = 303E021D38AD9C1D2CB29906E7D63C24601AC55736B438FB14F4093D6C32F63A10021D647AAD2599C21B6EE89BE7FF957D98F684B7921DE1FD3CC82C079624F4 + +DigestVerify = SHA256 +Key = K-233_PUB +Input = "sample" +Output = 303E021D38AD9C1D2CB29906E7D63C24601AC55736B438FB14F4093D6C32F63A10021D647AAD2599C21B6EE89BE7FF957D98F684B7921DE1FD3CC82C079624F5 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303E021D0C6510F57559C36FBCFF8C7BA4B81853DC618AD0BAAB03CFFDF3FD09FD021D0AD331EE1C9B91A88BA77997235769C60AD07EE69E11F7137E17C5CF67 + +DigestVerify = SHA384 +Key = K-233_PUB +Input = "sample" +Output = 303E021D0C6510F57559C36FBCFF8C7BA4B81853DC618AD0BAAB03CFFDF3FD09FD021D0AD331EE1C9B91A88BA77997235769C60AD07EE69E11F7137E17C5CF67 + +DigestVerify = SHA384 +Key = K-233_PUB +Input = "sample" +Output = 303E021D0C6510F57559C36FBCFF8C7BA4B81853DC618AD0BAAB03CFFDF3FD09FD021D0AD331EE1C9B91A88BA77997235769C60AD07EE69E11F7137E17C5CF66 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303E021D47C4AC1B344028CC740BA7BB9F8AA59D6390E3158153D4F2ADE4B74950021D26CE0CDE18A1B884B3EE1A879C13B42F11BB7C85F7A3745C8BECEC8E6E + +DigestVerify = SHA512 +Key = K-233_PUB +Input = "sample" +Output = 303E021D47C4AC1B344028CC740BA7BB9F8AA59D6390E3158153D4F2ADE4B74950021D26CE0CDE18A1B884B3EE1A879C13B42F11BB7C85F7A3745C8BECEC8E6E + +DigestVerify = SHA512 +Key = K-233_PUB +Input = "sample" +Output = 303E021D47C4AC1B344028CC740BA7BB9F8AA59D6390E3158153D4F2ADE4B74950021D26CE0CDE18A1B884B3EE1A879C13B42F11BB7C85F7A3745C8BECEC8E6F +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = K-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D4780B2DE4BAA5613872179AD90664249842E8B96FCD5653B55DD63EED4021D6AF46BA322E21D4A88DAEC1650EF38774231276266D6A45ED6A64ECB44 + +DigestVerify = SHA1 +Key = K-233_PUB +Input = "test" +Output = 303E021D4780B2DE4BAA5613872179AD90664249842E8B96FCD5653B55DD63EED4021D6AF46BA322E21D4A88DAEC1650EF38774231276266D6A45ED6A64ECB44 + +DigestVerify = SHA1 +Key = K-233_PUB +Input = "test" +Output = 303E021D4780B2DE4BAA5613872179AD90664249842E8B96FCD5653B55DD63EED4021D6AF46BA322E21D4A88DAEC1650EF38774231276266D6A45ED6A64ECB45 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D61D9CC8C842DF19B3D9F4BDA0D0E14A957357ADABC239444610FB39AEA021D66432278891CB594BA8D08A0C556053D15917E53449E03C2EF88474CF6 + +DigestVerify = SHA224 +Key = K-233_PUB +Input = "test" +Output = 303E021D61D9CC8C842DF19B3D9F4BDA0D0E14A957357ADABC239444610FB39AEA021D66432278891CB594BA8D08A0C556053D15917E53449E03C2EF88474CF6 + +DigestVerify = SHA224 +Key = K-233_PUB +Input = "test" +Output = 303E021D61D9CC8C842DF19B3D9F4BDA0D0E14A957357ADABC239444610FB39AEA021D66432278891CB594BA8D08A0C556053D15917E53449E03C2EF88474CF7 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D05E4E6B4DB0E13034E7F1F2E5DBAB766D37C15AE4056C7EE607C8AC7F4021D5FC46AA489BF828B34FBAD25EC432190F161BEA8F60D3FCADB0EE3B725 + +DigestVerify = SHA256 +Key = K-233_PUB +Input = "test" +Output = 303E021D05E4E6B4DB0E13034E7F1F2E5DBAB766D37C15AE4056C7EE607C8AC7F4021D5FC46AA489BF828B34FBAD25EC432190F161BEA8F60D3FCADB0EE3B725 + +DigestVerify = SHA256 +Key = K-233_PUB +Input = "test" +Output = 303E021D05E4E6B4DB0E13034E7F1F2E5DBAB766D37C15AE4056C7EE607C8AC7F4021D5FC46AA489BF828B34FBAD25EC432190F161BEA8F60D3FCADB0EE3B724 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D50F1EFEDFFEC1088024620280EE0D7641542E4D4B5D61DB32358FC571B021D4614EAE449927A9EB2FCC42EA3E955B43D194087719511A007EC9217A5 + +DigestVerify = SHA384 +Key = K-233_PUB +Input = "test" +Output = 303E021D50F1EFEDFFEC1088024620280EE0D7641542E4D4B5D61DB32358FC571B021D4614EAE449927A9EB2FCC42EA3E955B43D194087719511A007EC9217A5 + +DigestVerify = SHA384 +Key = K-233_PUB +Input = "test" +Output = 303E021D50F1EFEDFFEC1088024620280EE0D7641542E4D4B5D61DB32358FC571B021D4614EAE449927A9EB2FCC42EA3E955B43D194087719511A007EC9217A4 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D6FE6D0D3A953BB66BB01BC6B9EDFAD9F35E88277E5768D1B214395320F021D7C01A236E4BFF0A771050AD01EC1D24025D3130BBD9E4E81978EB3EC09 + +DigestVerify = SHA512 +Key = K-233_PUB +Input = "test" +Output = 303E021D6FE6D0D3A953BB66BB01BC6B9EDFAD9F35E88277E5768D1B214395320F021D7C01A236E4BFF0A771050AD01EC1D24025D3130BBD9E4E81978EB3EC09 + +DigestVerify = SHA512 +Key = K-233_PUB +Input = "test" +Output = 303E021D6FE6D0D3A953BB66BB01BC6B9EDFAD9F35E88277E5768D1B214395320F021D7C01A236E4BFF0A771050AD01EC1D24025D3130BBD9E4E81978EB3EC08 +Result = VERIFY_ERROR + +Title = RFC 6979 K-283 deterministic ECDSA tests + +PrivateKey=K-283_PRIV +-----BEGIN PRIVATE KEY----- +MEECAQAwEAYHKoZIzj0CAQYFK4EEABAEKjAoAgEBBCNqB3c1boe4m6HtOj2EU1e+MyFzyPemW9x9 +tPqzxMx5rMgZTg== +-----END PRIVATE KEY----- + +PublicKey=K-283_PUB +-----BEGIN PUBLIC KEY----- +MF4wEAYHKoZIzj0CAQYFK4EEABADSgAEAlMw0KZR1aINxjibwCNFEXclZArsPBJmEs5ETt0ZZJve +zAPWBQW9YKS2cYJHTsTRxminMUD3BQSmjznvzZckh+lTDgUIp2GT +-----END PUBLIC KEY----- + +PrivPubKeyPair=K-283_PRIV:K-283_PUB + +DigestSign = SHA1 +Key = K-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304B022401B66D1E33FBDB6E107A69B610995C93C744CEBAEAF623CB42737C27D60188BD1D045A6802232E45B62C9C258643532FD536594B46C63B063946494F95DAFF8759FD552502324295C5 + +DigestVerify = SHA1 +Key = K-283_PUB +Input = "sample" +Output = 304B022401B66D1E33FBDB6E107A69B610995C93C744CEBAEAF623CB42737C27D60188BD1D045A6802232E45B62C9C258643532FD536594B46C63B063946494F95DAFF8759FD552502324295C5 + +DigestVerify = SHA1 +Key = K-283_PUB +Input = "sample" +Output = 304B022401B66D1E33FBDB6E107A69B610995C93C744CEBAEAF623CB42737C27D60188BD1D045A6802232E45B62C9C258643532FD536594B46C63B063946494F95DAFF8759FD552502324295C4 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304B022318CF2F371BE86BB62E02B27CDE56DDAC83CCFBB3141FC59AEE022B66AC1A60DBBD8B76022401854E02A381295EA7F184CEE71AB7222D6974522D3B99B309B1A8025EB84118A28BF20E + +DigestVerify = SHA224 +Key = K-283_PUB +Input = "sample" +Output = 304B022318CF2F371BE86BB62E02B27CDE56DDAC83CCFBB3141FC59AEE022B66AC1A60DBBD8B76022401854E02A381295EA7F184CEE71AB7222D6974522D3B99B309B1A8025EB84118A28BF20E + +DigestVerify = SHA224 +Key = K-283_PUB +Input = "sample" +Output = 304B022318CF2F371BE86BB62E02B27CDE56DDAC83CCFBB3141FC59AEE022B66AC1A60DBBD8B76022401854E02A381295EA7F184CEE71AB7222D6974522D3B99B309B1A8025EB84118A28BF20F +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304C0224019E90AA3DE5FB20AED22879F92C6FED278D9C9B9293CC5E94922CD952C9DBF20DF1753A02240135AA7443B6A25D11BB64AC482E04D47902D017752882BD72527114F46CF8BB56C5A8C3 + +DigestVerify = SHA256 +Key = K-283_PUB +Input = "sample" +Output = 304C0224019E90AA3DE5FB20AED22879F92C6FED278D9C9B9293CC5E94922CD952C9DBF20DF1753A02240135AA7443B6A25D11BB64AC482E04D47902D017752882BD72527114F46CF8BB56C5A8C3 + +DigestVerify = SHA256 +Key = K-283_PUB +Input = "sample" +Output = 304C0224019E90AA3DE5FB20AED22879F92C6FED278D9C9B9293CC5E94922CD952C9DBF20DF1753A02240135AA7443B6A25D11BB64AC482E04D47902D017752882BD72527114F46CF8BB56C5A8C2 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304C022400F8C1CA9C221AD9907A136F787D33BA56B0495A40E86E671C940FD767EDD75EB6001A49022401071A56915DEE89E22E511975AA09D00CDC4AA7F5054CBE83F5977EE6F8E1CC31EC43FD + +DigestVerify = SHA384 +Key = K-283_PUB +Input = "sample" +Output = 304C022400F8C1CA9C221AD9907A136F787D33BA56B0495A40E86E671C940FD767EDD75EB6001A49022401071A56915DEE89E22E511975AA09D00CDC4AA7F5054CBE83F5977EE6F8E1CC31EC43FD + +DigestVerify = SHA384 +Key = K-283_PUB +Input = "sample" +Output = 304C022400F8C1CA9C221AD9907A136F787D33BA56B0495A40E86E671C940FD767EDD75EB6001A49022401071A56915DEE89E22E511975AA09D00CDC4AA7F5054CBE83F5977EE6F8E1CC31EC43FC +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304C022401D0008CF4BA4A701BEF70771934C2A4A87386155A2354140E2ED52E18553C35B47D9E50022400D15F4FA1B7A4D41D9843578E22EF98773179103DC4FF0DD1F74A6B5642841B91056F78 + +DigestVerify = SHA512 +Key = K-283_PUB +Input = "sample" +Output = 304C022401D0008CF4BA4A701BEF70771934C2A4A87386155A2354140E2ED52E18553C35B47D9E50022400D15F4FA1B7A4D41D9843578E22EF98773179103DC4FF0DD1F74A6B5642841B91056F78 + +DigestVerify = SHA512 +Key = K-283_PUB +Input = "sample" +Output = 304C022401D0008CF4BA4A701BEF70771934C2A4A87386155A2354140E2ED52E18553C35B47D9E50022400D15F4FA1B7A4D41D9843578E22EF98773179103DC4FF0DD1F74A6B5642841B91056F79 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = K-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304C02240140932FA7307666A8CCB1E1A09656CC40F5932965841ABD5E8E43559D93CF2311B027670224016A2FD46DA497E5E739DED67F426308C45C2E16528BF2A17EB5D65964FD88B770FBB9C6 + +DigestVerify = SHA1 +Key = K-283_PUB +Input = "test" +Output = 304C02240140932FA7307666A8CCB1E1A09656CC40F5932965841ABD5E8E43559D93CF2311B027670224016A2FD46DA497E5E739DED67F426308C45C2E16528BF2A17EB5D65964FD88B770FBB9C6 + +DigestVerify = SHA1 +Key = K-283_PUB +Input = "test" +Output = 304C02240140932FA7307666A8CCB1E1A09656CC40F5932965841ABD5E8E43559D93CF2311B027670224016A2FD46DA497E5E739DED67F426308C45C2E16528BF2A17EB5D65964FD88B770FBB9C7 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304C022400E72AF7E39CD72EF21E61964D87C838F977485FA6A7E999000AFA97A381B2445FCEE541022401644FF7D848DA1A040F77515082C27C763B1B4BF332BCF5D08251C6B57D806319778208 + +DigestVerify = SHA224 +Key = K-283_PUB +Input = "test" +Output = 304C022400E72AF7E39CD72EF21E61964D87C838F977485FA6A7E999000AFA97A381B2445FCEE541022401644FF7D848DA1A040F77515082C27C763B1B4BF332BCF5D08251C6B57D806319778208 + +DigestVerify = SHA224 +Key = K-283_PUB +Input = "test" +Output = 304C022400E72AF7E39CD72EF21E61964D87C838F977485FA6A7E999000AFA97A381B2445FCEE541022401644FF7D848DA1A040F77515082C27C763B1B4BF332BCF5D08251C6B57D806319778209 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304B02240158FAEB2470B306C57764AFC8528174589008449E11DB8B36994B607A65956A597155310223521BC667CA1CA42B5649E78A3D76823C678B7BB3CD58D2E93CD791D53043A6F83F1FD1 + +DigestVerify = SHA256 +Key = K-283_PUB +Input = "test" +Output = 304B02240158FAEB2470B306C57764AFC8528174589008449E11DB8B36994B607A65956A597155310223521BC667CA1CA42B5649E78A3D76823C678B7BB3CD58D2E93CD791D53043A6F83F1FD1 + +DigestVerify = SHA256 +Key = K-283_PUB +Input = "test" +Output = 304B02240158FAEB2470B306C57764AFC8528174589008449E11DB8B36994B607A65956A597155310223521BC667CA1CA42B5649E78A3D76823C678B7BB3CD58D2E93CD791D53043A6F83F1FD0 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304B022401CC4DC5479E0F34C4339631A45AA690580060BF0EB518184C983E0E618C3B93AAB14BBE0223284D72FF8AFA83DE364502CBA0494BB06D40AE08F9D9746E747EA87240E589BA0683B7 + +DigestVerify = SHA384 +Key = K-283_PUB +Input = "test" +Output = 304B022401CC4DC5479E0F34C4339631A45AA690580060BF0EB518184C983E0E618C3B93AAB14BBE0223284D72FF8AFA83DE364502CBA0494BB06D40AE08F9D9746E747EA87240E589BA0683B7 + +DigestVerify = SHA384 +Key = K-283_PUB +Input = "test" +Output = 304B022401CC4DC5479E0F34C4339631A45AA690580060BF0EB518184C983E0E618C3B93AAB14BBE0223284D72FF8AFA83DE364502CBA0494BB06D40AE08F9D9746E747EA87240E589BA0683B6 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304C022401E7912517C6899732E09756B1660F6B96635D638283DF9A8A11D30E008895D7F5C9C7F3022400887E75CBD0B7DD9DE30ED79BDB3D78E4F1121C5EAFF5946918F594F88D363644789DA7 + +DigestVerify = SHA512 +Key = K-283_PUB +Input = "test" +Output = 304C022401E7912517C6899732E09756B1660F6B96635D638283DF9A8A11D30E008895D7F5C9C7F3022400887E75CBD0B7DD9DE30ED79BDB3D78E4F1121C5EAFF5946918F594F88D363644789DA7 + +DigestVerify = SHA512 +Key = K-283_PUB +Input = "test" +Output = 304C022401E7912517C6899732E09756B1660F6B96635D638283DF9A8A11D30E008895D7F5C9C7F3022400887E75CBD0B7DD9DE30ED79BDB3D78E4F1121C5EAFF5946918F594F88D363644789DA6 +Result = VERIFY_ERROR + +Title = RFC 6979 K-409 deterministic ECDSA tests + +PrivateKey=K-409_PRIV +-----BEGIN PRIVATE KEY----- +MFECAQAwEAYHKoZIzj0CAQYFK4EEACQEOjA4AgEBBDMpwWdo8B0bion9qF4u/XOglVi5KheKKTHz +WeTXCthT5WnNrxbapWl1j7TnMInkUl2Lv88= +-----END PRIVATE KEY----- + +PublicKey=K-409_PUB +-----BEGIN PUBLIC KEY----- +MH4wEAYHKoZIzj0CAQYFK4EEACQDagAEAM+SP1I/40puhj2LpF+x/m14TI8hnEFO7024Ni2708px +rrKPVoZo1degCT4rhPb611nbQgE7HDdNUTKXihsRI+u+mlxU0anVawmv20rek8zXxNMy4pFvfUud +GFeO48Li3k0uzg3mNUk= +-----END PUBLIC KEY----- + +PrivPubKeyPair=K-409_PRIV:K-409_PUB + +DigestSign = SHA1 +Key = K-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A02337192EE99EC7AFE23E02CB1F9850D1ECE620475EDA6B65D04984029408EC1E5A6476BC940D81F218FC31D979814CAC6E78340FA02331DE75DE97CBE740FC79A6B5B22BC2B7832C687E6960F0B8173D5D8BE2A75AC6CA43438BAF69C669CE6D64E0FB93BC5854E0F81 + +DigestVerify = SHA1 +Key = K-409_PUB +Input = "sample" +Output = 306A02337192EE99EC7AFE23E02CB1F9850D1ECE620475EDA6B65D04984029408EC1E5A6476BC940D81F218FC31D979814CAC6E78340FA02331DE75DE97CBE740FC79A6B5B22BC2B7832C687E6960F0B8173D5D8BE2A75AC6CA43438BAF69C669CE6D64E0FB93BC5854E0F81 + +DigestVerify = SHA1 +Key = K-409_PUB +Input = "sample" +Output = 306A02337192EE99EC7AFE23E02CB1F9850D1ECE620475EDA6B65D04984029408EC1E5A6476BC940D81F218FC31D979814CAC6E78340FA02331DE75DE97CBE740FC79A6B5B22BC2B7832C687E6960F0B8173D5D8BE2A75AC6CA43438BAF69C669CE6D64E0FB93BC5854E0F80 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A023341C8EDF39D5E4E76A04D24E6BFD4B2EC35F99CD2483478FD8B0A03E99379576EDACC4167590B7D9C387857A5130B1220CB771F0233659652EEAC9747BCAD58034B25362B6AA61836E1BA50E2F37630813050D43457E62EAB0F13AE197E6CFE0244F983107555E269 + +DigestVerify = SHA224 +Key = K-409_PUB +Input = "sample" +Output = 306A023341C8EDF39D5E4E76A04D24E6BFD4B2EC35F99CD2483478FD8B0A03E99379576EDACC4167590B7D9C387857A5130B1220CB771F0233659652EEAC9747BCAD58034B25362B6AA61836E1BA50E2F37630813050D43457E62EAB0F13AE197E6CFE0244F983107555E269 + +DigestVerify = SHA224 +Key = K-409_PUB +Input = "sample" +Output = 306A023341C8EDF39D5E4E76A04D24E6BFD4B2EC35F99CD2483478FD8B0A03E99379576EDACC4167590B7D9C387857A5130B1220CB771F0233659652EEAC9747BCAD58034B25362B6AA61836E1BA50E2F37630813050D43457E62EAB0F13AE197E6CFE0244F983107555E268 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A023349EC220D6D24980693E6D33B191532EAB4C5D924E97E305E2C1CCFE6F1EAEF96C17F6EC27D1E06191023615368628A7E0BD6A902331A4AB1DD9BAAA21F77C503E1B39E770FFD44718349D54BA4CF08F688CE89D7D7C5F7213F225944BE5F7C9BA42B8BEE382F8AF9 + +DigestVerify = SHA256 +Key = K-409_PUB +Input = "sample" +Output = 306A023349EC220D6D24980693E6D33B191532EAB4C5D924E97E305E2C1CCFE6F1EAEF96C17F6EC27D1E06191023615368628A7E0BD6A902331A4AB1DD9BAAA21F77C503E1B39E770FFD44718349D54BA4CF08F688CE89D7D7C5F7213F225944BE5F7C9BA42B8BEE382F8AF9 + +DigestVerify = SHA256 +Key = K-409_PUB +Input = "sample" +Output = 306A023349EC220D6D24980693E6D33B191532EAB4C5D924E97E305E2C1CCFE6F1EAEF96C17F6EC27D1E06191023615368628A7E0BD6A902331A4AB1DD9BAAA21F77C503E1B39E770FFD44718349D54BA4CF08F688CE89D7D7C5F7213F225944BE5F7C9BA42B8BEE382F8AF8 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A0233562BB99EE027644EC04E493C5E81B41F261F6BD18FB2FAE3AFEAD91FAB8DD44AFA910B13B9C79C87555225219E44E72245BB7C023325BA5F28047DDDBDA7ED7E49DA31B62B20FD9C7E5B8988817BBF738B3F4DFDD2DCD06EE6DF2A1B744C850DAF952C12B9A56774 + +DigestVerify = SHA384 +Key = K-409_PUB +Input = "sample" +Output = 306A0233562BB99EE027644EC04E493C5E81B41F261F6BD18FB2FAE3AFEAD91FAB8DD44AFA910B13B9C79C87555225219E44E72245BB7C023325BA5F28047DDDBDA7ED7E49DA31B62B20FD9C7E5B8988817BBF738B3F4DFDD2DCD06EE6DF2A1B744C850DAF952C12B9A56774 + +DigestVerify = SHA384 +Key = K-409_PUB +Input = "sample" +Output = 306A0233562BB99EE027644EC04E493C5E81B41F261F6BD18FB2FAE3AFEAD91FAB8DD44AFA910B13B9C79C87555225219E44E72245BB7C023325BA5F28047DDDBDA7ED7E49DA31B62B20FD9C7E5B8988817BBF738B3F4DFDD2DCD06EE6DF2A1B744C850DAF952C12B9A56775 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A023316C7E7FB33B5577F7CF6F77762F0F2D531C6E7A3528BD2CF582498C1A48F200789E9DF7B754029DA0D7E3CE96A2DC76093260602332729617EFBF80DA5D2F201AC7910D3404A992C39921C2F65F8CF4601392DFE933E6457EAFDBD13DFE160D243100378B55C290A + +DigestVerify = SHA512 +Key = K-409_PUB +Input = "sample" +Output = 306A023316C7E7FB33B5577F7CF6F77762F0F2D531C6E7A3528BD2CF582498C1A48F200789E9DF7B754029DA0D7E3CE96A2DC76093260602332729617EFBF80DA5D2F201AC7910D3404A992C39921C2F65F8CF4601392DFE933E6457EAFDBD13DFE160D243100378B55C290A + +DigestVerify = SHA512 +Key = K-409_PUB +Input = "sample" +Output = 306A023316C7E7FB33B5577F7CF6F77762F0F2D531C6E7A3528BD2CF582498C1A48F200789E9DF7B754029DA0D7E3CE96A2DC76093260602332729617EFBF80DA5D2F201AC7910D3404A992C39921C2F65F8CF4601392DFE933E6457EAFDBD13DFE160D243100378B55C290B +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = K-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306A0233565648A5BAD24E747A7D7531FA9DBDFCB184ECFEFDB00A319459242B68D0989E52BED4107AED35C27D8ECA10E876ACA48006C902337420BA6FF72ECC5C92B7CA0309258B5879F26393DB22753B9EC5DF905500A04228AC08880C485E2AC8834E13E8FA44FA57BF18 + +DigestVerify = SHA1 +Key = K-409_PUB +Input = "test" +Output = 306A0233565648A5BAD24E747A7D7531FA9DBDFCB184ECFEFDB00A319459242B68D0989E52BED4107AED35C27D8ECA10E876ACA48006C902337420BA6FF72ECC5C92B7CA0309258B5879F26393DB22753B9EC5DF905500A04228AC08880C485E2AC8834E13E8FA44FA57BF18 + +DigestVerify = SHA1 +Key = K-409_PUB +Input = "test" +Output = 306A0233565648A5BAD24E747A7D7531FA9DBDFCB184ECFEFDB00A319459242B68D0989E52BED4107AED35C27D8ECA10E876ACA48006C902337420BA6FF72ECC5C92B7CA0309258B5879F26393DB22753B9EC5DF905500A04228AC08880C485E2AC8834E13E8FA44FA57BF19 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306A0233251DFE54EAEC8A781ADF8A623F7F36B4ABFC7EE0AE78C8406E93B5C3932A8120AB8DFC49D8E243C7C30CB5B1E021BADBDF9CA4023377854C2E72EAA6924CC0B5F6751379D132569843B1C7885978DBBAA6678967F643A50DBB06E6EA6102FFAB7766A57C3887BD22 + +DigestVerify = SHA224 +Key = K-409_PUB +Input = "test" +Output = 306A0233251DFE54EAEC8A781ADF8A623F7F36B4ABFC7EE0AE78C8406E93B5C3932A8120AB8DFC49D8E243C7C30CB5B1E021BADBDF9CA4023377854C2E72EAA6924CC0B5F6751379D132569843B1C7885978DBBAA6678967F643A50DBB06E6EA6102FFAB7766A57C3887BD22 + +DigestVerify = SHA224 +Key = K-409_PUB +Input = "test" +Output = 306A0233251DFE54EAEC8A781ADF8A623F7F36B4ABFC7EE0AE78C8406E93B5C3932A8120AB8DFC49D8E243C7C30CB5B1E021BADBDF9CA4023377854C2E72EAA6924CC0B5F6751379D132569843B1C7885978DBBAA6678967F643A50DBB06E6EA6102FFAB7766A57C3887BD23 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306A023358075FF7E8D36844EED0FC3F78B7CFFDEEF6ADE5982D5636552A081923E24841C9E37DF2C8C4BF2F2F7A174927F3B7E6A0BEB202330A737469D013A31B91E781CE201100FDE1FA488ABF2252C025C678462D715AD3078C9D049E06555CABDF37878CFB909553FF51 + +DigestVerify = SHA256 +Key = K-409_PUB +Input = "test" +Output = 306A023358075FF7E8D36844EED0FC3F78B7CFFDEEF6ADE5982D5636552A081923E24841C9E37DF2C8C4BF2F2F7A174927F3B7E6A0BEB202330A737469D013A31B91E781CE201100FDE1FA488ABF2252C025C678462D715AD3078C9D049E06555CABDF37878CFB909553FF51 + +DigestVerify = SHA256 +Key = K-409_PUB +Input = "test" +Output = 306A023358075FF7E8D36844EED0FC3F78B7CFFDEEF6ADE5982D5636552A081923E24841C9E37DF2C8C4BF2F2F7A174927F3B7E6A0BEB202330A737469D013A31B91E781CE201100FDE1FA488ABF2252C025C678462D715AD3078C9D049E06555CABDF37878CFB909553FF50 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306A02331C5C88642EA216682244E46E24B7CE9AAEF9B3F97E585577D158C3CBC3C598250A53F6D46DFB1E2DD9DC302E7DA4F0CAAFF29102331D3FD721C35872C74514359F88AD983E170E5DE5B31AFC0BE12E9F4AB2B2538C7797686BA955C1D042FD1F8CDC482775579F11 + +DigestVerify = SHA384 +Key = K-409_PUB +Input = "test" +Output = 306A02331C5C88642EA216682244E46E24B7CE9AAEF9B3F97E585577D158C3CBC3C598250A53F6D46DFB1E2DD9DC302E7DA4F0CAAFF29102331D3FD721C35872C74514359F88AD983E170E5DE5B31AFC0BE12E9F4AB2B2538C7797686BA955C1D042FD1F8CDC482775579F11 + +DigestVerify = SHA384 +Key = K-409_PUB +Input = "test" +Output = 306A02331C5C88642EA216682244E46E24B7CE9AAEF9B3F97E585577D158C3CBC3C598250A53F6D46DFB1E2DD9DC302E7DA4F0CAAFF29102331D3FD721C35872C74514359F88AD983E170E5DE5B31AFC0BE12E9F4AB2B2538C7797686BA955C1D042FD1F8CDC482775579F10 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306A02331A32CD7764149DF79349DBF79451F4585BB490BD63A200700D7111B45DDA414000AE1B0A69AEACBA1364DD7719968AAD123F930233582AB1076CAFAE23A76244B82341AEFC4C6D8D8060A62A352C33187720C8A37F3DAC227E62758B11DF1562FD249941C1679F82 + +DigestVerify = SHA512 +Key = K-409_PUB +Input = "test" +Output = 306A02331A32CD7764149DF79349DBF79451F4585BB490BD63A200700D7111B45DDA414000AE1B0A69AEACBA1364DD7719968AAD123F930233582AB1076CAFAE23A76244B82341AEFC4C6D8D8060A62A352C33187720C8A37F3DAC227E62758B11DF1562FD249941C1679F82 + +DigestVerify = SHA512 +Key = K-409_PUB +Input = "test" +Output = 306A02331A32CD7764149DF79349DBF79451F4585BB490BD63A200700D7111B45DDA414000AE1B0A69AEACBA1364DD7719968AAD123F930233582AB1076CAFAE23A76244B82341AEFC4C6D8D8060A62A352C33187720C8A37F3DAC227E62758B11DF1562FD249941C1679F83 +Result = VERIFY_ERROR + +Title = RFC 6979 K-571 deterministic ECDSA tests + +PrivateKey=K-571_PRIV +-----BEGIN PRIVATE KEY----- +MGUCAQAwEAYHKoZIzj0CAQYFK4EEACYETjBMAgEBBEfBb1hVDYJO17lVadREU3XTpJC8fgGUxBo5 +3rcywpOWzfHWbeAt0UYKgWYG877A8yICx70Yoy2HUGRmqpIDLxMU7XsZdisNIg== +-----END PRIVATE KEY----- + +PublicKey=K-571_PUB +-----BEGIN PUBLIC KEY----- +MIGnMBAGByqGSM49AgEGBSuBBAAmA4GSAAQGz7DfdUHN1MQe8xnqiOhJ78hgXZd3kUgILsmRxGPt +MjGVlvn99HecF8ryDv2b61fp9O1Vv8UqL6FcojvGK3vwGdtZeT3XcxgBz8kRAvd1mlYb2NW1Gqru +x/QOZZ1nhwNhmQ1t4p9rT34YrhO95epcH3eyPWdvRAUMnb/M3Xs3VjKN2gWXearoRG/FFYp1wic= +-----END PUBLIC KEY----- + +PrivPubKeyPair=K-571_PRIV:K-571_PUB + +DigestSign = SHA1 +Key = K-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 3081930247767913F96C82E38B7146A505938B79EC07E9AA3214377651BE968B52C039D3E4837B4A2DE26C481C4E1DE96F4D9DE63845D9B32E26D0D332725678E3CE57F668A5E3108FB6CEA502480109F89F55FA39FF465E40EBCF869A9B1DB425AEA53AB4ECBCE3C310572F79315F5D4891461372A0C36E63871BEDDBB3BA2042C6410B67311F1A185589FF4C987DBA02F9D992B9DF + +DigestVerify = SHA1 +Key = K-571_PUB +Input = "sample" +Output = 3081930247767913F96C82E38B7146A505938B79EC07E9AA3214377651BE968B52C039D3E4837B4A2DE26C481C4E1DE96F4D9DE63845D9B32E26D0D332725678E3CE57F668A5E3108FB6CEA502480109F89F55FA39FF465E40EBCF869A9B1DB425AEA53AB4ECBCE3C310572F79315F5D4891461372A0C36E63871BEDDBB3BA2042C6410B67311F1A185589FF4C987DBA02F9D992B9DF + +DigestVerify = SHA1 +Key = K-571_PUB +Input = "sample" +Output = 3081930247767913F96C82E38B7146A505938B79EC07E9AA3214377651BE968B52C039D3E4837B4A2DE26C481C4E1DE96F4D9DE63845D9B32E26D0D332725678E3CE57F668A5E3108FB6CEA502480109F89F55FA39FF465E40EBCF869A9B1DB425AEA53AB4ECBCE3C310572F79315F5D4891461372A0C36E63871BEDDBB3BA2042C6410B67311F1A185589FF4C987DBA02F9D992B9DE +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 308192024710774B9F14DE6C9525131AD61531FA30987170D43782E9FB84FF0D70F093946DF75ECB69D400FE39B12D58C67C19DCE96335CEC1D9AADE004FE5B498AB8A940D46C8444348686A02476DFE9AA5FEA6CF2CEDC06EE1F9FD9853D411F0B958F1C9C519C90A85F6D24C1C3435B3CDF4E207B4A67467C87B7543F6C0948DD382D24D1E48B3763EC27D4D32A0151C240CC5E0 + +DigestVerify = SHA224 +Key = K-571_PUB +Input = "sample" +Output = 308192024710774B9F14DE6C9525131AD61531FA30987170D43782E9FB84FF0D70F093946DF75ECB69D400FE39B12D58C67C19DCE96335CEC1D9AADE004FE5B498AB8A940D46C8444348686A02476DFE9AA5FEA6CF2CEDC06EE1F9FD9853D411F0B958F1C9C519C90A85F6D24C1C3435B3CDF4E207B4A67467C87B7543F6C0948DD382D24D1E48B3763EC27D4D32A0151C240CC5E0 + +DigestVerify = SHA224 +Key = K-571_PUB +Input = "sample" +Output = 308192024710774B9F14DE6C9525131AD61531FA30987170D43782E9FB84FF0D70F093946DF75ECB69D400FE39B12D58C67C19DCE96335CEC1D9AADE004FE5B498AB8A940D46C8444348686A02476DFE9AA5FEA6CF2CEDC06EE1F9FD9853D411F0B958F1C9C519C90A85F6D24C1C3435B3CDF4E207B4A67467C87B7543F6C0948DD382D24D1E48B3763EC27D4D32A0151C240CC5E1 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 308194024801604BE98D1A27CEC2D3FA4BD07B42799E07743071E4905D7DCE7F6992B21A27F14F55D0FE5A7810DF65CF07F2F2554658817E5A88D952282EA1B8310514C0B40FFF46F1599651680248018249377C654B8588475510F7B797081F68C2F8CCCE49F730353B2DA3364B1CD3E984813E11BB791824038EA367BA74583AB97A69AF2D77FA691AA694E348E15DA76F5A44EC1F40 + +DigestVerify = SHA256 +Key = K-571_PUB +Input = "sample" +Output = 308194024801604BE98D1A27CEC2D3FA4BD07B42799E07743071E4905D7DCE7F6992B21A27F14F55D0FE5A7810DF65CF07F2F2554658817E5A88D952282EA1B8310514C0B40FFF46F1599651680248018249377C654B8588475510F7B797081F68C2F8CCCE49F730353B2DA3364B1CD3E984813E11BB791824038EA367BA74583AB97A69AF2D77FA691AA694E348E15DA76F5A44EC1F40 + +DigestVerify = SHA256 +Key = K-571_PUB +Input = "sample" +Output = 308194024801604BE98D1A27CEC2D3FA4BD07B42799E07743071E4905D7DCE7F6992B21A27F14F55D0FE5A7810DF65CF07F2F2554658817E5A88D952282EA1B8310514C0B40FFF46F1599651680248018249377C654B8588475510F7B797081F68C2F8CCCE49F730353B2DA3364B1CD3E984813E11BB791824038EA367BA74583AB97A69AF2D77FA691AA694E348E15DA76F5A44EC1F41 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 308193024801E6D7FB237040EA1904CCBF0984B81B866DE10D8AA93B06364C4A46F6C9573FA288C8BDDCC0C6B984E6AA75B42E7BF82FF34D51DFFBD7C87FDBFAD971656185BD12E4B8372F4BF102474F94550072ADA7E8C82B7E83577DD39959577799CDABCEA60E267F36F1BEB981ABF24E722A7F031582D2CC5D80DAA7C0DEEBBE1AC5E729A6DBB34A5D645B698719FCA409FBA370 + +DigestVerify = SHA384 +Key = K-571_PUB +Input = "sample" +Output = 308193024801E6D7FB237040EA1904CCBF0984B81B866DE10D8AA93B06364C4A46F6C9573FA288C8BDDCC0C6B984E6AA75B42E7BF82FF34D51DFFBD7C87FDBFAD971656185BD12E4B8372F4BF102474F94550072ADA7E8C82B7E83577DD39959577799CDABCEA60E267F36F1BEB981ABF24E722A7F031582D2CC5D80DAA7C0DEEBBE1AC5E729A6DBB34A5D645B698719FCA409FBA370 + +DigestVerify = SHA384 +Key = K-571_PUB +Input = "sample" +Output = 308193024801E6D7FB237040EA1904CCBF0984B81B866DE10D8AA93B06364C4A46F6C9573FA288C8BDDCC0C6B984E6AA75B42E7BF82FF34D51DFFBD7C87FDBFAD971656185BD12E4B8372F4BF102474F94550072ADA7E8C82B7E83577DD39959577799CDABCEA60E267F36F1BEB981ABF24E722A7F031582D2CC5D80DAA7C0DEEBBE1AC5E729A6DBB34A5D645B698719FCA409FBA371 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 30819402480086C9E048EADD7D3D2908501086F3AF449A01AF6BEB2026DC381B39530BCDDBE8E854251CBD5C31E6976553813C11213E4761CB8CA2E5352240AD9FB9C635D55FAB13AE42E4EE4F0248009FEE0A68F322B380217FCF6ABFF15D78C432BD8DD82E18B6BA877C01C860E24410F5150A44F979920147826219766ECB4E2E11A151B6A15BB8E2E825AC95BCCA228D8A1C9D3568 + +DigestVerify = SHA512 +Key = K-571_PUB +Input = "sample" +Output = 30819402480086C9E048EADD7D3D2908501086F3AF449A01AF6BEB2026DC381B39530BCDDBE8E854251CBD5C31E6976553813C11213E4761CB8CA2E5352240AD9FB9C635D55FAB13AE42E4EE4F0248009FEE0A68F322B380217FCF6ABFF15D78C432BD8DD82E18B6BA877C01C860E24410F5150A44F979920147826219766ECB4E2E11A151B6A15BB8E2E825AC95BCCA228D8A1C9D3568 + +DigestVerify = SHA512 +Key = K-571_PUB +Input = "sample" +Output = 30819402480086C9E048EADD7D3D2908501086F3AF449A01AF6BEB2026DC381B39530BCDDBE8E854251CBD5C31E6976553813C11213E4761CB8CA2E5352240AD9FB9C635D55FAB13AE42E4EE4F0248009FEE0A68F322B380217FCF6ABFF15D78C432BD8DD82E18B6BA877C01C860E24410F5150A44F979920147826219766ECB4E2E11A151B6A15BB8E2E825AC95BCCA228D8A1C9D3569 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = K-571_PRIV +NonceType = deterministic +Input = "test" +Output = 308194024801D055F499A3F7E3FC73D6E7D517B470879BDCB14ABC938369F23643C7B96D0242C1FF326FDAF1CCC8593612ACE982209658E73C24C9EC493B785608669DA74A5B7C9A1D8EA843BC024801621376C53CFE3390A0520D2C657B1FF0EBB10E4B9C2510EDC39D04FEBAF12B8502B098A8B8F842EA6E8EB9D55CFEF94B7FF6D145AC3FFCE71BD978FEA3EF8194D4AB5293A8F3EA + +DigestVerify = SHA1 +Key = K-571_PUB +Input = "test" +Output = 308194024801D055F499A3F7E3FC73D6E7D517B470879BDCB14ABC938369F23643C7B96D0242C1FF326FDAF1CCC8593612ACE982209658E73C24C9EC493B785608669DA74A5B7C9A1D8EA843BC024801621376C53CFE3390A0520D2C657B1FF0EBB10E4B9C2510EDC39D04FEBAF12B8502B098A8B8F842EA6E8EB9D55CFEF94B7FF6D145AC3FFCE71BD978FEA3EF8194D4AB5293A8F3EA + +DigestVerify = SHA1 +Key = K-571_PUB +Input = "test" +Output = 308194024801D055F499A3F7E3FC73D6E7D517B470879BDCB14ABC938369F23643C7B96D0242C1FF326FDAF1CCC8593612ACE982209658E73C24C9EC493B785608669DA74A5B7C9A1D8EA843BC024801621376C53CFE3390A0520D2C657B1FF0EBB10E4B9C2510EDC39D04FEBAF12B8502B098A8B8F842EA6E8EB9D55CFEF94B7FF6D145AC3FFCE71BD978FEA3EF8194D4AB5293A8F3EB +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = K-571_PRIV +NonceType = deterministic +Input = "test" +Output = 3081940248018709BDE4E9B73D046CE0D48842C97063DA54DCCA28DCB087168FA37DA2BF5FDBE4720EE48D49EDE4DD5BD31AC0149DB8297BD410F9BC02A11EB79B60C8EE63AF51B65267D718810248012D8B9E98FBF1D264D78669E236319D8FFD8426C56AFB10C76471EE88D7F0AB1B158E685B6D93C850D47FB1D02E4B24527473DB60B8D1AEF26CEEBD3467B65A70FFDDC0DBB64D5F + +DigestVerify = SHA224 +Key = K-571_PUB +Input = "test" +Output = 3081940248018709BDE4E9B73D046CE0D48842C97063DA54DCCA28DCB087168FA37DA2BF5FDBE4720EE48D49EDE4DD5BD31AC0149DB8297BD410F9BC02A11EB79B60C8EE63AF51B65267D718810248012D8B9E98FBF1D264D78669E236319D8FFD8426C56AFB10C76471EE88D7F0AB1B158E685B6D93C850D47FB1D02E4B24527473DB60B8D1AEF26CEEBD3467B65A70FFDDC0DBB64D5F + +DigestVerify = SHA224 +Key = K-571_PUB +Input = "test" +Output = 3081940248018709BDE4E9B73D046CE0D48842C97063DA54DCCA28DCB087168FA37DA2BF5FDBE4720EE48D49EDE4DD5BD31AC0149DB8297BD410F9BC02A11EB79B60C8EE63AF51B65267D718810248012D8B9E98FBF1D264D78669E236319D8FFD8426C56AFB10C76471EE88D7F0AB1B158E685B6D93C850D47FB1D02E4B24527473DB60B8D1AEF26CEEBD3467B65A70FFDDC0DBB64D5E +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = K-571_PRIV +NonceType = deterministic +Input = "test" +Output = 308194024801F5BF6B044048E0E310309FFDAC825290A69634A0D3592DBEE7BE71F69E45412F766AC92E174CC99AABAA5C9C89FCB187DFDBCC7A26765DB6D9F1EEC8A6127BBDFA5801E44E3BEC024801B44CBFB233BFA2A98D5E8B2F0B2C27F9494BEAA77FEB59CDE3E7AE9CB2E385BE8DA7B80D7944AA71E0654E5067E9A70E88E68833054EED49F28283F02B229123995AF37A6089F0 + +DigestVerify = SHA256 +Key = K-571_PUB +Input = "test" +Output = 308194024801F5BF6B044048E0E310309FFDAC825290A69634A0D3592DBEE7BE71F69E45412F766AC92E174CC99AABAA5C9C89FCB187DFDBCC7A26765DB6D9F1EEC8A6127BBDFA5801E44E3BEC024801B44CBFB233BFA2A98D5E8B2F0B2C27F9494BEAA77FEB59CDE3E7AE9CB2E385BE8DA7B80D7944AA71E0654E5067E9A70E88E68833054EED49F28283F02B229123995AF37A6089F0 + +DigestVerify = SHA256 +Key = K-571_PUB +Input = "test" +Output = 308194024801F5BF6B044048E0E310309FFDAC825290A69634A0D3592DBEE7BE71F69E45412F766AC92E174CC99AABAA5C9C89FCB187DFDBCC7A26765DB6D9F1EEC8A6127BBDFA5801E44E3BEC024801B44CBFB233BFA2A98D5E8B2F0B2C27F9494BEAA77FEB59CDE3E7AE9CB2E385BE8DA7B80D7944AA71E0654E5067E9A70E88E68833054EED49F28283F02B229123995AF37A6089F1 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = K-571_PRIV +NonceType = deterministic +Input = "test" +Output = 3081940248011F61A6EFAB6D83053D9C52665B3542FF3F63BD5913E527BDBA07FBAF34BC766C2EC83163C5273243AA834C75FDDD1BC8A2BEAD388CD06C4EBA1962D645EEB35E92D44E8F2E081D0248016BF6341876F051DF224770CC8BA0E4D48B3332568A2B014BC80827BAA89DE18D1AEBC73E3BE8F85A8008C682AAC7D5F0E9FB5ECBEFBB637E30E4A0F226D2C2AA3E569BB54AB72B + +DigestVerify = SHA384 +Key = K-571_PUB +Input = "test" +Output = 3081940248011F61A6EFAB6D83053D9C52665B3542FF3F63BD5913E527BDBA07FBAF34BC766C2EC83163C5273243AA834C75FDDD1BC8A2BEAD388CD06C4EBA1962D645EEB35E92D44E8F2E081D0248016BF6341876F051DF224770CC8BA0E4D48B3332568A2B014BC80827BAA89DE18D1AEBC73E3BE8F85A8008C682AAC7D5F0E9FB5ECBEFBB637E30E4A0F226D2C2AA3E569BB54AB72B + +DigestVerify = SHA384 +Key = K-571_PUB +Input = "test" +Output = 3081940248011F61A6EFAB6D83053D9C52665B3542FF3F63BD5913E527BDBA07FBAF34BC766C2EC83163C5273243AA834C75FDDD1BC8A2BEAD388CD06C4EBA1962D645EEB35E92D44E8F2E081D0248016BF6341876F051DF224770CC8BA0E4D48B3332568A2B014BC80827BAA89DE18D1AEBC73E3BE8F85A8008C682AAC7D5F0E9FB5ECBEFBB637E30E4A0F226D2C2AA3E569BB54AB72A +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = K-571_PRIV +NonceType = deterministic +Input = "test" +Output = 308194024800F1E50353A39EA64CDF23081D6BB4B2A91DD73E99D3DD5A1AA1C49B4F6E34A665EAD24FD530B9103D522609A395AF3EF174C85206F67EF84835ED1632E0F6BAB718EA90DF9E2DA0024800B385004D7596625028E3FDE72282DE4EDC5B4CE33C1127F21CC37527C90B7307AE7D09281B840AEBCECAA711B00718103DDB32B3E9F6A9FBC6AF23E224A73B9435F619D9C62527 + +DigestVerify = SHA512 +Key = K-571_PUB +Input = "test" +Output = 308194024800F1E50353A39EA64CDF23081D6BB4B2A91DD73E99D3DD5A1AA1C49B4F6E34A665EAD24FD530B9103D522609A395AF3EF174C85206F67EF84835ED1632E0F6BAB718EA90DF9E2DA0024800B385004D7596625028E3FDE72282DE4EDC5B4CE33C1127F21CC37527C90B7307AE7D09281B840AEBCECAA711B00718103DDB32B3E9F6A9FBC6AF23E224A73B9435F619D9C62527 + +DigestVerify = SHA512 +Key = K-571_PUB +Input = "test" +Output = 308194024800F1E50353A39EA64CDF23081D6BB4B2A91DD73E99D3DD5A1AA1C49B4F6E34A665EAD24FD530B9103D522609A395AF3EF174C85206F67EF84835ED1632E0F6BAB718EA90DF9E2DA0024800B385004D7596625028E3FDE72282DE4EDC5B4CE33C1127F21CC37527C90B7307AE7D09281B840AEBCECAA711B00718103DDB32B3E9F6A9FBC6AF23E224A73B9435F619D9C62526 +Result = VERIFY_ERROR + +Title = RFC 6979 B-163 deterministic ECDSA tests + +PrivateKey=B-163_PRIV +-----BEGIN PRIVATE KEY----- +MDMCAQAwEAYHKoZIzj0CAQYFK4EEAA8EHDAaAgEBBBUDUxj8RH1I1+a8k7SGF93e3yaqZY8= +-----END PRIVATE KEY----- + +PublicKey=B-163_PUB +-----BEGIN PUBLIC KEY----- +MEAwEAYHKoZIzj0CAQYFK4EEAA8DLAAEASbPVi2Vodd9OHunWj6joUB/I0JaB9fLUnPJTajKkwSa +/aGHIcJGcr1x +-----END PUBLIC KEY----- + +PrivPubKeyPair=B-163_PRIV:B-163_PUB + +DigestSign = SHA1 +Key = B-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E02150153FEBD179A69B6122DEBF5BC61EB947B24C935260215037AC9C670F8CF18045049BAE7DD35553545C19E49 + +DigestVerify = SHA1 +Key = B-163_PUB +Input = "sample" +Output = 302E02150153FEBD179A69B6122DEBF5BC61EB947B24C935260215037AC9C670F8CF18045049BAE7DD35553545C19E49 + +DigestVerify = SHA1 +Key = B-163_PUB +Input = "sample" +Output = 302E02150153FEBD179A69B6122DEBF5BC61EB947B24C935260215037AC9C670F8CF18045049BAE7DD35553545C19E48 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302D021500A379E69C44F9C16EA3215EA39EB1A9B5D58CC95502144BAFF5308DA2A7FE2C1742769265AD3ED1D24E74 + +DigestVerify = SHA224 +Key = B-163_PUB +Input = "sample" +Output = 302D021500A379E69C44F9C16EA3215EA39EB1A9B5D58CC95502144BAFF5308DA2A7FE2C1742769265AD3ED1D24E74 + +DigestVerify = SHA224 +Key = B-163_PUB +Input = "sample" +Output = 302D021500A379E69C44F9C16EA3215EA39EB1A9B5D58CC95502144BAFF5308DA2A7FE2C1742769265AD3ED1D24E75 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E02150134E00F78FC1CB9501675D91C401DE20DDF228CDC02150373273AEC6C36CB7BAFBB1903A5F5EA6A1D50B624 + +DigestVerify = SHA256 +Key = B-163_PUB +Input = "sample" +Output = 302E02150134E00F78FC1CB9501675D91C401DE20DDF228CDC02150373273AEC6C36CB7BAFBB1903A5F5EA6A1D50B624 + +DigestVerify = SHA256 +Key = B-163_PUB +Input = "sample" +Output = 302E02150134E00F78FC1CB9501675D91C401DE20DDF228CDC02150373273AEC6C36CB7BAFBB1903A5F5EA6A1D50B625 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E0215029430B935AF8E77519B0CA4F6903B0B82E6A21A66021501EA1415306E9353FA5AA54BC7C2581DFBB888440D + +DigestVerify = SHA384 +Key = B-163_PUB +Input = "sample" +Output = 302E0215029430B935AF8E77519B0CA4F6903B0B82E6A21A66021501EA1415306E9353FA5AA54BC7C2581DFBB888440D + +DigestVerify = SHA384 +Key = B-163_PUB +Input = "sample" +Output = 302E0215029430B935AF8E77519B0CA4F6903B0B82E6A21A66021501EA1415306E9353FA5AA54BC7C2581DFBB888440C +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-163_PRIV +NonceType = deterministic +Input = "sample" +Output = 302E021500B2F177A99F9DF2D51CCAF55F015F326E4B65E7A0021500DF1FB4487E9B120C5E970EFE48F55E406306C3A1 + +DigestVerify = SHA512 +Key = B-163_PUB +Input = "sample" +Output = 302E021500B2F177A99F9DF2D51CCAF55F015F326E4B65E7A0021500DF1FB4487E9B120C5E970EFE48F55E406306C3A1 + +DigestVerify = SHA512 +Key = B-163_PUB +Input = "sample" +Output = 302E021500B2F177A99F9DF2D51CCAF55F015F326E4B65E7A0021500DF1FB4487E9B120C5E970EFE48F55E406306C3A0 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = B-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E02150256D4079C6C7169B8BC92529D701776A269D5630802150341D3FFEC9F1EB6A6ACBE88E3C86A1C8FDEB8B8E1 + +DigestVerify = SHA1 +Key = B-163_PUB +Input = "test" +Output = 302E02150256D4079C6C7169B8BC92529D701776A269D5630802150341D3FFEC9F1EB6A6ACBE88E3C86A1C8FDEB8B8E1 + +DigestVerify = SHA1 +Key = B-163_PUB +Input = "test" +Output = 302E02150256D4079C6C7169B8BC92529D701776A269D5630802150341D3FFEC9F1EB6A6ACBE88E3C86A1C8FDEB8B8E0 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E0215028ECC6F1272CE80EA59DCF32F7AC2D861BA803393021500AD4AE2C06E60183C1567D2B82F19421FE3053CE2 + +DigestVerify = SHA224 +Key = B-163_PUB +Input = "test" +Output = 302E0215028ECC6F1272CE80EA59DCF32F7AC2D861BA803393021500AD4AE2C06E60183C1567D2B82F19421FE3053CE2 + +DigestVerify = SHA224 +Key = B-163_PUB +Input = "test" +Output = 302E0215028ECC6F1272CE80EA59DCF32F7AC2D861BA803393021500AD4AE2C06E60183C1567D2B82F19421FE3053CE3 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E02150227DF377B3FA50F90C1CB3CDCBBDBA552C1D35104021501F7BEAD92583FE920D353F368C1960D0E88B46A56 + +DigestVerify = SHA256 +Key = B-163_PUB +Input = "test" +Output = 302E02150227DF377B3FA50F90C1CB3CDCBBDBA552C1D35104021501F7BEAD92583FE920D353F368C1960D0E88B46A56 + +DigestVerify = SHA256 +Key = B-163_PUB +Input = "test" +Output = 302E02150227DF377B3FA50F90C1CB3CDCBBDBA552C1D35104021501F7BEAD92583FE920D353F368C1960D0E88B46A57 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E0215011811DAFEEA441845B6118A0DFEE8A0061231337D0215036258301865EE48C5C6F91D63F62695002AB55B57 + +DigestVerify = SHA384 +Key = B-163_PUB +Input = "test" +Output = 302E0215011811DAFEEA441845B6118A0DFEE8A0061231337D0215036258301865EE48C5C6F91D63F62695002AB55B57 + +DigestVerify = SHA384 +Key = B-163_PUB +Input = "test" +Output = 302E0215011811DAFEEA441845B6118A0DFEE8A0061231337D0215036258301865EE48C5C6F91D63F62695002AB55B56 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-163_PRIV +NonceType = deterministic +Input = "test" +Output = 302E021503B6BB95CA823BE2ED8E3972FF516EB8972D7655710215013DC6F420628969DF900C3FCC48220B38BE24A541 + +DigestVerify = SHA512 +Key = B-163_PUB +Input = "test" +Output = 302E021503B6BB95CA823BE2ED8E3972FF516EB8972D7655710215013DC6F420628969DF900C3FCC48220B38BE24A541 + +DigestVerify = SHA512 +Key = B-163_PUB +Input = "test" +Output = 302E021503B6BB95CA823BE2ED8E3972FF516EB8972D7655710215013DC6F420628969DF900C3FCC48220B38BE24A540 +Result = VERIFY_ERROR + +Title = RFC 6979 B-233 deterministic ECDSA tests + +PrivateKey=B-233_PRIV +-----BEGIN PRIVATE KEY----- +MDsCAQAwEAYHKoZIzj0CAQYFK4EEABsEJDAiAgEBBB163BPdW/NNHd7rULLOI7X15tGAZzBtYMX2 +/xHl0w== +-----END PRIVATE KEY----- + +PublicKey=B-233_PUB +-----BEGIN PUBLIC KEY----- +MFIwEAYHKoZIzj0CAQYFK4EEABsDPgAEAPs0izJGtHOqf7sqAbeNYbYsQiHQ+atV/HLbPfR4ARYv +ofbGrPf9jRn8fXS92RBAdugziYvEwEKm5r6/ +-----END PUBLIC KEY----- + +PrivPubKeyPair=B-233_PRIV:B-233_PUB + +DigestSign = SHA1 +Key = B-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303F021D15CC6FD78BB06E0878E71465515EA5A21A2C18E6FC77B4B158DBEB3944021E00822A4A6C2EB2DF213A5E90BF40377956365EE8C4B4A5A4E2EB9270CB6A + +DigestVerify = SHA1 +Key = B-233_PUB +Input = "sample" +Output = 303F021D15CC6FD78BB06E0878E71465515EA5A21A2C18E6FC77B4B158DBEB3944021E00822A4A6C2EB2DF213A5E90BF40377956365EE8C4B4A5A4E2EB9270CB6A + +DigestVerify = SHA1 +Key = B-233_PUB +Input = "sample" +Output = 303F021D15CC6FD78BB06E0878E71465515EA5A21A2C18E6FC77B4B158DBEB3944021E00822A4A6C2EB2DF213A5E90BF40377956365EE8C4B4A5A4E2EB9270CB6B +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303E021D5D9920B53471148E10502AB49AB7A3F11084820A074FD89883CF51BC1A021D4D3938900C0A9AAA7080D1DFEB56CFB0FADABE4214536C7ED5117ED13A + +DigestVerify = SHA224 +Key = B-233_PUB +Input = "sample" +Output = 303E021D5D9920B53471148E10502AB49AB7A3F11084820A074FD89883CF51BC1A021D4D3938900C0A9AAA7080D1DFEB56CFB0FADABE4214536C7ED5117ED13A + +DigestVerify = SHA224 +Key = B-233_PUB +Input = "sample" +Output = 303E021D5D9920B53471148E10502AB49AB7A3F11084820A074FD89883CF51BC1A021D4D3938900C0A9AAA7080D1DFEB56CFB0FADABE4214536C7ED5117ED13B +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303F021E00A797F3B8AEFCE7456202DF1E46CCC291EA5A49DA3D4BDDA9A4B62D5E0D021D1F6F81DA55C22DA4152134C661588F4BD6F82FDBAF0C5877096B070DC2 + +DigestVerify = SHA256 +Key = B-233_PUB +Input = "sample" +Output = 303F021E00A797F3B8AEFCE7456202DF1E46CCC291EA5A49DA3D4BDDA9A4B62D5E0D021D1F6F81DA55C22DA4152134C661588F4BD6F82FDBAF0C5877096B070DC2 + +DigestVerify = SHA256 +Key = B-233_PUB +Input = "sample" +Output = 303F021E00A797F3B8AEFCE7456202DF1E46CCC291EA5A49DA3D4BDDA9A4B62D5E0D021D1F6F81DA55C22DA4152134C661588F4BD6F82FDBAF0C5877096B070DC3 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303E021D15E85A8D46225DD7E314A1C4289731FC14DECE949349FE535D11043B85021D3F189D37F50493EFD5111A129443A662AB3C6B289129AD8C0CAC85119C + +DigestVerify = SHA384 +Key = B-233_PUB +Input = "sample" +Output = 303E021D15E85A8D46225DD7E314A1C4289731FC14DECE949349FE535D11043B85021D3F189D37F50493EFD5111A129443A662AB3C6B289129AD8C0CAC85119C + +DigestVerify = SHA384 +Key = B-233_PUB +Input = "sample" +Output = 303E021D15E85A8D46225DD7E314A1C4289731FC14DECE949349FE535D11043B85021D3F189D37F50493EFD5111A129443A662AB3C6B289129AD8C0CAC85119D +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-233_PRIV +NonceType = deterministic +Input = "sample" +Output = 303F021D3B62A4BF783919098B1E42F496E65F7621F01D1D466C46940F0F132A95021E00F4BE031C6E5239E7DAA014CBBF1ED19425E49DAEB426EC9DF4C28A2E30 + +DigestVerify = SHA512 +Key = B-233_PUB +Input = "sample" +Output = 303F021D3B62A4BF783919098B1E42F496E65F7621F01D1D466C46940F0F132A95021E00F4BE031C6E5239E7DAA014CBBF1ED19425E49DAEB426EC9DF4C28A2E30 + +DigestVerify = SHA512 +Key = B-233_PUB +Input = "sample" +Output = 303F021D3B62A4BF783919098B1E42F496E65F7621F01D1D466C46940F0F132A95021E00F4BE031C6E5239E7DAA014CBBF1ED19425E49DAEB426EC9DF4C28A2E31 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = B-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D2F1FEDC57BE203E4C8C6B8C1CEB35E13C1FCD956AB41E3BD4C8A6EFB1F021D5738EC8A8EDEA8E435EE7266AD3EDE1EEFC2CEBE2BE1D614008D5D2951 + +DigestVerify = SHA1 +Key = B-233_PUB +Input = "test" +Output = 303E021D2F1FEDC57BE203E4C8C6B8C1CEB35E13C1FCD956AB41E3BD4C8A6EFB1F021D5738EC8A8EDEA8E435EE7266AD3EDE1EEFC2CEBE2BE1D614008D5D2951 + +DigestVerify = SHA1 +Key = B-233_PUB +Input = "test" +Output = 303E021D2F1FEDC57BE203E4C8C6B8C1CEB35E13C1FCD956AB41E3BD4C8A6EFB1F021D5738EC8A8EDEA8E435EE7266AD3EDE1EEFC2CEBE2BE1D614008D5D2950 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-233_PRIV +NonceType = deterministic +Input = "test" +Output = 3040021E00CCE175124D3586BA7486F7146894C65C2A4A5A1904658E5C7F9DF5FA5D021E008804B456D847ACE5CA86D97BF79FD6335E5B17F6C0D964B5D0036C867E + +DigestVerify = SHA224 +Key = B-233_PUB +Input = "test" +Output = 3040021E00CCE175124D3586BA7486F7146894C65C2A4A5A1904658E5C7F9DF5FA5D021E008804B456D847ACE5CA86D97BF79FD6335E5B17F6C0D964B5D0036C867E + +DigestVerify = SHA224 +Key = B-233_PUB +Input = "test" +Output = 3040021E00CCE175124D3586BA7486F7146894C65C2A4A5A1904658E5C7F9DF5FA5D021E008804B456D847ACE5CA86D97BF79FD6335E5B17F6C0D964B5D0036C867F +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D35C3D6DFEEA1CFB29B93BE3FDB91A7B130951770C2690C16833A159677021D600F7301D12AB376B56D4459774159ADB51F97E282FF384406AFD53A02 + +DigestVerify = SHA256 +Key = B-233_PUB +Input = "test" +Output = 303E021D35C3D6DFEEA1CFB29B93BE3FDB91A7B130951770C2690C16833A159677021D600F7301D12AB376B56D4459774159ADB51F97E282FF384406AFD53A02 + +DigestVerify = SHA256 +Key = B-233_PUB +Input = "test" +Output = 303E021D35C3D6DFEEA1CFB29B93BE3FDB91A7B130951770C2690C16833A159677021D600F7301D12AB376B56D4459774159ADB51F97E282FF384406AFD53A03 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303E021D61602FC8068BFD5FB86027B97455D200EC603057446CCE4D76DB8EF42C021D3396DD0D59C067BB999B422D9883736CF9311DFD6951F91033BD03CA8D + +DigestVerify = SHA384 +Key = B-233_PUB +Input = "test" +Output = 303E021D61602FC8068BFD5FB86027B97455D200EC603057446CCE4D76DB8EF42C021D3396DD0D59C067BB999B422D9883736CF9311DFD6951F91033BD03CA8D + +DigestVerify = SHA384 +Key = B-233_PUB +Input = "test" +Output = 303E021D61602FC8068BFD5FB86027B97455D200EC603057446CCE4D76DB8EF42C021D3396DD0D59C067BB999B422D9883736CF9311DFD6951F91033BD03CA8C +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-233_PRIV +NonceType = deterministic +Input = "test" +Output = 303F021D7E12CB60FDD614958E8E34B3C12DDFF35D85A9C5800E31EA2CC2EF63B1021E00E8970FD99D836F3CC1C807A2C58760DE6EDAA23705A82B9CB1CE93FECC + +DigestVerify = SHA512 +Key = B-233_PUB +Input = "test" +Output = 303F021D7E12CB60FDD614958E8E34B3C12DDFF35D85A9C5800E31EA2CC2EF63B1021E00E8970FD99D836F3CC1C807A2C58760DE6EDAA23705A82B9CB1CE93FECC + +DigestVerify = SHA512 +Key = B-233_PUB +Input = "test" +Output = 303F021D7E12CB60FDD614958E8E34B3C12DDFF35D85A9C5800E31EA2CC2EF63B1021E00E8970FD99D836F3CC1C807A2C58760DE6EDAA23705A82B9CB1CE93FECD +Result = VERIFY_ERROR + +Title = RFC 6979 B-283 deterministic ECDSA tests + +PrivateKey=B-283_PRIV +-----BEGIN PRIVATE KEY----- +MEICAQAwEAYHKoZIzj0CAQYFK4EEABEEKzApAgEBBCQBRRDUvETy0m9FU5QsmAc8G9NVRc6rtcwT +iFPFFY0nKepAiDY= +-----END PRIVATE KEY----- + +PublicKey=B-283_PUB +-----BEGIN PUBLIC KEY----- +MF4wEAYHKoZIzj0CAQYFK4EEABEDSgAEAX40CaE8OZ8MqKGS8CjUbjRGvP/N9R/4qQXtLe14bnT5 +w+ipBH78vMMcAdhtGZL3v6wCd9vQKm0oknQJmiwPA5yPWfMYNxsO +-----END PUBLIC KEY----- + +PrivPubKeyPair=B-283_PRIV:B-283_PUB + +DigestSign = SHA1 +Key = B-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304C02240201E18D48C6DB3D5D097C4DCE1E25587E1501FC3CF47BDB5B4289D79E273D6A9ACB828502240151AE05712B024CE617358260774C8CA8B0E7A7E72EF8229BF2ACE7609560CB30322C4F + +DigestVerify = SHA1 +Key = B-283_PUB +Input = "sample" +Output = 304C02240201E18D48C6DB3D5D097C4DCE1E25587E1501FC3CF47BDB5B4289D79E273D6A9ACB828502240151AE05712B024CE617358260774C8CA8B0E7A7E72EF8229BF2ACE7609560CB30322C4F + +DigestVerify = SHA1 +Key = B-283_PUB +Input = "sample" +Output = 304C02240201E18D48C6DB3D5D097C4DCE1E25587E1501FC3CF47BDB5B4289D79E273D6A9ACB828502240151AE05712B024CE617358260774C8CA8B0E7A7E72EF8229BF2ACE7609560CB30322C4E +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304C02240143E878DDFD4DF40D97B8CD638B3C4706501C2201CF7108F2FB91478C11D69473246925022400CBF1B9717FEEA3AABB09D9654110144267098E0E1E8D0289A6211BE0EEDFDD86A3DB79 + +DigestVerify = SHA224 +Key = B-283_PUB +Input = "sample" +Output = 304C02240143E878DDFD4DF40D97B8CD638B3C4706501C2201CF7108F2FB91478C11D69473246925022400CBF1B9717FEEA3AABB09D9654110144267098E0E1E8D0289A6211BE0EEDFDD86A3DB79 + +DigestVerify = SHA224 +Key = B-283_PUB +Input = "sample" +Output = 304C02240143E878DDFD4DF40D97B8CD638B3C4706501C2201CF7108F2FB91478C11D69473246925022400CBF1B9717FEEA3AABB09D9654110144267098E0E1E8D0289A6211BE0EEDFDD86A3DB78 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304B0224029FD82497FB3E5CEF65579272138DE59E2B666B8689466572B3B69A172CEE83BE14565902235A89D9166B40795AF0FE5958201B9C0523E500013CA12B4840EA2BC53F25F9B3CE87C0 + +DigestVerify = SHA256 +Key = B-283_PUB +Input = "sample" +Output = 304B0224029FD82497FB3E5CEF65579272138DE59E2B666B8689466572B3B69A172CEE83BE14565902235A89D9166B40795AF0FE5958201B9C0523E500013CA12B4840EA2BC53F25F9B3CE87C0 + +DigestVerify = SHA256 +Key = B-283_PUB +Input = "sample" +Output = 304B0224029FD82497FB3E5CEF65579272138DE59E2B666B8689466572B3B69A172CEE83BE14565902235A89D9166B40795AF0FE5958201B9C0523E500013CA12B4840EA2BC53F25F9B3CE87C1 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304C022402F00689C1BFCD2A8C7A41E0DE55AE182E6463A152828EF89FE3525139B6603294E69353022401744514FE0A37447250C8A329EAAADA81572226CABA16F39270EE5DD03F27B1F665EB5D + +DigestVerify = SHA384 +Key = B-283_PUB +Input = "sample" +Output = 304C022402F00689C1BFCD2A8C7A41E0DE55AE182E6463A152828EF89FE3525139B6603294E69353022401744514FE0A37447250C8A329EAAADA81572226CABA16F39270EE5DD03F27B1F665EB5D + +DigestVerify = SHA384 +Key = B-283_PUB +Input = "sample" +Output = 304C022402F00689C1BFCD2A8C7A41E0DE55AE182E6463A152828EF89FE3525139B6603294E69353022401744514FE0A37447250C8A329EAAADA81572226CABA16F39270EE5DD03F27B1F665EB5C +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-283_PRIV +NonceType = deterministic +Input = "sample" +Output = 304C022400DA43A9ADFAA6AD767998A054C6A8F1CF77A562924628D73C62761847AD8286E0D91B47022401D118733AE2C88357827CAFC6F68ABC25C80C640532925E95CFE66D40F8792F3AC44C42 + +DigestVerify = SHA512 +Key = B-283_PUB +Input = "sample" +Output = 304C022400DA43A9ADFAA6AD767998A054C6A8F1CF77A562924628D73C62761847AD8286E0D91B47022401D118733AE2C88357827CAFC6F68ABC25C80C640532925E95CFE66D40F8792F3AC44C42 + +DigestVerify = SHA512 +Key = B-283_PUB +Input = "sample" +Output = 304C022400DA43A9ADFAA6AD767998A054C6A8F1CF77A562924628D73C62761847AD8286E0D91B47022401D118733AE2C88357827CAFC6F68ABC25C80C640532925E95CFE66D40F8792F3AC44C43 +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = B-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304A02235A408133919F2CDCDBE5E4C14FBC706C1F71BADAFEF41F5DE4EC27272FC1CA9366FBB2022312966272872C097FEA7BCE64FAB1A81982A773E26F6E4EF7C99969846E67CA9CBE1692 + +DigestVerify = SHA1 +Key = B-283_PUB +Input = "test" +Output = 304A02235A408133919F2CDCDBE5E4C14FBC706C1F71BADAFEF41F5DE4EC27272FC1CA9366FBB2022312966272872C097FEA7BCE64FAB1A81982A773E26F6E4EF7C99969846E67CA9CBE1692 + +DigestVerify = SHA1 +Key = B-283_PUB +Input = "test" +Output = 304A02235A408133919F2CDCDBE5E4C14FBC706C1F71BADAFEF41F5DE4EC27272FC1CA9366FBB2022312966272872C097FEA7BCE64FAB1A81982A773E26F6E4EF7C99969846E67CA9CBE1693 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304C0224008F3824E40C16FF1DDA8DC992776D26F4A5981AB5092956C4FDBB4F1AE0A711EEAA10E5022400A64B91EFADB213E11483FB61C73E3EF63D3B44EEFC56EA401B99DCC60CC28E99F0F1FA + +DigestVerify = SHA224 +Key = B-283_PUB +Input = "test" +Output = 304C0224008F3824E40C16FF1DDA8DC992776D26F4A5981AB5092956C4FDBB4F1AE0A711EEAA10E5022400A64B91EFADB213E11483FB61C73E3EF63D3B44EEFC56EA401B99DCC60CC28E99F0F1FA + +DigestVerify = SHA224 +Key = B-283_PUB +Input = "test" +Output = 304C0224008F3824E40C16FF1DDA8DC992776D26F4A5981AB5092956C4FDBB4F1AE0A711EEAA10E5022400A64B91EFADB213E11483FB61C73E3EF63D3B44EEFC56EA401B99DCC60CC28E99F0F1FB +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304C022403597B406F5329D11A79E887847E5EC60861CCBB19EC61F252DB7BD549C699951C182796022400A6A100B997BC622D91701D9F5C6F6D3815517E577622DA69D3A0E8917C1CBE63ACD345 + +DigestVerify = SHA256 +Key = B-283_PUB +Input = "test" +Output = 304C022403597B406F5329D11A79E887847E5EC60861CCBB19EC61F252DB7BD549C699951C182796022400A6A100B997BC622D91701D9F5C6F6D3815517E577622DA69D3A0E8917C1CBE63ACD345 + +DigestVerify = SHA256 +Key = B-283_PUB +Input = "test" +Output = 304C022403597B406F5329D11A79E887847E5EC60861CCBB19EC61F252DB7BD549C699951C182796022400A6A100B997BC622D91701D9F5C6F6D3815517E577622DA69D3A0E8917C1CBE63ACD344 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304C022401BB490926E5A1FDC7C5AA86D0835F9B994EDA315CA408002AF54A298728D422EBF59E4C0224036C682CFC9E2C89A782BFD3A191609D1F0C1910D5FD6981442070393159D65FBCC0A8BA + +DigestVerify = SHA384 +Key = B-283_PUB +Input = "test" +Output = 304C022401BB490926E5A1FDC7C5AA86D0835F9B994EDA315CA408002AF54A298728D422EBF59E4C0224036C682CFC9E2C89A782BFD3A191609D1F0C1910D5FD6981442070393159D65FBCC0A8BA + +DigestVerify = SHA384 +Key = B-283_PUB +Input = "test" +Output = 304C022401BB490926E5A1FDC7C5AA86D0835F9B994EDA315CA408002AF54A298728D422EBF59E4C0224036C682CFC9E2C89A782BFD3A191609D1F0C1910D5FD6981442070393159D65FBCC0A8BB +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-283_PRIV +NonceType = deterministic +Input = "test" +Output = 304B0224019944AA68F9778C2E3D6E240947613E6DA60EFCE9B9B2C063FF5466D72745B5A0B25BA202233F1567B3C5B02DF15C874F0EE22850824693D5ADC4663BAA19E384E550B1DD41F31EE6 + +DigestVerify = SHA512 +Key = B-283_PUB +Input = "test" +Output = 304B0224019944AA68F9778C2E3D6E240947613E6DA60EFCE9B9B2C063FF5466D72745B5A0B25BA202233F1567B3C5B02DF15C874F0EE22850824693D5ADC4663BAA19E384E550B1DD41F31EE6 + +DigestVerify = SHA512 +Key = B-283_PUB +Input = "test" +Output = 304B0224019944AA68F9778C2E3D6E240947613E6DA60EFCE9B9B2C063FF5466D72745B5A0B25BA202233F1567B3C5B02DF15C874F0EE22850824693D5ADC4663BAA19E384E550B1DD41F31EE7 +Result = VERIFY_ERROR + +Title = RFC 6979 B-409 deterministic ECDSA tests + +PrivateKey=B-409_PRIV +-----BEGIN PRIVATE KEY----- +MFECAQAwEAYHKoZIzj0CAQYFK4EEACUEOjA4AgEBBDNJSZTMMlsI57TOA4vZQ2+QteWaLBPDFAzT +rgfASgH8SJ9XLOBWmm23uAYDk952MwxiQXc= +-----END PRIVATE KEY----- + +PublicKey=B-409_PUB +-----BEGIN PUBLIC KEY----- +MH4wEAYHKoZIzj0CAQYFK4EEACUDagAEAacFWWHPHaS5oBWxixUk7wH92bk/rvwm+x8vgopyJ7cD +GSXaCsGooHXDszVUsiLqhZwX5wGBBcBC8pBzYIjzCux653MqRd5HvOCUAROrgTJRbR4Fmw9YH9WB +qaPLOgrEKhlic4rbhuY= +-----END PUBLIC KEY----- + +PrivPubKeyPair=B-409_PRIV:B-409_PUB + +DigestSign = SHA1 +Key = B-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306B023400D8783188E1A540E2022D389E1D35B32F56F8C2BB5636B8ABF7718806B27A713EBAE37F63ECD4B61445CEF5801B62594EF3E98202333A6B4A80E204DB0DE12E7415C13C9EC091C52935658316B4A0C591216A3879154BEB1712560E346E7EF26517707435B55C3141 + +DigestVerify = SHA1 +Key = B-409_PUB +Input = "sample" +Output = 306B023400D8783188E1A540E2022D389E1D35B32F56F8C2BB5636B8ABF7718806B27A713EBAE37F63ECD4B61445CEF5801B62594EF3E98202333A6B4A80E204DB0DE12E7415C13C9EC091C52935658316B4A0C591216A3879154BEB1712560E346E7EF26517707435B55C3141 + +DigestVerify = SHA1 +Key = B-409_PUB +Input = "sample" +Output = 306B023400D8783188E1A540E2022D389E1D35B32F56F8C2BB5636B8ABF7718806B27A713EBAE37F63ECD4B61445CEF5801B62594EF3E98202333A6B4A80E204DB0DE12E7415C13C9EC091C52935658316B4A0C591216A3879154BEB1712560E346E7EF26517707435B55C3140 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306B023400EE4F39ACC2E03CE96C3D9FCBAFA5C22C89053662F8D4117752A9B10F09ADFDA59DB061E247FE5321D6B170EE758ACE1BE4D15702330A2B83265B456A430A8BF27DCC8A9488B3F126C10F0D6D64BF7B8A218FAAF20E51A295A3AE78F205E5A4A6AE224C3639F1BB34 + +DigestVerify = SHA224 +Key = B-409_PUB +Input = "sample" +Output = 306B023400EE4F39ACC2E03CE96C3D9FCBAFA5C22C89053662F8D4117752A9B10F09ADFDA59DB061E247FE5321D6B170EE758ACE1BE4D15702330A2B83265B456A430A8BF27DCC8A9488B3F126C10F0D6D64BF7B8A218FAAF20E51A295A3AE78F205E5A4A6AE224C3639F1BB34 + +DigestVerify = SHA224 +Key = B-409_PUB +Input = "sample" +Output = 306B023400EE4F39ACC2E03CE96C3D9FCBAFA5C22C89053662F8D4117752A9B10F09ADFDA59DB061E247FE5321D6B170EE758ACE1BE4D15702330A2B83265B456A430A8BF27DCC8A9488B3F126C10F0D6D64BF7B8A218FAAF20E51A295A3AE78F205E5A4A6AE224C3639F1BB35 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A02332D8B1B31E33E74D7EB46C30FDE5AD2CA04EC8FE08FBA0E73BA5E568953AC5EA307C072942238DFC07F4A4D7C7C6A9F86436D17023379F7D471E6CB73234AF7F7C381D2CE15DE35BAF8BB68393B73235B3A26EC2DF4842CE433FB492D6E074E604D4870024D42189A + +DigestVerify = SHA256 +Key = B-409_PUB +Input = "sample" +Output = 306A02332D8B1B31E33E74D7EB46C30FDE5AD2CA04EC8FE08FBA0E73BA5E568953AC5EA307C072942238DFC07F4A4D7C7C6A9F86436D17023379F7D471E6CB73234AF7F7C381D2CE15DE35BAF8BB68393B73235B3A26EC2DF4842CE433FB492D6E074E604D4870024D42189A + +DigestVerify = SHA256 +Key = B-409_PUB +Input = "sample" +Output = 306A02332D8B1B31E33E74D7EB46C30FDE5AD2CA04EC8FE08FBA0E73BA5E568953AC5EA307C072942238DFC07F4A4D7C7C6A9F86436D17023379F7D471E6CB73234AF7F7C381D2CE15DE35BAF8BB68393B73235B3A26EC2DF4842CE433FB492D6E074E604D4870024D42189B +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A02337BC638B7E7CE6FEE5E9C64A0F966D722D01BB4BC3F3A35F30D4CDDA92DFC5F7F0B4BBFE8065D9AD452FD77A1914BE3A2440C1802336D904429850521B28A32CBF55C7C0FDF35DC4E0BDA2552C7BF68A171E970E6788ACC0B9521EACB4796E057C70DD9B95FED5BFB + +DigestVerify = SHA384 +Key = B-409_PUB +Input = "sample" +Output = 306A02337BC638B7E7CE6FEE5E9C64A0F966D722D01BB4BC3F3A35F30D4CDDA92DFC5F7F0B4BBFE8065D9AD452FD77A1914BE3A2440C1802336D904429850521B28A32CBF55C7C0FDF35DC4E0BDA2552C7BF68A171E970E6788ACC0B9521EACB4796E057C70DD9B95FED5BFB + +DigestVerify = SHA384 +Key = B-409_PUB +Input = "sample" +Output = 306A02337BC638B7E7CE6FEE5E9C64A0F966D722D01BB4BC3F3A35F30D4CDDA92DFC5F7F0B4BBFE8065D9AD452FD77A1914BE3A2440C1802336D904429850521B28A32CBF55C7C0FDF35DC4E0BDA2552C7BF68A171E970E6788ACC0B9521EACB4796E057C70DD9B95FED5BFA +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-409_PRIV +NonceType = deterministic +Input = "sample" +Output = 306A02335D178DECAFD2D02A3DA0D8BA1C4C1D95EE083C760DF782193A9F7B4A8BE6FC5C21FD60613BCA65C063A61226E050A680B3ABD4023313B7581E98F6A63FBBCB3E49BCDA60F816DB230B888506D105DC229600497C3B46588C784BE3AA9343BEF82F7C9C80AEB63C3B + +DigestVerify = SHA512 +Key = B-409_PUB +Input = "sample" +Output = 306A02335D178DECAFD2D02A3DA0D8BA1C4C1D95EE083C760DF782193A9F7B4A8BE6FC5C21FD60613BCA65C063A61226E050A680B3ABD4023313B7581E98F6A63FBBCB3E49BCDA60F816DB230B888506D105DC229600497C3B46588C784BE3AA9343BEF82F7C9C80AEB63C3B + +DigestVerify = SHA512 +Key = B-409_PUB +Input = "sample" +Output = 306A02335D178DECAFD2D02A3DA0D8BA1C4C1D95EE083C760DF782193A9F7B4A8BE6FC5C21FD60613BCA65C063A61226E050A680B3ABD4023313B7581E98F6A63FBBCB3E49BCDA60F816DB230B888506D105DC229600497C3B46588C784BE3AA9343BEF82F7C9C80AEB63C3A +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = B-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306A023349F54E7C10D2732B4638473053782C6919218BBEFCEC8B51640FC193E832291F05FA12371E9B448417B3290193F08EE93191950233499E267DEC84E02F6F108B10E82172C414F15B1B7364BE8BFD66ADC0C5DE23FEE3DF0D811134C25AFE0E05A6672F98889F28F1 + +DigestVerify = SHA1 +Key = B-409_PUB +Input = "test" +Output = 306A023349F54E7C10D2732B4638473053782C6919218BBEFCEC8B51640FC193E832291F05FA12371E9B448417B3290193F08EE93191950233499E267DEC84E02F6F108B10E82172C414F15B1B7364BE8BFD66ADC0C5DE23FEE3DF0D811134C25AFE0E05A6672F98889F28F1 + +DigestVerify = SHA1 +Key = B-409_PUB +Input = "test" +Output = 306A023349F54E7C10D2732B4638473053782C6919218BBEFCEC8B51640FC193E832291F05FA12371E9B448417B3290193F08EE93191950233499E267DEC84E02F6F108B10E82172C414F15B1B7364BE8BFD66ADC0C5DE23FEE3DF0D811134C25AFE0E05A6672F98889F28F0 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306B023400B1527FFAA7DD7C7E46B628587A5BEC0539A2D04D3CF27C54841C2544E1BBDB42FDBDAAF8671A4CA86DFD619B1E3732D7BB56F20233442C68C044868DF4832C807F1EDDEBF7F5052A64B826FD03451440794063F52B022DF304F47403D4069234CA9EB4C964B37C02 + +DigestVerify = SHA224 +Key = B-409_PUB +Input = "test" +Output = 306B023400B1527FFAA7DD7C7E46B628587A5BEC0539A2D04D3CF27C54841C2544E1BBDB42FDBDAAF8671A4CA86DFD619B1E3732D7BB56F20233442C68C044868DF4832C807F1EDDEBF7F5052A64B826FD03451440794063F52B022DF304F47403D4069234CA9EB4C964B37C02 + +DigestVerify = SHA224 +Key = B-409_PUB +Input = "test" +Output = 306B023400B1527FFAA7DD7C7E46B628587A5BEC0539A2D04D3CF27C54841C2544E1BBDB42FDBDAAF8671A4CA86DFD619B1E3732D7BB56F20233442C68C044868DF4832C807F1EDDEBF7F5052A64B826FD03451440794063F52B022DF304F47403D4069234CA9EB4C964B37C03 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306C023400BB27755B991D6D31757BCBF68CB01225A38E1CFA20F775E861055DD108ED7EA455E4B96B2F6F7CD6C6EC2B3C70C3EDDEB9743B023400C5BE90980E7F444B5F7A12C9E9AC7A04CA81412822DD5AD1BE7C45D5032555EA070864245CF69266871FEB8CD1B7EDC30EF6D5 + +DigestVerify = SHA256 +Key = B-409_PUB +Input = "test" +Output = 306C023400BB27755B991D6D31757BCBF68CB01225A38E1CFA20F775E861055DD108ED7EA455E4B96B2F6F7CD6C6EC2B3C70C3EDDEB9743B023400C5BE90980E7F444B5F7A12C9E9AC7A04CA81412822DD5AD1BE7C45D5032555EA070864245CF69266871FEB8CD1B7EDC30EF6D5 + +DigestVerify = SHA256 +Key = B-409_PUB +Input = "test" +Output = 306C023400BB27755B991D6D31757BCBF68CB01225A38E1CFA20F775E861055DD108ED7EA455E4B96B2F6F7CD6C6EC2B3C70C3EDDEB9743B023400C5BE90980E7F444B5F7A12C9E9AC7A04CA81412822DD5AD1BE7C45D5032555EA070864245CF69266871FEB8CD1B7EDC30EF6D4 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306B02334EFEB7098772187907C87B33E0FBBA4584226C50C11E98CA7AAC6986F8D3BE044E5B52D201A410B852536527724CA5F8CE65490234009574102FEB3EF87E6D66B94119F5A6062950FF4F902EA1E6BD9E2037F33FF991E31F5956C23AFE48FCDC557FD6F088C7C9B2B3 + +DigestVerify = SHA384 +Key = B-409_PUB +Input = "test" +Output = 306B02334EFEB7098772187907C87B33E0FBBA4584226C50C11E98CA7AAC6986F8D3BE044E5B52D201A410B852536527724CA5F8CE65490234009574102FEB3EF87E6D66B94119F5A6062950FF4F902EA1E6BD9E2037F33FF991E31F5956C23AFE48FCDC557FD6F088C7C9B2B3 + +DigestVerify = SHA384 +Key = B-409_PUB +Input = "test" +Output = 306B02334EFEB7098772187907C87B33E0FBBA4584226C50C11E98CA7AAC6986F8D3BE044E5B52D201A410B852536527724CA5F8CE65490234009574102FEB3EF87E6D66B94119F5A6062950FF4F902EA1E6BD9E2037F33FF991E31F5956C23AFE48FCDC557FD6F088C7C9B2B2 +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-409_PRIV +NonceType = deterministic +Input = "test" +Output = 306B02337E0249C68536AE2AEC2EC30090340DA49E6DC9E9EEC8F85E5AABFB234B6DA7D2E9524028CF821F21C6019770474CC40B01FAF60234008125B5A03FB44AE81EA46D446130C2A415ECCA265910CA69D55F2453E16CD7B2DFA4E28C50FA8137F9C0C6CEE4CD37ABCCF6D8 + +DigestVerify = SHA512 +Key = B-409_PUB +Input = "test" +Output = 306B02337E0249C68536AE2AEC2EC30090340DA49E6DC9E9EEC8F85E5AABFB234B6DA7D2E9524028CF821F21C6019770474CC40B01FAF60234008125B5A03FB44AE81EA46D446130C2A415ECCA265910CA69D55F2453E16CD7B2DFA4E28C50FA8137F9C0C6CEE4CD37ABCCF6D8 + +DigestVerify = SHA512 +Key = B-409_PUB +Input = "test" +Output = 306B02337E0249C68536AE2AEC2EC30090340DA49E6DC9E9EEC8F85E5AABFB234B6DA7D2E9524028CF821F21C6019770474CC40B01FAF60234008125B5A03FB44AE81EA46D446130C2A415ECCA265910CA69D55F2453E16CD7B2DFA4E28C50FA8137F9C0C6CEE4CD37ABCCF6D9 +Result = VERIFY_ERROR + +Title = RFC 6979 B-571 deterministic ECDSA tests + +PrivateKey=B-571_PRIV +-----BEGIN PRIVATE KEY----- +MGUCAQAwEAYHKoZIzj0CAQYFK4EEACcETjBMAgEBBEcooEhX8kwcCC3w2QnA5y9FPy4jQMywcfDj +ibyiV12hkSQZjFcXSSmtJuNIz2P3jSgCHvWpvy1cvq9rfMtsTagk3VyCz7JOEQ== +-----END PRIVATE KEY----- + +PublicKey=B-571_PUB +-----BEGIN PUBLIC KEY----- +MIGnMBAGByqGSM49AgEGBSuBBAAnA4GSAAQEtLPOk3dVAUC2LBBhdjqlJIFN3O83sAzVzelPd5K7 +DpZ1jlXaLp/qj/KotoMK4dV6nKenf8sINr9D6lRUzdn+rVzP5zdcaoMERTsY8mHnoOdXDNcvI16n +UEOOQ5Rvvr0lGLaWlUdnqnhJwXGeGOHFFlLCjKhTQm8VwJqktXlIczirx/M3aPrdYbWjpkQ6gYk= +-----END PUBLIC KEY----- + +PrivPubKeyPair=B-571_PRIV:B-571_PUB + +DigestSign = SHA1 +Key = B-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 30819402480147D3EB0EDA9F2152DFD014363D6A9CE816D7A1467D326A625FC4AB0C786E1B74DDF7CD4D0E99541391B266C704BB6B6E8DCCD27B460802E0867143727AA415555454321EFE5CB60248017319571CAF533D90D2E78A64060B9C53169AB7FC908947B3EDADC54C79CCF0A7920B4C64A4EAB6282AFE9A459677CDA37FD6DD50BEF18709590FE18B923BDF74A66B189A850819 + +DigestVerify = SHA1 +Key = B-571_PUB +Input = "sample" +Output = 30819402480147D3EB0EDA9F2152DFD014363D6A9CE816D7A1467D326A625FC4AB0C786E1B74DDF7CD4D0E99541391B266C704BB6B6E8DCCD27B460802E0867143727AA415555454321EFE5CB60248017319571CAF533D90D2E78A64060B9C53169AB7FC908947B3EDADC54C79CCF0A7920B4C64A4EAB6282AFE9A459677CDA37FD6DD50BEF18709590FE18B923BDF74A66B189A850819 + +DigestVerify = SHA1 +Key = B-571_PUB +Input = "sample" +Output = 30819402480147D3EB0EDA9F2152DFD014363D6A9CE816D7A1467D326A625FC4AB0C786E1B74DDF7CD4D0E99541391B266C704BB6B6E8DCCD27B460802E0867143727AA415555454321EFE5CB60248017319571CAF533D90D2E78A64060B9C53169AB7FC908947B3EDADC54C79CCF0A7920B4C64A4EAB6282AFE9A459677CDA37FD6DD50BEF18709590FE18B923BDF74A66B189A850818 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 3081940248010F4B63E79B2E54E4F4F6A2DBC786D8F4A143ECA7B2AD97810F6472AC6AE20853222854553BE1D44A7974599DB7061AE8560DF57F2675BE5F9DD94ABAF3D47F1582B318E459748B024803BBEA07C6B269C2B7FE9AE4DDB118338D0C2F0022920A7F9DCFCB7489594C03B536A9900C4EA6A10410007222D3DAE1A96F291C4C9275D75D98EB290DC0EEF176037B2C7A7A39A3 + +DigestVerify = SHA224 +Key = B-571_PUB +Input = "sample" +Output = 3081940248010F4B63E79B2E54E4F4F6A2DBC786D8F4A143ECA7B2AD97810F6472AC6AE20853222854553BE1D44A7974599DB7061AE8560DF57F2675BE5F9DD94ABAF3D47F1582B318E459748B024803BBEA07C6B269C2B7FE9AE4DDB118338D0C2F0022920A7F9DCFCB7489594C03B536A9900C4EA6A10410007222D3DAE1A96F291C4C9275D75D98EB290DC0EEF176037B2C7A7A39A3 + +DigestVerify = SHA224 +Key = B-571_PUB +Input = "sample" +Output = 3081940248010F4B63E79B2E54E4F4F6A2DBC786D8F4A143ECA7B2AD97810F6472AC6AE20853222854553BE1D44A7974599DB7061AE8560DF57F2675BE5F9DD94ABAF3D47F1582B318E459748B024803BBEA07C6B269C2B7FE9AE4DDB118338D0C2F0022920A7F9DCFCB7489594C03B536A9900C4EA6A10410007222D3DAE1A96F291C4C9275D75D98EB290DC0EEF176037B2C7A7A39A2 +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 30819402480213EF9F3B0CFC4BF996B8AF3A7E1F6CACD2B87C8C63820000800AC787F17EC99C04BCEDF29A8413CFF83142BB88A50EF8D9A086AF4EB03E97C567500C21D865714D832E03C6D054024803D32322559B094E20D8935E250B6EC139AC4AAB77920812C119AF419FB62B332C8D226C6C9362AE3C1E4AABE19359B8428EA74EC8FBE83C8618C2BCCB6B43FBAA0F2CCB7D303945 + +DigestVerify = SHA256 +Key = B-571_PUB +Input = "sample" +Output = 30819402480213EF9F3B0CFC4BF996B8AF3A7E1F6CACD2B87C8C63820000800AC787F17EC99C04BCEDF29A8413CFF83142BB88A50EF8D9A086AF4EB03E97C567500C21D865714D832E03C6D054024803D32322559B094E20D8935E250B6EC139AC4AAB77920812C119AF419FB62B332C8D226C6C9362AE3C1E4AABE19359B8428EA74EC8FBE83C8618C2BCCB6B43FBAA0F2CCB7D303945 + +DigestVerify = SHA256 +Key = B-571_PUB +Input = "sample" +Output = 30819402480213EF9F3B0CFC4BF996B8AF3A7E1F6CACD2B87C8C63820000800AC787F17EC99C04BCEDF29A8413CFF83142BB88A50EF8D9A086AF4EB03E97C567500C21D865714D832E03C6D054024803D32322559B094E20D8935E250B6EC139AC4AAB77920812C119AF419FB62B332C8D226C6C9362AE3C1E4AABE19359B8428EA74EC8FBE83C8618C2BCCB6B43FBAA0F2CCB7D303944 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 30819402480375D8F49C656A0BBD21D3F54CDA287D853C4BB1849983CD891EF6CD6BB56A62B687807C16685C2C9BCA2663C33696ACCE344C45F3910B1DF806204FF731ECB289C100EF4D1805EC024801CDEC6F46DFEEE44BCE71D41C60550DC67CF98D6C91363625AC2553E4368D2DFB734A8E8C72E118A76ACDB0E58697940A0F3DF49E72894BD799450FC9E550CC04B9FF9B0380021C + +DigestVerify = SHA384 +Key = B-571_PUB +Input = "sample" +Output = 30819402480375D8F49C656A0BBD21D3F54CDA287D853C4BB1849983CD891EF6CD6BB56A62B687807C16685C2C9BCA2663C33696ACCE344C45F3910B1DF806204FF731ECB289C100EF4D1805EC024801CDEC6F46DFEEE44BCE71D41C60550DC67CF98D6C91363625AC2553E4368D2DFB734A8E8C72E118A76ACDB0E58697940A0F3DF49E72894BD799450FC9E550CC04B9FF9B0380021C + +DigestVerify = SHA384 +Key = B-571_PUB +Input = "sample" +Output = 30819402480375D8F49C656A0BBD21D3F54CDA287D853C4BB1849983CD891EF6CD6BB56A62B687807C16685C2C9BCA2663C33696ACCE344C45F3910B1DF806204FF731ECB289C100EF4D1805EC024801CDEC6F46DFEEE44BCE71D41C60550DC67CF98D6C91363625AC2553E4368D2DFB734A8E8C72E118A76ACDB0E58697940A0F3DF49E72894BD799450FC9E550CC04B9FF9B0380021D +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-571_PRIV +NonceType = deterministic +Input = "sample" +Output = 308194024801C26F40D940A7EAA0EB1E62991028057D91FEDA0366B606F6C434C361F04E545A6A51A435E26416F6838FFA260C617E798E946B57215284182BE55F29A355E6024FE32A47289CF0024803691DE4369D921FE94EDDA67CB71FBBEC9A436787478063EB1CC778B3DCDC1C4162662752D28DEEDF6F32A269C82D1DB80C87CE4D3B662E03AC347806E3F19D18D6D4DE7358DF7E + +DigestVerify = SHA512 +Key = B-571_PUB +Input = "sample" +Output = 308194024801C26F40D940A7EAA0EB1E62991028057D91FEDA0366B606F6C434C361F04E545A6A51A435E26416F6838FFA260C617E798E946B57215284182BE55F29A355E6024FE32A47289CF0024803691DE4369D921FE94EDDA67CB71FBBEC9A436787478063EB1CC778B3DCDC1C4162662752D28DEEDF6F32A269C82D1DB80C87CE4D3B662E03AC347806E3F19D18D6D4DE7358DF7E + +DigestVerify = SHA512 +Key = B-571_PUB +Input = "sample" +Output = 308194024801C26F40D940A7EAA0EB1E62991028057D91FEDA0366B606F6C434C361F04E545A6A51A435E26416F6838FFA260C617E798E946B57215284182BE55F29A355E6024FE32A47289CF0024803691DE4369D921FE94EDDA67CB71FBBEC9A436787478063EB1CC778B3DCDC1C4162662752D28DEEDF6F32A269C82D1DB80C87CE4D3B662E03AC347806E3F19D18D6D4DE7358DF7F +Result = VERIFY_ERROR + +DigestSign = SHA1 +Key = B-571_PRIV +NonceType = deterministic +Input = "test" +Output = 30819402480133F5414F2A9BC41466D339B79376038A64D045E5B0F792A98E5A7AA87E0AD016419E5F8D176007D5C9C10B5FD9E2E0AB8331B195797C0358BA05ECBF24ACE59C5F368A6C0997CC024803D16743AE9F00F0B1A500F738719C5582550FEB64689DA241665C4CE4F328BA0E34A7EF527ED13BFA5889FD2D1D214C11EB17D6BC338E05A56F41CAFF1AF7B8D574DB62EF0D0F21 + +DigestVerify = SHA1 +Key = B-571_PUB +Input = "test" +Output = 30819402480133F5414F2A9BC41466D339B79376038A64D045E5B0F792A98E5A7AA87E0AD016419E5F8D176007D5C9C10B5FD9E2E0AB8331B195797C0358BA05ECBF24ACE59C5F368A6C0997CC024803D16743AE9F00F0B1A500F738719C5582550FEB64689DA241665C4CE4F328BA0E34A7EF527ED13BFA5889FD2D1D214C11EB17D6BC338E05A56F41CAFF1AF7B8D574DB62EF0D0F21 + +DigestVerify = SHA1 +Key = B-571_PUB +Input = "test" +Output = 30819402480133F5414F2A9BC41466D339B79376038A64D045E5B0F792A98E5A7AA87E0AD016419E5F8D176007D5C9C10B5FD9E2E0AB8331B195797C0358BA05ECBF24ACE59C5F368A6C0997CC024803D16743AE9F00F0B1A500F738719C5582550FEB64689DA241665C4CE4F328BA0E34A7EF527ED13BFA5889FD2D1D214C11EB17D6BC338E05A56F41CAFF1AF7B8D574DB62EF0D0F20 +Result = VERIFY_ERROR + +DigestSign = SHA224 +Key = B-571_PRIV +NonceType = deterministic +Input = "test" +Output = 308194024803048E76506C5C43D92B2E33F62B33E3111CEEB87F6C7DF7C7C01E3CDA28FA5E8BE04B5B23AA03C0C70FEF8F723CBCEBFF0B7A52A3F5C8B84B741B4F6157E69A5FB0524B48F31828024802C99078CCFE5C82102B8D006E3703E020C46C87C75163A2CD839C885550BA5CB501AC282D29A1C26D26773B60FBE05AAB62BFA0BA32127563D42F7669C97784C8897C22CFB4B8FA + +DigestVerify = SHA224 +Key = B-571_PUB +Input = "test" +Output = 308194024803048E76506C5C43D92B2E33F62B33E3111CEEB87F6C7DF7C7C01E3CDA28FA5E8BE04B5B23AA03C0C70FEF8F723CBCEBFF0B7A52A3F5C8B84B741B4F6157E69A5FB0524B48F31828024802C99078CCFE5C82102B8D006E3703E020C46C87C75163A2CD839C885550BA5CB501AC282D29A1C26D26773B60FBE05AAB62BFA0BA32127563D42F7669C97784C8897C22CFB4B8FA + +DigestVerify = SHA224 +Key = B-571_PUB +Input = "test" +Output = 308194024803048E76506C5C43D92B2E33F62B33E3111CEEB87F6C7DF7C7C01E3CDA28FA5E8BE04B5B23AA03C0C70FEF8F723CBCEBFF0B7A52A3F5C8B84B741B4F6157E69A5FB0524B48F31828024802C99078CCFE5C82102B8D006E3703E020C46C87C75163A2CD839C885550BA5CB501AC282D29A1C26D26773B60FBE05AAB62BFA0BA32127563D42F7669C97784C8897C22CFB4B8FB +Result = VERIFY_ERROR + +DigestSign = SHA256 +Key = B-571_PRIV +NonceType = deterministic +Input = "test" +Output = 30819402480184BC808506E11A65D628B457FDA60952803C604CC7181B59BD25AEE1411A66D12A777F3A0DC99E1190C58D0037807A95E5080FA1B2E5CCAA37B50D401CFFC3417C005AEE9634690248027280D45F81B19334DBDB07B7E63FE8F39AC7E9AE14DE1D2A6884D2101850289D70EE400F26ACA5E7D73F534A14568478E59D00594981ABE6A1BA18554C13EB5E03921E4DC98333 + +DigestVerify = SHA256 +Key = B-571_PUB +Input = "test" +Output = 30819402480184BC808506E11A65D628B457FDA60952803C604CC7181B59BD25AEE1411A66D12A777F3A0DC99E1190C58D0037807A95E5080FA1B2E5CCAA37B50D401CFFC3417C005AEE9634690248027280D45F81B19334DBDB07B7E63FE8F39AC7E9AE14DE1D2A6884D2101850289D70EE400F26ACA5E7D73F534A14568478E59D00594981ABE6A1BA18554C13EB5E03921E4DC98333 + +DigestVerify = SHA256 +Key = B-571_PUB +Input = "test" +Output = 30819402480184BC808506E11A65D628B457FDA60952803C604CC7181B59BD25AEE1411A66D12A777F3A0DC99E1190C58D0037807A95E5080FA1B2E5CCAA37B50D401CFFC3417C005AEE9634690248027280D45F81B19334DBDB07B7E63FE8F39AC7E9AE14DE1D2A6884D2101850289D70EE400F26ACA5E7D73F534A14568478E59D00594981ABE6A1BA18554C13EB5E03921E4DC98332 +Result = VERIFY_ERROR + +DigestSign = SHA384 +Key = B-571_PRIV +NonceType = deterministic +Input = "test" +Output = 30819402480319EE57912E7B0FAA1FBB145B0505849A89C6DB1EC06EA20A6A7EDE072A6268AF6FD9C809C7E422A5F33C6C3326EAD7402467DF3272A1B2726C1C20975950F0F50D8324578F13EC024802CF3EA27EADD0612DD2F96F46E89AB894B01A10DF985C5FC099CFFE0EA083EB44BE682B08BFE405DAD5F37D0A2C59015BA41027E24B99F8F75A70B6B7385BF39BBEA02513EB880C + +DigestVerify = SHA384 +Key = B-571_PUB +Input = "test" +Output = 30819402480319EE57912E7B0FAA1FBB145B0505849A89C6DB1EC06EA20A6A7EDE072A6268AF6FD9C809C7E422A5F33C6C3326EAD7402467DF3272A1B2726C1C20975950F0F50D8324578F13EC024802CF3EA27EADD0612DD2F96F46E89AB894B01A10DF985C5FC099CFFE0EA083EB44BE682B08BFE405DAD5F37D0A2C59015BA41027E24B99F8F75A70B6B7385BF39BBEA02513EB880C + +DigestVerify = SHA384 +Key = B-571_PUB +Input = "test" +Output = 30819402480319EE57912E7B0FAA1FBB145B0505849A89C6DB1EC06EA20A6A7EDE072A6268AF6FD9C809C7E422A5F33C6C3326EAD7402467DF3272A1B2726C1C20975950F0F50D8324578F13EC024802CF3EA27EADD0612DD2F96F46E89AB894B01A10DF985C5FC099CFFE0EA083EB44BE682B08BFE405DAD5F37D0A2C59015BA41027E24B99F8F75A70B6B7385BF39BBEA02513EB880D +Result = VERIFY_ERROR + +DigestSign = SHA512 +Key = B-571_PRIV +NonceType = deterministic +Input = "test" +Output = 308194024802AA1888EAB05F7B00B6A784C4F7081D2C833D50794D9FEAF6E22B8BE728A2A90BFCABDC803162020AA629718295A1489EE7ED0ECB8AAA197B9BDFC49D18DDD78FC85A48F9715544024800AA5371FE5CA671D6ED9665849C37F394FED85D51FEF72DA2B5F28EDFB2C6479CA63320C19596F5E1101988E2C619E302DD05112F47E8823040CE540CD3E90DCF41DBC461744EE9 + +DigestVerify = SHA512 +Key = B-571_PUB +Input = "test" +Output = 308194024802AA1888EAB05F7B00B6A784C4F7081D2C833D50794D9FEAF6E22B8BE728A2A90BFCABDC803162020AA629718295A1489EE7ED0ECB8AAA197B9BDFC49D18DDD78FC85A48F9715544024800AA5371FE5CA671D6ED9665849C37F394FED85D51FEF72DA2B5F28EDFB2C6479CA63320C19596F5E1101988E2C619E302DD05112F47E8823040CE540CD3E90DCF41DBC461744EE9 + +DigestVerify = SHA512 +Key = B-571_PUB +Input = "test" +Output = 308194024802AA1888EAB05F7B00B6A784C4F7081D2C833D50794D9FEAF6E22B8BE728A2A90BFCABDC803162020AA629718295A1489EE7ED0ECB8AAA197B9BDFC49D18DDD78FC85A48F9715544024800AA5371FE5CA671D6ED9665849C37F394FED85D51FEF72DA2B5F28EDFB2C6479CA63320C19596F5E1101988E2C619E302DD05112F47E8823040CE540CD3E90DCF41DBC461744EE8 +Result = VERIFY_ERROR diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh b/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh index b18c338b3803..4a494bda1153 100755 --- a/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh @@ -19,10 +19,13 @@ getecbits() { genkey() { fn="$1" args="-f $fn -C $fn" + sk="-O application=ssh:the-application-string" case "$fn" in + sk-ecdsa-*) args="$args -t ecdsa-sk -b $(getecbits) $sk" ;; ecdsa-*) args="$args -t ecdsa -b $(getecbits)" ;; rsa-*) args="$args -t rsa" ;; dsa-*) args="$args -t dsa" ;; + sk-ed25519-*) args="$args -t ed25519-sk $sk" ;; ed25519-*) args="$args -t ed25519" ;; esac password='' @@ -33,12 +36,13 @@ genkey() { } # generate private key files -for ktype in rsa dsa ecdsa ed25519; do +for ktype in rsa dsa ecdsa sk-ecdsa ed25519 sk-ed25519; do for psw in nopsw psw; do genkey "${ktype}-${psw}.key" done done + # generate public key files for fn in *.key; do ssh-keygen -q -y -f "$fn" > /dev/null diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key new file mode 100644 index 000000000000..23fd193a92fa --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key @@ -0,0 +1,11 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlQAAACJzay1lY2 +RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQQ7XunI8QRf +myT0PKWJXtaE0lA6+Hy5HTfIDfHexsZV68AGAj0nYyf2+mAK/vPp6IyVBALJqdzdJYiyeX +p/3neLAAAAGnNzaDp0aGUtYXBwbGljYXRpb24tc3RyaW5nAAABAOGdI7jhnSO4AAAAInNr +LWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBDte6c +jxBF+bJPQ8pYle1oTSUDr4fLkdN8gN8d7GxlXrwAYCPSdjJ/b6YAr+8+nojJUEAsmp3N0l +iLJ5en/ed4sAAAAac3NoOnRoZS1hcHBsaWNhdGlvbi1zdHJpbmcBAAAAQDkL+WvhalaEJi +Lf/MaFsFeYzwvC06GZVqUXgCnzyutZzMB9a1deF9uFke1ib56tgZR9iVsskIJeWuwiAIg0 +es4AAAAAAAAAEnNrLWVjZHNhLW5vcHN3LmtleQECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key.pub new file mode 100644 index 000000000000..7c4193df3826 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBDte6cjxBF+bJPQ8pYle1oTSUDr4fLkdN8gN8d7GxlXrwAYCPSdjJ/b6YAr+8+nojJUEAsmp3N0liLJ5en/ed4sAAAAac3NoOnRoZS1hcHBsaWNhdGlvbi1zdHJpbmc= sk-ecdsa-nopsw.key diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key new file mode 100644 index 000000000000..b406fa06800d --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key @@ -0,0 +1,12 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDIj2qUG3 +LdljUMp0/4zuFuAAAAEAAAAAEAAACVAAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3Bl +bnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBACdJuKxgDLk+a1NeeCtRqCropd0hXume/cTdO +vV/B4lmupr9viNQsUT09wbKRflnOc9jxPAiQOzZbXTkmnV8kkAAAAac3NoOnRoZS1hcHBs +aWNhdGlvbi1zdHJpbmcAAAEAO6Vsfb59XIe524NKbXMjA0xleAi3lcZ5EF0dF48yRO2LfA +12B948LzsKOrgo+Cdq7BMLkCCA1z2811yvKtvy/7cR3D/p31cW7VEun4OAn+QoPCHmv25r +WVfUAv5PC5Ofdm7dtExTcMmyNUMcziovirTyhnlpc/wHD+wgp2oQGpcm+rjQlqX96cLJ7H +PM3wls38biP3wh2QWkoKWPyq7tMR4PiJOw9h6YNeZY3M1JnC9b2b0iHD6Ra/5LBBqV/Uyu +irkHWLB7ASchamexxRqu4fLFK4tjijhLV8hc/XLsQGeDNBHf4QSvZJP0usSSP37F1Ai+XM +stjM1iCsk1UEV9aA== +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key.pub new file mode 100644 index 000000000000..b9a6fa34156c --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBACdJuKxgDLk+a1NeeCtRqCropd0hXume/cTdOvV/B4lmupr9viNQsUT09wbKRflnOc9jxPAiQOzZbXTkmnV8kkAAAAac3NoOnRoZS1hcHBsaWNhdGlvbi1zdHJpbmc= sk-ecdsa-psw.key diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key new file mode 100644 index 000000000000..db48fcd3e9a5 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAYAAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACB6auRr7BwVOqTawgDOxUpaUFcN8SZ7SWzoR2Vs +ubbk3wAAABpzc2g6dGhlLWFwcGxpY2F0aW9uLXN0cmluZwAAARCWIPLyliDy8gAAABpzay +1zc2gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACB6auRr7BwVOqTawgDOxUpaUFcN8SZ7SWzo +R2Vsubbk3wAAABpzc2g6dGhlLWFwcGxpY2F0aW9uLXN0cmluZwEAAACAQPv/aY2F3YN1kD +1FHPa1HpEHOGAbsYj/2b6h8Rn+N4pU6hdTD5v19Efdz5jlt8Y84c61+8HKDPCI/g5Cbcvd +3uuGHuFUdgiarOZqKyuwBj3Kll9Whb/yV4wGo/NVXtCHa2SnWr2wjYtRTGPNNCgGPsLU05 +/KTNCStsNhEcsNDjEAAAAAAAAAFHNrLWVkMjU1MTktbm9wc3cua2V5AQIDBAUGBw== +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key.pub new file mode 100644 index 000000000000..dc900ed9dd6f --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHpq5GvsHBU6pNrCAM7FSlpQVw3xJntJbOhHZWy5tuTfAAAAGnNzaDp0aGUtYXBwbGljYXRpb24tc3RyaW5n sk-ed25519-nopsw.key diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key new file mode 100644 index 000000000000..92328aa1ecdd --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key @@ -0,0 +1,11 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBZQIE5S+ +fq0J5esB3Jo4smAAAAEAAAAAEAAABgAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t +AAAAIHf0iiNQTiR7NNAbeAwY+READVx9G0mP6idSAZ7bPTrMAAAAGnNzaDp0aGUtYXBwbG +ljYXRpb24tc3RyaW5nAAABEEeyENyjnVry24AKkT0cC6nRakzHeBY7nSmDiy3MX7sQNRze +illy4uWLZyv022QlMR4GqnXwnQ9bPqcPD0S/SAhuYnFRWI6PPUXkNqiqiS/ZsMkaSKDvBS +UKv5EXjBBk3Sh9IjNXXK8tt0+WIIR973hVEtolcgxvFZpc1IJuRl9gkpKlQFNzwcANTuwB +kr6t0qad/fp0bZldBL/zRtqfgMHTSFzNoITTaxA8ZQZ1Zm585u0NIX4ZDrTaoZVaO8t7Z5 +3r1784oCk6h/lomf9Qsg2eBf6CHMGlTHVFPop5VtGDKFVlgIxQCdwt0V1e6dWK6j5zOzBh +mNA7qT0q3quRLBqUADN698q5fLRFR1PzQ5bx +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key.pub new file mode 100644 index 000000000000..65fc4c31591b --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHf0iiNQTiR7NNAbeAwY+READVx9G0mP6idSAZ7bPTrMAAAAGnNzaDp0aGUtYXBwbGljYXRpb24tc3RyaW5n sk-ed25519-psw.key 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/asymmetric/Traditional_OpenSSL_Serialization/key1.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1.pem index 50ad95cfbf82..cf27f92c618c 100644 --- a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1.pem +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key1.pem @@ -1,12 +1,30 @@ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,82B2F7684A1713F8 +DEK-Info: DES-EDE3-CBC,F277212EDBD61604 -1zzOuu89dfFc2UkFCtSJBsBeEFxV8wE84OSxoWu4aYkPhl1LR08BchaTbjeLTP0b -t961vVpva0ekJkwGDEgmqlGjmhJq9y2sJfq7IeYa8OdTilfGrG1xeJ1QGBi6SCfR -s/PhkMxwGBtrZ2Z7bEcLT5dQKmKRqsthnClQggmngvk7zX7bPk0hKQKvf+FDxt6x -hzEaF3k9juU6vAVVSakrZ4QDqk9MUuTGHx0ksTDcC4EESS0l3Ybuum/rAzR4lQKR -4OLmAeYBDl+l/PSMllfd5x/z1YXYoiAbkpT4ix0lyZJgHrvrYIeUtJk2ODiMHezL -9BbK7EobtOGmrDLUNVX5BpdaExkWMGkioqzs2QqD/VkKu8RcNSsHVGqkdWKuhzXo -wcczQ+RiHckN2uy/zApubEWZNLPeDQ499kaF+QdZ+h4RM6E1r1Gu+A== +18phyq8pG3Tgov4rWiT0moaDbzIOk7v4/4Jnw3sc6IuMFmAYnIKHRs75hQdlFAxG +uSXcAKzCzjhkzgSNyNaJ8ZgeDM+DskDTA109iQWCeSxKZkuHBm2Xux9p7ynEhrMf ++z0Dd5W36KRPs0PRwVoUAv/AYaLizBbAXaEx/e21uDB2cVnA2EhjEXEz7KZnqTWm +qbSEAv/IJos1Eh1IvLupxh5naaRxfrHZgKu638ybxuxzJx+zn2DeB7g9uqVf3lCp +B5bsoqumIhxBmIS7pKeWIq+GFVQuuHcDozRVolFuUvMkPdPfaGQjLI+ynaAfA9WH +MULcRcBL+S8cp4xv8jmyW0n4Elak0ixw1UJLjeSrIGYLB+ZkYXPiUjhYZPzbKzdE +rLstyGfFXH8Vjw6921P6iVH/JvskF9aj4NvYyZqxo9YznIN9nI8GWmqJgLyIYHET +Ur5mp1/O+KGLWMzfX09/fUVF/mXBibcnJ/sixGCH4yNZR5kpnas6H8SmaGgKE1zk +KYeuicGHm6nZ/uyjoL/AwvbUL1y9tHJ0vn816cCRdJ4ELZ5dotGPREPmkWzjv08A +ZeTmdsgsGuUY/5mKZdIqlWCgrSKaZvS81+5tYgf0qMLBsAbLPDJy9kzTwCsEYxmh +x9QxUeQ/UWVsMn6JqeBVp0B5z/sLcdx6GkFVGs9U2Al3aykVhrVq+0RUiYafluod +Mkz1AczAxFtqdgaQIJbrwEAXoMc8/l8dunbuYoRuuf1y259U61aTm6wcknnDUZKs +13sDVdcRZq1Lc1JI3B586Z7Jh0r/4HPiK1zearKLBPKZA6kEj4RzG3GUQVPxzpoD +NDP8FxVgMy022+gylWr2EwZ/QWigIKeop0qRCeuPgju44Fvf0Z300GmpIwOjsPWT +Ksmqw+erTT2UcN62z4+J0TvL44T9wpWbPcyxOe1r5HLpRkkBebMPNMlPZ4WGagsz +jn0ctw7GwsJbKgyqturB83ZfuJv9lGkrXHOjrjeQNCebYDmybHl/aag8BKKYOiFW +MkHmda+Jmq817aqcwVedMKs4CwdrE6frp2wgAIngzCILLVfyTa8v5HxpkezpKS3p +Lia3/xkSrJwzd9ncNe43OVDlFbTE6fm/ycES8vhvS2NotuL/gZ9WpLOFPKCFl2CZ +Cg6CUlTngEevd8kUrlt8BIEOPyhWqZOkxb1Q+Jr7PUQjgjQXmuxYoZ647xOYdIbd +RQZd3oEFjQYTXTT7hHOuB+FehaJPEfIqJDIxVSs0gVhETaCn7L7jcq3uko3W2IpV +qbVYBDv6+ae6Ia0dSTCtWGmqj0heIE1OOtMe7do0RijeeUz8snn6N7GYxVsQv+dg +0zeV/2RdPz/N898agdJZywjCUwxVPIKXl4MpFEy79rhGBq7q8aImDRlrdMZNy9BJ +nARaiDZ0ifmdh+smPWj/WuiAsYnuJBEFAQ88xECHbSXeJ6+Y/VS2jaJlMtL2tObW +mB/vq+Kfj6yfMxYaxtjOIpqBQfGZVlNwkq9BEeEwUcas5QBrRktUS5taU3/FlfyC +P3DsU4vseQILnqmEty7TWdHqw3up3Japzc3cTP9h4xxXuux+FmRuVdq0lfSPXB5E -----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key2.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key2.pem index 6bd476d7593d..7fdd12338729 100644 --- a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key2.pem +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/key2.pem @@ -1,12 +1,30 @@ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED -DEK-Info: AES-128-CBC,2A57FF97B701B3F760145D7446929481 +DEK-Info: AES-128-CBC,5334E33DCBCAB62637BB26E3CD983AC1 -mGAPhSw48wZBnkHOhfMDg8yL2IBgMuTmeKE4xoHi7T6isHBNfkqMd0iJ+DJP/OKb -t+7lkKjj/xQ7w/bOBvBxlfRe4MW6+ejCdAFD9XSolW6WN6CEJPMI4UtmOK5inqcC -8l2l54f/VGrVN9uavU3KlXCjrd3Jp9B0Mu4Zh/UU4+EWs9rJAZfLIn+vHZ3OHetx -g74LdV7nC7lt/fjxc1caNIfgHs40dUt9FVrnJvAtkcNMtcjX/D+L8ZrLgQzIWFcs -WAbUZj7Me22mCli3RPET7Je37K59IzfWgbWFCGaNu3X02g5xtCfdcn/Uqy9eofH0 -YjKRhpgXPeGJCkoRqDeUHQNPpVP5HrzDZMVK3E4DC03C8qvgsYvuwYt3KkbG2fuA -F3bDyqlxSOm7uxF/K3YzI44v8/D8GGnLBTpN+ANBdiY= +7C3LlvoHTY/cpg8x875/vmWoV3mjePa0zUR1gwALdijlG3w+aQyzZWKlo8NSSAgt +i67PjT5dP6E842m1tOguLFuuBbu8jOuxQPMMUNECG6qot9wHikJ07UlnYhOEqW1v +v9tvTKkfLpK9lCNBPyDNgmF4n9MNePQonqLDqz0ezp6o7+mFkbtN1L21QIo7rafw +E2zoJ17Qx8zx36YxpO/DPF2x2YMgPsClLTRHVRYr6rNsH6r+feVMIrsAX4riL7pP +I0tQRGuLnK/n0AcMTnmwhp2jbbKdWVv7ptkEwrYNWGSBlvDUoxXOtw3HBjeyFpZw +2/8rZE07AG0Iek35eLZMwPsmERRyIX037x2vwHpsYnYHoAME6wqoxClo+0HnYOKM +1a8SCaocOvstNEKtllOfxyUSLpz/xXpHU9COUtVhuXZbF/x3+3uK/Qgo6zDpjz8J +6ghbBtuFcBxV5sBMau+6M3lXqzwRdAvcEEh3UVbVRI9Wm5IGo0lor7OVdoTxFCzu +nSin+IBTTzwlZNGoSS1PRq+Ta/BtC8pAT0JnL1yi5QO9Kbrwf5kxMMIkIsK0b3OH +MleHNwC08On9si9btnmpdQuFphL4I68N0NomYHPdZj77uAbTUlVSQ5Cm8IYmHT7/ +fiU2MwJLzMYwi3vAIgxKY89LqQLaUSj3H6OjusPlLHVxnpSPid8CDfCCE6bU0vru +XRnC1lEoES55N992+HSDHOyKFT4IdofehOw09mFB11yZGZb6ER2urEqzmjaAoeRv +0rFS7r61AaGRxtmIOhdXwovHfkxcF9dpU6hnEON/EaBS9NZv8RxuLMBv042eM0tJ +YxV8Q/w4YgQXHnPo3YNyKdSF1ZecZ0Si4LEL8vUHiQOF3k1PrPd4QO8G4wC/bv8a +zJzk3xEd3NyewU2v1S9fcbNIqT5NPjnF3EfYc0iORGYfcdrEuiGIbWut5h2GFnXX +gOFXjQfTkQzdOTxLIRKHLfB/Eo6pR/YymBk9QVt+YdGvPxrwiXIu9ZxErB2pArxX +m9RRt/Uwz1QygnmRZGxuMeO1HnbZ1ZujGnt347QQD5g6rJmPQBxM3eBLR0Arqif5 +qiuiCOSDAHym2g23cku1VK2/VBOQLZAe6MLSefw6KZJLSnmWFZU2Aat9oz/5dpt0 +BcX5DKUyPjF3goEfn+jfF3SNTZ/qBKpylQlDgJRxTOYwbMuNoBgJkrrp7ccPp+v4 +mytkxZbxXcGGjxL1NDRkIgZXNFxI9QHpRGIsAuYdGXWmOlI7rkZL8GtAHRV5ZZ9e +t99di0e5iNGwLqFTfSiUeaQNYXMxgbILYLNdHXUkYQ0tepQTTVGwOVYBhjTRiTpd +5e2IBOjugCfzaxAHJxotp0MhCoLoqKB10s2q4J+VxkPkOlyp9tzSsya2AD1HEACk +sT2f/9w4z4QfiEZrOn4aShsgA3XSrX2zw5CTWnxqsAN/7ki1hJMuzc/C3aq83jw4 +sWhzz3Q0JVTkSzQVERPZDHsSHTZ2D5Yw5ONOJ16umrvtGZIQeQwraHWYngbE5gfK +Hf0TvybJnNupQ4+lNQx1ee1KGTO83nOi17qCWseV3PJiocQ0/n+JMbYDJ2QG//ea -----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa-encrypted.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa-encrypted.pem index cacab087c0f6..8bf362ecc319 100644 --- a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa-encrypted.pem +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa-encrypted.pem @@ -1,12 +1,30 @@ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED -DEK-Info: AES-128-CBC,5E22A2BD85A653FB7A3ED20DE84F54CD +DEK-Info: AES-128-CBC,2371A6F3F6DEF67420EED171CA8434D6 -hAqtb5ZkTMGcs4BBDQ1SKZzdQThWRDzEDxM3qBfjvYa35KxZ54aic013mW/lwj2I -v5bbpOjrHYHNAiZYZ7RNb+ztbF6F/g5PA5g7mFwEq+LFBY0InIplYBSv9QtE+lot -Dy4AlZa/+NzJwgdKDb+JVfk5SddyD4ywnyeORnMPy4xXKvjXwmW+iLibZVKsjIgw -H8hSxcD+FhWyJm9h9uLtmpuqhQo0jTUYpnTezZx2xeVPB53Ev7YCxR9Nsgj5GsVf -9Z/hqLB7IFgM3pa0z3PQeUIZF/cEf72fISWIOBwwkzVrPUkXWfbuWeJXQXSs3amE -5A295jD9BQp9CY0nNFSsy+qiXWToq2xT3y5zVNEStmN0SCGNaIlUnJzL9IHW+oMI -kPmXZMnAYBWeeCF1gf3J3aE5lZInegHNfEI0+J0LazC2aNU5Dg/BNqrmRqKWEIo/ +PqAIAklz79i2dRUlG7yUZQ03i951enRysHzT8iaU+UNO5BJwqQX/menlS7Ct3y55 +unPcY+Jx1yVerEPgIjhe9DR/HuqqH5TlC+OvfCsdlzj1+QJE3S7pQ/hwsuShNslM +RCppzdpYBpFI9Hc5LUJB32J2VP//1Y112+Cw+gS27Q8ZiWhH3ljYZpa6pcD6irk8 +JKSbC1pITxAy/66Cnf7CSKDj1852vwr9anUOr3Rq4CaDao0gNgV9qI+afzGYK0is +fqmyCSlazjNE2j4+mq3DSZB4CWMKVtJnNYcyPor+Xsfa48idY4sFjcxgVTb9kUGe +GoZTWW0uDfC1SM2fRMvc2AUvZ1E9NCC79yvJ4/joiNU3On5I221IdVQHmVLde2Y+ +RXmu2B4STboFkaHz4VTJp5iZzYjS5qYOYnwCdidiqi1VplNKpVIKcx7bY/ZqSSSQ +JHX5bUhmMFZaIQfXdO6sZZYel93enurPf64Yo3yoyoe9X2FxvIWF0bcNH7WDmpDi +T15VafsNu/x6ZGqjoF3cqeuI/ymJZ4Sx1GpWjqp9QQEp0vRnAA/kge7zs+WC3X8v +IV6/Tq5zGvhekDS9eHu11dR541CDxbWnIdwnxj5yluQPyzPbHLvSGMi5Rp6QyuT9 +wl9G5PJQGbLExnSAT55aBvFxA/OYW1yn80LutqCq2Vw9CW7JcvV2XPqa6y6nxmMf +gwDR6lwOIVzxx5jd+jjck4S5LOyswA4egbtTTJ5NEXLVBGZKqHS6tAd92oPmonuB +FHfKcqGGoMUYW0CKnPzyI1iCSKqiMaoQ8Ihpw1kdU0X3dC3uFsoYwYpebhWYQhus +DVcdLFgkHNQPg6jZ84V15y1kvlj4h57bUysurxbTSSy1L7bEDu5NNKkpvotKwPTH +qdk8rW1FyXcNGmuz6hmEMatySvpkyyIT81BMHkiT69i6KHedKxitRg3d7czZVyUA +iucnuyKg3+YeOwuZx4agxPVgWcHjiPJkbipyaAKUYZ3pPjU5ZiFBnNhESToZ+MyS +jUJL00yc1OgKa3LmBM0DRjhMWOFrDBOLFlzz6q/FIkj25PfvHApjZvVtfu8lj5tf ++uIIGHx7tgizGPwht/ZD1ah4QTo/hBr4tInFm0DWyHVgbwcY5+f2naWswRk91V/f +VVBaFO7GrjOF1Ej8CcdlUAt/drTtUf1Oehla9F3r17qXjD6+QRMY3LFcrCP0szet +aq8QyB1Z8PqwfAPV5JdBKlTDwCRdoEMPEjnTq0t5AXWPkhRjTvumWE3rl/HYbZla +0D+uMhWiA1Z0YQie8hxI5ZflZkfLAEk+5IFrOzTYZcPM9KqKMnrF/lvAi/mPb1lD +sEQypp+6SxhVI34rFySwSDxb/Wg6DqPXhCEOciYpDLkrkMBLcHz73x1njPuZ3wVS +iaxhInMljtTNZFDMKlNGFd2tI6CWDffkU106dwSqJ0KiQWnkZuF41rIkYSVxHU1S +iRvCDGHpisx2hzF1m+ZEsR5WmNKoI7C+XCiN9cZPGVOy/Kv6WyZDRSp6x4n2Whp7 +7qWzffq+OPGJpsG92L7mKCpvdveJtkCilxi/thkDnRtLzkiuANTyoQ2re9pMADl5 -----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa.pem b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa.pem index aad21067a8f7..b8176670327f 100644 --- a/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa.pem +++ b/vectors/cryptography_vectors/asymmetric/Traditional_OpenSSL_Serialization/testrsa.pem @@ -1,9 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIBPAIBAAJBAKrbeqkuRk8VcRmWFmtP+LviMB3+6dizWW3DwaffznyHGAFwUJ/I -Tv0XtbsCyl3QoyKGhrOAy3RvPK5M38iuXT0CAwEAAQJAZ3cnzaHXM/bxGaR5CR1R -rD1qFBAVfoQFiOH9uPJgMaoAuoQEisPHVcZDKcOv4wEg6/TInAIXBnEigtqvRzuy -oQIhAPcgZzUq3yVooAaoov8UbXPxqHlwo6GBMqnv20xzkf6ZAiEAsP4BnIaQTM8S -mvcpHZwQJdmdHHkGKAs37Dfxi67HbkUCIQCeZGliHXFa071Fp06ZeWlR2ADonTZz -rJBhdTe0v5pCeQIhAIZfkiGgGBX4cIuuckzEm43g9WMUjxP/0GlK39vIyihxAiEA -mymehFRT0MvqW5xAKAx7Pgkt8HVKwVhc2LwGKHE0DZM= +MIIEpAIBAAKCAQEAubZR/vxN1MmxwDEu5p8IA5kNWlOXhd0U8faIDZGY7h9xs7q7 +Hr6Xd4azC+oXDyS3oOexFvLGkIzzdJI5hJJBh4benU4PXz5W176euXHT+KT4EgV8 ++fkFO4KdHFTRo0D+XJCm4iilhx2pAHcBQbTG5vKYQJcYyxZGek9f9jiCsgQlUCj0 +l0Xe3Hyktcum14rPMrZQ8Gv4GGLtoIVqFOh2ftQIY0IoSm+XUulkNfcRmgXMMiCp +VHdKkx2+vh8asN+drq7bEydBw7XEjhoUJszZVPubUUDBTa7Jp5vpx8jlBhDftInH +U5mZz8FKx1dlurSuio312Ww940wSQ1saAs9uyQIDAQABAoIBAAmnaNIfWIZtaQrr +ePDZJzKqA/qEP5YLB5nfwx59c/HmUDlTxYK+zU3pLSk7OoakKyg3Ux/fxU23Xg0w +cBgBqFwSDpl7zisZKQI0cQ4v1MvnUNP9qrZYk8U5BXohuKIgG05Bi23/R0I5Bajg +sX/dFL07CDTMsKfCA9jmLmq0xlUtm3d4R8h050OsFZQqIYFrsXeRkhXuI1Bk+wp7 +O6qvrBSS4psvyA3Ba2M1Jdg+7XP6R6VamJQUilA1jrlMYrGehPPX2vhmzWpgaSDV +S6QdeqZI53fVJp/gCxKoz1zPgj9iwejcRC7Dp+M1aRP0RJGbqkpccpk0WBdUO0rd +X5waR38CgYEA+DN/vNS1ThTUImiJcl2dxxPkDIfmLOGIalF8cps9Ez3FGb+wJggX +iFCdK1A7wJZr3GfEV3HkH5hEzuG+losyY3NdbEfZgdrP3h/iEQxKy/5lZZmJC48T +HCDSRokZWfRdBtT63yBflPnqBQxmHv3HYNdHGhljvxYzODfvbcT4268CgYEAv4wq +1UrPZ/i2h4SfkezkdhkB6KvIsLyGBPVeZK1BOmIC27KOrARj+HgRwcqCaw7q+1PR +FbUN5ad190xenPgWG/wDD15AJmQ4jqHvfQrehVWeTmjO9RnLT1guxB+ZQknYuGCn +Qz8GEjIoJ6h7PMDXhQdYEbdrzLyQ/xU6EVkvowcCgYEA4M3MUd0bBkjJRw0GCOcQ +BANZF5xzd40jAKEjpa5DqEzXXBYJ1riXj+jsIhH+vNXBhhUaedV3OMKy9+rxs+sJ +zZftMyj0sa/dfKPGH4jRqmiVsGta/HQva9eyfR6qLpatN4XqX/QzfnzJYJ81U7aq +QmVaSiJa/PV/mNjY7MRuXpMCgYEAkErtpVlCnocMMVAlyI6Ul6ZE+toVR5Xsu2V/ +YwXkwi89CfUbZtez22PPtJVx42YMe6FrOxf1zQ92XQGJsGNufEw+neAZIRKUTFYO +i7qZYAXcSCLJ7Hcu4amDKTjIgdgRSut8dLrQPvrLpvxTQbPfZpXesRHkQgm2jIGY +CaOOsBcCgYA3ijrhl4w4Hc47SGsDhgHPBt+ndof9zS1WcyOAv/TzLuwgAnA0vNU7 +6AFi5AVKt/79vD5f6SOqgTDSyasB1qcP2jYV8GaIbqYQ4Gwpz1wuBkmkDKk28pC3 +ec2eK8O4cJUmZn91oQFuJorjuVAa5GluyMGvCdxWeAQVH96xSG7lEg== -----END RSA PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/ciphers/RC2/rc2-cbc.txt b/vectors/cryptography_vectors/ciphers/RC2/rc2-cbc.txt new file mode 100644 index 000000000000..4bff7c3518b5 --- /dev/null +++ b/vectors/cryptography_vectors/ciphers/RC2/rc2-cbc.txt @@ -0,0 +1,8 @@ +# RC2 128-bit CBC vector built for https://github.com/pyca/cryptography +# Verified against OpenSSL and Go crypto + +COUNT = 0 +Key = 30303030303030303030303030303030 +IV = 3030303030303030 +Plaintext = 74686520717569636b2062726f776e20666f78206a756d706564206f76657220746865206c617a7920646f6721212121 +Ciphertext = 5b886175cdbb0161badf64936b8ee4cb8f4b75fc28833f61668bb2bea88cfd32c410ac7ec016c5028f75078a88968887 diff --git a/vectors/cryptography_vectors/pkcs7/amazon-roots.der b/vectors/cryptography_vectors/pkcs7/amazon-roots.der index f9eab5c17771..cba6154224c6 100644 Binary files a/vectors/cryptography_vectors/pkcs7/amazon-roots.der and b/vectors/cryptography_vectors/pkcs7/amazon-roots.der differ diff --git a/vectors/cryptography_vectors/pkcs7/enveloped-aes-256-cbc.pem b/vectors/cryptography_vectors/pkcs7/enveloped-aes-256-cbc.pem new file mode 100644 index 000000000000..bddac0b4ea30 --- /dev/null +++ b/vectors/cryptography_vectors/pkcs7/enveloped-aes-256-cbc.pem @@ -0,0 +1,16 @@ +-----BEGIN PKCS7----- +MIICmwYJKoZIhvcNAQcDoIICjDCCAogCAQAxggJDMIICPwIBADAnMBoxGDAWBgNV +BAMMD2NyeXB0b2dyYXBoeSBDQQIJAOcS06ClbtbJMA0GCSqGSIb3DQEBAQUABIIC +ACTeTHyg8zwnBdhLFogSBMInoAqc8HHZ+3vRN57MJ9UA4MIkqgrUEMg2sYwNkpuS +pT3B0tw3CbrJwL4SemPul1FuYMluTRdhJuI9wskR9BvE6d+BlmnFSjNGdt1y9RM+ +7ZqViXGA2t2HVRQ42Q43tkDUL7gMzveYZ1LxG1d+GNbfKLHVqJLokIe+IQYtyRay +3Tck7l/cC2VpI9lwmF+DugpZbagmb3pSij/ZSzzub3PwNp4YaL2YSa1Vkswdm3LD +jhOMSKyw7jIn2e9gQ3VI8vzh/38OFFFoKq7sAGvNGSLDbCHm6AKvOylksnTCUBF2 +6mbNWaaNpRjCQU+8N5/1UblJAs/voG+hGuWbGjS6z4v6mYvIr5731rQjxYbIpZRT +B6+lu9sCbwHuYQKe8MBlsn0+Y/o7l25m+xOfeRK1UGViUNV+2G2SQKY2CnfBoPis +lZSwKv1mfYifT1bsVyTsDWi0yr3BdbhVRI4pLziNrMFJ5tJhN2Y8HB2FGLlmzJtM +YRyljlMtj3YrYnhX82dKIwlrLfoWYP90tiiGh3DlqUTVCj4Y/IBmFGF6VpKWYZ0F +1VGwR8dDt0a0IonoBo3T4OtqUStlMkWgwGyNlauZnXt4jHoP5ECZ23TLpAtLCgUE +BuTiSXYFHaz+ToomhzTqrqznhLf9PRV+TM96/66xYdSYMDwGCSqGSIb3DQEHATAd +BglghkgBZQMEASoEEFSk9vw7RRWfjkB3sVedCgqAEPYXgbXvcA4rj2DCHA80Etg= +-----END PKCS7----- diff --git a/vectors/cryptography_vectors/pkcs7/enveloped-no-content.der b/vectors/cryptography_vectors/pkcs7/enveloped-no-content.der new file mode 100644 index 000000000000..3bdf58523f6c Binary files /dev/null and b/vectors/cryptography_vectors/pkcs7/enveloped-no-content.der differ diff --git a/vectors/cryptography_vectors/pkcs7/enveloped-rsa-oaep.pem b/vectors/cryptography_vectors/pkcs7/enveloped-rsa-oaep.pem new file mode 100644 index 000000000000..6acec6915e7d --- /dev/null +++ b/vectors/cryptography_vectors/pkcs7/enveloped-rsa-oaep.pem @@ -0,0 +1,16 @@ +-----BEGIN PKCS7----- +MIICmwYJKoZIhvcNAQcDoIICjDCCAogCAQAxggJDMIICPwIBADAnMBoxGDAWBgNV +BAMMD2NyeXB0b2dyYXBoeSBDQQIJAOcS06ClbtbJMA0GCSqGSIb3DQEBBzAABIIC +AKQssr4/Kd+CcT6waZG2xeaM8z8AcL1ISOqcul01uZNG/7LmGffjkpSWZmv4fZsY +ZkmZI5eKYk1DcOmMAx8lbKt3uAqOLQi2UuZBk/iY0k20GXk9G6hA7fhOy6yL4ntR +h4I+iX5DeVvGu4HTMV0gAGHBf3mCrpZkZrXdX8iL4N4xMpwNim5FO9js+9/I4c2u +AOWGKrOO8oR5cc8ty7rC/PZ3qQ0B26SdXr4kiQPdLZAE10WR0A7WZdTwzIBGRX8S +r9SCi5cKokE30ft/J7ckojpu6hmfFOdPY6+14p+1+7WoqNmDkcROiFB7kDnkkBp/ +hDnMHIlmP0/tzsAr0FWnIgP9ht2dJrCL0aA/pITh3IVgIxdB5cIqTfUbRSm/ahpI +XnR8cZjV864vx9ioqVqCxR6FOtV0faFwie3gIy4M4gD5VFWX+cWX3KQRHN6tYLAR +5yu9jt1ArB9kO+q8fUZ99MC6DesnLraYldWUI/nmv3ioUxOPYFEMyFR00y2fjDBf +zyB5w/uHcqP2Im1hXqjixcIKLoijNe2KSdYhNngE3vwl/hxlhCgjncsZulL8Nlyv +VFeaphRJcHrKwoEUO4PCkoMi6TbrrS/wYwjgIW6ftBvgXGr751NJdDSDbfT3bkdm +ixQrG7Osq9sV83s9cAkuXsrxLj5Vou0KjaWWrwNxBVWXMDwGCSqGSIb3DQEHATAd +BglghkgBZQMEAQIEECvpZHTTj4XIKBhqcfKQrGaAEJuq6z8EFxz5sbr6W0opVEA= +-----END PKCS7----- diff --git a/vectors/cryptography_vectors/pkcs7/isrg.pem b/vectors/cryptography_vectors/pkcs7/isrg.pem index 63698aa11348..3f7d54956644 100644 --- a/vectors/cryptography_vectors/pkcs7/isrg.pem +++ b/vectors/cryptography_vectors/pkcs7/isrg.pem @@ -1,33 +1,32 @@ -----BEGIN PKCS7----- -MIIFngYJKoZIhvcNAQcCoIIFjzCCBYsCAQExADAPBgkqhkiG9w0BBwGgAgQAoIIF -bzCCBWswggNToAMCAQICEQCCEM+w0kDjWURj4LtjgosAMA0GCSqGSIb3DQEBCwUA -ME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNl -YXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgxMB4XDTE1MDYwNDExMDQz -OFoXDTM1MDYwNDExMDQzOFowTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVy -bmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3Qg -WDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1co -HIe+3LffOJCMbjzmV6B493XCov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZsh -ftEzPLpI9d1537O4/xLxIZpLwYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+ -lAOf00eXfJlII1PoOK5PCm+DLtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vr -Fk/CjhFLfs8L6P+1dy70sntK4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6s -hweU9GNx7C7ib1uYgeGJXDR5bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98fl -AgeYjzYIlefiN5YNNnWe+w5ysR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81 -LygXbNKYwagJZHduRze6zqxZXmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1 -pzpRboY7nn1ypxIFeFntPlF4FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0 -544fAQjQMNRbcTa0B7rBMDBcSLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K2 -8Kh8hjtGqEgqiNx2mna/H2qlPRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdw -iK1O5tmLOsbdJ1Fu/7xk9TNDTwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJ -KoZIhvcNAQELBQADggIBAFUfWKm8sqhQ0Ayx2BppICcpCKxhdVyKbviC5Wkv1fZW -S7m4cxBZ0yGXfudMcfuy0mCtOagL6hchVoXxUA5Z687gWem6yRXvhp2PhID25OmR -kNwXm2IbRfBmldJ8b8LqO+8fz8vWrifxqbDIrv19fpr6IgTr/9l/6pErIrEXDo/y -ijRbWNj8AclUubgmzIqIM4lMLYQ8gt/ullcFuiy798S3x047gr4xyCJzc5LRwoCk -OTkQMyOCTDyfhrJVmB2+KYaMIpue4ms7VzqCcE3cCceJywoHTWzoXY7J786rx7u1 -K05F1krQJszlcsoIaqWV4xWh96TtySxfpfv/rCgCLr7Xe7vjcXuQFtMHXkZTfDcH -QozTxJac1Zm1KuCVGoBIrkw5B87MR6RSlSu6uPut0jNTfeUdTW3VobHHQm/mQCc1 -XKMotweN540zkOcjn/tQnHlsRtW0FbOWbn6bDJY6uFItP9Zb4fsIwoT+JKijidqs -auEYKrGoQ2Fb0x/cO4128i3ojXXfFzNsPVP7e8tBX//cotBhOOGWuKxdizfXddUz -wJkRrp1BwXJ1hL4CQUJfZyRIlNGbJ74HP7m4T4F0UeF6t+2dI+K+4NUoBBM8MQOe -3Xpsj8YHGMZ/3keOPyieBAbPpVQ0d73siZvpF0PfW9tf/o4eV6LNQJ1+YiLa3hgn -MQA= +MIIFmgYJKoZIhvcNAQcCoIIFizCCBYcCAQExADALBgkqhkiG9w0BBwGgggVvMIIF +azCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzEL +MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo +IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcN +MzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQg +U2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygch77c +t984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8 +ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/T +R5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KO +EUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0 +Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UCB5iP +NgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUvKBds +0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8B +CNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyG +O0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m +2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkqhkiG +9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZLubhz +EFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3Beb +YhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY +2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAz +I4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXW +StAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdCjNPE +lpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVcoyi3 +B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4Rgq +sahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGu +nUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyP +xgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCcxAA== -----END PKCS7----- diff --git a/vectors/cryptography_vectors/x509/custom/admissions_extension_authority_not_provided.pem b/vectors/cryptography_vectors/x509/custom/admissions_extension_authority_not_provided.pem new file mode 100644 index 000000000000..147f26196b8c --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/admissions_extension_authority_not_provided.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDiTCCAy+gAwIBAgIUDuURI/KxJjJlnU/YDGmX0V0DyNQwCgYIKoZIzj0EAwIw +JzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTAeFw0yNDEx +MDkxMzI4MjVaFw0yNDEyMDkxMzI4MjVaMCkxCzAJBgNVBAYTAlVTMRowGAYDVQQD +DBFjcnlwdG9ncmFwaHkgdGVzdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBANBIheRc1HT4MzV5GvUbDk9CFU6DTomRApNqRmizriRqm6OY4Ht3d71BXog6 +/IBkqAnZ4/XJQ40G4sVDb52k11oPvfJ/F5pc+6UqPBL+QGzYGkJoubAqXFpI6ow0 +qayFNQLv0T9o4yh0QQOoGvgCmv91qmitLrZNXu4U9S76G+DiGST+QyMkMxj+VsGR +sRRBufV1urcnvFWjU6Q2+cr2cp0mMAG96NTyIskYiJ8vL03Wz4DX4klO4X47fPmD +nU/OMn4SbvMZ896j1L0J04S+uVThTkxQWcFcqXhX5qM8kzcjJUmybFlbf150j3Wi +ucW48K/j7fJ0x9q3iUo4Gva0coScglJWcgo/BBCwFDw8NVba7npxSRMiaS3qTv0d +EFcRnvByc+7hyGxxlWdTE9tHisUI1eZVk9P9ziqNOZKscY8ZX1+/C4M9X69Y7A8I +74F5dO27IRycEgOrSo2z1NhfSwbqJr9a2TBtRsFinn8rjKBIzNn0E5p9jO1Wjxtk +cjHfXXpLN8FFMvoYI9l/K+ZWDm9sboaF8jrgozSc004AFemAH79mmCGVRKXn1vDA +o4DLC6p3NiBFYQcYbW9V+beGD6srsF6xJtuY/UwtPROLWSzuCCrZ/4BlmpNsR0eh +IFFvzEKjX6rR2yp3YKlguDbMBMKMpfSGxAFwcZ7OiaxR20UHAgMBAAGjbDBqMA0G +BSskCAMDBAQwAjAAMB0GA1UdDgQWBBTWrADzmGKoPZIVNf6QvnOYMOtMhDA6BgNV +HSMEMzAxoSukKTAnMQswCQYDVQQGEwJVUzEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5 +IENBggIDCTAKBggqhkjOPQQDAgNIADBFAiAnRuoEuL/8c/B3Cb89FOSMlV/sX1QW +MXM8X69xVWxyjAIhAIuZ8HI2TUtuTOGascFW46AjkPfwCggknB7kkq86QOn3 +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/admissions_extension_optional_data_not_provided.pem b/vectors/cryptography_vectors/x509/custom/admissions_extension_optional_data_not_provided.pem new file mode 100644 index 000000000000..5899cf19769a --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/admissions_extension_optional_data_not_provided.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF1zCCBXygAwIBAgIUckdGKz+upx7gGI/r6y1UvvQQFKowCgYIKoZIzj0EAwIw +JzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTAeFw0yNDEx +MDkxMzI0NTlaFw0yNDEyMDkxMzI0NTlaMCkxCzAJBgNVBAYTAlVTMRowGAYDVQQD +DBFjcnlwdG9ncmFwaHkgdGVzdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBANBIheRc1HT4MzV5GvUbDk9CFU6DTomRApNqRmizriRqm6OY4Ht3d71BXog6 +/IBkqAnZ4/XJQ40G4sVDb52k11oPvfJ/F5pc+6UqPBL+QGzYGkJoubAqXFpI6ow0 +qayFNQLv0T9o4yh0QQOoGvgCmv91qmitLrZNXu4U9S76G+DiGST+QyMkMxj+VsGR +sRRBufV1urcnvFWjU6Q2+cr2cp0mMAG96NTyIskYiJ8vL03Wz4DX4klO4X47fPmD +nU/OMn4SbvMZ896j1L0J04S+uVThTkxQWcFcqXhX5qM8kzcjJUmybFlbf150j3Wi +ucW48K/j7fJ0x9q3iUo4Gva0coScglJWcgo/BBCwFDw8NVba7npxSRMiaS3qTv0d +EFcRnvByc+7hyGxxlWdTE9tHisUI1eZVk9P9ziqNOZKscY8ZX1+/C4M9X69Y7A8I +74F5dO27IRycEgOrSo2z1NhfSwbqJr9a2TBtRsFinn8rjKBIzNn0E5p9jO1Wjxtk +cjHfXXpLN8FFMvoYI9l/K+ZWDm9sboaF8jrgozSc004AFemAH79mmCGVRKXn1vDA +o4DLC6p3NiBFYQcYbW9V+beGD6srsF6xJtuY/UwtPROLWSzuCCrZ/4BlmpNsR0eh +IFFvzEKjX6rR2yp3YKlguDbMBMKMpfSGxAFwcZ7OiaxR20UHAgMBAAGjggK3MIIC +szCCAlQGBSskCAMDBIICSTCCAkWkQjBAMQswCQYDVQQGEwJERTExMC8GA1UECgwo +RWxla3Ryb25pc2NoZXMgR2VzdW5kaGVpdHNiZXJ1ZmVyZWdpc3RlcjCCAf0wgfKg +BYgDVQQKoTQwMgYIKoIUAEwEgV8WAAwkQmV0cmllYnNzdMODwqR0dGUgR0tWLVNw +aXR6ZW52ZXJiYW5kMIGyMIGvoE8wTQYIKoIUAEwEgWEWE2h0dHBzOi8vZXhhbXBs +ZS5jb20MLEJldHJpZWJzc3TDg8KkdHRlIERldXRzY2hlciBBcG90aGVrZXJ2ZXJi +YW5kMBIMDsODwoRyenRpbi9Bcnp0DAAwEgYHKoIUAEwEHgYHKoIUAEwEHxMOOS05 +OTkvOTk5OTk5OTkEJBYiYWRkaXRpb25hbCBwcm9mZXNzaW9uIGluZm8gZXhhbXBs +ZTCB8aAPoA0GA1UEBqAGBAQTAkRFMIHdMIGcoGYwZAYIKoIUAEwEgWMMWEJldHJp +ZWJzc3TDg8KkdHRlIGRlciBEZXV0c2NoZSBLcmFua2VuaGF1cyBUcnVzdENlbnRl +ciB1bmQgSW5mb3JtYXRpb25zdmVyYXJiZWl0dW5nIEdtYkgwDQwLS3Jhbmtlbmhh +dXMwEwYHKoIUAEwENQYIKoIUAEwEgXYTDjkuOS45LTk5OTk5OTk5MDwwLQwLS3Jh +bmtlbmhhdXMMHkJldHJpZWJzc3TDg8KkdHRlIEdlYnVydHNoaWxmZTAJBgcqghQA +TAQ1EwAwBjAEMAIwADAGoQIwADAAMAIwADAdBgNVHQ4EFgQU1qwA85hiqD2SFTX+ +kL5zmDDrTIQwOgYDVR0jBDMwMaErpCkwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMM +D2NyeXB0b2dyYXBoeSBDQYICAwkwCgYIKoZIzj0EAwIDSQAwRgIhAMz8iUp3Tj0W +3mMOPIyNyQ6ZwydHCX199oH5j0opH+4GAiEAyOF2Mw4H6xDOfsEa2NvnpO4mt8Pa +y7msciyCxhMgUZY= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/ca/rsae_ca.pem b/vectors/cryptography_vectors/x509/custom/ca/rsae_ca.pem new file mode 100644 index 000000000000..1b357a1007d6 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/ca/rsae_ca.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFczCCAyygAwIBAgIUXd3jDutyo6oiszLWxbtjcQQQh9kwPAYJKoZIhvcNAQEK +MC+gDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF +ADAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8wHhcNMjQwMzIzMjMwNzU1WhcN +NDMwNTIzMjMwNzU1WjAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8wggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDQSIXkXNR0+DM1eRr1Gw5PQhVOg06J +kQKTakZos64kapujmOB7d3e9QV6IOvyAZKgJ2eP1yUONBuLFQ2+dpNdaD73yfxea +XPulKjwS/kBs2BpCaLmwKlxaSOqMNKmshTUC79E/aOModEEDqBr4Apr/daporS62 +TV7uFPUu+hvg4hkk/kMjJDMY/lbBkbEUQbn1dbq3J7xVo1OkNvnK9nKdJjABvejU +8iLJGIifLy9N1s+A1+JJTuF+O3z5g51PzjJ+Em7zGfPeo9S9CdOEvrlU4U5MUFnB +XKl4V+ajPJM3IyVJsmxZW39edI91ornFuPCv4+3ydMfat4lKOBr2tHKEnIJSVnIK +PwQQsBQ8PDVW2u56cUkTImkt6k79HRBXEZ7wcnPu4chscZVnUxPbR4rFCNXmVZPT +/c4qjTmSrHGPGV9fvwuDPV+vWOwPCO+BeXTtuyEcnBIDq0qNs9TYX0sG6ia/Wtkw +bUbBYp5/K4ygSMzZ9BOafYztVo8bZHIx3116SzfBRTL6GCPZfyvmVg5vbG6GhfI6 +4KM0nNNOABXpgB+/ZpghlUSl59bwwKOAywuqdzYgRWEHGG1vVfm3hg+rK7BesSbb +mP1MLT0Ti1ks7ggq2f+AZZqTbEdHoSBRb8xCo1+q0dsqd2CpYLg2zATCjKX0hsQB +cHGezomsUdtFBwIDAQABo1MwUTAdBgNVHQ4EFgQU1qwA85hiqD2SFTX+kL5zmDDr +TIQwHwYDVR0jBBgwFoAU1qwA85hiqD2SFTX+kL5zmDDrTIQwDwYDVR0TAQH/BAUw +AwEB/zA8BgkqhkiG9w0BAQowL6APMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcN +AQEIMA0GCWCGSAFlAwQCAQUAA4ICAQCyy7dZwQGOiS7id+sSFIm7EPR8GGFEE49D +2CfKl6eRqfwwRBeGE8NO+Ndh3ZD88cVKDlyHLZdNefnY0fXK5dakZDAP6cCSvJYP +lo0q2ugZy80SmQstDtMTfOic6sfQTmdtCf5PqFgSt+zeDnU7RpmAVY8QO2WVS1HK +5X4/WW1YG/fEU1r/5KN80GsLaxyWip9xBlQ5M0FvFML7kKawbQn2e2juckvJMMhL +bQnS/viPqFjqk6e9NwXO7uTr3eXKJ2gLasFrP2WDXLvpnfjFIPyE7cg+oZFSNa96 +i0bzDGgQPa13cT5Bz5BzHrCmvnFOV5xX54MdkKNROxmyLBC8rTLqtUqaoW27q05S +novxXRVfxDbHVgNcealaAX40xLPXAF+Os8wWbZ58Gnhi4g/UvxOV5oqT7oql3n4M +f67B5ko45fetLAbyezT6znAd7sapaukEDWyiSOftHdxhnDKi16F96EMdh1h0ZrRE +u/CfUUntm6ET6sGAM+exrH7Rd3NTYfTof00I9H0hVxEIHSmszWTQjrF8EScJkgcL +PgkuKOQ32TzKjq+QQVIvk5tXf02VlBSUA9THctPxGewGzk9YJBCSYiBkSjqXqyiS +5MflShh/ktK07jGGMlC+k8+IhPjMUnEzQxwseHiIVlwMz6h7tmsL1ciVN1oLrAld +zvv7WyNrLA== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/ekucrit-testuser-cert.pem b/vectors/cryptography_vectors/x509/custom/ekucrit-testuser-cert.pem new file mode 100644 index 000000000000..907fc7bc3fd2 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/ekucrit-testuser-cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDyTCCArGgAwIBAgIUQWZSqoDvybWdo39pxRgeN0bLh8QwDQYJKoZIhvcNAQEL +BQAwLDEUMBIGA1UECgwLVGVzdCBJc3N1ZXIxFDASBgNVBAMMC2V4YW1wbGUubmV0 +MB4XDTI0MDYyNTIyNTY0MFoXDTI0MDkyMzIyNTY0MFowLzEtMCsGA1UEAwwkZTBk +Y2JmNTEtMDIyNC00MzYzLWI3NWUtYjZjZmIxODE3NzUzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAwq9wRSIpDGjEfRSOHxcfaOQmi1QR2AV0m1Exu8RW +WwE+SycflSQOcPxNWn1B0dvVAIAmp5fSBram+6fdB+qgP/fz9/mHBBvP1+J7lLue +1CUUDkci6P136HQ+kSsEDqrwMXzPESVNJk6b0FusF0gCEGTe01pgHKd82mpXK62W +tSYFOYEFV4kB7u0ckkWEhiKGTKQ+zI5GSeApy23ao8q+oHDdBcD91ViYwgoWwKMY +mYhZyLFZHh4D7axi275HjqVZZ1AmCy0bSLMgxwgHKEeFRmR3Yaoz3TkTi0fAUs4e +w6Rdtor/PMecunp6atiHVUj9FWraAafGzVrM8Wfj6t88FwIDAQABo4HfMIHcMGQG +A1UdEQRdMFuGKnVybjpwdWJsaWNpZDpJRE4rZXhhbXBsZS5uZXQrdXNlcit0ZXN0 +dXNlcoYtdXJuOnV1aWQ6ZTBkY2JmNTEtMDIyNC00MzYzLWI3NWUtYjZjZmIxODE3 +NzUzMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjAMBgNV +HRMBAf8EAjAAMB0GA1UdDgQWBBQOeL5d5FUOQeZD99n1nxTvFMmN6DAfBgNVHSME +GDAWgBQOeL5d5FUOQeZD99n1nxTvFMmN6DANBgkqhkiG9w0BAQsFAAOCAQEAjL4c +TUCEYWDWW03AWskf7GGeUb2wehWOoH7cw5dtZa4UC1JghuPs+HbMLxRvy6/NsnrV +7ZzzXiutTQEbE5EBQBhJAjuh34uogNe1itRvCFq8xUTQ+e8xP1nXCfZ2UMD0rb1F +kvpqm4cFpX9AizjhnwOi4X7/svnv79yovfwGKPgUMfVb3Vbnd6aMeZbBh34hSSBn +Emigl7tmS2KOs/eD+O2zQFu4NgUe4HH+jdE0+FDBkYwIOhLPGL2pCmdb7kM60Oo4 +W4yvwiQSJkfn1u4xvBoONsp8lNVkpYfFHWotuwCrHchVgCyaXcp7fEFUrl6mb+CY +s4x++eieNDpxzcFsuw== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/empty-eku.pem b/vectors/cryptography_vectors/x509/custom/empty-eku.pem new file mode 100644 index 000000000000..d8f8880f4cad --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/empty-eku.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBpjCCAUygAwIBAgIUXbgOb3WRImMh6PjbldAK3smepIkwCgYIKoZIzj0EAwIw +GjEYMBYGA1UEAwwPeDUwOS1saW1iby1yb290MCAXDTcwMDEwMTAwMDAwMVoYDzI5 +NjkwNTAzMDAwMDAxWjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABM3LPV6xuBpFrGXEPvnjF2VnXwhfqYbfIrWUSVQFf6Eb +TiPFZH96VPllxT176ftzTAHWMSG0oCdEduz2MFR0nqWjcjBwMB0GA1UdDgQWBBS+ +VOamU8j9i+62OkrB1PsJXEHTpTAfBgNVHSMEGDAWgBTrOA5ME/MKp4PpBUmEBQ6U +vTpcWjALBgNVHQ8EBAMCB4AwCQYDVR0lBAIwADAWBgNVHREEDzANggtleGFtcGxl +LmNvbTAKBggqhkjOPQQDAgNIADBFAiEAq8/MoJb/PyG710O0o/dAXYvsCbQgNNvg +CAcF/8JQGxUCIEJgYI2pX8slVoRke9RDDMKzNQ49qkKOd++v2tTb+rbh +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/malformed-ian.pem b/vectors/cryptography_vectors/x509/custom/malformed-ian.pem new file mode 100644 index 000000000000..a7c7d609339d --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/malformed-ian.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlDCB/qADAgECAgo/X5syqzQbiVZiMA0GCSqGSIb3DQEBBQUAMAAwHhcNMTIw +OTI3MTEyNDQzWhcNMTcwOTI3MTEyNDQzWjAAMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDEyUkICYplDtDRdLjZV0nF5oK5tBjoXWPxnfx6Msg5Ywvxjh4jq8Jf +FRwn9oLYpFmnhPYaVNWO7fykCrYz8O6mMtYInUbodvIPniZXjoTlYOPUmLj/XcU0 +iGhUmdo8yquPoe7TC9DDeSfaAwoLMDZjJoQjlBuRk+qTmfySJCNZrQIDAQABoxYw +FDASBgNVHRIECzAJoAcGA1UEAwwAMA0GCSqGSIb3DQEBBQUAA4GBAD5jUyH8eLrZ +tJtEJIVH/cvjtATXWwUnPX5NUGrgIBFwKx1f4csOFe6MIhA7j0VwSJ/iOd4xszLA +r8/2ijoBc+cPbThPSHLdOvOrGJsdrywOUYzGHRh/zoMEnT/FN9p7YbYnQIwFGqx1 +HUFnXljOXCezE5ytzEcpQ/43EvT4u74O +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/malformed-san.pem b/vectors/cryptography_vectors/x509/custom/malformed-san.pem new file mode 100644 index 000000000000..00aa6feeaedc --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/malformed-san.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlDCB/qADAgECAgo/X5syqzQbiVZiMA0GCSqGSIb3DQEBBQUAMAAwHhcNMTIw +OTI3MTEyNDQzWhcNMTcwOTI3MTEyNDQzWjAAMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDEyUkICYplDtDRdLjZV0nF5oK5tBjoXWPxnfx6Msg5Ywvxjh4jq8Jf +FRwn9oLYpFmnhPYaVNWO7fykCrYz8O6mMtYInUbodvIPniZXjoTlYOPUmLj/XcU0 +iGhUmdo8yquPoe7TC9DDeSfaAwoLMDZjJoQjlBuRk+qTmfySJCNZrQIDAQABoxYw +FDASBgNVHREECzAJoAcGA1UEAwwAMA0GCSqGSIb3DQEBBQUAA4GBAD5jUyH8eLrZ +tJtEJIVH/cvjtATXWwUnPX5NUGrgIBFwKx1f4csOFe6MIhA7j0VwSJ/iOd4xszLA +r8/2ijoBc+cPbThPSHLdOvOrGJsdrywOUYzGHRh/zoMEnT/FN9p7YbYnQIwFGqx1 +HUFnXljOXCezE5ytzEcpQ/43EvT4u74O +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/requests/zero-element-attribute.pem b/vectors/cryptography_vectors/x509/requests/zero-element-attribute.pem new file mode 100644 index 000000000000..df380fab6e38 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/zero-element-attribute.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICgDCCAWgCAQAwLDEQMA4GCSqGSIb3DQEJARYBLzEYMBYGA1UEAwwPbWl0ZWwu +YmxvbmF5LmNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA765FwcoI +JtKM566SSLXtz85h1ejx3G+efgG2OSiFIcZzPHQnuUPJ5ONL16VedcWi+8OB2Rbx +KWLf8DH3YK9CAxYeMX/eAay4MCbl9AROiDVhyhHL1DU3pUH4MkVKdwPhZiW1b7gM +W0DcY6iAuhLsftz5J/uyjGztfNRciErBZeNCh34fZcls4Iddkh0A6mz7KT4PmfNt +Ywo6+5sG4G0TZPlmXM803soWqfWCX/8FnzXd9ch1oApLE9zfxOlvWM7YBwyGCzZd +92PfX6D6sbMNmQxoZzT4LXeM4wZ11Jv9PHaGIDV/ub/1/7W0hYWnTHvvJRm9Tiyv +5JCH9/VpGhjIGQIDAQABoA8wDQYJKoZIhvcNAQkOMQAwDQYJKoZIhvcNAQELBQAD +ggEBAA9i4mqUrcakDp4YmjwQXaYQhSzxQZjk8xveHLRcyx4Cg8FAE5iUW8s1S+1f +pODlPrsdmZzRq3o+ZEkZNTM63kaXjDQEzlihlQ2yAScKAV22934pLyrMLn3mo5lO +oYgfSCHgYQE3YpNe8a2UFgWU5dhDbucCqbUO/AnBNTcBHpGHyvijbOBJn1cheLjZ +I7jbylyJBjyRgDiG3QNsgc/Iw58ys3DNCTsG0ghAwOh1g1u0LnZJKll1IWuK/HHI +D8d1ZsJic8ok8BkC/qGsrgQmoJpOP1Fu087svKcUbFT9T8UXzPigL1wEaxRPwkI8 +ECT4bDqrtBADIblEpqq4rNp4QoA= +-----END CERTIFICATE REQUEST----- diff --git a/vectors/pyproject.toml b/vectors/pyproject.toml index 0c43684bb92a..4d9c48e8713c 100644 --- a/vectors/pyproject.toml +++ b/vectors/pyproject.toml @@ -4,11 +4,12 @@ build-backend = "flit_core.buildapi" [project] name = "cryptography_vectors" -version = "42.0.0" +version = "44.0.1" authors = [ {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} ] description = "Test vectors for the cryptography package." +readme = "README.rst" license = {text = "Apache-2.0 OR BSD-3-Clause"} [project.urls]