Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleaning up api #13

Merged
merged 6 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 6 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ It uses amazing [sequoia-pgp](https://sequoia-pgp.org/) library for the actual O
### Build dependencies in Fedora

```
sudo dnf install nettle clang clang-devel
sudo dnf install nettle clang clang-devel nettle-dev
```


Expand All @@ -27,84 +27,17 @@ maturin develop

```Python
>>> import johnnycanencrypt as jce
>>> j = jce.Johnny("secret.asc")
>>> j = jce.Johnny("public.asc")
>>> data = j.encrypt_bytes(b"kushal \xf0\x9f\x90\x8d")
>>> print(data)
-----BEGIN PGP MESSAGE-----

wcFMAwhsWpR1vDokAQ//UQGjrmmPLP0Td8pELf8XZEPh6fY9Xad6XHH6vQjGwvjG
36kK8ejRqyLbZpwVOO1FUfiZt6AyaeIEeEagoolMxmFl67mWBHsw5Z2NUPhydAwJ
EX+VdFn6CtRzQ0xG3T7rOCrsR50COO13gc4fIAn7Rxj1DyjqlFvur10FNnxRm0iJ
jnOwPnWVWKwoROzevfQd1Oef0n4nbkDUuyrS9oHSRFhFF/9I9bGtJhho0VIIcmFG
YVkhR0+QROTZ4edKotUg0R3UUfHmfwT0XcybGWMG/8Nh3W8pYuxwDdtbSMNDZzxu
o9TdpLrgoRIkhyGmuYWrURrRN1hmce5B6XOagWu7aKL7pFhP7Vd6LLoliDwY4G6x
1yKHbSo/1FEof7WBDujCksuVedUO8Gs9giitR/p/U9PBadeyiW0CKTYiTURkkNiF
g79lVfbmM54eZ9zmU+PraVNpekYXvH3o+lvXd5T39mo4Y/dv2fDCjo2aiZ/bE56Q
yn/0Hhmj2ikrsUk3NcuhO4zxt+VLctJt+lfk+R2hal+6NTaRkREdprPp4ltAt/bm
8xwBmqp+FDdxGgY+ItJkG69vsIf4WpPsvBI37fVbeYqrPsaz9NGlz2QKdfQvaH7j
R7rgxf24H2FjbbyNuHF3tJJa4Kfpnhq4nkxA/EdRP9JcVm/X568jLayTLyJGmrbS
PAHlVMLSBXQDApkY+5Veu3teRR5M2BLPr7X/gfeNnTlbZ4kF5S+E+0bjTjrz+6oo
dcsnTYxmcAm9hdPjng==
=1IYb
-----END PGP MESSAGE-----



>>> result = j.decrypt_bytes(data.encode("utf-8"), "mysecretpassword")
>>> js = jce.Johnny("secret.asc")
>>> result = js.decrypt_bytes(data, "mysecretpassword")
>>> print(result.decode("utf-8"))
kushal 🐍

```

## Quick API documentation

Remember that this will change a lot in the coming days.


```Python
import johnnycanencrypt as jce
```

To create new RSA4096 size key, call `jce.newkey("password", "userid")`, both *password* and *userid* are Python str.
Remember to save them into different files ending with *.asc*.

To do any encryption/decryption we have to create an object of the **Johnny** class with the private or public key file.
Remember, except **password** input, every else takes `bytes` as input type.

### Signing a file with detached signature

```Python
j = Johnny("private.asc")
signature = j.sign_file_detached(b"filename.txt", "password")
with open(b"filename.txt.asc", "wb") as f:
f.write(signature)
```

### Verifying a signature


```Python
j = Johnny("public.asc")
with open(b"filename.txt.asc", "b") as f:
sig = f.read()

verified = j.verify_file(b"filename.txt", sig)
print(f"Verified: {verified}")
```

For signing and verifying there are similar method available for bytes, `verify_bytes`, `sign_bytes_detached`.


### Encrypting and decrypting files

```Python
j = jce.Johnny("public.asc")
assert j.encrypt_file(inputfile, output_file_path)
jp = jce.Johnny("secret.asc")

result = jp.decrypt_file(output_file_path, decrypted_output_path, "password")
```
## API documentation

Note that, in this context, `inputfile`, `output_file_path`, and `decrypted_output_path` should be binary, not strings.
Please go through the [full API documentation](https://johnnycanencrypt.readthedocs.io/en/latest/) for detailed descriptions.

## LICENSE: GPLv3+
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

- If the public/secret key file is missing, while trying to create a `Johnny` object will raise `FileNotFound` error.
- If one tries to decrypt using a public key file, it will throw `AttributeError`.
- `encrypt_bytes` now returns bytes (instead of string).
- `encrypt_bytes` takes a third argument, `armor` as boolean, to return ascii-armored bytes or not.
- `encrypt_file` takes a third argument, `armor` as boolean, writes the output file ascii armored if true.

## [0.1.0] - 2020-07-11

Expand Down
92 changes: 92 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
API Documentation
==================

For the rest of the documentation we assume that you imported the module as following.

::


>>> import johnnycanencrypt as jce


.. function:: newkey(password, userid)

Use the `newkey` function in the module to create a new keypair. It takes two arguments as str, a password, and userid.
By default it creates the key with RSA4096, and returns a tuple of public,secret key as str. Raises `FileNotFound` error
if the key file can not be accessed.

::

>>> public, secret = jce.newkey("my super secret password using diceware", "test <[email protected]>")


.. note:: Remember to save both the public and serect keys in a file to use in future.


.. class:: Johnny(filepath)

It creates an object of type `Johnny`, you can provide path to the either public key, or the private key based on the operation
you want to do.

.. method:: encrypt_bytes(data: bytes, armor=False)

This method encrypts the given bytes and returns the encrypted bytes. If you pass `armor=True` to the method, then the
returned value will be ascii armored bytes.

::

>>> j = jce.Johnny("tests/files/public.asc")
>>> enc = j.encrypt_bytes(b"mysecret", armor=True)


.. method:: encrypt_file(inputfile: bytes, output: bytes, armor=False)

This method encrypts the given inputfile and writes the raw encrypted bytes to the output path. If you pass `armor=True` to the method, then the
output file will be written as ascii armored.

::

>>> j = jce.Johnny("tests/files/public.asc")
>>> enc = j.encrypt_file(b"blueleaks.tar.gz", b"notblueleaks.tar.gz.pgp", armor=True)


.. method:: decrypt_bytes(data: bytes, password: str)

Decrypts the given bytes based on the secret key and given password. If you try to decrypt while just using the public key,
then it will raise `AttributeError`.

::

>>> jp = jce.Johnny("tests/files/secret.asc")
>>> result = jp.decrypt_bytes(enc, "redhat")


.. method:: decrypt_file(inputfile: bytes, output: bytes, password: str)

Decrypts the inputfile path (in bytes) and wrties the decrypted data to the `output` file. Both the filepaths to be given as bytes.

::

>>> jp = jce.Johnny("tests/files/secret.asc")
>>> result = jp.decrypt_file(b"notblueleaks.tar.gz.pgp", "blueleaks.tar.gz", "redhat")


.. method:: sign_bytes_detached(data: bytes, pasword: str)

Signs the given bytes and returns the detached ascii armored signature as bytes.

::

>>> j = jce.Johnny("tests/files/secret.asc")
>>> signature = j.sign_bytes_detached(b"mysecret", "redhat")

.. note:: Remember to save the signature somewhere on disk.

.. method:: verify_bytes(data: bytes, signature: bytes)

Verifies if the signature is correct for the given data (as bytes). Returns `True` or `False`.

::

>>> j = jce.Johnny("tests/files/secret.asc")
>>> j.verify_bytes(encrypted_bytes, signature)
31 changes: 31 additions & 0 deletions docs/build.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Building Johnny Can Encrypt
============================

Building this module requires Rust's nightly toolchain. You can install it following
the instructions from `rustup.rs <https://rustup.rs>`_.

You will need `libnettle` and `libnettle-dev` & `clang` (on Debian/Ubuntu) and `nettle` & `nettle-dev` & `clang` packages in Fedora.

Then you can follow the steps below to build a wheel.

::

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install requirements-dev.txt
maturin build

Only to build and test locally, you should execute

::

maturin develop

## How to run the tests?

After you did the `maturin develop` as mentioned above, execute the following command.

::

python3 -m pytest -vvv

6 changes: 6 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
Welcome to Johnny Can Encrypt's documentation!
==============================================

This is a Python module providing encryption and decryption operations based on OpenPGP. It
uses `sequoia-pgp <https://sequoia-pgp.org>`_ project for the actual operations. This module
is written.

.. toctree::
:maxdepth: 2
:caption: Contents:

build
api


Indices and tables
Expand Down
103 changes: 74 additions & 29 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,12 @@ impl Johnny {
Ok(Johnny { filepath, cert })
}

pub fn encrypt_bytes(&self, data: Vec<u8>) -> PyResult<String> {
pub fn encrypt_bytes(
&self,
py: Python,
data: Vec<u8>,
armor: Option<bool>,
) -> PyResult<PyObject> {
let mode = KeyFlags::default().set_storage_encryption(true);
let p = &P::new();
let recipients = self
Expand All @@ -263,11 +268,15 @@ impl Johnny {
.alive()
.revoked(false)
.key_flags(&mode);
// TODO: Find better way to do this in rust
let mut result = Vec::new();
let mut sink = armor::Writer::new(&mut result, armor::Kind::Message)?;
let mut result2 = Vec::new();
let mut sink = armor::Writer::new(&mut result2, armor::Kind::Message)?;
// Stream an OpenPGP message.
let message = Message::new(&mut sink);

let message = match armor {
Some(true) => Message::new(&mut sink),
_ => Message::new(&mut result),
};
// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
Expand All @@ -285,9 +294,18 @@ impl Johnny {
// writer stack.
literal_writer.finalize().unwrap();

// Finalize the armor writer.
sink.finalize().expect("Failed to write data");
Ok(str::from_utf8(&result).unwrap().to_string())
match armor {
Some(true) => {
// Finalize the armor writer.
sink.finalize().expect("Failed to write data");
let res = PyBytes::new(py, &result2);
return Ok(res.into());
}
_ => {
let res = PyBytes::new(py, &result);
return Ok(res.into());
}
}
}

pub fn decrypt_bytes(&self, py: Python, data: Vec<u8>, password: String) -> PyResult<PyObject> {
Expand All @@ -303,7 +321,7 @@ impl Johnny {
let res = PyBytes::new(py, &result);
Ok(res.into())
}
pub fn encrypt_file(&self, filepath: Vec<u8>, output: Vec<u8>) -> PyResult<bool> {
pub fn encrypt_file(&self, filepath: Vec<u8>, output: Vec<u8>, armor: Option<bool>) -> PyResult<bool> {
let mode = KeyFlags::default().set_storage_encryption(true);
let p = &P::new();
let recipients = self
Expand All @@ -315,28 +333,55 @@ impl Johnny {
.key_flags(&mode);
let mut input = File::open(str::from_utf8(&filepath[..]).unwrap()).unwrap();
let mut outfile = File::create(str::from_utf8(&output[..]).unwrap()).unwrap();
// Stream an OpenPGP message.
let message = Message::new(&mut outfile);

// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
.expect("Failed to create encryptor");

let mut literal_writer = LiteralWriter::new(encryptor)
.build()
.expect("Failed to create literal writer");

// Copy stdin to our writer stack to encrypt the data.
io::copy(&mut input, &mut literal_writer).expect("Failed to encrypt");
//literal_writer.write_all(&data).unwrap();

// Finally, finalize the OpenPGP message by tearing down the
// writer stack.
literal_writer.finalize().unwrap();
// TODO: Find better ways to write this code
match armor {
// For armored output file.
Some(true) => {
let mut sink = armor::Writer::new(&mut outfile, armor::Kind::Message).unwrap();
// Stream an OpenPGP message.
let message = Message::new(&mut sink);

// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
.expect("Failed to create encryptor");

let mut literal_writer = LiteralWriter::new(encryptor)
.build()
.expect("Failed to create literal writer");

// Copy stdin to our writer stack to encrypt the data.
io::copy(&mut input, &mut literal_writer).expect("Failed to encrypt");
//literal_writer.write_all(&data).unwrap();

// Finally, finalize the OpenPGP message by tearing down the
// writer stack.
literal_writer.finalize().unwrap();

// Finalize the armor writer.
sink.finalize().expect("Failed to write data");}
_ => {
let message = Message::new(&mut outfile);

// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
.expect("Failed to create encryptor");

let mut literal_writer = LiteralWriter::new(encryptor)
.build()
.expect("Failed to create literal writer");

// Copy stdin to our writer stack to encrypt the data.
io::copy(&mut input, &mut literal_writer).expect("Failed to encrypt");
//literal_writer.write_all(&data).unwrap();

// Finally, finalize the OpenPGP message by tearing down the
// writer stack.
literal_writer.finalize().unwrap();
}
}

// Finalize the armor writer.
//sink.finalize().expect("Failed to write data");
Ok(true)
}

Expand Down
Loading