From 54ac985fba18d509e0e6b3eec7c23ff874a7f075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:06:45 -0400 Subject: [PATCH 01/10] build(deps): bump github/codeql-action in the actions group (#109) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 18ed27c..eafcf0f 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -27,7 +27,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 + uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 with: sarif_file: results.sarif category: zizmor From 23022d2a90f6725f629aea8ff351f2c70fcec7fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:38:15 -0400 Subject: [PATCH 02/10] build(deps): bump astral-sh/setup-uv in the actions group (#110) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index eafcf0f..2370640 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -21,7 +21,7 @@ jobs: with: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 - name: Run zizmor 🌈 run: uvx zizmor --format sarif . > results.sarif env: From 6c6e7d7085ac32bcd9f07c91dd092d8a2733a4cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:36:51 -0400 Subject: [PATCH 03/10] build(deps): bump the actions group with 2 updates (#111) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 2370640..01bea96 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -21,13 +21,13 @@ jobs: with: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6.0.0 - name: Run zizmor 🌈 run: uvx zizmor --format sarif . > results.sarif env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 + uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 with: sarif_file: results.sarif category: zizmor From 2c1df87e087241df0bfbab3d298d34e687c96aab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 11:00:09 -0400 Subject: [PATCH 04/10] build(deps): bump the actions group with 2 updates (#113) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 01bea96..a1ab683 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -21,13 +21,13 @@ jobs: with: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6.0.0 + uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 - name: Run zizmor 🌈 run: uvx zizmor --format sarif . > results.sarif env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif category: zizmor From bd232686fa6f0e7f510385ce0a3bf5b0245213b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 11:02:02 -0400 Subject: [PATCH 05/10] build(deps): bump pyo3 from 0.24.1 to 0.24.2 in the cargo group (#112) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a3c512..43169ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -971,9 +971,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229" +checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" dependencies = [ "cfg-if", "indoc", @@ -989,9 +989,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1" +checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" dependencies = [ "once_cell", "target-lexicon", @@ -999,9 +999,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc" +checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" dependencies = [ "libc", "pyo3-build-config", @@ -1019,9 +1019,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44" +checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1031,9 +1031,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855" +checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 008c175..45d4ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ crate-type = ["cdylib"] [dependencies] age-core = "0.11" age = { version = "0.11.1", features = ["ssh", "plugin"] } -pyo3 = { version = "0.24.1", features = [ +pyo3 = { version = "0.24.2", features = [ "extension-module", "abi3", "abi3-py39", From b11d7b4c0b1daae8a68d08c584ab03f3460f7780 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 09:51:07 -0400 Subject: [PATCH 06/10] build(deps): bump github/codeql-action in the actions group (#114) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index a1ab683..0e13fea 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -27,7 +27,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: sarif_file: results.sarif category: zizmor From 16e97e81626a57232017f2a26b2fd4f0a637edda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 20:06:57 -0400 Subject: [PATCH 07/10] build(deps): bump astral-sh/setup-uv in the actions group (#115) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 0e13fea..3403f10 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -21,7 +21,7 @@ jobs: with: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 + uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 - name: Run zizmor 🌈 run: uvx zizmor --format sarif . > results.sarif env: From e7e51fe4b7e357176309e39f696d11605abdea2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:28:47 -0400 Subject: [PATCH 08/10] build(deps): bump github/codeql-action in the actions group (#117) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 3403f10..666143c 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -27,7 +27,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: sarif_file: results.sarif category: zizmor From f1137465ddc95eca62786f91fdaf4ae8bbba108b Mon Sep 17 00:00:00 2001 From: Naveen Nathan Date: Sat, 14 Jun 2025 11:17:05 +1000 Subject: [PATCH 09/10] Implement Armored Ciphertext (#116) --- Cargo.toml | 2 +- dev-requirements.txt | 1 + pyrage-stubs/pyrage/__init__.pyi | 6 +-- pyrage-stubs/pyrage/passphrase.pyi | 8 +--- src/lib.rs | 72 +++++++++++++++++++++++------- src/passphrase.rs | 35 ++++++++++++--- test/test_passphrase.py | 7 ++- test/test_pyrage.py | 38 +++++++++------- 8 files changed, 118 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45d4ea0..d9a08b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["cdylib"] [dependencies] age-core = "0.11" -age = { version = "0.11.1", features = ["ssh", "plugin"] } +age = { version = "0.11.1", features = ["ssh", "plugin", "armor"] } pyo3 = { version = "0.24.2", features = [ "extension-module", "abi3", diff --git a/dev-requirements.txt b/dev-requirements.txt index 3118f58..2e30b5e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,3 +2,4 @@ build wheel twine maturin +parameterized diff --git a/pyrage-stubs/pyrage/__init__.pyi b/pyrage-stubs/pyrage/__init__.pyi index 4247717..8c815d5 100644 --- a/pyrage-stubs/pyrage/__init__.pyi +++ b/pyrage-stubs/pyrage/__init__.pyi @@ -42,9 +42,9 @@ class DecryptError(Exception): ... -def encrypt(plaintext: bytes, recipients: Sequence[_Recipient]) -> bytes: ... -def encrypt_file(infile: str, outfile: str, recipients: Sequence[_Recipient]) -> None: ... -def encrypt_io(in_io: BufferedIOBase, out_io: BufferedIOBase, recipients: Sequence[_Recipient]) -> bytes: ... +def encrypt(plaintext: bytes, recipients: Sequence[_Recipient], armored: bool) -> bytes: ... +def encrypt_file(infile: str, outfile: str, recipients: Sequence[_Recipient], armored: bool) -> None: ... +def encrypt_io(in_io: BufferedIOBase, out_io: BufferedIOBase, recipients: Sequence[_Recipient], armored: bool) -> bytes: ... def decrypt(ciphertext: bytes, identities: Sequence[_Identity]) -> bytes: ... def decrypt_file(infile: str, outfile: str, identities: Sequence[_Identity]) -> None: ... diff --git a/pyrage-stubs/pyrage/passphrase.pyi b/pyrage-stubs/pyrage/passphrase.pyi index 5c7a26b..1b485ab 100644 --- a/pyrage-stubs/pyrage/passphrase.pyi +++ b/pyrage-stubs/pyrage/passphrase.pyi @@ -1,6 +1,2 @@ -def encrypt(plaintext: bytes, passphrase: str) -> bytes: - ... - - -def decrypt(ciphertext: bytes, passphrase: str) -> bytes: - ... +def encrypt(plaintext: bytes, passphrase: str, armored: bool = False) -> bytes: ... +def decrypt(ciphertext: bytes, passphrase: str, armored: bool = False) -> bytes: ... diff --git a/src/lib.rs b/src/lib.rs index 4d3608a..490a068 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,8 @@ use std::io::Write; use std::{fs::File, io::Read}; use age::{ - DecryptError as RageDecryptError, EncryptError as RageEncryptError, Encryptor, Identity, - Recipient, + armor::ArmoredReader, armor::ArmoredWriter, armor::Format, DecryptError as RageDecryptError, + EncryptError as RageEncryptError, Encryptor, Identity, Recipient, }; use age_core::format::{FileKey, Stanza}; use pyo3::{ @@ -136,10 +136,12 @@ impl<'source> FromPyObject<'source> for Box { create_exception!(pyrage, EncryptError, PyException); #[pyfunction] +#[pyo3(signature = (plaintext, recipients, armored=false))] fn encrypt<'p>( py: Python<'p>, plaintext: &[u8], recipients: Vec>, + armored: bool, ) -> PyResult> { // This turns each `dyn PyrageRecipient` into a `dyn Recipient`, which // is what the underlying `age` API expects. @@ -151,13 +153,25 @@ fn encrypt<'p>( let encryptor = Encryptor::with_recipients(recipients.iter().map(|r| r.as_ref())) .map_err(|_| EncryptError::new_err("expected at least one recipient"))?; let mut encrypted = vec![]; - let mut writer = encryptor - .wrap_output(&mut encrypted) - .map_err(|e| EncryptError::new_err(e.to_string()))?; + + let mut writer = match armored { + true => encryptor + .wrap_output(ArmoredWriter::wrap_output( + &mut encrypted, + Format::AsciiArmor, + )?) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + false => encryptor + .wrap_output(ArmoredWriter::wrap_output(&mut encrypted, Format::Binary)?) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + }; + writer .write_all(plaintext) .map_err(|e| EncryptError::new_err(e.to_string()))?; writer + .finish() + .map_err(|e| EncryptError::new_err(e.to_string()))? .finish() .map_err(|e| EncryptError::new_err(e.to_string()))?; @@ -166,10 +180,12 @@ fn encrypt<'p>( } #[pyfunction] +#[pyo3(signature = (infile, outfile, recipients, armored=false))] fn encrypt_file( infile: String, outfile: String, recipients: Vec>, + armored: bool, ) -> PyResult<()> { // This turns each `dyn PyrageRecipient` into a `dyn Recipient`, which // is what the underlying `age` API expects. @@ -186,13 +202,21 @@ fn encrypt_file( let encryptor = Encryptor::with_recipients(recipients.iter().map(|r| r.as_ref())) .map_err(|_| EncryptError::new_err("expected at least one recipient"))?; - let mut writer = encryptor - .wrap_output(&mut writer) - .map_err(|e| EncryptError::new_err(e.to_string()))?; + + let mut writer = match armored { + true => encryptor + .wrap_output(ArmoredWriter::wrap_output(&mut writer, Format::AsciiArmor)?) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + false => encryptor + .wrap_output(ArmoredWriter::wrap_output(&mut writer, Format::Binary)?) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + }; std::io::copy(&mut reader, &mut writer).map_err(|e| EncryptError::new_err(e.to_string()))?; writer + .finish() + .map_err(|e| EncryptError::new_err(e.to_string()))? .finish() .map_err(|e| EncryptError::new_err(e.to_string()))?; @@ -209,8 +233,8 @@ fn decrypt<'p>( ) -> PyResult> { let identities = identities.iter().map(|pi| pi.as_ref().as_identity()); - let decryptor = - age::Decryptor::new(ciphertext).map_err(|e| DecryptError::new_err(e.to_string()))?; + let decryptor = age::Decryptor::new(ArmoredReader::new(ciphertext)) + .map_err(|e| DecryptError::new_err(e.to_string()))?; let mut decrypted = vec![]; let mut reader = decryptor @@ -238,8 +262,8 @@ fn decrypt_file( let reader = std::io::BufReader::new(reader); let mut writer = std::io::BufWriter::new(writer); - let decryptor = - age::Decryptor::new_buffered(reader).map_err(|e| DecryptError::new_err(e.to_string()))?; + let decryptor = age::Decryptor::new_buffered(ArmoredReader::new(reader)) + .map_err(|e| DecryptError::new_err(e.to_string()))?; let mut reader = decryptor .decrypt(identities) @@ -256,10 +280,12 @@ fn from_pyobject(file: PyObject, read_only: bool) -> PyResult } #[pyfunction] +#[pyo3(signature = (reader, writer, recipients, armored=false))] fn encrypt_io( reader: PyObject, writer: PyObject, recipients: Vec>, + armored: bool, ) -> PyResult<()> { // This turns each `dyn PyrageRecipient` into a `dyn Recipient`, which // is what the underlying `age` API expects. @@ -271,15 +297,27 @@ fn encrypt_io( let writer = from_pyobject(writer, false)?; let mut reader = std::io::BufReader::new(reader); let mut writer = std::io::BufWriter::new(writer); + let encryptor = Encryptor::with_recipients(recipients.iter().map(|r| r.as_ref())) .map_err(|_| EncryptError::new_err("expected at least one recipient"))?; - let mut writer = encryptor - .wrap_output(&mut writer) - .map_err(|e| EncryptError::new_err(e.to_string()))?; + + let mut writer = match armored { + true => encryptor + .wrap_output(ArmoredWriter::wrap_output(&mut writer, Format::AsciiArmor)?) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + false => encryptor + .wrap_output(ArmoredWriter::wrap_output(&mut writer, Format::Binary)?) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + }; + std::io::copy(&mut reader, &mut writer).map_err(|e| EncryptError::new_err(e.to_string()))?; + writer + .finish() + .map_err(|e| EncryptError::new_err(e.to_string()))? .finish() .map_err(|e| EncryptError::new_err(e.to_string()))?; + Ok(()) } @@ -294,8 +332,8 @@ fn decrypt_io( let writer = from_pyobject(writer, false)?; let reader = std::io::BufReader::new(reader); let mut writer = std::io::BufWriter::new(writer); - let decryptor = - age::Decryptor::new_buffered(reader).map_err(|e| DecryptError::new_err(e.to_string()))?; + let decryptor = age::Decryptor::new_buffered(ArmoredReader::new(reader)) + .map_err(|e| DecryptError::new_err(e.to_string()))?; let mut reader = decryptor .decrypt(identities) .map_err(|e| DecryptError::new_err(e.to_string()))?; diff --git a/src/passphrase.rs b/src/passphrase.rs index 5bed36e..07f3c98 100644 --- a/src/passphrase.rs +++ b/src/passphrase.rs @@ -3,22 +3,44 @@ use std::{ iter, }; -use age::{scrypt, Decryptor, Encryptor}; +use age::{ + armor::ArmoredReader, armor::ArmoredWriter, armor::Format, scrypt, Decryptor, Encryptor, +}; use pyo3::{prelude::*, types::PyBytes}; use crate::{DecryptError, EncryptError}; #[pyfunction] -fn encrypt<'p>(py: Python<'p>, plaintext: &[u8], passphrase: &str) -> PyResult> { +#[pyo3(signature = (plaintext, passphrase, armored=false))] +fn encrypt<'p>( + py: Python<'p>, + plaintext: &[u8], + passphrase: &str, + armored: bool, +) -> PyResult> { let encryptor = Encryptor::with_user_passphrase(passphrase.into()); let mut encrypted = vec![]; - let mut writer = encryptor - .wrap_output(&mut encrypted) - .map_err(|e| EncryptError::new_err(e.to_string()))?; + + let writer_result = match armored { + true => encryptor.wrap_output( + ArmoredWriter::wrap_output(&mut encrypted, Format::AsciiArmor) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + ), + false => encryptor.wrap_output( + ArmoredWriter::wrap_output(&mut encrypted, Format::Binary) + .map_err(|e| EncryptError::new_err(e.to_string()))?, + ), + }; + + let mut writer = writer_result.map_err(|e| EncryptError::new_err(e.to_string()))?; + writer .write_all(plaintext) .map_err(|e| EncryptError::new_err(e.to_string()))?; + writer + .finish() + .map_err(|e| EncryptError::new_err(e.to_string()))? .finish() .map_err(|e| EncryptError::new_err(e.to_string()))?; @@ -31,7 +53,8 @@ fn decrypt<'p>( ciphertext: &[u8], passphrase: &str, ) -> PyResult> { - let decryptor = Decryptor::new(ciphertext).map_err(|e| DecryptError::new_err(e.to_string()))?; + let decryptor = Decryptor::new_buffered(ArmoredReader::new(ciphertext)) + .map_err(|e| DecryptError::new_err(e.to_string()))?; let mut decrypted = vec![]; let mut reader = decryptor .decrypt(iter::once(&scrypt::Identity::new(passphrase.into()) as _)) diff --git a/test/test_passphrase.py b/test/test_passphrase.py index fb908ca..a5b1380 100644 --- a/test/test_passphrase.py +++ b/test/test_passphrase.py @@ -1,12 +1,15 @@ import unittest +from parameterized import parameterized + from pyrage import passphrase class TestPassphrase(unittest.TestCase): - def test_roundtrip(self): + @parameterized.expand([(False,), (True,)]) + def test_roundtrip(self, armored): plaintext = b"junk" - encrypted = passphrase.encrypt(plaintext, "some password") + encrypted = passphrase.encrypt(plaintext, "some password", armored=armored) decrypted = passphrase.decrypt(encrypted, "some password") self.assertEqual(plaintext, decrypted) diff --git a/test/test_pyrage.py b/test/test_pyrage.py index 4d1211e..3be80c7 100644 --- a/test/test_pyrage.py +++ b/test/test_pyrage.py @@ -3,6 +3,8 @@ import unittest from io import BytesIO +from parameterized import parameterized + import pyrage from .utils import ssh_keypair @@ -15,23 +17,25 @@ def test_encrypt_fails_with_no_receipients(self): ): pyrage.encrypt(b"test", []) - def test_roundtrip(self): + @parameterized.expand([(False,), (True,)]) + def test_roundtrip(self, armored): identity = pyrage.x25519.Identity.generate() recipient = identity.to_public() - encrypted = pyrage.encrypt(b"test", [recipient]) + encrypted = pyrage.encrypt(b"test", [recipient], armored=armored) decrypted = pyrage.decrypt(encrypted, [identity]) self.assertEqual(b"test", decrypted) - def test_roundtrip_io_fh(self): + @parameterized.expand([(False,), (True,)]) + def test_roundtrip_io_fh(self, armored): identity = pyrage.x25519.Identity.generate() recipient = identity.to_public() with tempfile.TemporaryFile() as unencrypted: unencrypted.write(b"test") unencrypted.seek(0) with tempfile.TemporaryFile() as encrypted: - pyrage.encrypt_io(unencrypted, encrypted, [recipient]) + pyrage.encrypt_io(unencrypted, encrypted, [recipient], armored=armored) encrypted.seek(0) with tempfile.TemporaryFile() as decrypted: pyrage.decrypt_io(encrypted, decrypted, [identity]) @@ -39,13 +43,14 @@ def test_roundtrip_io_fh(self): unencrypted.seek(0) self.assertEqual(unencrypted.read(), decrypted.read()) - def test_roundtrip_io_bytesio(self): + @parameterized.expand([(False,), (True,)]) + def test_roundtrip_io_bytesio(self, armored): identity = pyrage.x25519.Identity.generate() recipient = identity.to_public() - unencrypted = BytesIO(b'test') + unencrypted = BytesIO(b"test") encrypted = BytesIO() decrypted = BytesIO() - pyrage.encrypt_io(unencrypted, encrypted, [recipient]) + pyrage.encrypt_io(unencrypted, encrypted, [recipient], armored=armored) encrypted.seek(0) pyrage.decrypt_io(encrypted, decrypted, [identity]) decrypted.seek(0) @@ -57,27 +62,27 @@ def test_roundtrip_io_fail(self): recipient = identity.to_public() with self.assertRaises(TypeError): - input = 'test' + input = "test" output = BytesIO() pyrage.encrypt_io(input, output, [recipient]) with self.assertRaises(TypeError): input = BytesIO() - output = 'test' + output = "test" pyrage.encrypt_io(input, output, [recipient]) with self.assertRaises(TypeError): - input = 'test' + input = "test" output = BytesIO() pyrage.decrypt_io(input, output, [recipient]) with self.assertRaises(TypeError): input = BytesIO() - output = 'test' + output = "test" pyrage.decrypt_io(input, output, [recipient]) - - def test_roundtrip_file(self): + @parameterized.expand([(False,), (True,)]) + def test_roundtrip_file(self, armored): identity = pyrage.x25519.Identity.generate() recipient = identity.to_public() @@ -89,7 +94,7 @@ def test_roundtrip_file(self): with open(unencrypted, "wb") as file: file.write(b"test") - pyrage.encrypt_file(unencrypted, encrypted, [recipient]) + pyrage.encrypt_file(unencrypted, encrypted, [recipient], armored=armored) pyrage.decrypt_file(encrypted, decrypted, [identity]) with open(unencrypted, "rb") as file1: @@ -111,7 +116,8 @@ def test_decrypt_fails_wrong_recipient(self): decrypted = pyrage.decrypt(encrypted, [alice, bob]) self.assertEqual(b"test", decrypted) - def test_roundtrip_matrix(self): + @parameterized.expand([(False,), (True,)]) + def test_roundtrip_matrix(self, armored): identities = [] recipients = [] @@ -126,7 +132,7 @@ def test_roundtrip_matrix(self): recipients.append(pyrage.ssh.Recipient.from_str(pubkey)) # Encrypt to all recipients, decode using each identity. - encrypted = pyrage.encrypt(b"test matrix", recipients) + encrypted = pyrage.encrypt(b"test matrix", recipients, armored=armored) for identity in identities: self.assertEqual(b"test matrix", pyrage.decrypt(encrypted, [identity])) From 902190a698e34f7d03a0233cb30e3c2ac57beb40 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 13 Jun 2025 21:23:36 -0400 Subject: [PATCH 10/10] chore: prep for release 1.3.0 (#118) --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43169ee..9dbb9cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1044,7 +1044,7 @@ dependencies = [ [[package]] name = "pyrage" -version = "1.2.5" +version = "1.3.0" dependencies = [ "age", "age-core", diff --git a/Cargo.toml b/Cargo.toml index d9a08b7..046d208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyrage" -version = "1.2.5" +version = "1.3.0" authors = ["William Woodruff "] edition = "2021" description = "Python bindings for rage (age in Rust)"