diff --git a/.github/workflows/centos-and-fedora.yml b/.github/workflows/centos-and-fedora.yml index 6b7aeb2a81..abd34a803c 100644 --- a/.github/workflows/centos-and-fedora.yml +++ b/.github/workflows/centos-and-fedora.yml @@ -54,28 +54,24 @@ jobs: - { CC: clang, CXX: clang++, BUILD_MODE: sanitize, SHARED_LIBS: on } # All cotainers have gpg stable and lts installed -# centos-8-amd64 has botan 2.18.2 installed -# fedora-35-amd64 has botan 3.1.1 installed +# centos-9-amd64 has botan 2.19.3 installed +# fedora-39-amd64 has botan 2.19.4 installed # Any other version has to be built explicitly ! # Pls refer to https://github.com/rnpgp/rnp-ci-containers#readme for more image details image: - { name: 'CentOS 7', container: 'centos-7-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'stable' } - - { name: 'CentOS 8', container: 'centos-8-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'system' } - - { name: 'CentOS 8', container: 'centos-8-amd64', backend: 'Botan', botan_ver: '2.18.2', sm2: On, gpg_ver: 'lts' } - - { name: 'CentOS 8', container: 'centos-8-amd64', backend: 'Botan', botan_ver: '2.18.2', sm2: Off, gpg_ver: 'stable' } - - { name: 'CentOS 9', container: 'centos-9-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'stable' } - - { name: 'Fedora 35', container: 'fedora-35-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'system' } - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'system' } - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'Botan', botan_ver: '3.1.1', gpg_ver: 'system' } -# Tests against gpg head fails -# - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'head' } - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'Botan', botan_ver: 'head', gpg_ver: 'system' } - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'Botan', botan_ver: '3.3.0', pqc: On, gpg_ver: 'system' } - - { name: 'CentOS 8', container: 'centos-8-amd64', backend: 'OpenSSL', gpg_ver: 'lts' } - - { name: 'CentOS 9', container: 'centos-9-amd64', backend: 'OpenSSL', idea: On, gpg_ver: 'stable' } - - { name: 'CentOS 9', container: 'centos-9-amd64', backend: 'OpenSSL', idea: Off,gpg_ver: 'stable' } - - { name: 'Fedora 35', container: 'fedora-35-amd64', backend: 'OpenSSL', gpg_ver: 'system' } - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'OpenSSL', gpg_ver: 'system' } + - { name: 'CentOS 9', container: 'centos-9-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'system' } + - { name: 'CentOS 9', container: 'centos-9-amd64', backend: 'Botan', botan_ver: 'system', sm2: Off, gpg_ver: 'lts' } + - { name: 'Fedora 39', container: 'fedora-39-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'system' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'Botan', botan_ver: 'system', gpg_ver: 'system' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'Botan', botan_ver: '3.1.1', gpg_ver: 'system' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'Botan', botan_ver: 'head', gpg_ver: 'system' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'Botan', botan_ver: '3.3.0', pqc: On, gpg_ver: 'system' } + - { name: 'CentOS 9', container: 'centos-9-amd64', backend: 'OpenSSL', gpg_ver: 'lts' } + - { name: 'Fedora 39', container: 'fedora-39-amd64', backend: 'OpenSSL', gpg_ver: 'system' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'OpenSSL', gpg_ver: 'system' } + - { name: 'RHEL 8', container: 'redhat-8-ubi', backend: 'OpenSSL', gpg_ver: 'system' } + - { name: 'RHEL 9', container: 'redhat-9-ubi', backend: 'OpenSSL', gpg_ver: 'system' } # There is some ABI incompatibility between llvm-7, bitan shared library from ribose repo and sanitizer # So we are enforving static lib for sanitizers on CentOS 7 @@ -86,32 +82,26 @@ jobs: - image: { name: 'CentOS 7', container: 'centos-7-amd64', gpg_ver: stable, backend: Botan, botan_ver: 'system' } env: { CC: clang, CXX: clang++, BUILD_MODE: sanitize, SHARED_LIBS: off } # Coverage report for Botan 2.x backend - - image: { name: 'CentOS 8', container: 'centos-8-amd64', gpg_ver: stable, backend: Botan, botan_ver: '2.18.2' } + - image: { name: 'CentOS 9 Coverage', container: 'centos-9-amd64', gpg_ver: stable, backend: Botan, botan_ver: 'system' } env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: on } # Coverage report for Botan 3.x backend - - image: { name: 'Fedora 36', container: 'fedora-36-amd64', gpg_ver: stable, backend: Botan, botan_ver: '3.1.1' } - env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: on } - # Coverage report for OpenSSL 1.1.1 backend - - image: { name: 'CentOS 8', container: 'centos-8-amd64', gpg_ver: stable, backend: OpenSSL } + - image: { name: 'Fedora 40 Coverage', container: 'fedora-40-amd64', gpg_ver: stable, backend: Botan, botan_ver: '3.3.0' } env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: on } # Coverage report for OpenSSL 3.0 backend - - image: { name: 'Fedora 36', container: 'fedora-36-amd64', gpg_ver: stable, backend: OpenSSL } + - image: { name: 'Fedora 40 Coverage', container: 'fedora-40-amd64', gpg_ver: stable, backend: OpenSSL } env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: on } # Coverage report for OpenSSL 3.0 backend with disabled algos - - image: { name: 'Fedora 36', container: 'fedora-36-amd64', gpg_ver: stable, backend: OpenSSL, idea: Off, sm2: Off, two: Off, blow: Off, rmd: Off, bp: Off } + - image: { name: 'Fedora 40 Coverage', container: 'fedora-40-amd64', gpg_ver: stable, backend: OpenSSL, idea: Off, sm2: Off, two: Off, blow: Off, rmd: Off, bp: Off } env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: on } # Coverage report for Botan backend with disabled algos - - image: { name: 'Fedora 36', container: 'fedora-36-amd64', gpg_ver: stable, backend: Botan, idea: Off, sm2: Off, two: Off, blow: Off, rmd: Off, bp: Off } + - image: { name: 'Fedora 40 Coverage', container: 'fedora-40-amd64', gpg_ver: stable, backend: Botan, idea: Off, sm2: Off, two: Off, blow: Off, rmd: Off, bp: Off } env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: on } - # Coverage report for PQC - - image: { name: 'Fedora 36', container: 'fedora-36-amd64', gpg_ver: stable, backend: Botan, botan_ver: '3.3.0', pqc: On } - env: { CC: clang, CXX: clang++, BUILD_MODE: sanitize, SHARED_LIBS: off } - # Fedora 38 - - image: { name: 'Fedora 38', container: 'fedora-38-amd64', gpg_ver: system, backend: Botan, botan_ver: 'system' } - env: { CC: gcc, CXX: g++, BUILD_MODE: normal, SHARED_LIBS: on } - # Fedora 39 - - image: { name: 'Fedora 38', container: 'fedora-39-amd64', gpg_ver: system, backend: Botan, botan_ver: 'system' } - env: { CC: gcc, CXX: g++, BUILD_MODE: normal, SHARED_LIBS: on } + # Coverage report for OpenSSL 1.1.1 backend within RHEL 8 + - image: { name: 'RHEL 8 Coverage', container: 'redhat-8-ubi', gpg_ver: stable, backend: OpenSSL } + env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: on } + # Coverage report for PQC - not running yet due to very low coverage + #- image: { name: 'Fedora 40 PQC Coverage', container: 'fedora-40-amd64', gpg_ver: stable, backend: Botan, botan_ver: '3.3.0', pqc: On } + # env: { CC: gcc, CXX: g++, BUILD_MODE: coverage, SHARED_LIBS: off } container: ghcr.io/rnpgp/ci-rnp-${{ matrix.image.container }} @@ -242,10 +232,9 @@ jobs: matrix: image: - { name: 'CentOS 7', container: 'centos-7-amd64' } - - { name: 'CentOS 8', container: 'centos-8-amd64' } - { name: 'CentOS 9', container: 'centos-9-amd64' } - - { name: 'Fedora 35', container: 'fedora-35-amd64' } - - { name: 'Fedora 36', container: 'fedora-36-amd64' } + - { name: 'Fedora 39', container: 'fedora-39-amd64' } + - { name: 'Fedora 40', container: 'fedora-40-amd64' } name: Package ${{ matrix.image.name }} SRPM @@ -313,10 +302,9 @@ jobs: matrix: image: - { name: 'CentOS 7', container: 'centos-7-amd64' } - - { name: 'CentOS 8', container: 'centos-8-amd64' } - { name: 'CentOS 9', container: 'centos-9-amd64' } - - { name: 'Fedora 35', container: 'fedora-35-amd64' } - - { name: 'Fedora 36', container: 'fedora-36-amd64' } + - { name: 'Fedora 39', container: 'fedora-39-amd64' } + - { name: 'Fedora 40', container: 'fedora-40-amd64' } name: Package ${{ matrix.image.name }} RPM steps: @@ -381,23 +369,24 @@ jobs: matrix: image: - { name: 'CentOS 7', container: 'centos:7' } - - { name: 'CentOS 8', container: 'tgagor/centos:stream8' } - { name: 'CentOS 9', container: 'quay.io/centos/centos:stream9' } - - { name: 'Fedora 35', container: 'fedora:35' } - - { name: 'Fedora 36', container: 'fedora:36' } + # Fedora 39 is disabled since it has cmake issue which prevents man pages to be packaged. + # Please see package step for error message. + #- { name: 'Fedora 39', container: 'fedora:39' } + - { name: 'Fedora 40', container: 'fedora:40' } name: RPM test on ${{ matrix.image.name }} steps: - name: Install prerequisites run: yum -y install sudo wget binutils -# CentOS 7/8 packages depend on botan.so.16 that gets installed from ribose repo -# Fedora 35/36 packages depend on botan.so.19 that comes Fedora package, that is available by default +# CentOS 7 packages depend on botan.so.16 that gets installed from ribose repo +# Fedora 39/40 packages depend on botan.so.19 that comes Fedora package, that is available by default # CentOS 9 depend on botan.so.19 and needs EPEL9 repo that needs to be installed # ribose repo is also a source of json-c (v12 aka json-c12) for CentOS 7 - name: Install ribose-packages - if: matrix.image.container == 'centos:7' || matrix.image.container == 'tgagor/centos:stream8' + if: matrix.image.container == 'centos:7' run: | sudo rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages-next.pub sudo wget https://github.com/riboseinc/yum/raw/master/ribose.repo -O /etc/yum.repos.d/ribose.repo @@ -410,7 +399,7 @@ jobs: sudo dnf -y install epel-release - name: Install xargs - if: matrix.image.container == 'fedora:35' + if: matrix.image.container == 'fedora:39' run: sudo yum -y install findutils - name: Download rnp rpms v3 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d538724771..024becf28d 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -103,7 +103,11 @@ jobs: echo "CRYPTO_BACKEND=openssl" >> $GITHUB_ENV - name: Install dependencies - run: brew bundle + run: | + rm -f '/usr/local/bin/2to3' 'usr/local/bin/2to3-3.12' '/usr/local/bin/idle3' '/usr/local/bin/idle3.12' \ + '/usr/local/bin/pydoc3' '/usr/local/bin/pydoc3.12' '/usr/local/bin/python3' '/usr/local/bin/python3-config' \ + '/usr/local/bin/python3.12' '/usr/local/bin/python3.12-config' + brew bundle - name: Botan2 cache id: cache diff --git a/.github/workflows/time-machine.yml b/.github/workflows/time-machine.yml index 87d4ab3d33..7f69ce6f82 100644 --- a/.github/workflows/time-machine.yml +++ b/.github/workflows/time-machine.yml @@ -78,8 +78,8 @@ jobs: # but uses just few of tehm # Pls refer to https://github.com/rnpgp/rnp-ci-containers#readme for image details image: - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'Botan' } - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'OpenSSL' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'Botan' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'OpenSSL' } container: ghcr.io/rnpgp/ci-rnp-${{ matrix.image.container }} @@ -138,8 +138,8 @@ jobs: # Pls refer to https://github.com/rnpgp/rnp-ci-containers#readme for image details image: - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'Botan' } - - { name: 'Fedora 36', container: 'fedora-36-amd64', backend: 'OpenSSL' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'Botan' } + - { name: 'Fedora 40', container: 'fedora-40-amd64', backend: 'OpenSSL' } date-offset: - '+0y' diff --git a/cmake/Modules/FindJSON-C.cmake b/cmake/Modules/FindJSON-C.cmake index e66a011657..cdb8d6be55 100644 --- a/cmake/Modules/FindJSON-C.cmake +++ b/cmake/Modules/FindJSON-C.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ribose Inc. +# Copyright (c) 2018, 2024 Ribose Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -58,18 +58,23 @@ if (NOT PC_JSON-C_FOUND) pkg_check_modules(PC_JSON-C QUIET json-c12) endif() +# ..or even json-c13, accompanied by non-develop json-c (RHEL 8 ubi) +if (NOT PC_JSON-C_FOUND) + pkg_check_modules(PC_JSON-C QUIET json-c13) +endif() + # find the headers find_path(JSON-C_INCLUDE_DIR NAMES json_c_version.h HINTS ${PC_JSON-C_INCLUDEDIR} ${PC_JSON-C_INCLUDE_DIRS} - PATH_SUFFIXES json-c + PATH_SUFFIXES json-c json-c12 json-c13 ) # find the library find_library(JSON-C_LIBRARY - NAMES json-c libjson-c json-c12 libjson-c12 + NAMES json-c libjson-c json-c12 libjson-c12 json-c13 libjson-c13 HINTS ${PC_JSON-C_LIBDIR} ${PC_JSON-C_LIBRARY_DIRS} @@ -120,4 +125,3 @@ if (JSON-C_FOUND AND NOT TARGET JSON-C::JSON-C) endif() mark_as_advanced(JSON-C_INCLUDE_DIR JSON-C_LIBRARY) - diff --git a/src/lib/crypto/symmetric.cpp b/src/lib/crypto/symmetric.cpp index ee1531b46b..8f713e0b58 100644 --- a/src/lib/crypto/symmetric.cpp +++ b/src/lib/crypto/symmetric.cpp @@ -277,26 +277,27 @@ pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len) } bool -pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +pgp_cipher_aead_update( + pgp_crypt_t &crypt, uint8_t *out, const uint8_t *in, size_t len, size_t &read) { - size_t outwr = 0; - size_t inread = 0; - - if (len % crypt->aead.granularity) { + if (len % crypt.aead.granularity) { RNP_LOG("aead wrong update len"); return false; } - if (botan_cipher_update(crypt->aead.obj, 0, out, len, &outwr, in, len, &inread) != 0) { + size_t outwr = 0; + size_t inread = 0; + if (botan_cipher_update(crypt.aead.obj, 0, out, len, &outwr, in, len, &inread)) { RNP_LOG("aead update failed"); return false; } - if ((outwr != len) || (inread != len)) { - RNP_LOG("wrong aead usage"); + if (outwr != inread) { + RNP_LOG("wrong aead usage: %zu vs %zu, len is %zu", outwr, inread, len); return false; } + read = inread; return true; } diff --git a/src/lib/crypto/symmetric.h b/src/lib/crypto/symmetric.h index 33c605c4c7..f2ab927410 100644 --- a/src/lib/crypto/symmetric.h +++ b/src/lib/crypto/symmetric.h @@ -213,10 +213,12 @@ bool pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len) * len bytes * @param in buffer with input, cannot be NULL * @param len number of bytes to process. Should be multiple of update granularity. + * @param read number of bytes read and processed, in rare cases could be less then len. * @return true on success or false otherwise. On success exactly len processed bytes will be * stored in out buffer */ -bool pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); +bool pgp_cipher_aead_update( + pgp_crypt_t &crypt, uint8_t *out, const uint8_t *in, size_t len, size_t &read); /** @brief Do final update on the cipher. For decryption final chunk should contain at least * authentication tag, for encryption input could be zero-size. diff --git a/src/lib/crypto/symmetric_ossl.cpp b/src/lib/crypto/symmetric_ossl.cpp index 5c19ee6a9f..e9a42aed91 100644 --- a/src/lib/crypto/symmetric_ossl.cpp +++ b/src/lib/crypto/symmetric_ossl.cpp @@ -268,17 +268,19 @@ pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len) } bool -pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +pgp_cipher_aead_update( + pgp_crypt_t &crypt, uint8_t *out, const uint8_t *in, size_t len, size_t &read) { if (!len) { return true; } int out_len = 0; - bool res = EVP_CipherUpdate(crypt->aead.obj, out, &out_len, in, len) == 1; + bool res = EVP_CipherUpdate(crypt.aead.obj, out, &out_len, in, len) == 1; if (!res) { RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error()); // LCOV_EXCL_LINE } assert(out_len == (int) len); + read = len; return res; } diff --git a/src/librepgp/stream-def.h b/src/librepgp/stream-def.h index 7a1108f827..583bfb4110 100644 --- a/src/librepgp/stream-def.h +++ b/src/librepgp/stream-def.h @@ -54,7 +54,7 @@ #define ST_FROM ("From") /* Preallocated cache length for AEAD encryption/decryption */ -#define PGP_AEAD_CACHE_LEN (PGP_INPUT_CACHE_SIZE + PGP_AEAD_MAX_TAG_LEN) +#define PGP_AEAD_CACHE_LEN (PGP_INPUT_CACHE_SIZE + 2 * PGP_AEAD_MAX_TAG_LEN) /* Maximum OpenPGP packet nesting level */ #define MAXIMUM_NESTING_LEVEL 32 diff --git a/src/librepgp/stream-parse.cpp b/src/librepgp/stream-parse.cpp index 129c99514d..325363d441 100644 --- a/src/librepgp/stream-parse.cpp +++ b/src/librepgp/stream-parse.cpp @@ -96,14 +96,15 @@ typedef struct pgp_source_encrypted_param_t { size_t chunklen{}; /* size of AEAD chunk in bytes */ size_t chunkin{}; /* number of bytes read from the current chunk */ size_t chunkidx{}; /* index of the current chunk */ - uint8_t cache[PGP_AEAD_CACHE_LEN]; /* read cache */ - size_t cachelen{}; /* number of bytes in the cache */ - size_t cachepos{}; /* index of first unread byte in the cache */ - pgp_aead_hdr_t aead_hdr; /* AEAD encryption parameters */ - uint8_t aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */ - size_t aead_adlen{}; /* length of the additional data */ - pgp_symm_alg_t salg; /* data encryption algorithm */ - pgp_parse_handler_t * handler{}; /* parsing handler with callbacks */ + size_t rawbytes{}; /* number of bytes in cache read but not decrypted */ + uint8_t cache[PGP_AEAD_CACHE_LEN]; /* read cache */ + size_t cachelen{}; /* number of bytes in the cache */ + size_t cachepos{}; /* index of first unread byte in the cache */ + pgp_aead_hdr_t aead_hdr; /* AEAD encryption parameters */ + uint8_t aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */ + size_t aead_adlen{}; /* length of the additional data */ + pgp_symm_alg_t salg; /* data encryption algorithm */ + pgp_parse_handler_t *handler{}; /* parsing handler with callbacks */ #ifdef ENABLE_CRYPTO_REFRESH pgp_seipdv2_hdr_t seipdv2_hdr; /* SEIPDv2 encryption parameters */ #endif @@ -483,9 +484,6 @@ compressed_src_close(pgp_source_t *src) static bool encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool last) { - uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; - size_t nlen; - size_t default_ad_len = param->aead_adlen; if (last) { @@ -550,7 +548,8 @@ encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool param->chunkin = 0; /* set chunk index for nonce */ - nlen = pgp_cipher_aead_nonce(param->aead_hdr.aalg, param->aead_hdr.iv, nonce, idx); + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen = pgp_cipher_aead_nonce(param->aead_hdr.aalg, param->aead_hdr.iv, nonce, idx); /* start cipher */ return pgp_cipher_aead_start(¶m->decrypt, nonce, nlen); @@ -560,13 +559,10 @@ encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool static bool encrypted_src_read_aead_part(pgp_source_encrypted_param_t *param) { - bool lastchunk = false; - bool chunkend = false; - bool res = false; - size_t read; - size_t tagread; - size_t taglen; - + /* Check whether we have some bytes which were read but not decrypted */ + if (param->rawbytes) { + memcpy(param->cache, param->cache + param->cachelen, param->rawbytes); + } param->cachepos = 0; param->cachelen = 0; @@ -575,32 +571,39 @@ encrypted_src_read_aead_part(pgp_source_encrypted_param_t *param) } /* it is always 16 for defined EAX and OCB, however this may change in future */ - taglen = pgp_cipher_aead_tag_len(param->aead_hdr.aalg); - read = sizeof(param->cache) - 2 * PGP_AEAD_MAX_TAG_LEN; + size_t taglen = pgp_cipher_aead_tag_len(param->aead_hdr.aalg); + size_t read = sizeof(param->cache) - 2 * PGP_AEAD_MAX_TAG_LEN - param->rawbytes; + bool chunkend = false; if (read >= param->chunklen - param->chunkin) { - read = param->chunklen - param->chunkin; + /* param->rawbytes is smaller then param->chunklen - param->chunkin due to the previous + * call */ + read = param->chunklen - param->chunkin - param->rawbytes; chunkend = true; } else { - read = read - read % pgp_cipher_aead_granularity(¶m->decrypt); + read = read - (read + param->rawbytes) % pgp_cipher_aead_granularity(¶m->decrypt); } - if (!param->pkt.readsrc->read(param->cache, read, &read)) { + if (!param->pkt.readsrc->read(param->cache + param->rawbytes, read, &read)) { return false; } /* checking whether we have enough input for the final tags */ - if (!param->pkt.readsrc->peek(param->cache + read, taglen * 2, &tagread)) { + size_t tagread = 0; + if (!param->pkt.readsrc->peek( + param->cache + param->rawbytes + read, taglen * 2, &tagread)) { return false; } + bool lastchunk = false; + size_t avail = param->rawbytes + read + tagread; if (tagread < taglen * 2) { /* this would mean the end of the stream */ - if ((param->chunkin == 0) && (read + tagread == taglen)) { + if ((param->chunkin == 0) && (avail == taglen)) { /* we have empty chunk and final tag */ chunkend = false; lastchunk = true; - } else if (read + tagread >= 2 * taglen) { + } else if (avail >= 2 * taglen) { /* we have end of chunk and final tag */ chunkend = true; lastchunk = true; @@ -611,55 +614,68 @@ encrypted_src_read_aead_part(pgp_source_encrypted_param_t *param) } if (!chunkend && !lastchunk) { - param->chunkin += read; - res = pgp_cipher_aead_update(¶m->decrypt, param->cache, param->cache, read); + size_t used = 0; + bool res = pgp_cipher_aead_update( + param->decrypt, param->cache, param->cache, param->rawbytes + read, used); + + param->chunkin += used; if (res) { - param->cachelen = read; + param->cachelen = used; + param->rawbytes = param->rawbytes + read - used; } return res; } + /* Processing end of chunk */ if (chunkend) { if (tagread > taglen) { param->pkt.readsrc->skip(tagread - taglen); } - res = pgp_cipher_aead_finish( - ¶m->decrypt, param->cache, param->cache, read + tagread - taglen); - if (!res) { + if (!pgp_cipher_aead_finish(¶m->decrypt, + param->cache, + param->cache, + param->rawbytes + read + tagread - taglen)) { RNP_LOG("failed to finalize aead chunk"); - return res; + return false; } - param->cachelen = read + tagread - 2 * taglen; + param->cachelen = param->rawbytes + read + tagread - 2 * taglen; param->chunkin += param->cachelen; + param->rawbytes = 0; } + /* Starting a new chunk */ size_t chunkidx = param->chunkidx; if (chunkend && param->chunkin) { chunkidx++; } - if (!(res = encrypted_start_aead_chunk(param, chunkidx, lastchunk))) { + if (!encrypted_start_aead_chunk(param, chunkidx, lastchunk)) { RNP_LOG("failed to start aead chunk"); - return res; + return false; } - if (lastchunk) { - if (tagread > 0) { - param->pkt.readsrc->skip(tagread); - } + if (!lastchunk) { + return true; + } - size_t off = read + tagread - taglen; - res = pgp_cipher_aead_finish( - ¶m->decrypt, param->cache + off, param->cache + off, taglen); - if (!res) { - RNP_LOG("wrong last chunk"); - return res; - } - param->auth_validated = true; + /* Processing last chunk */ + if (tagread > 0) { + param->pkt.readsrc->skip(tagread); } - return res; + /* Probably math below could be improved. The reason of this is that for chunkend we set + * rawbytes to 0 but it still be useful. */ + size_t off = + chunkend ? param->cachelen + taglen : param->rawbytes + read + tagread - taglen; + if (!pgp_cipher_aead_finish( + ¶m->decrypt, param->cache + off, param->cache + off, taglen)) { + RNP_LOG("wrong last chunk"); + return false; + } + param->rawbytes = 0; + param->auth_validated = true; + return true; } #endif @@ -669,13 +685,12 @@ encrypted_src_read_aead(pgp_source_t *src, void *buf, size_t len, size_t *read) #if !defined(ENABLE_AEAD) return false; #else - pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; - size_t cbytes; - size_t left = len; + auto param = (pgp_source_encrypted_param_t *) src->param; + size_t left = len; do { /* check whether we have something in the cache */ - cbytes = param->cachelen - param->cachepos; + size_t cbytes = param->cachelen - param->cachepos; if (cbytes > 0) { if (cbytes >= left) { memcpy(buf, param->cache + param->cachepos, left); @@ -689,7 +704,6 @@ encrypted_src_read_aead(pgp_source_t *src, void *buf, size_t len, size_t *read) memcpy(buf, param->cache + param->cachepos, cbytes); buf = (uint8_t *) buf + cbytes; left -= cbytes; - param->cachepos = param->cachelen = 0; } /* read something into cache */ diff --git a/src/librepgp/stream-write.cpp b/src/librepgp/stream-write.cpp index cf6d2e4d91..9b72438473 100644 --- a/src/librepgp/stream-write.cpp +++ b/src/librepgp/stream-write.cpp @@ -467,7 +467,8 @@ encrypted_dst_write_aead(pgp_dest_t *dst, const void *buf, size_t len) } while (len > 0) { - sz = std::min(sizeof(param->cache) - PGP_AEAD_MAX_TAG_LEN - param->cachelen, len); + /* 2 tags to align to the PGP_INPUT_CACHE_SIZE size */ + sz = std::min(sizeof(param->cache) - 2 * PGP_AEAD_MAX_TAG_LEN - param->cachelen, len); sz = std::min(sz, param->chunklen - param->chunkout - param->cachelen); memcpy(param->cache + param->cachelen, buf, sz); param->cachelen += sz; @@ -481,9 +482,17 @@ encrypted_dst_write_aead(pgp_dest_t *dst, const void *buf, size_t len) } else if (param->cachelen >= gran) { /* we have part of the chunk - so need to adjust it to the granularity */ size_t gransz = param->cachelen - param->cachelen % gran; - if (!pgp_cipher_aead_update(¶m->encrypt, param->cache, param->cache, gransz)) { + size_t inread = 0; + if (!pgp_cipher_aead_update( + param->encrypt, param->cache, param->cache, gransz, inread)) { return RNP_ERROR_BAD_STATE; } + if (inread != gransz) { + /* LCOV_EXCL_START */ + RNP_LOG("Unexpected aead update: read %zu instead of %zu.", inread, gransz); + return RNP_ERROR_BAD_STATE; + /* LCOV_EXCL_END */ + } dst_write(param->pkt.writedst, param->cache, gransz); memmove(param->cache, param->cache + gransz, param->cachelen - gransz); param->cachelen -= gransz; diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 634c88504e..ebea4a7517 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -100,6 +100,7 @@ def escape_regex(str): PUBRING_1 = 'keyrings/1/pubring.gpg' SECRING_1 = 'keyrings/1/secring.gpg' KEYRING_DIR_1 = 'keyrings/1' +KEYRING_DIR_2 = 'keyrings/2' KEYRING_DIR_3 = 'keyrings/3' SECRING_G10 = 'test_stream_key_load/g10' KEY_ALICE_PUB = 'test_key_validity/alice-pub.asc' @@ -521,7 +522,7 @@ def gpg_encrypt_file(src, dst, cipher=None, z=None, armor=False): if armor: params[2:2] = ['--armor'] if GPG_NO_OLD: params[2:2] = ['--allow-old-cipher-algos'] - ret, out, err = run_proc(GPG, params) + ret, _, err = run_proc(GPG, params) if ret != 0: raise_err('gpg encryption failed for cipher ' + cipher, err) @@ -541,7 +542,7 @@ def gpg_symencrypt_file(src, dst, cipher=None, z=None, armor=False, aead=None): params[3:3] = ['--chunk-size', str(aead[1] + 6)] params[3:3] = ['--rfc4880bis', '--force-aead'] - ret, out, err = run_proc(GPG, params) + ret, _, err = run_proc(GPG, params) if ret != 0: raise_err('gpg symmetric encryption failed for cipher ' + cipher, err) @@ -549,7 +550,7 @@ def gpg_symencrypt_file(src, dst, cipher=None, z=None, armor=False, aead=None): def gpg_decrypt_file(src, dst, keypass): src = path_for_gpg(src) dst = path_for_gpg(dst) - ret, out, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, GPG_LOOPBACK, '--batch', + ret, _, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--yes', '--passphrase', keypass, '--trust-model', 'always', '-o', dst, '-d', src]) if ret != 0: @@ -559,7 +560,7 @@ def gpg_decrypt_file(src, dst, keypass): def gpg_verify_file(src, dst, signer=None): src = path_for_gpg(src) dst = path_for_gpg(dst) - ret, out, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch', + ret, _, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch', '--yes', '--trust-model', 'always', '-o', dst, '--verify', src]) if ret != 0: raise_err('gpg verification failed', err) @@ -1182,15 +1183,15 @@ def test_generate_to_kbx(self): pipe = pswd_pipe(PASSWORD) kbx_userid_tracker = 'kbx_userid_tracker@rnp' # Run key generation - ret, out, err = run_proc(RNPK, ['--gen-key', '--keystore-format', 'GPG21', + ret, out, _ = run_proc(RNPK, ['--gen-key', '--keystore-format', 'GPG21', '--userid', kbx_userid_tracker, '--homedir', RNPDIR, '--pass-fd', str(pipe)]) os.close(pipe) self.assertEqual(ret, 0, KEY_GEN_FAILED) # Read KBX with GPG - ret, out, err = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys']) + ret, out, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys']) self.assertEqual(ret, 0, 'gpg : failed to read KBX') - self.assertTrue(kbx_userid_tracker in out, 'gpg : failed to read expected key from KBX') + self.assertIn(kbx_userid_tracker, out, 'gpg : failed to read expected key from KBX') clear_keyrings() def test_generate_protection_pass_fd(self): @@ -1438,46 +1439,47 @@ def test_export_revocation(self): def test_import_keys(self): clear_keyrings() + KEY_BOTH=data_path('test_stream_key_merge/key-both.asc') # try to import non-existing file - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('thiskeyfiledoesnotexist')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('thiskeyfiledoesnotexist')]) self.assertEqual(ret, 1) self.assertRegex(err, r'(?s)^.*Failed to create input for .*thiskeyfiledoesnotexist.*') # try malformed file - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sigs-malf.pgp')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sigs-malf.pgp')]) self.assertEqual(ret, 1) self.assertRegex(err, r'(?s)^.*failed to import key\(s\) from .*test_key_validity/alice-sigs-malf.pgp, stopping\..*') self.assertRegex(err, r'(?s)^.*Import finished: 0 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*') # try --import - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 2 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 2 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_stream_key_merge/key-both.asc')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', KEY_BOTH]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 3 new public keys, 3 new secret keys, 0 updated, 0 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_stream_key_merge/key-both.asc')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', KEY_BOTH]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 6 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 1 new public keys, 0 new secret keys, 1 updated, 0 unchanged\..*') clear_keyrings() # try --import-key - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 2 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 2 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_stream_key_merge/key-both.asc')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', KEY_BOTH]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 3 new public keys, 3 new secret keys, 0 updated, 0 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_stream_key_merge/key-both.asc')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', KEY_BOTH]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 6 unchanged\..*') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')]) + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')]) self.assertEqual(ret, 0) self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 1 new public keys, 0 new secret keys, 1 updated, 0 unchanged\..*') clear_keyrings() @@ -1700,13 +1702,13 @@ def _test_userid_genkey(self, userid_beginning, weird_part, userid_end, weird_pa for userid in USERS: rnp_genkey_rsa(userid, 1024) # Read with GPG - ret, out, err = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys', '--charset', CONSOLE_ENCODING]) + ret, out, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys', '--charset', CONSOLE_ENCODING]) self.assertEqual(ret, 0, 'gpg : failed to read keystore') tracker_escaped = re.findall(r'' + userid_beginning + '.*' + userid_end + '', out) tracker_gpg = list(map(decode_string_escape, tracker_escaped)) self.assertEqual(tracker_gpg, USERS, 'gpg : failed to find expected userids from keystore') # Read with rnpkeys - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys']) + ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys']) self.assertEqual(ret, 0, 'rnpkeys : failed to read keystore') tracker_escaped = re.findall(r'' + userid_beginning + '.*' + userid_end + '', out) tracker_rnp = list(map(decode_string_escape, tracker_escaped)) @@ -1820,31 +1822,33 @@ def test_additional_subkeys_default(self): clear_keyrings() def test_expert_mode_no_endless_loop(self): - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, - '--userid', 'noendlessloop@rnp', '--expert', '--generate-key'], + TOO_MANY=r'(?s)Too many attempts. Aborting.' + UID='noendlessloop@rnp' + ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, + '--userid', UID, '--expert', '--generate-key'], '\n\n\n\n\n') self.assertEqual(ret, 1) - self.assertRegex(out, r'(?s)Too many attempts. Aborting.') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, - '--userid', 'noendlessloop@rnp', '--expert', '--generate-key'], + self.assertRegex(out, TOO_MANY) + ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, + '--userid', UID, '--expert', '--generate-key'], '1\n1\n1\n1\n1\n1\n') self.assertEqual(ret, 1) - self.assertRegex(out, r'(?s)Too many attempts. Aborting.') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, - '--userid', 'noendlessloop@rnp', '--expert', '--generate-key'], + self.assertRegex(out, TOO_MANY) + ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, + '--userid', UID, '--expert', '--generate-key'], '16\n1\n1\n1\n1\n1\n') self.assertEqual(ret, 1) - self.assertRegex(out, r'(?s)Too many attempts. Aborting.') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, - '--userid', 'noendlessloop@rnp', '--expert', '--generate-key'], + self.assertRegex(out, TOO_MANY) + ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, + '--userid', UID, '--expert', '--generate-key'], '17\n1\n1\n1\n1\n1\n') self.assertEqual(ret, 1) - self.assertRegex(out, r'(?s)Too many attempts. Aborting.') - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, - '--userid', 'noendlessloop@rnp', '--expert', '--generate-key'], + self.assertRegex(out, TOO_MANY) + ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, + '--userid', UID, '--expert', '--generate-key'], '19\n\n\n\n\n\n') self.assertEqual(ret, 1) - self.assertRegex(out, r'(?s)Too many attempts. Aborting.') + self.assertRegex(out, TOO_MANY) clear_keyrings() @@ -2239,7 +2243,7 @@ def test_armor(self): def test_rnpkeys_lists(self): KEYRING_1 = data_path(KEYRING_DIR_1) - KEYRING_2 = data_path('keyrings/2') + KEYRING_2 = data_path(KEYRING_DIR_2) KEYRING_3 = data_path(KEYRING_DIR_3) KEYRING_5 = data_path('keyrings/5') path = data_path('test_cli_rnpkeys') + '/' @@ -2337,7 +2341,7 @@ def test_rnpkeys_g10_list_order(self): self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys_sec_no_bp')), out, 'g10 secret key listing failed') def test_rnpkeys_list_from_keyfile(self): - KEYRING_2 = data_path('keyrings/2') + KEYRING_2 = data_path(KEYRING_DIR_2) ret, out, err = run_proc(RNPK, ['--homedir', KEYRING_2, '--list-keys', '--keyfile', data_path(KEY_ALICE_PUB)]) self.assertEqual(ret, 0) self.assertRegex(out, r'1 key found.*') @@ -2669,7 +2673,7 @@ def test_rnp_list_packets_edge_cases(self): def test_debug_log(self): if RNP_CAST5: run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_1), '--list-keys', '--debug', '--all']) - run_proc(RNPK, ['--homedir', data_path('keyrings/2'), '--list-keys', '--debug', '--all']) + run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_2), '--list-keys', '--debug', '--all']) run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_3), '--list-keys', '--debug', '--all']) run_proc(RNPK, ['--homedir', data_path(SECRING_G10), '--list-keys', '--debug', '--all']) @@ -2851,55 +2855,55 @@ def test_output_to_specifier(self): def test_literal_filename(self): EMPTY_FNAME = r'(?s)^.*literal data packet.*mode b.*created 0, name="".*$' HELLO_FNAME = r'(?s)^.*literal data packet.*mode b.*created 0, name="hello".*$' - src, enc, dec = reg_workfiles('source', '.txt', EXT_PGP, '.dec') + src, enc = reg_workfiles('source', '.txt', EXT_PGP) with open(src, 'w+') as f: f.write('Literal filename check') # Encrypt file and make sure it's name is stored in literal data packet ret, out, _ = run_proc(RNP, ['-c', src, '--password', 'password']) self.assertEqual(ret, 0) - ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, r'(?s)^.*literal data packet.*mode b.*created \d+.*name="source.txt".*$') remove_files(enc) # Encrypt file, overriding it's name ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', src, '--password', 'password']) self.assertEqual(ret, 0) - ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, HELLO_FNAME) remove_files(enc) # Encrypt file, using empty name ret, out, _ = run_proc(RNP, ['--set-filename', '', '-c', src, '--password', 'password']) self.assertEqual(ret, 0) - ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, EMPTY_FNAME) remove_files(enc) # Encrypt stdin, making sure empty name is stored ret, out, _ = run_proc(RNP, ['-c', '--password', 'password', '--output', enc], 'Data from stdin') self.assertEqual(ret, 0) - ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, EMPTY_FNAME) remove_files(enc) # Encrypt stdin, setting the file name ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', '--password', 'password', '--output', enc], 'Data from stdin') self.assertEqual(ret, 0) - ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, HELLO_FNAME) remove_files(enc) # Encrypt env, making sure empty name is stored ret, out, _ = run_proc(RNP, ['-c', 'env:HOME', '--password', 'password', '--output', enc]) self.assertEqual(ret, 0) - ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, EMPTY_FNAME) remove_files(enc) # Encrypt env, setting the file name ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', 'env:HOME', '--password', 'password', '--output', enc]) self.assertEqual(ret, 0) - ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, HELLO_FNAME) remove_files(enc) @@ -3692,15 +3696,15 @@ def test_ext_adding_stripping(self): def test_interactive_password(self): # Reuse password for subkey, say "yes" stdinstr = 'password\npassword\ny\n' - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) self.assertEqual(ret, 0) # Do not reuse same password for subkey, say "no" stdinstr = 'password\npassword\nN\nsubkeypassword\nsubkeypassword\n' - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) self.assertEqual(ret, 0) # Set empty password and reuse it stdinstr = '\n\ny\ny\n' - ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) self.assertEqual(ret, 0) def test_set_current_time(self): @@ -3779,7 +3783,7 @@ def test_keystore_formats(self): def test_no_twofish(self): if (RNP_TWOFISH): - return + self.skipTest('Twofish is available') src, dst, dec = reg_workfiles('cleartext', '.txt', '.pgp', '.dec') random_text(src, 100) # Attempt to encrypt to twofish @@ -3809,7 +3813,7 @@ def test_no_twofish(self): def test_no_idea(self): if (RNP_IDEA): - return + self.skipTest('IDEA is available') src, dst, dec = reg_workfiles('cleartext', '.txt', '.pgp', '.dec') random_text(src, 100) # Attempt to encrypt to twofish @@ -4206,7 +4210,7 @@ def s2k_msec_iters(msec): ret, out, _ = run_proc(RNP, ['--list-packets', enc]) self.assertEqual(ret, 0) self.assertRegex(out, r'(?s)^.*s2k iterations: [0-9]+ \(encoded as [0-9]+\).*') - matches = re.findall(r'(?s)^.*s2k iterations: ([0-9]+) \(encoded as [0-9]+\).*', out) + matches = re.findall(r'(?s)^.*s2k iterations: (\d+) \(encoded as \d+\).*', out) remove_files(enc) return int(matches[0]) @@ -4250,8 +4254,7 @@ def test_sym_encryption_wrong_s2k(self): def test_sym_encryption__rnp_aead(self): if not RNP_AEAD: - print('AEAD is not available for RNP - skipping.') - return + self.skipTest('AEAD is not available for RNP - skipping.') CIPHERS = rnp_supported_ciphers(True) AEADS = [None, 'eax', 'ocb'] if not RNP_AEAD_EAX: @@ -4654,6 +4657,22 @@ def test_encryption_no_wrap(self): self.assertEqual(file_text(dec), file_text(src)) clear_workfiles() + def test_aead_eax_botan35_decryption(self): + # Artifact which was obtained from tests used EAX + Twofish + if not RNP_AEAD_EAX or not RNP_TWOFISH: + self.skipTest('AEAD-EAX is not supported') + # See issue #2245 for the details + [dec] = reg_workfiles('cleartext', '.txt') + EAXSRC = data_path('test_messages/cleartext.rnp-aead-eax') + # Decrypt and verify AEAD-EAX encrypted message by RNP + ret, _, _ = run_proc(RNP, ['--keyfile', data_path('test_messages/seckey-aead-eax.gpg'), '--password', 'encsign1pass', '-d', EAXSRC, '--output', dec]) + self.assertEqual(ret, 0) + remove_files(dec) + # Decrypt it using the password + ret, _, _ = run_proc(RNP, ['--keyfile', data_path('test_messages/pubkey-aead-eax.gpg'), '--password', 'password1', '-d', EAXSRC, '--output', dec]) + self.assertEqual(ret, 0) + clear_workfiles() + class Compression(unittest.TestCase): @classmethod def setUpClass(cls): diff --git a/src/tests/data/test_messages/cleartext.rnp-aead-eax b/src/tests/data/test_messages/cleartext.rnp-aead-eax new file mode 100644 index 0000000000..c338d09136 Binary files /dev/null and b/src/tests/data/test_messages/cleartext.rnp-aead-eax differ diff --git a/src/tests/data/test_messages/pubkey-aead-eax.gpg b/src/tests/data/test_messages/pubkey-aead-eax.gpg new file mode 100644 index 0000000000..df5dfc4702 Binary files /dev/null and b/src/tests/data/test_messages/pubkey-aead-eax.gpg differ diff --git a/src/tests/data/test_messages/seckey-aead-eax.gpg b/src/tests/data/test_messages/seckey-aead-eax.gpg new file mode 100644 index 0000000000..5a89350575 Binary files /dev/null and b/src/tests/data/test_messages/seckey-aead-eax.gpg differ