diff --git a/crypt4gh/cli.py b/crypt4gh/cli.py index 325be55..345fdfe 100644 --- a/crypt4gh/cli.py +++ b/crypt4gh/cli.py @@ -29,7 +29,7 @@ {PROG} [-hv] [--log ] encrypt [--sk ] --recipient_pk [--recipient_pk ]... [--range ] [--header ] {PROG} [-hv] [--log ] decrypt [--sk ] [--sender_pk ] [--range ] {PROG} [-hv] [--log ] rearrange [--sk ] --range - {PROG} [-hv] [--log ] reencrypt [--sk ] --recipient_pk [--recipient_pk ]... [--trim] + {PROG} [-hv] [--log ] reencrypt [--sk ] --recipient_pk [--recipient_pk ]... [--trim] [--header-only] Options: -h, --help Prints this help and exit @@ -42,7 +42,7 @@ --range Byte-range either as or just (Start included, End excluded) -t, --trim Keep only header packets that you can decrypt --header Where to write the header (default: stdout) - + --header-only Whether the input data consists only of a header (default: false) Environment variables: C4GH_LOG If defined, it will be used as the default logger @@ -222,4 +222,5 @@ def build_recipients(): recipient_keys, sys.stdin.buffer, sys.stdout.buffer, - trim=args['--trim']) + trim=args['--trim'], + header_only=args['--header-only']) diff --git a/crypt4gh/lib.py b/crypt4gh/lib.py index fd24325..341795c 100644 --- a/crypt4gh/lib.py +++ b/crypt4gh/lib.py @@ -407,7 +407,7 @@ def decrypt(keys, infile, outfile, sender_pubkey=None, offset=0, span=None): @close_on_broken_pipe -def reencrypt(keys, recipient_keys, infile, outfile, chunk_size=4096, trim=False): +def reencrypt(keys, recipient_keys, infile, outfile, chunk_size=4096, trim=False, header_only=False): '''Extract header packets from infile and generate another one to outfile. The encrypted data section is only copied from infile to outfile.''' @@ -416,6 +416,11 @@ def reencrypt(keys, recipient_keys, infile, outfile, chunk_size=4096, trim=False packets = header.reencrypt(header_packets, keys, recipient_keys, trim=trim) outfile.write(header.serialize(packets)) + # If header-only reencryption, we are done. + if header_only: + LOG.info(f'Header-only reencryption Successful') + return + # Stream the remainder LOG.info(f'Streaming the remainder of the file') while True: diff --git a/docs/conf.py b/docs/conf.py index 320df70..bde48db 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -130,7 +130,7 @@ today_fmt = '%B %d, %Y' def setup(app): - app.add_stylesheet('custom.css') + app.add_css_file('custom.css') # -- Other stuff ---------------------------------------------------------- htmlhelp_basename = 'crypt4gh' diff --git a/docs/usage.rst b/docs/usage.rst index 8d95c3f..a84ff0b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -64,3 +64,26 @@ Any user can generate a keypair with: $ crypt4gh-keygen --sk user.sec --pk user.pub The private key will be encrypted with a passphrase. The user is prompted at the terminal for that passphrase. + +Storing the encrypted header separately +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The encrypted header can be stored separately from the encrypted data. This is useful, for example, when sharing the encrypted message with many recipients. In this case, only the header needs to be re-encrypted (for a specific recipient) while the encrypted data can stay the same. + +To store the encrypted header in a separate file ``header.dat``, use the flag ``--header``: + +.. code-block:: console + + $ crypt4gh encrypt --sk alice.sec --recipient_pk bob.pub --header header.bob.c4gh < M > M.data.c4gh + +Bob can then decrypt the message by concatenating the header and the data, and decrypting the whole file: + +.. code-block:: console + + $ cat header.bob.c4gh M.data.c4gh | crypt4gh decrypt --sk bob.sec > M + +To re-encrypt the message for another user Eve, with public key ``eve.pub``, Alice can run the ``crypt4gh reencrypt`` command: + +.. code-block:: console + + $ crypt4gh reencrypt --sk alice.sec --recipient_pk eve.pub < header.alice.c4gh > header.eve.c4gh diff --git a/tests/header_stream.bats b/tests/header_stream.bats new file mode 100644 index 0000000..123785b --- /dev/null +++ b/tests/header_stream.bats @@ -0,0 +1,54 @@ +#!/usr/bin/env bats + +load _common/helpers + +function setup() { + + # Defining the TMP dir + TESTFILES=${BATS_TEST_FILENAME}.d + mkdir -p "$TESTFILES" + +} + +function teardown() { + rm -rf ${TESTFILES} +} + +@test "Bob sends the testfile secretly (with separate header and data) to Alice" { + + TESTFILE=${BATS_TEST_DIRNAME}/_common/testfile.abcd + + # Bob encrypts the testfile for Alice, storing the header separately + export C4GH_PASSPHRASE=${BOB_PASSPHRASE} + crypt4gh encrypt --sk ${BOB_SECKEY} --recipient_pk ${ALICE_PUBKEY} --header $TESTFILES/header.alice.c4gh < $TESTFILE > $TESTFILES/data.c4gh + + # Alice concatenates the header and the data and decrypts the combined result + export C4GH_PASSPHRASE=${ALICE_PASSPHRASE} + cat $TESTFILES/header.alice.c4gh $TESTFILES/data.c4gh | crypt4gh decrypt --sk ${ALICE_SECKEY} > $TESTFILES/message.received + + run diff $TESTFILE $TESTFILES/message.received + [ "$status" -eq 0 ] + + unset C4GH_PASSPHRASE +} + +@test "Bob encrypts the testfile for himself (with separate header) and reencrypts the header for Alice" { + + TESTFILE=${BATS_TEST_DIRNAME}/_common/testfile.abcd + + # Bob encrypts the testfile for himself + export C4GH_PASSPHRASE=${BOB_PASSPHRASE} + crypt4gh encrypt --sk ${BOB_SECKEY} --recipient_pk ${BOB_PUBKEY} --header $TESTFILES/header.bob.c4gh < $TESTFILE > $TESTFILES/data.c4gh + + # Bob changes the header for Alice + crypt4gh reencrypt --sk ${BOB_SECKEY} --recipient_pk ${ALICE_PUBKEY} --header-only < $TESTFILES/header.bob.c4gh > $TESTFILES/header.alice.c4gh + + # Alice concatenates the header and data and decrypts the results + export C4GH_PASSPHRASE=${ALICE_PASSPHRASE} + cat $TESTFILES/header.alice.c4gh $TESTFILES/data.c4gh | crypt4gh decrypt --sk ${ALICE_SECKEY} > $TESTFILES/message.received + + run diff $TESTFILE $TESTFILES/message.received + [ "$status" -eq 0 ] + + unset C4GH_PASSPHRASE +} \ No newline at end of file